-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathmain.js
More file actions
16 lines (16 loc) · 109 KB
/
Copy pathmain.js
File metadata and controls
16 lines (16 loc) · 109 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
"use strict";var Mt=Object.defineProperty;var me=Object.getOwnPropertyDescriptor;var ye=Object.getOwnPropertyNames;var be=Object.prototype.hasOwnProperty;var ve=(s,t)=>{for(var e in t)Mt(s,e,{get:t[e],enumerable:!0})},we=(s,t,e,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of ye(t))!be.call(s,i)&&i!==e&&Mt(s,i,{get:()=>t[i],enumerable:!(n=me(t,i))||n.enumerable});return s};var Se=s=>we(Mt({},"__esModule",{value:!0}),s);var Fn={};ve(Fn,{default:()=>Ct});module.exports=Se(Fn);var C=require("obsidian");function G(s){return s.provider==="forgejo"&&s.instanceUrl?s.instanceUrl.replace(/\/+$/,"")+"/api/v1":K}function tt(s){if(s.provider==="forgejo"){if(!s.instanceUrl)return"Set the Instance URL for your Forgejo/Gitea server in settings.";if(!/^https?:\/\//i.test(s.instanceUrl))return"The Forgejo/Gitea Instance URL must start with http:// or https://."}return null}var Tt=100,et={auth:{method:"none",token:"",provider:"github"},mappings:[],defaultCommitTemplate:"Sync from Obsidian ({mapping}): {datetime} \u2014 {added}+ {modified}~ {deleted}-",excludedPaths:[".trash/**",".git/**","node_modules/**",".easy-git-backup/**",".claude/**",".DS_Store","Thumbs.db","*.tmp","*.swp"],maxFileSizeBytes:95*1024*1024,showNotifications:!0,debugLogging:!1,autoResolveByMtime:!0,autoMergeText:!0},Ft="Ov23lihov6s6zVguusXe",K="https://api.github.com";function U(){return typeof crypto<"u"&&"randomUUID"in crypto?crypto.randomUUID():"id-"+Math.random().toString(36).slice(2)+Date.now().toString(36)}var $=require("obsidian");var It=require("obsidian");var L=class extends Error{status;url;body;rateLimitRemaining;rateLimitReset;constructor(t){super(t.message),this.status=t.status,this.url=t.url,this.body=t.body,this.rateLimitRemaining=t.rateLimitRemaining,this.rateLimitReset=t.rateLimitReset}},B=class{token;baseUrl;userAgent;lastRateLimit;constructor(t){this.token=t.token,this.baseUrl=t.baseUrl??K,this.userAgent=t.userAgent??"Easy-Git-Obsidian-Plugin"}setToken(t){this.token=t}hasToken(){return this.token.length>0}isGitHub(){return this.baseUrl===K}async request(t,e,n,i){let o=e.startsWith("http")?e:`${this.baseUrl}${e}`,r={Accept:"application/vnd.github+json","User-Agent":this.userAgent,...i??{}};this.baseUrl===K&&(r["X-GitHub-Api-Version"]="2022-11-28"),t==="GET"&&(r["Cache-Control"]="no-cache",r.Pragma="no-cache"),this.token&&(r.Authorization=`Bearer ${this.token}`),n!=null&&(r["Content-Type"]="application/json");let a={url:o,method:t,headers:r,throw:!1};n!=null&&(a.body=JSON.stringify(n));let l;try{l=await(0,It.requestUrl)(a)}catch(c){throw new L({status:0,url:o,message:`Network error: ${c instanceof Error?c.message:String(c)}`,body:null})}if(this.updateRateLimit(l.headers),l.status>=200&&l.status<300){if(l.status===204)return;let c=l.text;if(!c)return;try{return JSON.parse(c)}catch{return c}}let d=null;try{d=JSON.parse(l.text)}catch{d=l.text}let u=(d&&typeof d=="object"&&"message"in d?String(d.message):`HTTP ${l.status}`)+` (${t} ${o})`;throw new L({status:l.status,url:o,message:u,body:d,rateLimitRemaining:this.lastRateLimit?.remaining,rateLimitReset:this.lastRateLimit?.reset})}async paginate(t,e={}){let n=e.perPage??100,i=e.maxPages??5,o=[];for(let r=1;r<=i;r++){let a=t.includes("?")?"&":"?",l=`${t}${a}per_page=${n}&page=${r}`,d=await this.request("GET",l);if(!Array.isArray(d)||(o.push(...d),d.length<n))break}return o}updateRateLimit(t){let e=t["x-ratelimit-remaining"]??t["X-RateLimit-Remaining"],n=t["x-ratelimit-reset"]??t["X-RateLimit-Reset"];if(e===void 0)return;let i=Number(e),o=Number(n??"0");Number.isNaN(i)||(this.lastRateLimit={remaining:i,reset:o})}};async function Gt(s){return(await s.paginate("/user/repos?sort=updated&affiliation=owner,collaborator,organization_member",{perPage:100,maxPages:5})).map(e=>({owner:e.owner.login,name:e.name,fullName:e.full_name,private:e.private,defaultBranch:e.default_branch,updatedAt:e.updated_at}))}async function Ht(s){let t=[];for(let n=1;n<=5;n++){let i=await s.request("GET",`/repos/search?sort=updated&limit=50&page=${n}`);if(!i.ok||!Array.isArray(i.data)||i.data.length===0)break;for(let o of i.data)t.push({owner:o.owner.login,name:o.name,fullName:o.full_name,private:o.private,defaultBranch:o.default_branch,updatedAt:o.updated_at});if(i.data.length<50)break}return t}async function nt(s,t,e){let n=await s.request("GET",`/repos/${t}/${e}`);return{owner:n.owner.login,name:n.name,fullName:n.full_name,defaultBranch:n.default_branch,private:n.private}}async function _t(s,t,e){return(await s.paginate(`/repos/${t}/${e}/branches`,{perPage:100,maxPages:3})).map(i=>({name:i.name,commitSha:i.commit.sha??i.commit.id??""}))}async function W(s,t,e,n){let i=await s.request("GET",`/repos/${t}/${e}/branches/${encodeURIComponent(n)}`),o=i.commit.sha??i.commit.id??"";if(i.commit.commit?.tree?.sha)return{commitSha:o,treeSha:i.commit.commit.tree.sha};let r=await s.request("GET",`/repos/${t}/${e}/git/commits/${o}`),a=r.tree?.sha??r.commit?.tree?.sha??"";return{commitSha:o,treeSha:a}}async function $t(s,t,e,n){let i=await s.request("GET",`/repos/${t}/${e}/git/trees/${n}?recursive=1`);return{entries:i.tree??[],truncated:!!i.truncated}}async function Ut(s,t,e,n){if(s.isGitHub())return(await s.request("GET",`/repos/${t}/${e}/git/trees/${n}`)).tree??[];let i=1e3,o=100,r=[];for(let a=1;a<=o;a++){let l=await s.request("GET",`/repos/${t}/${e}/git/trees/${n}?per_page=${i}&page=${a}`),d=l.tree??[];if(r.push(...d),!l.truncated||d.length<i)break}return r}async function it(s,t,e,n,i){let o,r;if(i){let u=await ke(s,t,e,n,i);if(!u.subtreeSha)return{files:{},truncatedFallback:!1};o=u.subtreeSha,r=u.correctedPath}else o=n;let{entries:a,truncated:l}=await $t(s,t,e,o);if(!l){let u={};for(let c of a)c.type==="blob"&&(u[c.path]={path:c.path,sha:c.sha,size:c.size??0});return{files:u,correctedPath:r,truncatedFallback:!1}}return{files:await xe(s,t,e,o),correctedPath:r,truncatedFallback:!0}}async function xe(s,t,e,n){let i={},o=[{sha:n,prefix:""}];for(;o.length>0;){let{sha:r,prefix:a}=o.pop(),l=await Ut(s,t,e,r);for(let d of l){let u=a?`${a}/${d.path}`:d.path;d.type==="blob"?i[u]={path:u,sha:d.sha,size:d.size??0}:d.type==="tree"&&o.push({sha:d.sha,prefix:u})}}return i}async function ke(s,t,e,n,i){let o=i.split("/").filter(d=>d.length>0),r=n,a=[],l=!1;for(let d of o){let u;try{u=await Ut(s,t,e,r)}catch(p){if(p instanceof L&&p.status===404)return{subtreeSha:null};throw p}let c=u.find(p=>p.path===d&&p.type==="tree");if(!c){let p=d.toLowerCase();c=u.find(h=>h.path.toLowerCase()===p&&h.type==="tree"),c&&(l=!0)}if(!c)return{subtreeSha:null};a.push(c.path),r=c.sha}return{subtreeSha:r,correctedPath:l?a.join("/"):void 0}}async function st(s,t,e,n){let i=await s.request("GET",`/repos/${t}/${e}/git/blobs/${n}`);if(i.content==null)throw new Error(`Remote file is too large to pull (${i.size} bytes); the server returned no contents.`);return{sha:i.sha,size:i.size,encoding:i.encoding??"base64",content:i.content}}async function Wt(s,t,e,n){return(await s.request("POST",`/repos/${t}/${e}/git/blobs`,{content:n,encoding:"base64"})).sha}async function jt(s,t,e,n,i){return(await s.request("POST",`/repos/${t}/${e}/git/trees`,{base_tree:n,tree:i})).sha}async function Vt(s,t,e,n,i,o){return(await s.request("POST",`/repos/${t}/${e}/git/commits`,{message:n,tree:i,parents:o})).sha}async function zt(s,t,e,n,i,o,r,a){let l=n.split("/").map(encodeURIComponent).join("/"),d={message:o,content:i,branch:r};a&&(d.sha=a);let u=a?"PUT":"POST",c=await s.request(u,`/repos/${t}/${e}/contents/${l}`,d);return{commitSha:c.commit.sha,fileSha:c.content.sha}}async function qt(s,t,e,n,i,o,r){let a=n.split("/").map(encodeURIComponent).join("/");return(await s.request("DELETE",`/repos/${t}/${e}/contents/${a}`,{message:i,sha:r,branch:o})).commit.sha}async function Kt(s,t,e,n,i){try{return await s.request("PATCH",`/repos/${t}/${e}/git/refs/heads/${encodeURIComponent(n)}`,{sha:i,force:!1}),!0}catch(o){if(o instanceof L&&o.status===422)return!1;throw o}}async function Xt(s){let t=`blob ${s.byteLength}\0`,e=new TextEncoder().encode(t),n=new Uint8Array(e.byteLength+s.byteLength);n.set(e,0),n.set(s,e.byteLength);let i=await crypto.subtle.digest("SHA-1",n);return Ee(new Uint8Array(i))}async function X(s){let t=new TextEncoder().encode(s);return Xt(t)}async function Pt(s){return Xt(new Uint8Array(s))}function J(s){let t=new Uint8Array(s),e="";for(let n=0;n<t.byteLength;n+=32768){let i=Math.min(n+32768,t.byteLength),o=t.subarray(n,i);e+=String.fromCharCode.apply(null,Array.from(o))}return btoa(e)}function Lt(s){let t=s.replace(/\s/g,""),e=atob(t),n=new Uint8Array(e.length);for(let i=0;i<e.length;i++)n[i]=e.charCodeAt(i);return n.buffer}function ot(s){let t=Lt(s);return new TextDecoder().decode(t)}function Ee(s){let t="";for(let e=0;e<s.byteLength;e++){let n=s[e];t+=(n<16?"0":"")+n.toString(16)}return t}var Re=new Set(["md","markdown","txt","text","json","jsonc","yaml","yml","toml","ini","cfg","conf","csv","tsv","html","htm","xml","svg","css","scss","sass","less","js","mjs","cjs","jsx","ts","tsx","py","rb","go","rs","java","c","h","cpp","hpp","cs","sh","bash","zsh","fish","log","gitignore","gitattributes","editorconfig","npmrc"]);function H(s){let t=s.lastIndexOf(".");if(t<0){let n=s.slice(s.lastIndexOf("/")+1).toLowerCase();return n==="readme"||n==="license"||n==="changelog"}let e=s.slice(t+1).toLowerCase();return Re.has(e)}function Jt(s){let{local:t,remote:e,lastState:n,direction:i}=s,o=new Set([...Object.keys(t),...Object.keys(e),...Object.keys(n)]),r=[],a=[],l=[],d=[],u=0;for(let c of o){let p=n[c],h=t[c],y=e[c],f=p?.sha,m=h?.sha,w=y?.sha,S=!!h,x=!!y,v=!!p;if(i==="pull"){x?S?m!==w?r.push({path:c,op:"pull-modify",remoteSha:w}):u+=1:r.push({path:c,op:"pull-add",remoteSha:w}):S?v&&m===f?r.push({path:c,op:"pull-delete",localSha:m}):v&&m!==f&&d.push(c):u+=1;continue}if(i==="push"){S?x?m!==w?r.push({path:c,op:"push-modify",localSha:m}):u+=1:r.push({path:c,op:"push-add",localSha:m}):x?v&&w===f?r.push({path:c,op:"push-delete",remoteSha:w}):v&&w!==f&&l.push(c):u+=1;continue}if(!v){S&&!x?r.push({path:c,op:"push-add",localSha:m}):!S&&x?r.push({path:c,op:"pull-add",remoteSha:w}):S&&x&&(m===w?u+=1:a.push({path:c,kind:"both-added-different",localSha:m,remoteSha:w}));continue}if(S&&x){let k=m!==f,E=w!==f;!k&&!E?u+=1:k&&!E?r.push({path:c,op:"push-modify",localSha:m}):!k&&E?r.push({path:c,op:"pull-modify",remoteSha:w}):m===w?u+=1:a.push({path:c,kind:"both-edited",localSha:m,remoteSha:w})}else!S&&x?w!==f?a.push({path:c,kind:"remote-edited-local-deleted",remoteSha:w}):r.push({path:c,op:"push-delete",remoteSha:w}):S&&!x?m!==f?a.push({path:c,kind:"local-edited-remote-deleted",localSha:m}):r.push({path:c,op:"pull-delete",localSha:m}):u+=1}return{actions:r,conflicts:a,noopCount:u,informationalRemoteChanges:l,informationalLocalChanges:d}}function Yt(s,t){let e=[],n=[];for(let i of s)if(i.resolution){if(i.resolution==="keep-local"){if(t==="pull")continue;i.kind==="local-edited-remote-deleted"?e.push({path:i.path,op:"push-add",localSha:i.localSha}):i.kind==="remote-edited-local-deleted"?e.push({path:i.path,op:"push-delete",remoteSha:i.remoteSha}):e.push({path:i.path,op:"push-modify",localSha:i.localSha})}else if(i.resolution==="keep-remote"){if(t==="push")continue;i.kind==="remote-edited-local-deleted"?e.push({path:i.path,op:"pull-add",remoteSha:i.remoteSha}):i.kind==="local-edited-remote-deleted"?e.push({path:i.path,op:"pull-delete",localSha:i.localSha}):e.push({path:i.path,op:"pull-modify",remoteSha:i.remoteSha})}else if(i.resolution==="keep-both"){let o=Zt(i.path);n.push({from:i.path,to:o}),t!=="pull"&&e.push({path:o,op:"push-add",localSha:i.localSha}),t!=="push"&&i.remoteSha&&e.push({path:i.path,op:i.kind==="remote-edited-local-deleted"?"pull-add":"pull-modify",remoteSha:i.remoteSha})}}return{extraActions:e,keepBothRenames:n}}function Zt(s){let t=s.lastIndexOf("."),e=s.lastIndexOf("/"),n=new Date().toISOString().replace(/[:.]/g,"-").slice(0,19);return t>e&&t!==-1?`${s.slice(0,t)}-conflict-local-${n}${s.slice(t)}`:`${s}-conflict-local-${n}`}function Qt(s,t){let e=new Date,n=e.toISOString().slice(0,10),i=e.toISOString().slice(0,16).replace("T"," ")+" UTC",o=t.added+t.modified+t.deleted,r=t.files.slice(0,5).join(", "),a=t.files.length>5?`${r}, \u2026+${t.files.length-5} more`:r,l={"{date}":n,"{datetime}":i,"{n}":String(o),"{added}":String(t.added),"{modified}":String(t.modified),"{deleted}":String(t.deleted),"{files}":a,"{vault}":t.vaultName,"{mapping}":t.mappingName},d=s;for(let[u,c]of Object.entries(l))d=d.split(u).join(c);return d}function O(s,t){for(let e of t){let n=e.trim();if(!(!n||n.startsWith("#"))&&(te(s,n)||!n.includes("/")&&te(Ce(s),n)))return!0}return!1}function Ce(s){let t=s.lastIndexOf("/");return t<0?s:s.slice(t+1)}function te(s,t){let e=t;return e.endsWith("/")&&(e+="**"),new RegExp("^"+Me(e)+"$").test(s)}function Me(s){let t="";for(let e=0;e<s.length;e++){let n=s[e];n==="*"?s[e+1]==="*"?(t+=".*",e++,s[e+1]==="/"&&e++):t+="[^/]*":n==="?"?t+="[^/]":".+()|^$[]{}\\".includes(n)?t+="\\"+n:t+=n}return t}var Te=new Set(["png","jpg","jpeg","gif","webp","svg","bmp","avif","ico","tiff"]);function ee(s,t){let e=[],n=[],i=new Set,o=0,r=0,a=0,l=0,d=rt(s);for(let u of d){if(u.kind==="code"){e.push(u.text);continue}e.push($e(u.text,t,{addBlob:c=>{i.has(c.remoteRelPath)||(i.add(c.remoteRelPath),n.push(c))},onUnresolved:()=>{o+=1},onRewritten:()=>{r+=1},onExcalidrawResolved:()=>{a+=1},onExcalidrawMissingCompanion:()=>{l+=1}}))}return{markdown:e.join(""),extraBlobs:n,unresolvedCount:o,rewrittenCount:r,excalidrawResolved:a,excalidrawMissingCompanion:l}}var Fe=/!\[\[([^\]\n]+)\]\]/g;function $e(s,t,e){return s.replace(Fe,(n,i,o)=>{if(o>0&&s.charAt(o-1)==="\\")return n;let r=i.indexOf("|"),a=r<0?i:i.slice(0,r),l=r<0?"":i.slice(r+1);if(/[#^]/.test(a))return n;let d=a.trim();if(!d)return n;let u=t.resolve(d,t.sourcePath);if(!u)return e.onUnresolved(),n;let c=u;if(Pe(u.path)){let E=null;for(let F of Le(u.path))if(E=t.resolve(F,t.sourcePath),E)break;E?(c=E,e.onExcalidrawResolved()):e.onExcalidrawMissingCompanion()}let p=l.trim(),h=p.length>0&&/^\d+$/.test(p),y=p&&!h?p:"",f=c.path,m=Ae(f).toLowerCase(),w=Te.has(m),S;if(De(f,t.mappingVaultFolder))S=Be(t.sourcePath,f);else{let E=j(f),F=`attachments/${E}`;e.addBlob({vaultPath:f,remoteRelPath:F}),S=Ne(t.sourcePath,t.mappingVaultFolder,E)}let v=Oe(S);return e.onRewritten(),w&&h?`<img src="${v}" width="${p}" alt="">`:w?``:`[${y||j(f)}](${v})`})}function Pe(s){let t=j(s).toLowerCase();return t.endsWith(".excalidraw")||t.endsWith(".excalidraw.md")}function Le(s){let t=At(s),n=j(s).replace(/\.excalidraw(\.md)?$/i,""),i=t?`${t}/`:"";return[`${i}${n}.svg`,`${i}${n}.png`,`${i}${n}.excalidraw.svg`,`${i}${n}.excalidraw.png`]}function rt(s){let t=s.split(/\r?\n/),e=[],n=[],i="prose",o=null,r=()=>{n.length!==0&&(e.push({kind:i,text:n.join(`
`)}),n=[])};for(let a=0;a<t.length;a++){let l=t[a],d=l.match(/^([ \t]*)(```+|~~~+)(.*)$/);i==="prose"&&d?(r(),i="code",o=d[2][0]==="`"?"```":"~~~",n.push(l)):i==="code"&&d&&l.trim().startsWith(o)?(n.push(l),r(),i="prose",o=null):n.push(l)}r();for(let a=0;a<e.length-1;a++)e[a].text+=`
`;return e}function j(s){let t=s.lastIndexOf("/");return t<0?s:s.slice(t+1)}function Ae(s){let t=j(s),e=t.lastIndexOf(".");return e<0?"":t.slice(e+1)}function De(s,t){let e=t.replace(/^\/+|\/+$/g,"");return e?s===e||s.startsWith(e+"/"):!0}function Be(s,t){let e=At(s);return ne(e,t)}function Ne(s,t,e){let n=At(s),i=t.replace(/^\/+|\/+$/g,""),o=i?`${i}/attachments/${e}`:`attachments/${e}`;return ne(n,o)}function At(s){let t=s.lastIndexOf("/");return t<0?"":s.slice(0,t)}function ne(s,t){let e=s?s.split("/"):[],n=t.split("/"),i=0;for(;i<e.length&&i<n.length-1&&e[i]===n[i];)i+=1;let o=e.length-i,r=n.slice(i).join("/");return(o>0?"../".repeat(o):"")+r||j(t)}function Oe(s){return s.split("/").map(t=>t.replace(/%/g,"%25").replace(/ /g,"%20").replace(/\(/g,"%28").replace(/\)/g,"%29").replace(/</g,"%3C").replace(/>/g,"%3E").replace(/\?/g,"%3F").replace(/#/g,"%23")).join("/")}function ie(s){let t=0,e=0,n=0,i=rt(s),o=[];for(let r of i){if(r.kind==="code"){o.push(r.text);continue}let a=r.text,l=Ye(a);a=l.markdown,n+=l.count;let d=Ke(a);a=d.markdown,e+=d.count;let u=Ue(a);a=u.markdown,t+=u.count,o.push(a)}return{markdown:o.join(""),calloutsRewritten:t,highlightsRewritten:e,mathMacrosRewritten:n}}function se(s){let t=0,e=0,n=0,i=rt(s),o=[];for(let r of i){if(r.kind==="code"){o.push(r.text);continue}let a=r.text,l=We(a);a=l.markdown,t+=l.count;let d=Xe(a);a=d.markdown,e+=d.count;let u=Ze(a);a=u.markdown,n+=u.count,o.push(a)}return{markdown:o.join(""),calloutsRestored:t,highlightsRestored:e,mathMacrosRestored:n}}var Ie={note:"NOTE",info:"NOTE",summary:"NOTE",tldr:"NOTE",abstract:"NOTE",todo:"NOTE",question:"NOTE",faq:"NOTE",help:"NOTE",tip:"TIP",hint:"TIP",success:"TIP",done:"TIP",check:"TIP",example:"TIP",important:"IMPORTANT",warning:"WARNING",attention:"WARNING",caution:"CAUTION",danger:"CAUTION",error:"CAUTION",bug:"CAUTION",failure:"CAUTION",fail:"CAUTION",missing:"CAUTION"},Ge=new Set(["quote","cite"]),He=/^(\s*>\s*)\[!([a-zA-Z]+)\]([+-]?)(\s.*)?$/,oe=/^(\s*>\s*)\[!([A-Z]+)\]\s*<!--easygit-callout:original=([a-zA-Z]+),collapse=([+-]?)-->(\s.*)?$/,_e=new Set(["NOTE","TIP","IMPORTANT","WARNING","CAUTION"]);function Ue(s){let t=0,e=s.split(`
`);for(let n=0;n<e.length;n++){let i=e[n];if(je(i)||oe.test(i))continue;let o=i.match(He);if(!o)continue;let[,r,a,l,d]=o,u=a.toLowerCase();if(Ge.has(u)||a===a.toUpperCase()&&_e.has(a))continue;let c=Ie[u]??"NOTE",p=`<!--easygit-callout:original=${a},collapse=${l}-->`,h=d??"";e[n]=`${r}[!${c}] ${p}${h}`,t+=1}return{markdown:e.join(`
`),count:t}}function We(s){let t=0,e=s.split(`
`);for(let n=0;n<e.length;n++){let i=e[n].match(oe);if(!i)continue;let[,o,,r,a,l]=i,d=l??"";e[n]=`${o}[!${r}]${a}${d}`,t+=1}return{markdown:e.join(`
`),count:t}}function je(s){let t=0,e=0;for(;e<s.length;){if(s[e]===" "||s[e]===" "){e++;continue}if(s[e]===">"){if(t++,t>1)return!0;e++;continue}break}return!1}var Ve=/(?<!=)==(?=\S)([^\n=]+?[^=\s])==(?!=)/g,ze=/(?<!=)==([^\n=\s])==(?!=)/g,qe=/<mark>([^\n]+?)<\/mark>/g;function Ke(s){let t=0,e=lt(s),n=[];for(let i of e){if(i.kind==="math"){n.push(i.text);continue}let o=i.text.replace(Ve,(r,a)=>(t++,`<mark>${a}</mark>`)).replace(ze,(r,a)=>(t++,`<mark>${a}</mark>`));n.push(o)}return{markdown:n.join(""),count:t}}function Xe(s){let t=0,e=lt(s),n=[];for(let i of e){if(i.kind==="math"){n.push(i.text);continue}let o=i.text.replace(qe,(r,a)=>(t++,`==${a}==`));n.push(o)}return{markdown:n.join(""),count:t}}var at="\\hspace{0.5em}",Je=/<!--easygit-math:phantoms=(\[.*?\])-->$/;function Ye(s){let t=0,e=lt(s),n=[];for(let i of e){if(i.kind!=="math"){n.push(i.text);continue}let{transformed:o,records:r}=Qe(i.text);if(r.length===0){n.push(i.text);continue}t+=r.length;let a=`<!--easygit-math:phantoms=${JSON.stringify(r)}-->`;n.length>0?n[n.length-1]=n[n.length-1]+a:n.push(a),n.push(o)}return{markdown:n.join(""),count:t}}function Ze(s){let t=0,e=lt(s),n=[],i=null;for(let o of e)if(o.kind==="prose"){let r=o.text.match(Je);if(r)try{i=JSON.parse(r[1]),n.push(o.text.slice(0,o.text.length-r[0].length))}catch{n.push(o.text),i=null}else n.push(o.text)}else if(i){let{restored:r,restoredCount:a}=tn(o.text,i);t+=a,n.push(r),i=null}else n.push(o.text);return{markdown:n.join(""),count:t}}function Qe(s){let t=[],e="",n=0;for(;n<s.length;){if(s[n]==="\\"){let i=s.slice(n).match(/^\\(h|v)?phantom\s*\{/);if(i){let o=n+i[0].length,r=en(s,o);if(r!==-1){let a=i[1]??"",l=a==="h"?"hphantom":a==="v"?"vphantom":"phantom",d=s.slice(o,r);t.push({kind:l,args:d}),e+=at,n=r+1;continue}}}e+=s[n],n++}return{transformed:e,records:t}}function tn(s,t){let e="",n=0,i=0;for(;n<s.length;){if(s.slice(n,n+at.length)===at&&i<t.length){let o=t[i];e+=`\\${o.kind}{${o.args}}`,i++,n+=at.length;continue}e+=s[n],n++}return{restored:e,restoredCount:i}}function en(s,t){let e=1;for(let n=t;n<s.length;n++){let i=s[n];if(i==="\\"){n++;continue}if(i==="{")e++;else if(i==="}"&&(e--,e===0))return n}return-1}function lt(s){let t=[],e=0,n="",i=()=>{n.length!==0&&(t.push({kind:"prose",text:n}),n="")};for(;e<s.length;){if(s[e]==="$")if(s[e+1]==="$"){let o=nn(s,e+2,"$$");if(o!==-1){i(),t.push({kind:"math",text:s.slice(e,o+2)}),e=o+2;continue}}else{let o=sn(s,e);if(o!==-1){i(),t.push({kind:"math",text:s.slice(e,o+1)}),e=o+1;continue}}n+=s[e],e++}return i(),t}function nn(s,t,e){let n=t;for(;n<=s.length-e.length;){if(s[n]==="\\"){n+=2;continue}if(s.slice(n,n+e.length)===e)return n;n++}return-1}function sn(s,t){if(t+1>=s.length||/\s/.test(s[t+1]))return-1;let e=t+1;for(;e<s.length;){let n=s[e];if(n===`
`)return-1;if(n==="\\"){e+=2;continue}if(n==="$"&&!/\s/.test(s[e-1]))return e;e++}return-1}function on(s,t){let e={};for(let o=0;o<t.length;o++){let r=t[o];e[r]?e[r].push(o):e[r]=[o]}let i=[{buffer1index:-1,buffer2index:-1,chain:null}];for(let o=0;o<s.length;o++){let r=s[o],a=e[r]||[],l=0,d=i[0];for(let u=0;u<a.length;u++){let c=a[u],p;for(p=l;p<i.length&&!(i[p].buffer2index<c&&(p===i.length-1||i[p+1].buffer2index>c));p++);if(p<i.length){let h={buffer1index:o,buffer2index:c,chain:i[p]};if(l===i.length?i.push(d):i[l]=d,l=p+1,d=h,l===i.length)break}}i[l]=d}return i[i.length-1]}function re(s,t){let e=on(s,t),n=[],i=s.length,o=t.length;for(let r=e;r!==null;r=r.chain){let a=i-r.buffer1index-1,l=o-r.buffer2index-1;i=r.buffer1index,o=r.buffer2index,(a||l)&&n.push({buffer1:[i+1,a],buffer1Content:s.slice(i+1,i+1+a),buffer2:[o+1,l],buffer2Content:t.slice(o+1,o+1+l)})}return n.reverse(),n}function rn(s,t,e){let n=[];function i(l,d){n.push({ab:d,oStart:l.buffer1[0],oLength:l.buffer1[1],abStart:l.buffer2[0],abLength:l.buffer2[1]})}re(t,s).forEach(l=>i(l,"a")),re(t,e).forEach(l=>i(l,"b")),n.sort((l,d)=>l.oStart-d.oStart);let o=[],r=0;function a(l){l>r&&(o.push({stable:!0,buffer:"o",bufferStart:r,bufferLength:l-r,bufferContent:t.slice(r,l)}),r=l)}for(;n.length;){let l=n.shift(),d=l.oStart,u=l.oStart+l.oLength,c=[l];for(a(d);n.length;){let p=n[0],h=p.oStart;if(h>u)break;u=Math.max(u,h+p.oLength),c.push(n.shift())}if(c.length===1){if(l.abLength>0){let p=l.ab==="a"?s:e;o.push({stable:!0,buffer:l.ab,bufferStart:l.abStart,bufferLength:l.abLength,bufferContent:p.slice(l.abStart,l.abStart+l.abLength)})}}else{let p={a:[s.length,-1,t.length,-1],b:[e.length,-1,t.length,-1]};for(;c.length;){l=c.shift();let S=l.oStart,x=S+l.oLength,v=l.abStart,k=v+l.abLength,E=p[l.ab];E[0]=Math.min(v,E[0]),E[1]=Math.max(k,E[1]),E[2]=Math.min(S,E[2]),E[3]=Math.max(x,E[3])}let h=p.a[0]+(d-p.a[2]),y=p.a[1]+(u-p.a[3]),f=p.b[0]+(d-p.b[2]),m=p.b[1]+(u-p.b[3]),w={stable:!1,aStart:h,aLength:y-h,aContent:s.slice(h,y),oStart:d,oLength:u-d,oContent:t.slice(d,u),bStart:f,bLength:m-f,bContent:e.slice(f,m)};o.push(w)}r=u}return a(t.length),o}function an(s,t,e,n){n=Object.assign({excludeFalseConflicts:!0,stringSeparator:/\s+/},n),typeof s=="string"&&(s=s.split(n.stringSeparator)),typeof t=="string"&&(t=t.split(n.stringSeparator)),typeof e=="string"&&(e=e.split(n.stringSeparator));let o=[],r=rn(s,t,e),a=[];function l(){a.length&&o.push({ok:a}),a=[]}function d(u,c){if(u.length!==c.length)return!1;for(let p=0;p<u.length;p++)if(u[p]!==c[p])return!1;return!0}return r.forEach(u=>{u.stable?a.push(...u.bufferContent):n.excludeFalseConflicts&&d(u.aContent,u.bContent)?a.push(...u.aContent):(l(),o.push({conflict:{a:u.aContent,aIndex:u.aStart,o:u.oContent,oIndex:u.oStart,b:u.bContent,bIndex:u.bStart}}))}),l(),o}function ae(s,t,e,n){n=Object.assign({excludeFalseConflicts:!0,stringSeparator:/\s+/,label:{}},n);let o="<<<<<<<"+(n.label.a?` ${n.label.a}`:""),r="=======",a=">>>>>>>"+(n.label.b?` ${n.label.b}`:""),l=an(s,t,e,n),d=!1,u=[];return l.forEach(c=>{c.ok?u=u.concat(c.ok):c.conflict&&(d=!0,u=u.concat([o],c.conflict.a,[r],c.conflict.b,[a]))}),{conflict:d,result:u}}var ln=3,dt=class{constructor(t){this.deps=t}async syncMapping(t){let e=[];for(let n of t.destinations)e.push(await this.syncDestination(t,n));try{await this.pruneBackups()}catch{}return e}async syncDestination(t,e){let n=Date.now(),i={mappingId:t.id,destinationId:e.id,ok:!1,added:0,modified:0,deleted:0,conflicts:[],durationMs:0},o=this.deps.settings.auth;if(o.method==="none"||!o.token)return i.error="Not signed in. Configure auth in settings.",i.durationMs=Date.now()-n,i;let r=tt(o);if(r)return i.error=r,i.durationMs=Date.now()-n,i;let a=new B({token:o.token,baseUrl:G(o)}),l=0;for(;l<ln;){l+=1;try{let d=await this.runOnce(a,t,e);if(d==="retry"){await hn(gn(l));continue}return Object.assign(i,d),i.ok=!d.error,i.durationMs=Date.now()-n,i}catch(d){return i.error=d instanceof L||d instanceof Error?d.message:String(d),i.durationMs=Date.now()-n,i}}return i.error="Remote moved during sync; gave up after retries.",i.durationMs=Date.now()-n,i}async runOnce(t,e,n){let i={mappingId:e.id,destinationId:n.id,ok:!1,added:0,modified:0,deleted:0,conflicts:[],durationMs:0},o=await W(t,n.repoOwner,n.repoName,n.branch),r=await it(t,n.repoOwner,n.repoName,o.treeSha,n.remoteFolder),a=r.files;r.correctedPath&&r.correctedPath!==n.remoteFolder&&(this.deps.settings.debugLogging&&console.log(`Easy Git: remote folder case auto-corrected "${n.remoteFolder}" \u2192 "${r.correctedPath}" (${n.repoOwner}/${n.repoName})`),n.remoteFolder=r.correctedPath,await this.deps.saveSettings()),r.truncatedFallback&&this.deps.settings.showNotifications&&new $.Notice(`Easy Git (${e.name}): the ${n.repoOwner}/${n.repoName} tree is large enough that GitHub truncated the recursive listing. Walked it folder-by-folder to make sure nothing was missed.`,8e3),this.deps.settings.debugLogging&&console.log(`Easy Git: remote scan for ${n.repoOwner}/${n.repoName}:${n.branch}/${n.remoteFolder||"/"} \u2192 ${Object.keys(a).length} files at commit ${o.commitSha.slice(0,7)}`),e.rewriteWikilinks===void 0&&!e.rewriteWikilinksMigrated&&e.direction!=="pull"&&(e.rewriteWikilinks=!0,e.rewriteWikilinksMigrated=!0,this.deps.settings.showNotifications&&new $.Notice(`Easy Git (${e.name}): wikilinks will be rewritten to standard Markdown so GitHub renders images. This first sync touches every .md in the mapping.`,8e3));let l=await this.scanLocalFolder(e);i.skippedLarge=l.skipped,l.rewrittenWikilinks>0&&(i.rewrittenWikilinks=l.rewrittenWikilinks),l.unresolvedWikilinks>0&&(i.unresolvedWikilinks=l.unresolvedWikilinks),l.excalidrawMissingCompanion>0&&(i.excalidrawMissingCompanion=l.excalidrawMissingCompanion),l.calloutsRewritten>0&&(i.calloutsRewritten=l.calloutsRewritten),l.highlightsRewritten>0&&(i.highlightsRewritten=l.highlightsRewritten),l.mathMacrosRewritten>0&&(i.mathMacrosRewritten=l.mathMacrosRewritten);let d=n.lastSyncState?.files??{};for(let g of Object.keys(a))(O(g,l.excludePatterns)||O(I(e,g),l.excludePatterns))&&delete a[g];let u=e.direction==="pull"&&e.destinations.length>1?un(l.files,e,n):l.files,c=Jt({local:u,remote:a,lastState:d,direction:e.direction}),p=cn(new Date),h={extraActions:[],keepBothRenames:[]};if(c.conflicts.length>0){if(this.deps.settings.autoResolveByMtime!==!1){let D=dn(c.conflicts,l.files,d);i.autoResolvedConflicts=(i.autoResolvedConflicts??0)+D}if(this.deps.settings.autoMergeText!==!1){let D=await this.tryThreeWayMerges(t,n,e,c.conflicts,l.files,d,p);D>0&&(i.mergedConflicts=(i.mergedConflicts??0)+D)}let g=c.conflicts.some(D=>!D.resolution),P=c.conflicts;if(g&&(P=await this.deps.resolveConflicts(e,n,c.conflicts),!P))return i.conflicts=c.conflicts,i.error="Sync cancelled at conflict resolution.",i;h=Yt(P,e.direction)}for(let{from:g,to:P}of h.keepBothRenames){await this.renameInVault(e,g,P);let D=l.files[g];D&&(l.files[P]={...D,path:P},delete l.files[g])}let y=[...c.actions,...h.extraActions],f=0,m=0,w=0,S=0,x=g=>{g.calloutsRestored&&(m+=g.calloutsRestored),g.highlightsRestored&&(w+=g.highlightsRestored),g.mathMacrosRestored&&(S+=g.mathMacrosRestored)};for(let g of y)if(g.op==="pull-modify"){let P=I(e,g.path);await this.backupVaultFile(e,P,p)&&(f+=1),x(await this.applyPullModify(e,n,g,t))}else if(g.op==="pull-add")x(await this.applyPullModify(e,n,g,t));else if(g.op==="pull-delete"){let P=I(e,g.path);await this.backupVaultFile(e,P,p)&&(f+=1),await this.applyPullDelete(e,g)}f>0&&(i.backupsCreated=(i.backupsCreated??0)+f,i.backupFolder=`.easy-git-backup/${p}/`),m>0&&(i.calloutsRestored=m),w>0&&(i.highlightsRestored=w),S>0&&(i.mathMacrosRestored=S);let v=y.filter(g=>g.op==="push-add"||g.op==="push-modify"||g.op==="push-delete"),k;if(v.length>0)if(this.deps.settings.auth.provider==="forgejo"){let g=await this.pushViaContentsApi(t,e,n,v,l.files,a,o.commitSha);if(g===null)return"retry";k=g||void 0}else{let g=await this.buildAndPushCommit(t,e,n,o.commitSha,o.treeSha,v,l.files);if(g===null)return"retry";k=g}let E=pn(k??o.commitSha,o.treeSha,y,l.files,a,d);n.lastSyncState=E,n.lastSyncAt=Date.now(),n.lastSyncError=void 0,await this.deps.saveSettings();let F=[];for(let g of y)g.op!=="noop"&&(F.push(g.path),g.op==="push-add"||g.op==="pull-add"?i.added+=1:g.op==="push-modify"||g.op==="pull-modify"?i.modified+=1:(g.op==="push-delete"||g.op==="pull-delete")&&(i.deleted+=1));i.commitSha=k,i.conflicts=c.conflicts.map(g=>({...g})),i.changedPaths=F;let b=Y(n);if(e.direction==="push"&&c.informationalRemoteChanges.length>0){let g=c.informationalRemoteChanges.length;this.deps.settings.showNotifications&&new $.Notice(`Easy Git (${e.name} \u2192 ${b}): ${g} file${g===1?"":"s"} changed on remote (not pulled \u2014 push-only mapping).`)}if(e.direction==="pull"&&c.informationalLocalChanges.length>0){let g=c.informationalLocalChanges.length;this.deps.settings.showNotifications&&new $.Notice(`Easy Git (${e.name} \u2192 ${b}): ${g} file${g===1?"":"s"} changed locally (not pushed \u2014 pull-only mapping).`)}return i}rewriteContentCache=new Map;attachmentSourceMap=new Map;async scanLocalFolder(t){let e=ut(t.vaultFolder),n;if(e)n=this.deps.app.vault.getRoot();else{let x=t.vaultFolder.replace(/^\/+|\/+$/g,"");if(n=this.deps.app.vault.getFolderByPath(x),!n){let v=x.toLowerCase();for(let k of this.deps.app.vault.getAllFolders())if(k.path.toLowerCase()===v){n=k,t.vaultFolder=k.path,await this.deps.saveSettings();break}}}if(!n)throw new Error(`Vault folder "${t.vaultFolder}" no longer exists. Edit the mapping or restore the folder.`);let i={},o=[],r=await this.loadLocalIgnore(t),a=[".easygitignore",".easy-git-backup/**",...this.deps.settings.excludedPaths,...r],l=this.deps.settings.maxFileSizeBytes;this.rewriteContentCache=new Map,this.attachmentSourceMap=new Map;let d=Dt(t)&&t.direction!=="pull",u=0,c=0,p=0,h=0,y=0,f=0,m=[],w=[n];for(;w.length>0;){let x=w.pop();for(let v of x.children)if(v instanceof $.TFolder)w.push(v);else if(v instanceof $.TFile){let k=ce(t.vaultFolder,v.path);if(O(v.path,a)||O(k,a))continue;if(v.stat.size>l){o.push(k);continue}if(d&&v.extension==="md"){let E=await this.deps.app.vault.read(v),F=ee(E,{sourcePath:v.path,mappingVaultFolder:t.vaultFolder,mappingRemoteFolder:"",resolve:this.makeResolver()}),b=ie(F.markdown),g=b.markdown;u+=F.unresolvedCount,c+=F.rewrittenCount,p+=F.excalidrawMissingCompanion,h+=b.calloutsRewritten,y+=b.highlightsRewritten,f+=b.mathMacrosRewritten;for(let P of F.extraBlobs)m.push(P);this.rewriteContentCache.set(k,g),i[k]={path:k,sha:await X(g),size:new TextEncoder().encode(g).byteLength,mtime:v.stat.mtime}}else{let E=await this.computeLocalSha(v);i[k]={path:k,sha:E,size:v.stat.size,mtime:v.stat.mtime}}}}await this.augmentScanWithDotfiles(n,t,i,a,l);let S=new Set;for(let x of m){if(S.has(x.remoteRelPath))continue;S.add(x.remoteRelPath);let v=this.deps.app.vault.getFileByPath(x.vaultPath);if(!v)continue;if(v.stat.size>l){o.push(x.remoteRelPath);continue}let k=await this.computeLocalSha(v);i[x.remoteRelPath]={path:x.remoteRelPath,sha:k,size:v.stat.size,mtime:v.stat.mtime},this.attachmentSourceMap.set(x.remoteRelPath,x.vaultPath)}return{files:i,skipped:o,unresolvedWikilinks:u,rewrittenWikilinks:c,excalidrawMissingCompanion:p,calloutsRewritten:h,highlightsRewritten:y,mathMacrosRewritten:f,excludePatterns:a}}async tryThreeWayMerges(t,e,n,i,o,r,a){let l=0,d=Dt(n)&&n.direction!=="pull";for(let u of i){if(u.resolution||u.kind!=="both-edited"||!u.localSha||!u.remoteSha)continue;let c=r[u.path]?.sha;if(!(!c||!H(u.path)||u.path.toLowerCase().endsWith(".md")&&d))try{let[h,y]=await Promise.all([st(t,e.repoOwner,e.repoName,c),st(t,e.repoOwner,e.repoName,u.remoteSha)]),f=ot(h.content),m=ot(y.content),w=I(n,u.path),S=this.deps.app.vault.getFileByPath(w);if(!S)continue;let x=await this.deps.app.vault.read(S),v=ae(x.split(`
`),f.split(`
`),m.split(`
`));if(v.conflict)continue;await this.backupVaultFile(n,w,a);let k=v.result.join(`
`);await this.deps.app.vault.modify(S,k);let E=await X(k);o[u.path]={path:u.path,sha:E,size:new TextEncoder().encode(k).byteLength,mtime:Date.now()},u.resolution="keep-local",l+=1}catch{continue}}return l}async backupVaultFile(t,e,n){let i=this.deps.app.vault.adapter,o=this.deps.app.vault.getFileByPath(e),r=o?!0:await i.exists(e);if(!o&&!r)return!1;let a=`.easy-git-backup/${n}/${e}`;await Nt(this.deps.app,Bt(a));try{if(o)if(H(o.path)){let l=await this.deps.app.vault.read(o);await this.deps.app.vault.create(a,l)}else{let l=await this.deps.app.vault.readBinary(o);await this.deps.app.vault.createBinary(a,l)}else if(H(e)){let l=await i.read(e);await i.write(a,l)}else{let l=await i.readBinary(e);await i.writeBinary(a,l)}return!0}catch(l){throw new Error(`Easy Git: could not back up "${e}" before overwrite (${l instanceof Error?l.message:String(l)}). Sync aborted to prevent data loss.`)}}async pruneBackups(){let t=this.deps.settings.backupRetentionDays;if(!t||t<=0)return 0;let e=this.deps.app.vault.getFolderByPath(".easy-git-backup");if(!e)return 0;let n=Date.now()-t*24*60*60*1e3,i=0,o=[...e.children];for(let r of o){if(!(r instanceof $.TFolder))continue;let a=r.name.match(/^(\d{4})-(\d{2})-(\d{2})-(\d{2})(\d{2})(\d{2})$/);if(!a)continue;let l=new Date(Number(a[1]),Number(a[2])-1,Number(a[3]),Number(a[4]),Number(a[5]),Number(a[6])).getTime();if(!(Number.isNaN(l)||l>=n))try{await this.deps.app.vault.adapter.rmdir(r.path,!0),i+=1}catch{}}return i}async loadLocalIgnore(t){let e=ut(t.vaultFolder)?"":t.vaultFolder.replace(/^\/+|\/+$/g,""),n=e?`${e}/.easygitignore`:".easygitignore",i=this.deps.app.vault.getFileByPath(n);if(!i)return[];try{return(await this.deps.app.vault.read(i)).split(/\r?\n/)}catch{return[]}}makeResolver(){let t=this.deps.app.metadataCache;return(e,n)=>{let i=t.getFirstLinkpathDest(e,n);return i?{path:i.path}:null}}async computeLocalSha(t){if(H(t.path)){let n=await this.deps.app.vault.read(t);return X(n)}let e=await this.deps.app.vault.readBinary(t);return Pt(e)}async augmentScanWithDotfiles(t,e,n,i,o){let r=this.deps.app.vault.adapter,a=[],l=[t];for(;l.length>0;){let d=l.pop();a.push(d.path);for(let u of d.children)u instanceof $.TFolder&&l.push(u)}for(let d of a){let u;try{u=await r.list(d||"/")}catch{continue}for(let c of u.files){if(!c.substring(c.lastIndexOf("/")+1).startsWith("."))continue;let h=ce(e.vaultFolder,c);if(n[h]||O(c,i)||O(h,i))continue;let y;try{y=await r.stat(c)}catch{continue}if(!y||y.size>o)continue;let f;try{if(H(c)){let m=await r.read(c);f=await X(m)}else{let m=await r.readBinary(c);f=await Pt(m)}}catch{continue}n[h]={path:h,sha:f,size:y.size,mtime:y.mtime}}}}async applyPullModify(t,e,n,i){let o={};if(!n.remoteSha)return o;let r=I(t,n.path);try{let a=await st(i,e.repoOwner,e.repoName,n.remoteSha);await Nt(this.deps.app,Bt(r));let l=this.deps.app.vault.getFileByPath(r)??ct(this.deps.app,r);if(H(r)&&a.encoding!=="base64-binary"){let u=ot(a.content);if(Dt(t)&&t.direction!=="push"&&r.toLowerCase().endsWith(".md")){let c=se(u);u=c.markdown,c.calloutsRestored>0&&(o.calloutsRestored=c.calloutsRestored),c.highlightsRestored>0&&(o.highlightsRestored=c.highlightsRestored),c.mathMacrosRestored>0&&(o.mathMacrosRestored=c.mathMacrosRestored)}l?await this.deps.app.vault.modify(l,u):await this.createOrFallbackText(r,u)}else{let u=Lt(a.content);l?await this.deps.app.vault.modifyBinary(l,u):await this.createOrFallbackBinary(r,u)}}catch(a){throw de(a,r)}return o}async createOrFallbackText(t,e){try{await this.deps.app.vault.create(t,e);return}catch(i){if(!/already exists/i.test(String(i)))throw i}let n=ct(this.deps.app,t);if(n){await this.deps.app.vault.modify(n,e);return}await this.deps.app.vault.adapter.write(t,e)}async createOrFallbackBinary(t,e){try{await this.deps.app.vault.createBinary(t,e);return}catch(i){if(!/already exists/i.test(String(i)))throw i}let n=ct(this.deps.app,t);if(n){await this.deps.app.vault.modifyBinary(n,e);return}await this.deps.app.vault.adapter.writeBinary(t,e)}async applyPullDelete(t,e){let n=I(t,e.path);try{let i=this.deps.app.vault.getFileByPath(n)??ct(this.deps.app,n);i&&await this.deps.app.fileManager.trashFile(i)}catch(i){throw de(i,n)}}async renameInVault(t,e,n){let i=I(t,e),o=I(t,n),r=this.deps.app.vault.getFileByPath(i);r&&(await Nt(this.deps.app,Bt(o)),await this.deps.app.fileManager.renameFile(r,o))}async pushViaContentsApi(t,e,n,i,o,r,a){let l="",d=a,u=`[Easy Git] ${e.name}`;for(let c of i){let p=le(n,c.path);try{if(c.op==="push-delete"){let h=r[c.path]?.sha;if(!h)continue;l=await qt(t,n.repoOwner,n.repoName,p,`${u}: delete ${c.path}`,n.branch,h)}else{let h=await this.readVaultFile(e,c.path),y=c.op==="push-modify"?r[c.path]?.sha:void 0,f=c.op==="push-add"?"add":"update";l=(await zt(t,n.repoOwner,n.repoName,p,h.base64,`${u}: ${f} ${c.path}`,n.branch,y)).commitSha}d=l}catch(h){if(h instanceof L&&(h.status===409||h.status===422)){let y=await W(t,n.repoOwner,n.repoName,n.branch);if(y.commitSha&&y.commitSha!==d)return null}throw h}}return l}async buildAndPushCommit(t,e,n,i,o,r,a){let l=[],d=0,u=0,c=0,p=[];for(let S of r){let x=le(n,S.path);if(p.push(S.path),S.op==="push-delete"){l.push({path:x,mode:"100644",type:"blob",sha:null}),c+=1;continue}if(!a[S.path])continue;let k=await this.readVaultFile(e,S.path),E=await Wt(t,n.repoOwner,n.repoName,k.base64);l.push({path:x,mode:"100644",type:"blob",sha:E}),S.op==="push-add"?d+=1:S.op==="push-modify"&&(u+=1)}let h=await jt(t,n.repoOwner,n.repoName,o,l),y=e.commitTemplate??this.deps.settings.defaultCommitTemplate,f=Qt(y,{mappingName:e.name,vaultName:this.deps.app.vault.getName(),added:d,modified:u,deleted:c,files:p}),m=await Vt(t,n.repoOwner,n.repoName,f,h,[i]);return await Kt(t,n.repoOwner,n.repoName,n.branch,m)?m:null}async readVaultFile(t,e){let n=this.rewriteContentCache.get(e);if(n!==void 0){let l=new TextEncoder().encode(n);return{base64:J(l.buffer)}}let i=this.attachmentSourceMap.get(e);if(i){let l=this.deps.app.vault.getFileByPath(i);if(!l)throw new Error(`Attachment source not found: ${i}`);let d=await this.deps.app.vault.readBinary(l);return{base64:J(d)}}let o=I(t,e),r=this.deps.app.vault.getFileByPath(o);if(!r)throw new Error(`Vault file not found: ${o}`);if(H(r.path)){let l=await this.deps.app.vault.read(r),d=new TextEncoder().encode(l);return{base64:J(d.buffer)}}let a=await this.deps.app.vault.readBinary(r);return{base64:J(a)}}};function Dt(s){return s.rewriteWikilinks!==!1}function Y(s){let t=s.remoteFolder||"/";return`${s.repoOwner}/${s.repoName}:${s.branch}/${t}`}function cn(s){let t=e=>String(e).padStart(2,"0");return`${s.getFullYear()}-${t(s.getMonth()+1)}-${t(s.getDate())}-${t(s.getHours())}${t(s.getMinutes())}${t(s.getSeconds())}`}function dn(s,t,e){let i=0;for(let o of s){if(o.resolution||o.kind!=="both-edited")continue;let r=t[o.path],a=e[o.path];!r?.mtime||!a?.mtime||r.mtime>a.mtime+2e3&&(o.resolution="keep-local",i+=1)}return i}function un(s,t,e){let n=new Set;for(let o of t.destinations){if(o.id===e.id)continue;let r=o.lastSyncState?.files;if(r)for(let a of Object.keys(r))n.add(a)}if(n.size===0)return s;let i={};for(let[o,r]of Object.entries(s))n.has(o)||(i[o]=r);return i}function pn(s,t,e,n,i,o){let r={...o};for(let a of e)switch(a.op){case"push-add":case"push-modify":{let l=n[a.path];l&&(r[a.path]={sha:l.sha,size:l.size,mtime:l.mtime});break}case"pull-add":case"pull-modify":{let l=i[a.path],d=n[a.path];l&&(r[a.path]={sha:l.sha,size:l.size,mtime:d?.mtime});break}case"push-delete":case"pull-delete":delete r[a.path];break;default:break}for(let[a,l]of Object.entries(n)){if(r[a])continue;let d=i[a];d&&d.sha===l.sha&&(r[a]={sha:l.sha,size:l.size,mtime:l.mtime})}return{baseCommitSha:s,baseTreeSha:t,files:r}}function I(s,t){if(ut(s.vaultFolder))return(0,$.normalizePath)(t);let e=s.vaultFolder.replace(/^\/+|\/+$/g,"");return(0,$.normalizePath)(e?`${e}/${t}`:t)}function ut(s){let t=s.trim();return t===""||t==="/"}function le(s,t){let e=s.remoteFolder.replace(/^\/+|\/+$/g,"");return e?`${e}/${t}`:t}function ce(s,t){if(ut(s))return t;let e=s.replace(/^\/+|\/+$/g,"");return e?t===e?"":t.startsWith(e+"/")?t.slice(e.length+1):t:t}function Bt(s){let t=s.lastIndexOf("/");return t<0?"":s.slice(0,t)}async function Nt(s,t){!t||s.vault.getFolderByPath(t)||await s.vault.createFolder(t).catch(n=>{if(!/already exists/i.test(String(n)))throw n})}function ct(s,t){let e=t.toLowerCase();for(let n of s.vault.getFiles())if(n.path.toLowerCase()===e)return n;return null}function de(s,t){let e=s instanceof Error?s.message:String(s);if(e.includes(t))return s instanceof Error?s:new Error(e);let n=new Error(`${e} (at "${t}")`);return s instanceof Error&&s.stack&&(n.stack=s.stack),n}function hn(s){return new Promise(t=>window.setTimeout(t,s))}function gn(s){return Math.min(9e3,1e3*Math.pow(3,s-1))}var ht=require("obsidian");function V(s){let t=s;return typeof t.setDestructive=="function"?t.setDestructive():typeof t.setWarning=="function"&&t.setWarning(),s}var pt=class extends ht.Modal{opts;constructor(t,e){super(t),this.opts=e}onOpen(){let{contentEl:t}=this;if(t.empty(),t.addClass("easy-git-sync-log"),t.createEl("h2",{text:"Sync log"}),this.opts.entries.length===0){t.createEl("p",{text:"No sync activity recorded yet. Trigger a sync from the ribbon or settings to populate the log.",attr:{style:"color: var(--text-muted);"}}),this.renderFooter(t);return}t.createEl("p",{text:`Most recent ${this.opts.entries.length} sync run${this.opts.entries.length===1?"":"s"} across all mappings. Newest first.`,attr:{style:"color: var(--text-muted); margin-bottom: 1rem;"}});let e=t.createDiv({cls:"easy-git-log-list"});for(let n of this.opts.entries)this.renderEntry(e,n);this.renderFooter(t)}renderEntry(t,e){let n=t.createDiv({cls:"easy-git-log-row"});e.ok||n.addClass("is-error");let i=n.createDiv({cls:"easy-git-log-header"});i.createSpan({cls:"easy-git-log-status",text:e.ok?"\u2713":"\u2717"});let o=i.createDiv({cls:"easy-git-log-meta"});o.createDiv({cls:"easy-git-log-title",text:`${e.mappingName} \u2192 ${e.destinationLabel}`}),o.createDiv({cls:"easy-git-log-sub",text:`${fn(e.timestamp)} \xB7 trigger: ${e.trigger} \xB7 ${e.durationMs}ms`});let r=e.ok?e.filesTouched===0?"up to date":`${e.added}+ ${e.modified}~ ${e.deleted}-`:"error";if(i.createSpan({cls:"easy-git-log-summary",text:r}),e.error&&n.createDiv({cls:"easy-git-log-error"}).setText(e.error),e.changedPaths&&e.changedPaths.length>0){let a=n.createDiv({cls:"easy-git-log-files"});a.createEl("div",{cls:"easy-git-log-files-label",text:`Files (${e.changedPaths.length}):`});let l=a.createEl("ul");for(let d of e.changedPaths.slice(0,30))l.createEl("li",{text:d});e.changedPaths.length>30&&a.createEl("div",{cls:"easy-git-log-files-more",text:`\u2026and ${e.changedPaths.length-30} more`})}e.conflicts>0&&n.createDiv({cls:"easy-git-log-conflicts",text:`Conflicts shown: ${e.conflicts}`})}renderFooter(t){new ht.Setting(t).addButton(e=>V(e).setButtonText("Clear log").onClick(async()=>{await this.opts.onClear(),this.close()})).addButton(e=>e.setCta().setButtonText("Close").onClick(()=>this.close()))}onClose(){this.contentEl.empty()}};function fn(s){let t=Math.floor((Date.now()-s)/1e3);if(t<5)return"just now";if(t<60)return`${t}s ago`;let e=Math.floor(t/60);if(e<60)return`${e}m ago`;let n=Math.floor(e/60);return n<24?`${n}h ago`:`${Math.floor(n/24)}d ago`}var R=require("obsidian");var M=require("obsidian");var Ot=require("obsidian");async function gt(s){try{return{login:(await s.request("GET","/user")).login}}catch(t){if(t instanceof L&&t.status===403&&!s.isGitHub())return await s.request("GET","/repos/search?limit=1"),{login:""};throw t}}async function ue(s=Ft,t="repo"){let e=await(0,Ot.requestUrl)({url:"https://github.com/login/device/code",method:"POST",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({client_id:s,scope:t}),throw:!1});if(e.status<200||e.status>=300)throw new Error(`Device flow start failed: HTTP ${e.status} ${e.text}`);let n=JSON.parse(e.text);if(n.error)throw new Error(`Device flow start failed: ${n.error_description??n.error}`);return n}async function pe(s,t=Ft){let e=await(0,Ot.requestUrl)({url:"https://github.com/login/oauth/access_token",method:"POST",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({client_id:t,device_code:s,grant_type:"urn:ietf:params:oauth:grant-type:device_code"}),throw:!1});if(e.status<200||e.status>=300)throw new Error(`Token poll HTTP ${e.status}: ${e.text}`);return JSON.parse(e.text)}function z(s){if(s.method==="none"||!s.token)return"Not signed in";let t=s.username?` as ${s.username}`:"";return s.method==="pat"?`Signed in via PAT${t}`:s.method==="oauth"?`Signed in via GitHub${t}`:"Not signed in"}function N(s){if(s instanceof L){if(s.status===401)return"Invalid or expired token. Please sign in again.";if(s.status===403){if(s.rateLimitRemaining===0){let t=s.rateLimitReset?new Date(s.rateLimitReset*1e3):null;return`Rate limited${t?` until ${t.toLocaleTimeString()}`:""}.`}return"Forbidden. Check that the token has repository read/write access."}return s.status===404?"Not found. Check repo name and your access.":s.message}return s instanceof Error?s.message:String(s)}var A=require("obsidian");var ft=class extends A.FuzzySuggestModal{folders;onChoose;constructor(t,e){super(t),this.onChoose=e,this.folders=mn(t),this.setPlaceholder("Choose a folder in your vault")}getItems(){return this.folders}getItemText(t){return t.path===""?"/":t.path}onChooseItem(t){this.onChoose(t)}};function mn(s){let t=[],e=s.vault.getRoot();return n(e),t;function n(i){t.push(i);for(let o of i.children)o instanceof A.TFolder&&n(o)}}var mt=class extends A.FuzzySuggestModal{constructor(e,n,i,o){super(e);this.client=n;this.provider=o;this.onChoose=i,this.setPlaceholder("Loading repositories\u2026")}repos=[];onChoose;async load(){try{this.repos=this.provider==="forgejo"?await Ht(this.client):await Gt(this.client),this.setPlaceholder("Choose a repository"),this.inputEl?.dispatchEvent(new Event("input"))}catch(e){new A.Notice("Easy Git: failed to load repositories \u2014 "+N(e)),this.close()}}getItems(){return this.repos}getItemText(e){return`${e.fullName}${e.private?" [private]":""}`}onChooseItem(e){this.onChoose(e)}},yt=class extends A.FuzzySuggestModal{constructor(e,n,i,o,r){super(e);this.client=n;this.owner=i;this.repo=o;this.onChoose=r,this.setPlaceholder("Loading branches\u2026")}branches=[];onChoose;async load(){try{this.branches=await _t(this.client,this.owner,this.repo),this.setPlaceholder("Choose a branch"),this.inputEl?.dispatchEvent(new Event("input"))}catch(e){new A.Notice("Easy Git: failed to load branches \u2014 "+N(e)),this.close()}}getItems(){return this.branches}getItemText(e){return e.name}onChooseItem(e){this.onChoose(e)}},bt=class extends A.FuzzySuggestModal{constructor(e,n,i,o,r,a){super(e);this.client=n;this.owner=i;this.repo=o;this.branch=r;this.onChoose=a,this.setPlaceholder("Loading folders\u2026")}paths=["/"];onChoose;async load(){try{let e=this.branch;e||(e=(await nt(this.client,this.owner,this.repo)).defaultBranch);let n=await W(this.client,this.owner,this.repo,e),{entries:i,truncated:o}=await $t(this.client,this.owner,this.repo,n.treeSha),r=i.filter(a=>a.type==="tree").map(a=>a.path).sort();this.paths=["/",...r],o&&new A.Notice(`Easy Git: ${this.owner}/${this.repo} has too many entries to list completely; you may need to type a deep path manually.`),this.setPlaceholder("Choose a folder in the repo (or / for repo root)"),this.inputEl?.dispatchEvent(new Event("input"))}catch(e){new A.Notice("Easy Git: failed to load folders \u2014 "+N(e)),this.close()}}getItems(){return this.paths}getItemText(e){return e==="/"?"/ (repo root)":e}onChooseItem(e){this.onChoose(e==="/"?"":e)}},Z=class extends A.FuzzySuggestModal{items;onChoose;constructor(t,e,n,i){super(t),this.items=e,this.onChoose=i,this.setPlaceholder(n)}getItems(){return this.items}getItemText(t){return t.name}onChooseItem(t){this.onChoose(t)}};var vt=require("obsidian");var q=class extends vt.Modal{opts;constructor(t,e){super(t),this.opts=e}onOpen(){let{contentEl:t}=this;t.empty(),t.createEl("h2",{text:this.opts.title}),t.createEl("p",{text:this.opts.message}),new vt.Setting(t).addButton(e=>e.setButtonText(this.opts.cancelText??"Cancel").onClick(()=>this.close())).addButton(e=>{e.setButtonText(this.opts.confirmText??"Confirm").onClick(async()=>{this.close(),await this.opts.onConfirm()}),this.opts.destructive?V(e):e.setCta()})}onClose(){this.contentEl.empty()}};var T=require("obsidian");var wt=class extends T.Modal{opts;bodyEl;currentReport=null;constructor(t,e){super(t),this.opts=e}onOpen(){let{contentEl:t}=this;t.empty(),t.addClass("easy-git-diagnose");let{destination:e}=this.opts;t.createEl("h2",{text:`Diagnose: ${e.repoOwner}/${e.repoName}:${e.branch}`}),t.createEl("p",{text:`Vault folder: ${this.opts.mapping.vaultFolder||"(whole vault)"} \u2192 Remote folder: ${e.remoteFolder||"/"} (direction: ${this.opts.mapping.direction})`,attr:{style:"color: var(--text-muted); margin-top: -0.5rem;"}}),this.bodyEl=t.createDiv(),this.bodyEl.createEl("p",{text:"Gathering diagnostics\u2026",attr:{style:"color: var(--text-muted);"}}),this.run()}async run(){let t=await this.buildReport();this.currentReport=t,this.render(t)}async buildReport(){let t=[],{mapping:e,destination:n,settings:i,pluginVersion:o}=this.opts,r={method:i.auth.method,username:i.auth.username,scopes:i.auth.scopes},a=null;if(i.auth.method!=="none"&&i.auth.token){a=new B({token:i.auth.token,baseUrl:G(i.auth)});try{let b=await gt(a);r.username=b.login||void 0}catch(b){r.tokenError=N(b),t.push("Auth check failed: "+r.tokenError)}}else r.tokenError="no token configured";let l=null,d=null,u={};if(a)try{let b=await W(a,n.repoOwner,n.repoName,n.branch);l={headCommit:b.commitSha,treeSha:b.treeSha,commitUrl:`https://github.com/${n.repoOwner}/${n.repoName}/commit/${b.commitSha}`,folderUrl:`https://github.com/${n.repoOwner}/${n.repoName}/tree/${encodeURIComponent(n.branch)}/${n.remoteFolder}`};let g=await it(a,n.repoOwner,n.repoName,b.treeSha,n.remoteFolder);u=g.files,d={fileCount:Object.keys(g.files).length,correctedPath:g.correctedPath,truncatedFallback:g.truncatedFallback,paths:Object.keys(g.files).sort()}}catch(b){t.push("Remote scan failed: "+N(b))}let c=await this.scanLocalWithExcludes(e),p=c.filter(b=>!b.excluded),h=c.filter(b=>b.excluded),y=c.filter(b=>b.dotfile),f=new Set(p.map(b=>b.path)),m=await this.readLocalIgnore(e),w=n.lastSyncState?.files??{},S=Object.keys(w).sort(),x=new Set(d?.paths??[]),v=[...x].filter(b=>!f.has(b)).sort(),k=[...f].filter(b=>!x.has(b)).sort(),E=S.filter(b=>!x.has(b)),F=[];for(let b of p){let g=u[b.path];g&&b.sha&&b.sha!==g.sha&&F.push({path:b.path,remoteSha:g.sha,localSha:b.sha})}return{generatedAt:new Date().toISOString(),pluginVersion:o,obsidianVersion:this.detectObsidianVersion(),platform:this.detectPlatform(),mapping:{id:e.id,name:e.name,direction:e.direction,vaultFolder:e.vaultFolder||"(whole vault)",autoMode:this.formatAutoMode(e),rewriteWikilinks:e.rewriteWikilinks===!1?"off":e.rewriteWikilinks===!0?"on":"auto (default on)",destinationCount:e.destinations.length},destination:{id:n.id,repo:`${n.repoOwner}/${n.repoName}`,branch:n.branch,remoteFolder:n.remoteFolder||"(repo root)",lastSyncAt:n.lastSyncAt?new Date(n.lastSyncAt).toISOString():void 0,lastSyncCommit:n.lastSyncState?.baseCommitSha,lastSyncError:n.lastSyncError},auth:r,branch:l,remoteScan:d,localScan:{includedCount:p.length,excludedCount:h.length,dotfileCount:y.length,paths:p.map(b=>b.path).sort(),excludedPaths:h.map(b=>({path:b.path,pattern:b.excludedBy??"?"}))},lastState:{count:S.length,paths:S},diff:{onRemoteNotLocal:v,onLocalNotRemote:k,inLastStateNotRemote:E,bothButDifferentSha:F},settings:{maxFileSizeMB:Math.round(i.maxFileSizeBytes/(1024*1024)),excludedPathsGlobal:[...i.excludedPaths],localIgnore:m,autoResolveByMtime:i.autoResolveByMtime!==!1,autoMergeText:i.autoMergeText!==!1,backupRetentionDays:i.backupRetentionDays??0,showNotifications:i.showNotifications,debugLogging:i.debugLogging},syncLogTail:(i.syncLog??[]).filter(b=>b.destinationId===n.id).slice(0,5),fetchErrors:t}}detectObsidianVersion(){return this.app.appVersion??"unknown"}detectPlatform(){let t=[];return T.Platform.isMobile&&t.push("mobile"),T.Platform.isDesktop&&t.push("desktop"),T.Platform.isMacOS&&t.push("macOS"),T.Platform.isWin&&t.push("Windows"),T.Platform.isLinux&&t.push("Linux"),T.Platform.isIosApp&&t.push("iOS"),T.Platform.isAndroidApp&&t.push("Android"),t.length>0?t.join(", "):"unknown"}formatAutoMode(t){let e=t.autoMode;return!e||e.kind==="off"?"off":e.kind==="interval"?`every ${e.minutes}m`:e.kind==="startup"?"on startup":e.kind==="onSave"?`on save (debounce ${e.debounceMs}ms)`:JSON.stringify(e)}async scanLocalWithExcludes(t){let n=!t.vaultFolder||t.vaultFolder==="/"?this.app.vault.getRoot():this.app.vault.getFolderByPath(t.vaultFolder);if(!n)return[];let i=this.opts.settings.excludedPaths??[],o=[".easygitignore",".easy-git-backup/**"],r=await this.readLocalIgnore(t),a=[...o,...i,...r],l=[],d=[],u=[n];for(;u.length>0;){let p=u.pop();d.push(p.path);for(let h of p.children)if(h instanceof T.TFolder)u.push(h);else if(h instanceof T.TFile){let y=this.relativeTo(t.vaultFolder,h.path),f=this.firstMatchingPattern(h.path,y,a);l.push({path:y,size:h.stat.size,excluded:f!==null,excludedBy:f??void 0,dotfile:!1})}}let c=new Set(l.map(p=>p.path));for(let p of d)try{let h=await this.app.vault.adapter.list(p||"/");for(let y of h.files){if(!y.substring(y.lastIndexOf("/")+1).startsWith("."))continue;let m=this.relativeTo(t.vaultFolder,y);if(c.has(m))continue;let w=this.firstMatchingPattern(y,m,a),S;try{S=(await this.app.vault.adapter.stat(y))?.size}catch{}l.push({path:m,size:S,excluded:w!==null,excludedBy:w??void 0,dotfile:!0}),c.add(m)}}catch{}return l.sort((p,h)=>p.path.localeCompare(h.path)),l}firstMatchingPattern(t,e,n){for(let i of n)if(!(!i||i.trim().startsWith("#"))&&(O(t,[i])||O(e,[i])))return i;return null}async readLocalIgnore(t){let e=!t.vaultFolder||t.vaultFolder==="/"?"":t.vaultFolder.replace(/^\/+|\/+$/g,""),n=e?`${e}/.easygitignore`:".easygitignore";try{return await this.app.vault.adapter.exists(n)?(await this.app.vault.adapter.read(n)).split(`
`).map(r=>r.trim()).filter(r=>r.length>0&&!r.startsWith("#")):[]}catch{return[]}}relativeTo(t,e){if(!t||t==="/")return e;let n=t.replace(/^\/+|\/+$/g,"");return e===n?"":e.startsWith(n+"/")?e.slice(n.length+1):e}render(t){if(this.bodyEl.empty(),t.fetchErrors.length>0){let c=this.bodyEl.createDiv({cls:"easy-git-diag-error"});c.createEl("p",{text:"Some live data could not be fetched:"});let p=c.createEl("ul");for(let h of t.fetchErrors)p.createEl("li",{text:h})}if(t.branch){let c=this.bodyEl.createDiv({cls:"easy-git-diag-section"});c.createEl("h3",{text:"Branch state (live)"});let p=c.createEl("p");p.createSpan({text:"Head commit: "});let h=p.createEl("a",{text:t.branch.headCommit.slice(0,7),href:t.branch.commitUrl});h.setAttr("target","_blank"),h.setAttr("rel","noopener"),p.createSpan({text:" \xB7 "});let y=p.createEl("a",{text:"Open folder on GitHub",href:t.branch.folderUrl});y.setAttr("target","_blank"),y.setAttr("rel","noopener")}t.remoteScan?.correctedPath&&this.bodyEl.createEl("p",{cls:"easy-git-diag-note"}).setText(`Note: remote folder path auto-corrected from "${this.opts.destination.remoteFolder}" to "${t.remoteScan.correctedPath}" (case mismatch).`),t.remoteScan?.truncatedFallback&&this.bodyEl.createEl("p",{cls:"easy-git-diag-note"}).setText("Note: the recursive tree response was truncated. Walked it folder-by-folder.");let e=this.bodyEl.createDiv({cls:"easy-git-diag-section"});e.createEl("h3",{text:"Summary"});let n=e.createEl("ul");n.createEl("li",{text:`Plugin: Easy Git ${t.pluginVersion}`}),n.createEl("li",{text:`Obsidian: ${t.obsidianVersion} (${t.platform})`}),n.createEl("li",{text:`Auth: ${this.formatAuthSummary(t.auth)}`}),n.createEl("li",{text:`Files on remote: ${t.remoteScan?.fileCount??"n/a"}`}),n.createEl("li",{text:`Files in vault (included): ${t.localScan.includedCount}`+(t.localScan.dotfileCount>0?` (${t.localScan.dotfileCount} dotfiles)`:"")}),t.localScan.excludedCount>0&&n.createEl("li",{text:`Files in vault but excluded by patterns: ${t.localScan.excludedCount}`}),n.createEl("li",{text:`Files in last-sync state: ${t.lastState.count}`});let i=t.diff.onRemoteNotLocal.length>0?"color: var(--text-accent);":"";if(n.createEl("li",{text:`Would be pulled on next sync: ${t.diff.onRemoteNotLocal.length}`,attr:{style:i}}),t.diff.bothButDifferentSha.length>0&&n.createEl("li",{text:`Files differing on both sides (SHA mismatch): ${t.diff.bothButDifferentSha.length}`,attr:{style:"color: var(--text-accent);"}}),this.renderKV(this.bodyEl,"Mapping",{Name:t.mapping.name,ID:t.mapping.id,Direction:t.mapping.direction,"Vault folder":t.mapping.vaultFolder,"Auto mode":t.mapping.autoMode,"Wikilink rewrite":t.mapping.rewriteWikilinks,Destinations:String(t.mapping.destinationCount)}),this.renderKV(this.bodyEl,"Destination",{Repo:t.destination.repo,Branch:t.destination.branch,"Remote folder":t.destination.remoteFolder,"Last sync at":t.destination.lastSyncAt??"(never)","Last sync commit":t.destination.lastSyncCommit?.slice(0,7)??"(none)","Last sync error":t.destination.lastSyncError??"(none)"}),this.renderFileList(this.bodyEl,"Would be pulled on next sync",t.diff.onRemoteNotLocal,"Paths on remote but not in your vault (after exclude filter).",t.diff.onRemoteNotLocal.length>0?"easy-git-diag-positive":void 0),t.diff.bothButDifferentSha.length>0){let c=this.bodyEl.createDiv({cls:"easy-git-diag-section"});c.createEl("h3",{text:`Same path, different content (${t.diff.bothButDifferentSha.length})`}),c.createEl("p",{text:"These paths exist on both sides but with different SHAs. Pull-only \u2192 remote will overwrite (with backup). Bidirectional \u2192 may go to the conflict modal.",attr:{style:"color: var(--text-muted);"}});let p=c.createEl("ul",{cls:"easy-git-diag-files"});for(let h of t.diff.bothButDifferentSha.slice(0,50)){let y=p.createEl("li");y.createSpan({text:h.path}),y.createSpan({cls:"easy-git-diag-tag",text:` remote=${h.remoteSha.slice(0,7)} local=${h.localSha.slice(0,7)}`})}}if(t.diff.onLocalNotRemote.length>0&&this.renderFileList(this.bodyEl,"In vault, not on remote",t.diff.onLocalNotRemote,"Push-only or bidirectional mappings push these. Pull-only leaves them alone (they're considered user-added)."),t.diff.inLastStateNotRemote.length>0&&this.renderFileList(this.bodyEl,"In last-sync state but not on remote",t.diff.inLastStateNotRemote,"These were pulled previously but are gone from remote now. Pull-only \u2192 next sync deletes them locally (with backup), unless you edited them locally."),this.renderFileList(this.bodyEl,"All files on remote",t.remoteScan?.paths??[],"Every file Easy Git found in the configured remote folder."),this.renderFileList(this.bodyEl,"All files in local vault folder",t.localScan.paths,"Every file in your vault folder that passes the exclude filter."),t.localScan.excludedPaths.length>0){let c=this.bodyEl.createDiv({cls:"easy-git-diag-section"});c.createEl("h3",{text:`Excluded by patterns (${t.localScan.excludedPaths.length})`}),c.createEl("p",{text:"These files are in your vault but match an exclude pattern. If you expected one of them to sync, remove the matching pattern.",attr:{style:"color: var(--text-muted);"}});let p=c.createEl("ul",{cls:"easy-git-diag-files"});for(let h of t.localScan.excludedPaths.slice(0,50)){let y=p.createEl("li");y.createSpan({text:h.path}),y.createSpan({cls:"easy-git-diag-tag",text:` \u2190 ${h.pattern}`})}}if(this.renderKV(this.bodyEl,"Settings",{"Max file size (MB)":String(t.settings.maxFileSizeMB),"Auto-resolve by mtime":String(t.settings.autoResolveByMtime),"3-way merge text":String(t.settings.autoMergeText),"Backup retention (days)":t.settings.backupRetentionDays>0?String(t.settings.backupRetentionDays):"keep all",Notifications:String(t.settings.showNotifications),"Debug logging":String(t.settings.debugLogging)}),t.settings.excludedPathsGlobal.length>0&&this.renderFileList(this.bodyEl,"Global excluded paths",t.settings.excludedPathsGlobal,"From Easy Git \u2192 Excluded paths."),t.settings.localIgnore.length>0&&this.renderFileList(this.bodyEl,".easygitignore (per-folder)",t.settings.localIgnore,"Patterns read from a .easygitignore file at the root of the vault folder."),t.syncLogTail.length>0){let c=this.bodyEl.createDiv({cls:"easy-git-diag-section"});c.createEl("h3",{text:`Recent sync log entries for this destination (${t.syncLogTail.length})`});let p=c.createEl("ul",{cls:"easy-git-diag-files"});for(let h of t.syncLogTail){let y=p.createEl("li"),f=new Date(h.timestamp).toISOString(),m=h.ok?"\u2713":"\u2717";y.setText(`${m} ${f} \xB7 ${h.trigger} \xB7 +${h.added} ~${h.modified} -${h.deleted}`+(h.error?` error: ${h.error}`:""))}}let o=this.bodyEl.createDiv({cls:"easy-git-diag-section"});o.createEl("h3",{text:"If something's missing"});let r=o.createEl("ul");r.createEl("li",{text:"Click the commit SHA above to verify it matches the latest commit on GitHub."}),r.createEl("li",{text:`Empty folders aren't tracked by git. A "new folder" only exists once you commit at least one file inside it (often a .gitkeep placeholder).`}),r.createEl("li",{text:"If you pushed via the GitHub web UI, double-check the file landed on the correct branch \u2014 the mapping syncs only the branch shown above."}),r.createEl("li",{text:"A repo-level .gitignore can block files from ever being committed. If git didn't commit it, Easy Git can't see it."}),r.createEl("li",{text:`If remote shows the file but local doesn't get it after a sync, try the destination's "Reset sync state" button.`});let a=this.bodyEl.createDiv({cls:"easy-git-diag-footer"}),l=a.createEl("button",{text:"Copy as text"});l.addClass("mod-cta"),l.onclick=async()=>{let c=this.toMarkdown(t);await this.copyToClipboard(c),new T.Notice("Easy Git: diagnostic report copied to clipboard.")};let d=a.createEl("button",{text:"Re-run"});d.onclick=()=>{this.bodyEl.empty(),this.bodyEl.createEl("p",{text:"Gathering diagnostics\u2026",attr:{style:"color: var(--text-muted);"}}),this.run()};let u=a.createEl("button",{text:"Close"});u.onclick=()=>this.close()}formatAuthSummary(t){if(t.tokenError)return`${t.method} (${t.tokenError})`;let e=t.username??"(unknown user)",n=t.scopes&&t.scopes.length>0?`, scopes: ${t.scopes.join(",")}`:"";return`${t.method} as ${e}${n}`}renderKV(t,e,n){let i=t.createDiv({cls:"easy-git-diag-section"});i.createEl("h3",{text:e});let o=i.createEl("ul",{cls:"easy-git-diag-kv"});for(let[r,a]of Object.entries(n)){let l=o.createEl("li");l.createSpan({cls:"easy-git-diag-kv-key",text:`${r}:`}),l.createSpan({cls:"easy-git-diag-kv-val",text:" "+a})}}renderFileList(t,e,n,i,o){let r=t.createDiv({cls:"easy-git-diag-section"});if(o&&r.addClass(o),r.createEl("h3",{text:`${e} (${n.length})`}),i&&r.createEl("p",{text:i,attr:{style:"color: var(--text-muted);"}}),n.length===0){r.createEl("p",{text:"(none)",attr:{style:"color: var(--text-faint);"}});return}let a=r.createEl("ul",{cls:"easy-git-diag-files"});for(let l of n.slice(0,200))a.createEl("li",{text:l||"(root)"});n.length>200&&r.createEl("p",{text:`\u2026and ${n.length-200} more. (Use "Copy as text" to get the full list.)`,attr:{style:"color: var(--text-muted);"}})}toMarkdown(t){let e=[],n=(r,a)=>e.push("#".repeat(r)+" "+a,""),i=(r,a)=>e.push(`- **${r}:** ${a}`),o=r=>{if(r.length===0){e.push("(none)");return}for(let a of r)e.push(`- ${a||"(root)"}`)};if(n(1,"Easy Git diagnostic report"),e.push(`Generated: ${t.generatedAt}`,`Plugin: Easy Git ${t.pluginVersion}`,`Obsidian: ${t.obsidianVersion} (${t.platform})`,""),t.fetchErrors.length>0&&(n(2,"Fetch errors"),o(t.fetchErrors),e.push("")),n(2,"Mapping"),i("Name",t.mapping.name),i("ID",t.mapping.id),i("Direction",t.mapping.direction),i("Vault folder",t.mapping.vaultFolder),i("Auto mode",t.mapping.autoMode),i("Wikilink rewrite",t.mapping.rewriteWikilinks),i("Destinations",String(t.mapping.destinationCount)),e.push(""),n(2,"Destination"),i("Repo",t.destination.repo),i("Branch",t.destination.branch),i("Remote folder",t.destination.remoteFolder),i("Last sync at",t.destination.lastSyncAt??"(never)"),i("Last sync commit",t.destination.lastSyncCommit??"(none)"),i("Last sync error",t.destination.lastSyncError??"(none)"),e.push(""),n(2,"Auth"),i("Method",t.auth.method),t.auth.username&&i("Username",t.auth.username),t.auth.scopes&&t.auth.scopes.length>0&&i("Scopes",t.auth.scopes.join(",")),t.auth.tokenError&&i("Error",t.auth.tokenError),e.push(""),t.branch&&(n(2,"Branch state (live)"),i("Head commit",t.branch.headCommit),i("Tree SHA",t.branch.treeSha),i("Commit URL",t.branch.commitUrl),i("Folder URL",t.branch.folderUrl),e.push("")),t.remoteScan?.correctedPath&&e.push(`> NOTE: remote folder path auto-corrected from "${this.opts.destination.remoteFolder}" to "${t.remoteScan.correctedPath}" (case mismatch).`,""),t.remoteScan?.truncatedFallback&&e.push("> NOTE: the recursive tree response was truncated. Walked it folder-by-folder.",""),n(2,"Counts"),i("Files on remote",String(t.remoteScan?.fileCount??"n/a")),i("Files in vault (included)",String(t.localScan.includedCount)),i("Dotfiles in vault",String(t.localScan.dotfileCount)),i("Files in vault excluded by patterns",String(t.localScan.excludedCount)),i("Files in last-sync state",String(t.lastState.count)),i("Would be pulled on next sync",String(t.diff.onRemoteNotLocal.length)),i("Same path, different SHA",String(t.diff.bothButDifferentSha.length)),i("In vault, not on remote",String(t.diff.onLocalNotRemote.length)),i("In last-sync state, not on remote",String(t.diff.inLastStateNotRemote.length)),e.push(""),n(2,"Would be pulled on next sync"),o(t.diff.onRemoteNotLocal),e.push(""),t.diff.bothButDifferentSha.length>0){n(2,"Same path, different SHA");for(let r of t.diff.bothButDifferentSha)e.push(`- ${r.path} (remote ${r.remoteSha.slice(0,7)} \u2194 local ${r.localSha.slice(0,7)})`);e.push("")}if(t.diff.onLocalNotRemote.length>0&&(n(2,"In vault, not on remote"),o(t.diff.onLocalNotRemote),e.push("")),t.diff.inLastStateNotRemote.length>0&&(n(2,"In last-sync state but not on remote"),o(t.diff.inLastStateNotRemote),e.push("")),n(2,"All files on remote"),o(t.remoteScan?.paths??[]),e.push(""),n(2,"All files in vault folder (included)"),o(t.localScan.paths),e.push(""),t.localScan.excludedPaths.length>0){n(2,"Excluded by patterns");for(let r of t.localScan.excludedPaths)e.push(`- ${r.path} \u2190 \`${r.pattern}\``);e.push("")}if(n(2,"Settings"),i("Max file size (MB)",String(t.settings.maxFileSizeMB)),i("Auto-resolve by mtime",String(t.settings.autoResolveByMtime)),i("3-way merge text",String(t.settings.autoMergeText)),i("Backup retention (days)",t.settings.backupRetentionDays>0?String(t.settings.backupRetentionDays):"keep all"),i("Notifications",String(t.settings.showNotifications)),i("Debug logging",String(t.settings.debugLogging)),e.push(""),n(2,"Global excluded paths"),o(t.settings.excludedPathsGlobal),e.push(""),t.settings.localIgnore.length>0&&(n(2,".easygitignore (per-folder)"),o(t.settings.localIgnore),e.push("")),t.syncLogTail.length>0){n(2,"Recent sync log entries (this destination)");for(let r of t.syncLogTail){let a=new Date(r.timestamp).toISOString(),l=r.ok?"ok":"FAIL";e.push(`- ${a} [${l}] trigger=${r.trigger} +${r.added} ~${r.modified} -${r.deleted}`+(r.error?` error: ${r.error}`:""))}e.push("")}return e.join(`
`)}async copyToClipboard(t){await navigator.clipboard.writeText(t)}onClose(){this.contentEl.empty()}};var St=class extends M.Modal{mapping;opts;isNew;client;intervalSetting;destRefs=new Map;destinationsListEl;destinationsHeader;vaultFolderPicked;constructor(t,e){super(t),this.opts=e,this.isNew=!e.initial,this.mapping=e.initial?structuredClone(e.initial):yn(),Array.isArray(this.mapping.destinations)||(this.mapping.destinations=[]),this.vaultFolderPicked=!this.isNew,this.client=e.auth.method!=="none"&&e.auth.token?new B({token:e.auth.token,baseUrl:G(e.auth)}):null}onOpen(){let{contentEl:t}=this;t.empty(),t.addClass("easy-git-modal-form"),t.createEl("h2",{text:this.isNew?"Add folder mapping":"Edit folder mapping"}),new M.Setting(t).setName("Name").setDesc("Display name for this mapping").addText(e=>e.setPlaceholder("Work notes").setValue(this.mapping.name).onChange(n=>this.mapping.name=n)),new M.Setting(t).setName("Vault folder").setDesc("Folder inside your Obsidian vault. Pick the vault root for whole-vault sync.").addButton(e=>{e.setButtonText(this.vaultFolderPicked?he(this.mapping.vaultFolder):"Choose folder\u2026"),e.buttonEl.addClass("easy-git-picker-button"),e.onClick(()=>{new ft(this.app,n=>{this.mapping.vaultFolder=n.path,this.vaultFolderPicked=!0,e.setButtonText(he(n.path))}).open()})}),this.destinationsHeader=t.createDiv({cls:"easy-git-section-header"}),this.updateDestinationsHeader(),this.destinationsListEl=t.createDiv({cls:"easy-git-destinations-list"}),this.renderDestinationsList(),new M.Setting(t).addButton(e=>e.setButtonText("+ Add destination").onClick(()=>{let n={id:U(),repoOwner:"",repoName:"",branch:"",remoteFolder:""};this.mapping.destinations.push(n),this.renderDestinationsList(),this.updateDestinationsHeader()})),new M.Setting(t).setName("Direction").setDesc("Which way changes flow").addDropdown(e=>e.addOptions({push:"Push only (vault \u2192 remote)",pull:"Pull only (remote \u2192 vault)",both:"Both (bidirectional)"}).setValue(this.mapping.direction).onChange(n=>this.mapping.direction=n)),new M.Setting(t).setName("Auto mode").setDesc("When to sync automatically").addDropdown(e=>e.addOptions({off:"Off (manual only)",interval:"On interval",startup:"On Obsidian startup",onSave:"On file save (debounced)"}).setValue(this.mapping.autoMode.kind).onChange(n=>{this.mapping.autoMode=bn(n,this.mapping.autoMode),this.refreshIntervalSetting()})),this.intervalSetting=new M.Setting(t).setName("Interval (minutes)").setDesc("How often to sync (only used when Auto mode = On interval)").addText(e=>e.setPlaceholder("15").setValue(this.mapping.autoMode.kind==="interval"?String(this.mapping.autoMode.minutes):"15").onChange(n=>{let i=parseInt(n,10);Number.isFinite(i)&&i>0&&(this.mapping.autoMode={kind:"interval",minutes:i})})),this.refreshIntervalSetting(),new M.Setting(t).setName("Commit message template").setDesc("Optional. Leave empty to use the global default.").addText(e=>e.setPlaceholder("Use global default").setValue(this.mapping.commitTemplate??"").onChange(n=>{this.mapping.commitTemplate=n.length>0?n:void 0})),new M.Setting(t).setName("GitHub rendering pass (wikilinks, callouts, highlights, math)").setDesc("At push: rewrites ![[image.png]] to , Obsidian callouts to GitHub-compatible types, ==highlights== to <mark>, and KaTeX-blocked \\phantom to \\hspace. Each rewrite embeds a marker so the pull side restores the original Obsidian source losslessly. Your vault files are never modified.").addToggle(e=>e.setValue(this.mapping.rewriteWikilinks!==!1).onChange(n=>{this.mapping.rewriteWikilinks=n})),new M.Setting(t).addButton(e=>e.setButtonText("Cancel").onClick(()=>this.close())).addButton(e=>e.setCta().setButtonText("Save").onClick(()=>void this.save()))}updateDestinationsHeader(){if(!this.destinationsHeader)return;this.destinationsHeader.empty();let t=this.mapping.destinations.length;this.destinationsHeader.createEl("h3",{text:`Destinations${t>0?` (${t})`:""}`}),this.destinationsHeader.createEl("p",{cls:"easy-git-section-sub",text:t===0?"Add at least one destination \u2014 a repo + branch + path to sync this folder to.":"One mapping can push to several places. Each destination tracks its own sync state."})}renderDestinationsList(){if(this.destinationsListEl){this.destinationsListEl.empty(),this.destRefs.clear();for(let t of this.mapping.destinations)this.renderDestinationCard(this.destinationsListEl,t)}}renderDestinationCard(t,e){let n=t.createDiv({cls:"easy-git-destination-card"}),i=n.createDiv({cls:"easy-git-destination-row"});i.createSpan({cls:"easy-git-destination-label",text:"Repository"});let o=i.createEl("button",{text:e.repoOwner?`${e.repoOwner}/${e.repoName}`:"Choose repo\u2026"});o.addClass("easy-git-picker-button"),o.onclick=()=>{if(!this.client){new M.Notice("Please configure authentication first.");return}let f=new mt(this.app,this.client,m=>{e.repoOwner=m.owner,e.repoName=m.name,e.branch||(e.branch=m.defaultBranch),o.setText(m.fullName);let w=this.destRefs.get(e.id);w&&e.branch&&w.branchBtn.setText(e.branch)},this.opts.auth.provider);f.open(),f.load()};let r=n.createDiv({cls:"easy-git-destination-row"});r.createSpan({cls:"easy-git-destination-label",text:"Branch"});let a=r.createEl("button",{text:e.branch||"Choose branch\u2026"});a.addClass("easy-git-picker-button"),a.onclick=()=>{if(!this.client){new M.Notice("Please configure authentication first.");return}if(!e.repoOwner||!e.repoName){new M.Notice("Please choose a repository first.");return}let f=new yt(this.app,this.client,e.repoOwner,e.repoName,m=>{e.branch=m.name,a.setText(m.name)});f.open(),f.load()};let l=n.createDiv({cls:"easy-git-destination-row"});l.createSpan({cls:"easy-git-destination-label",text:"Remote path"});let d=l.createEl("input",{type:"text"});d.placeholder="notes/ (empty = repo root)",d.value=e.remoteFolder,d.oninput=()=>{e.remoteFolder=d.value.trim()};let u=l.createEl("button",{text:"Choose\u2026"});u.addClass("easy-git-picker-inline"),u.onclick=()=>{if(!this.client){new M.Notice("Please configure authentication first.");return}if(!e.repoOwner||!e.repoName){new M.Notice("Please choose a repository first.");return}let f=new bt(this.app,this.client,e.repoOwner,e.repoName,e.branch,m=>{e.remoteFolder=m,d.value=m});f.open(),f.load()};let c=n.createDiv({cls:"easy-git-destination-actions"}),p=c.createEl("button",{text:"Diagnose"});p.setAttr("title","Show a live snapshot of what Easy Git sees for this destination \u2014 files on remote, files locally, what's excluded, what's tracked."),(!e.repoOwner||!e.repoName||!e.branch)&&(p.disabled=!0),p.onclick=()=>{if(!this.opts.settings||!this.opts.pluginVersion){new M.Notice("Easy Git: diagnose isn't wired up from this entry point. Open settings \u2192 Edit mapping \u2192 Diagnose.");return}new wt(this.app,{mapping:this.mapping,destination:e,settings:this.opts.settings,pluginVersion:this.opts.pluginVersion}).open()};let h=c.createEl("button",{text:"Reset sync state"});h.setAttr("title","Clear this destination's sync history. Next sync re-scans the full remote tree as if it were a first run."),e.lastSyncState||(h.disabled=!0),h.onclick=()=>{let f=`${e.repoOwner}/${e.repoName}:${e.branch}`;new q(this.app,{title:"Reset sync state?",message:`Clear sync history for ${f}. The next sync will treat every remote file as new. For pull-only mappings this re-pulls the entire remote folder. For bidirectional mappings, anything where local and remote differ will surface in the conflict modal. Your local files are never deleted by this action.`,confirmText:"Reset",destructive:!0,onConfirm:async()=>{e.lastSyncState=void 0,e.lastSyncAt=void 0,e.lastSyncError=void 0,this.renderDestinationsList(),this.updateDestinationsHeader(),new M.Notice("Easy Git: sync state cleared for this destination.")}}).open()};let y=c.createEl("button",{text:"Remove"});y.addClass("mod-warning"),y.onclick=()=>{this.mapping.destinations=this.mapping.destinations.filter(f=>f.id!==e.id),this.renderDestinationsList(),this.updateDestinationsHeader()},this.destRefs.set(e.id,{cardEl:n,repoBtn:o,branchBtn:a,pathInput:d})}refreshIntervalSetting(){if(!this.intervalSetting)return;let t=this.mapping.autoMode.kind==="interval";this.intervalSetting.settingEl.style.display=t?"":"none"}async save(){let t=vn(this.mapping,this.vaultFolderPicked);if(t){new M.Notice("Easy Git: "+t);return}for(let e of this.mapping.destinations){if(e.repoOwner&&e.repoName&&!e.branch&&this.client)try{let n=await nt(this.client,e.repoOwner,e.repoName);e.branch=n.defaultBranch}catch(n){new M.Notice(`Easy Git: cannot determine default branch for ${e.repoOwner}/${e.repoName} \u2014 ${N(n)}`);return}e.remoteFolder.startsWith("/")&&(e.remoteFolder=e.remoteFolder.replace(/^\/+/,"")),e.remoteFolder.endsWith("/")&&(e.remoteFolder=e.remoteFolder.replace(/\/+$/,""))}await this.opts.onSave(this.mapping),this.close()}onClose(){this.contentEl.empty()}};function he(s){let t=(s??"").trim();return t===""||t==="/"?"Whole vault":t}function yn(){return{id:U(),name:"",vaultFolder:"",direction:"both",autoMode:{kind:"off"},rewriteWikilinks:!0,destinations:[]}}function bn(s,t){switch(s){case"interval":return t.kind==="interval"?t:{kind:"interval",minutes:15};case"startup":return{kind:"startup"};case"onSave":return t.kind==="onSave"?t:{kind:"onSave",debounceMs:1e4};case"off":default:return{kind:"off"}}}function vn(s,t){if(!s.name.trim())return"Name is required.";if(!t)return"Choose a vault folder (or pick the vault root for whole-vault sync).";if(!s.destinations||s.destinations.length===0)return"Add at least one destination.";for(let e=0;e<s.destinations.length;e++){let n=s.destinations[e];if(!n.repoOwner||!n.repoName)return`Destination ${e+1}: choose a repository.`;if(n.remoteFolder.startsWith("/"))return`Destination ${e+1}: remote folder path cannot start with '/'.`}return s.autoMode.kind==="interval"&&s.autoMode.minutes<1?"Interval must be at least 1 minute.":null}var _=require("obsidian");var xt=class extends _.Modal{opts;cancelled=!1;codeResponse=null;statusEl;constructor(t,e){super(t),this.opts=e}onOpen(){let{contentEl:t}=this;t.empty(),t.createEl("h2",{text:"Sign in with GitHub"}),this.statusEl=t.createDiv({cls:"easy-git-device-status",text:"Requesting device code\u2026"}),new _.Setting(t).addButton(e=>e.setButtonText("Cancel").onClick(()=>{this.cancelled=!0,this.close()})),this.startFlow()}onClose(){this.cancelled=!0,this.opts.onCancel?.(),this.contentEl.empty()}async startFlow(){let t;try{t=await ue()}catch(e){this.setStatus("Failed to start Device Flow: "+(e instanceof Error?e.message:String(e)),!0);return}this.cancelled||(this.codeResponse=t,this.renderCode(t),this.poll(t))}renderCode(t){let{contentEl:e}=this;e.empty(),e.createEl("h2",{text:"Sign in with GitHub"}),e.createEl("p",{text:"Open the link below in your browser and enter the code:"});let n=e.createDiv({cls:"easy-git-device-code",text:t.user_code});new _.Setting(e).addButton(i=>i.setButtonText("Copy code").onClick(()=>{navigator.clipboard.writeText(t.user_code),new _.Notice("Code copied to clipboard.")})).addButton(i=>i.setCta().setButtonText("Open browser").onClick(()=>{window.open(t.verification_uri,"_blank")})),e.createEl("p",{text:`Or visit ${t.verification_uri} manually.`,attr:{style:"font-size: 0.85em; color: var(--text-muted);"}}),this.statusEl=e.createDiv({cls:"easy-git-device-status",text:"Waiting for authorization\u2026"}),new _.Setting(e).addButton(i=>i.setButtonText("Cancel").onClick(()=>{this.cancelled=!0,this.close()})),this.attachKeepalive(n)}async attachKeepalive(t){}async poll(t){let e=Date.now(),n=(t.interval??5)*1e3;for(;!this.cancelled;){if(Date.now()-e>t.expires_in*1e3){this.setStatus("Code expired. Please reopen Sign in.",!0);return}if(await wn(n),this.cancelled)return;let i;try{i=await pe(t.device_code)}catch(o){this.setStatus("Network error while polling: "+(o instanceof Error?o.message:String(o)),!0);return}if(i.access_token){await this.opts.onSuccess({token:i.access_token,scope:i.scope}),this.cancelled=!0,this.close();return}if(i.error!=="authorization_pending"){if(i.error==="slow_down"){n+=5e3;continue}if(i.error==="expired_token"){this.setStatus("Code expired. Please reopen Sign in.",!0);return}if(i.error==="access_denied"){this.setStatus("Access denied. Sign-in cancelled.",!0);return}if(i.error){this.setStatus("Sign-in failed: "+(i.error_description??i.error),!0);return}}}}setStatus(t,e=!1){this.statusEl&&(this.statusEl.setText(t),this.statusEl.toggleClass("is-error",e))}};function wn(s){return new Promise(t=>window.setTimeout(t,s))}var kt=class extends R.PluginSettingTab{plugin;mappingRowRefs=new Map;constructor(t,e){super(t,e),this.plugin=e}display(){this.render()}render(){let{containerEl:t}=this;t.empty(),t.addClass("easy-git-settings"),this.mappingRowRefs.clear(),this.collapsibleSection(t,"Authentication",!0,e=>this.renderAuthSection(e)),this.collapsibleSection(t,"Folder mappings",!0,e=>this.renderMappingsSection(e)),this.collapsibleSection(t,"Conflict handling",!1,e=>this.renderConflictHandlingSection(e)),this.collapsibleSection(t,"Backups",!1,e=>this.renderBackupsSection(e)),this.collapsibleSection(t,"Sync behaviour",!1,e=>this.renderSyncBehaviourSection(e)),this.collapsibleSection(t,"Excluded paths",!1,e=>this.renderExcludedPathsSection(e)),this.collapsibleSection(t,"Notifications & diagnostics",!1,e=>this.renderDiagnosticsSection(e)),this.flatSection(t,"About",e=>this.renderAboutSection(e))}flatSection(t,e,n){let i=t.createDiv({cls:"easy-git-section easy-git-section-flat"});i.createDiv({cls:"easy-git-section-summary"}).createSpan({cls:"easy-git-section-title",text:e});let r=i.createDiv({cls:"easy-git-section-body"});n(r)}collapsibleSection(t,e,n,i){let o=t.createEl("details",{cls:"easy-git-section"});n&&o.setAttr("open","");let r=o.createEl("summary",{cls:"easy-git-section-summary"});r.createSpan({cls:"easy-git-section-chevron",text:"\u25B8"}),r.createSpan({cls:"easy-git-section-title",text:e});let a=o.createDiv({cls:"easy-git-section-body"});i(a)}refreshSyncStates(){for(let[t,e]of this.mappingRowRefs){let n=this.plugin.settings.mappings.find(r=>r.id===t);if(!n)continue;let i=this.plugin.isSyncing(t),o=(n.destinations??[]).some(r=>!!r.lastSyncError);e.syncBtn.disabled=i,e.syncBtn.setText(i?"Syncing\u2026":"Sync"),e.statusEl.setText(ge(n,i)),e.statusEl.toggleClass("is-syncing",i),e.statusEl.toggleClass("is-error",!i&&o)}}renderAuthSection(t){let e=this.plugin.settings.auth,n=(e.provider??"github")==="github",i=t.createDiv({attr:{style:"margin-bottom: 0.75rem; color: var(--text-muted);"},text:z(e)});new R.Setting(t).setName("Provider").setDesc("Which Git hosting service to sync with.").addDropdown(o=>o.addOption("github","GitHub").addOption("forgejo","Forgejo / Gitea (self-hosted)").setValue(e.provider??"github").onChange(async r=>{this.plugin.settings.auth.provider=r,await this.plugin.saveSettings(),this.render()})),n||new R.Setting(t).setName("Instance URL").setDesc("Base URL of your Forgejo/Gitea server, e.g. http://192.168.1.100:3000").addText(o=>o.setPlaceholder("http://...").setValue(e.instanceUrl??"").onChange(async r=>{this.plugin.settings.auth.instanceUrl=r.trim()||void 0,await this.plugin.saveSettings()})),new R.Setting(t).setName("Personal access token").setDesc(n?"Paste a token with the `repo` scope. Create one at github.com/settings/tokens (fine-grained tokens with content read/write also work).":"Paste a token from your Forgejo instance (User settings \u2192 Applications \u2192 Access tokens). Needs repository read/write access.").addText(o=>{o.inputEl.type="password",o.setPlaceholder(n?"ghp_...":"Forgejo token").setValue(e.method==="pat"?e.token:"").onChange(async r=>{this.plugin.settings.auth={...this.plugin.settings.auth,method:r?"pat":"none",token:r,username:void 0},await this.plugin.saveSettings(),i.setText(z(this.plugin.settings.auth))})}),n&&new R.Setting(t).setName("Sign in with GitHub (Device Flow)").setDesc("Open a one-time code in your browser. Easier than copying a PAT. Requires the plugin's OAuth App client_id to be configured.").addButton(o=>o.setButtonText("Sign in").onClick(()=>{new xt(this.app,{onSuccess:async({token:a,scope:l})=>{this.plugin.settings.auth={...this.plugin.settings.auth,method:"oauth",token:a,scopes:l?l.split(","):void 0},await this.plugin.saveSettings(),i.setText(z(this.plugin.settings.auth)),new R.Notice("Easy Git: signed in with GitHub."),this.render()}}).open()})),new R.Setting(t).setName("Test connection").setDesc("Verifies the current token works.").addButton(o=>o.setButtonText("Test").onClick(async()=>{if(!this.plugin.settings.auth.token){new R.Notice("Easy Git: no token configured.");return}let r=tt(this.plugin.settings.auth);if(r){new R.Notice("Easy Git: "+r);return}try{let a=new B({token:this.plugin.settings.auth.token,baseUrl:G(this.plugin.settings.auth)}),l=await gt(a);this.plugin.settings.auth.username=l.login||void 0,await this.plugin.saveSettings(),i.setText(z(this.plugin.settings.auth)),new R.Notice(l.login?`Easy Git: connected as ${l.login}.`:"Easy Git: connected (token has repository access).")}catch(a){new R.Notice("Easy Git: "+N(a))}})),new R.Setting(t).setName("Clear credentials").setDesc("Removes the stored token.").addButton(o=>V(o).setButtonText("Clear").onClick(async()=>{this.plugin.settings.auth={method:"none",token:"",provider:this.plugin.settings.auth.provider,instanceUrl:this.plugin.settings.auth.instanceUrl},await this.plugin.saveSettings(),i.setText(z(this.plugin.settings.auth)),new R.Notice("Easy Git: credentials cleared."),this.render()}))}renderMappingsSection(t){t.createEl("p",{attr:{style:"margin-top:0; color: var(--text-muted);"},text:"Each mapping pairs a vault folder with a folder inside a GitHub repo."});let e=t.createDiv();if(this.plugin.settings.mappings.length===0)e.createEl("p",{text:"No mappings yet. Click the button below to add one.",attr:{style:"color: var(--text-muted);"}});else for(let n of this.plugin.settings.mappings)this.renderMappingRow(e,n);new R.Setting(t).addButton(n=>n.setButtonText("+ Add mapping").setCta().onClick(()=>this.openMappingModal())).addButton(n=>n.setButtonText("View sync log").setTooltip("See recent sync runs, errors, and which files were touched").onClick(()=>this.plugin.openSyncLog()))}renderMappingRow(t,e){let n=t.createDiv({cls:"easy-git-mapping-row"}),i=n.createDiv({cls:"easy-git-mapping-info"});i.createDiv({cls:"easy-git-mapping-name",text:e.name}),i.createDiv({cls:"easy-git-mapping-summary",text:Sn(e)});let o=this.plugin.isSyncing(e.id),r=(e.destinations??[]).some(h=>!!h.lastSyncError),a=i.createDiv({cls:"easy-git-mapping-status",text:ge(e,o)});o?a.addClass("is-syncing"):r&&a.addClass("is-error");let l=this.plugin.mappingHealth(e);if(!l.ok&&!o){let h=i.createDiv({cls:"easy-git-mapping-warning"});h.createSpan({cls:"easy-git-warning-icon",text:"\u26A0"}),h.createSpan({text:" "+l.reason})}let d=n.createDiv({cls:"easy-git-mapping-actions"});d.createSpan({cls:"easy-git-direction-icon",text:kn(e.direction)});let u=d.createEl("button",{text:o?"Syncing\u2026":"Sync"});u.disabled=o||!l.ok,l.ok||u.setAttr("title",l.reason),u.onclick=async()=>{this.plugin.isSyncing(e.id)||(u.disabled=!0,u.setText("Syncing\u2026"),await this.plugin.syncMapping(e.id,"manual"))},this.mappingRowRefs.set(e.id,{syncBtn:u,statusEl:a});let c=d.createEl("button",{text:"Edit"});c.onclick=()=>this.openMappingModal(e);let p=d.createEl("button",{text:"Delete"});p.addClass("mod-warning"),p.onclick=()=>{new q(this.app,{title:"Delete mapping",message:`Delete mapping "${e.name}"? Your local and remote files are not touched.`,confirmText:"Delete",destructive:!0,onConfirm:async()=>{this.plugin.settings.mappings=this.plugin.settings.mappings.filter(h=>h.id!==e.id),await this.plugin.saveSettings(),this.plugin.refreshAutoSyncWiring(),this.render()}}).open()}}openMappingModal(t){new St(this.app,{initial:t,auth:this.plugin.settings.auth,excludedPaths:this.plugin.settings.excludedPaths,settings:this.plugin.settings,pluginVersion:this.plugin.manifest.version,onSave:async e=>{let n=this.plugin.settings.mappings.findIndex(i=>i.id===e.id);n>=0?this.plugin.settings.mappings[n]=e:this.plugin.settings.mappings.push(e),await this.plugin.saveSettings(),this.plugin.refreshAutoSyncWiring(),this.render()}}).open()}renderConflictHandlingSection(t){t.createEl("p",{attr:{style:"margin-top:0; color: var(--text-muted);"},text:"Three layers stop a conflict modal from popping up when there's a safe answer: mtime auto-resolve catches single-user device drift, 3-way merge catches disjoint edits, anything left over is shown for you to decide."}),new R.Setting(t).setName("Auto-resolve by local mtime").setDesc("When local was clearly edited after the last sync (2s grace), keep local without asking. Safe direction only \u2014 the reverse stays a user decision.").addToggle(e=>e.setValue(this.plugin.settings.autoResolveByMtime!==!1).onChange(async n=>{this.plugin.settings.autoResolveByMtime=n,await this.plugin.saveSettings()})),new R.Setting(t).setName("3-way merge text files").setDesc("For Markdown and other text files where both sides edited disjoint regions, merge automatically using GitHub's stored base blob as the common ancestor.").addToggle(e=>e.setValue(this.plugin.settings.autoMergeText!==!1).onChange(async n=>{this.plugin.settings.autoMergeText=n,await this.plugin.saveSettings()}))}renderBackupsSection(t){t.createEl("p",{attr:{style:"margin-top:0; color: var(--text-muted);"},text:"Before any pull operation overwrites or deletes a local file, Easy Git copies the original to .easy-git-backup/<timestamp>/. Always on for push and bidirectional mappings; skipped only for pull-only mappings."}),new R.Setting(t).setName("Auto-prune backups older than (days)").setDesc("0 keeps every snapshot forever. Pruning runs at the end of each sync that touches the mapping.").addSlider(e=>e.setLimits(0,90,1).setValue(this.plugin.settings.backupRetentionDays??0).setDynamicTooltip().onChange(async n=>{this.plugin.settings.backupRetentionDays=n,await this.plugin.saveSettings()})),new R.Setting(t).setName("Open backup folder").setDesc("Reveal .easy-git-backup/ in the vault file explorer.").addButton(e=>e.setButtonText("Open").onClick(async()=>{let n=this.app.vault.getFolderByPath(".easy-git-backup");if(!n){new R.Notice("Easy Git: no backups yet. Backups appear after the first pull-modify or pull-delete.");return}let i=this.app.commands?.executeCommandById?.("file-explorer:reveal-active-file");new R.Notice(`Easy Git: backups live at "${n.path}/" in your vault.`)}))}renderSyncBehaviourSection(t){new R.Setting(t).setName("Default commit message template").setDesc("Tokens: {date}, {datetime}, {n}, {added}, {modified}, {deleted}, {files}, {vault}, {mapping}").addText(e=>e.setValue(this.plugin.settings.defaultCommitTemplate).onChange(async n=>{this.plugin.settings.defaultCommitTemplate=n,await this.plugin.saveSettings()})),new R.Setting(t).setName("Max file size (MB)").setDesc("Files larger than this are skipped (GitHub blob limit is 100 MB).").addSlider(e=>e.setLimits(1,100,1).setValue(Math.round(this.plugin.settings.maxFileSizeBytes/(1024*1024))).setDynamicTooltip().onChange(async n=>{this.plugin.settings.maxFileSizeBytes=n*1024*1024,await this.plugin.saveSettings()}))}renderExcludedPathsSection(t){t.createEl("p",{attr:{style:"margin-top:0; color: var(--text-muted);"},text:"Global glob patterns applied to every mapping. For per-mapping exclusions, drop a .easygitignore file at the mapping's vault folder root."}),new R.Setting(t).setName("Excluded paths").setDesc("One glob per line. Matched against vault-relative paths.").addTextArea(e=>{e.inputEl.rows=5,e.inputEl.addClass("easy-git-fullwidth-input"),e.setValue(this.plugin.settings.excludedPaths.join(`
`)).onChange(async n=>{this.plugin.settings.excludedPaths=n.split(`
`).map(i=>i.trim()).filter(i=>i.length>0),await this.plugin.saveSettings()})})}renderDiagnosticsSection(t){new R.Setting(t).setName("Show notifications").setDesc("Show a Notice after each sync run.").addToggle(e=>e.setValue(this.plugin.settings.showNotifications).onChange(async n=>{this.plugin.settings.showNotifications=n,await this.plugin.saveSettings()})),new R.Setting(t).setName("Debug logging").setDesc("Log sync details to the developer console.").addToggle(e=>e.setValue(this.plugin.settings.debugLogging).onChange(async n=>{this.plugin.settings.debugLogging=n,await this.plugin.saveSettings()})),new R.Setting(t).setName("Sync log").setDesc("See recent sync runs, errors, and which files were touched.").addButton(e=>e.setButtonText("Open sync log").onClick(()=>this.plugin.openSyncLog()))}renderAboutSection(t){let e=this.plugin.manifest.version;t.createEl("p",{text:`Easy Git ${e} by Saiki77`,attr:{style:"margin-top:0; color: var(--text-muted);"}});let n=t.createDiv({attr:{style:"display: flex; gap: 1rem; flex-wrap: wrap;"}}),i=(o,r)=>{let a=n.createEl("a",{text:r,href:o});a.setAttr("target","_blank"),a.setAttr("rel","noopener")};i("https://github.com/Saiki77/Easy-Git","Source"),i("https://github.com/Saiki77/Easy-Git/issues","Report an issue"),i("https://github.com/Saiki77/Easy-Git/blob/main/README.md","Documentation"),i("https://github.com/Saiki77/Easy-Git/blob/main/LICENSE","License (MIT)")}};function Sn(s){let t=xn(s.vaultFolder)?"Whole vault":s.vaultFolder,e=s.rewriteWikilinks===!1?" (raw wikilinks)":"",n=s.destinations??[];if(n.length===0)return`${t} \u2194 (no destinations)${e}`;let i=n[0],o=i.remoteFolder||"/",r=`${i.repoOwner}/${i.repoName}:${i.branch}/${o}`,a=n.length>1?` +${n.length-1} more`:"";return`${t} \u2194 ${r}${a}${e}`}function xn(s){let t=s.trim();return t===""||t==="/"}function ge(s,t=!1){if(t)return"Syncing\u2026";let e=s.destinations??[],n=e.find(o=>o.lastSyncError);if(n)return(e.length>1?`Last sync error (${n.repoOwner}/${n.repoName}): `:"Last sync error: ")+n.lastSyncError;let i;for(let o of e)o.lastSyncAt&&(!i||o.lastSyncAt>i)&&(i=o.lastSyncAt);if(i){let o=Math.floor((Date.now()-i)/6e4);if(o<1)return"Synced just now";if(o<60)return`Synced ${o}m ago`;let r=Math.floor(o/60);return r<24?`Synced ${r}h ago`:`Synced ${Math.floor(r/24)}d ago`}return"Not synced yet"}function kn(s){return s==="push"?"\u2191":s==="pull"?"\u2193":"\u2195"}var Q=require("obsidian"),Et=class extends Q.Modal{conflicts;mappingName;resolver;resolved=!1;constructor(t,e,n,i){super(t),this.mappingName=e,this.conflicts=n.map(o=>({...o,resolution:o.resolution})),this.resolver=i}onOpen(){let{contentEl:t}=this;t.empty(),t.createEl("h2",{text:"Resolve sync conflicts"}),t.createEl("p",{text:`${this.conflicts.length} file${this.conflicts.length===1?"":"s"} changed on both sides of "${this.mappingName}". Choose what to keep for each.`});let e=t.createDiv({cls:"easy-git-conflict-list"}),n=[];for(let o of this.conflicts)n.push(this.renderConflictRow(e,o));let i=new Q.Setting(t).setName("Apply to all unset").setDesc("Set a resolution for every conflict that doesn't have one yet.");i.addButton(o=>o.setButtonText("Keep local").onClick(()=>{this.bulkApply("keep-local"),n.forEach(r=>r())})),i.addButton(o=>o.setButtonText("Keep remote").onClick(()=>{this.bulkApply("keep-remote"),n.forEach(r=>r())})),new Q.Setting(t).addButton(o=>o.setButtonText("Cancel sync").onClick(()=>{this.resolved=!0,this.resolver({resolutions:[],applied:!1}),this.close()})).addButton(o=>o.setCta().setButtonText("Apply resolutions").onClick(()=>{if(this.conflicts.some(r=>!r.resolution)){let r=o.buttonEl;r.classList.add("mod-warning"),r.setText("Pick a choice for every file first"),window.setTimeout(()=>{r.classList.remove("mod-warning"),r.setText("Apply resolutions")},1800);return}this.resolved=!0,this.resolver({resolutions:this.conflicts,applied:!0}),this.close()}))}bulkApply(t){for(let e of this.conflicts)e.resolution||fe(e.kind,t)&&(e.resolution=t)}renderConflictRow(t,e){let n=t.createDiv({cls:"easy-git-conflict-row"});n.createDiv({cls:"easy-git-conflict-path",text:e.path}),n.createDiv({cls:"easy-git-conflict-kind",text:En(e)});let i=n.createDiv({cls:"easy-git-conflict-choices"}),o=`conflict-${e.path}-${Math.random().toString(36).slice(2,8)}`,r=new Map;for(let a of["keep-local","keep-remote","keep-both"]){if(!fe(e.kind,a))continue;let l=i.createEl("label",{cls:"easy-git-conflict-choice"}),d=l.createEl("input",{type:"radio"});d.name=o,d.value=a,e.resolution===a&&(d.checked=!0),l.createSpan({text:Rn(e.kind,a)}),d.addEventListener("change",()=>{e.resolution=a}),r.set(a,d)}return()=>{for(let[a,l]of r)l.checked=e.resolution===a}}onClose(){this.resolved||this.resolver({resolutions:[],applied:!1}),this.contentEl.empty()}};function En(s){switch(s.kind){case"both-edited":return"Edited on both sides";case"both-added-different":return"Added on both sides with different content";case"local-edited-remote-deleted":return"Edited locally; deleted on remote";case"remote-edited-local-deleted":return"Edited on remote; deleted locally"}}function fe(s,t){return t==="keep-both"?s==="both-edited"||s==="both-added-different":!0}function Rn(s,t){return t==="keep-local"?s==="local-edited-remote-deleted"?"Restore on remote (keep local)":"Keep local":t==="keep-remote"?s==="remote-edited-local-deleted"?"Restore locally (keep remote)":"Keep remote":"Keep both (rename local copy)"}var Rt=class{el;getState;openSettings;constructor(t,e,n){this.el=t,this.el.addClass("easy-git-status-bar"),this.getState=e,this.openSettings=n,this.el.addEventListener("click",()=>this.openSettings()),this.refresh()}startTicker(t){let e=window.setInterval(()=>this.refresh(),3e4);t.registerInterval(e)}refresh(){let t=this.getState();if(this.el.removeClass("is-syncing","is-error","is-ok","is-hidden"),this.el.empty(),!t.hasMappings){this.el.addClass("is-hidden");return}if(t.anySyncing){this.el.addClass("is-syncing"),this.el.setText("\u21BB Syncing\u2026");return}if(t.anyErrored){this.el.addClass("is-error"),this.el.setText("! Easy Git error");return}this.el.addClass("is-ok"),t.mostRecentSync?this.el.setText(`\u21BB Synced ${Cn(t.mostRecentSync)}`):this.el.setText("\u21BB Ready")}};function Cn(s){let t=Math.floor((Date.now()-s)/1e3);if(t<5)return"just now";if(t<60)return`${t}s ago`;let e=Math.floor(t/60);if(e<60)return`${e}m ago`;let n=Math.floor(e/60);return n<24?`${n}h ago`:`${Math.floor(n/24)}d ago`}var Ct=class extends C.Plugin{engine;intervalHandles=new Map;onSaveDebouncers=new Map;modifyListener;createListener;deleteListener;renameListener;syncing=new Set;pendingAfterSync=new Set;statusBar;settingsTab;isSyncing(t){return this.syncing.has(t)}async onload(){await this.loadSettings(),this.engine=new dt({app:this.app,settings:this.settings,saveSettings:()=>this.saveData(this.settings),resolveConflicts:(t,e,n)=>this.openConflictModal(t,e,n)}),this.settingsTab=new kt(this.app,this),this.addSettingTab(this.settingsTab),this.addRibbonIcon("git-branch","Easy Git: sync menu",t=>{this.openSyncMenu(t)}),this.addCommand({id:"sync-all",name:"Sync all mappings",callback:()=>void this.syncAll("command")}),this.addCommand({id:"sync-mapping",name:"Sync mapping\u2026",callback:()=>{this.openMappingPicker("Sync mapping\u2026",t=>void this.syncMapping(t.id,"command"))}}),this.addCommand({id:"push-mapping",name:"Push mapping\u2026",callback:()=>{this.openMappingPicker("Push mapping\u2026",t=>void this.runDirectionalOnce(t,"push"))}}),this.addCommand({id:"pull-mapping",name:"Pull mapping\u2026",callback:()=>{this.openMappingPicker("Pull mapping\u2026",t=>void this.runDirectionalOnce(t,"pull"))}}),this.addCommand({id:"open-settings",name:"Open settings",callback:()=>this.openEasyGitSettings()}),this.addCommand({id:"show-log",name:"Show sync log",callback:()=>this.openSyncLog()}),this.addCommand({id:"reset-sync-state",name:"Reset sync state (force full re-scan on next sync)",callback:()=>this.pickMappingToResetState()}),this.statusBar=new Rt(this.addStatusBarItem(),()=>this.computeStatusState(),()=>this.openEasyGitSettings()),this.statusBar.startTicker(this),this.registerEvent(this.app.vault.on("rename",(t,e)=>{t instanceof C.TFolder&&this.handleFolderRename(e,t.path)})),this.app.workspace.onLayoutReady(()=>{this.refreshAutoSyncWiring(),this.runStartupSyncs(),this.statusBar?.refresh()})}computeStatusState(){let t=this.settings?.mappings??[],e=this.syncing.size>0,n=!1,i;for(let o of t)for(let r of o.destinations??[])r.lastSyncError&&(n=!0),r.lastSyncAt&&(!i||r.lastSyncAt>i)&&(i=r.lastSyncAt);return{hasMappings:t.length>0,anySyncing:e,anyErrored:n,mostRecentSync:i}}openEasyGitSettings(){let t=this.app;t.setting.open(),t.setting.openTabById(this.manifest.id)}handleFolderRename(t,e){if(!t||t===e)return;let n=0;for(let i of this.settings.mappings)i.vaultFolder===t?(i.vaultFolder=e,n+=1):i.vaultFolder.startsWith(t+"/")&&(i.vaultFolder=e+i.vaultFolder.slice(t.length),n+=1);if(n>0){if(this.saveSettings(),this.settings.showNotifications){let i=n===1?"mapping":"mappings";new C.Notice(`Easy Git: updated ${n} ${i} for folder rename.`)}this.refreshAutoSyncWiring(),this.statusBar?.refresh()}}onunload(){for(let t of this.intervalHandles.values())window.clearInterval(t);this.intervalHandles.clear();for(let t of this.onSaveDebouncers.values())t.cancel();this.onSaveDebouncers.clear(),this.unregisterVaultListeners()}async loadSettings(){let t=null;try{t=await this.loadData()}catch(n){console.error("Easy Git: failed to read plugin data, using defaults.",n),new C.Notice("Easy Git: could not read saved settings (data.json may be corrupted). Started with defaults.",1e4),t=null}this.settings=Object.assign({},et,t??{});let e=!1;this.migrateLegacyMappings()&&(e=!0),this.healSettings()&&(e=!0),e&&await this.saveSettings()}migrateLegacyMappings(){let t=!1;for(let e of this.settings.mappings){let n=e;if(Array.isArray(n.destinations)&&n.destinations.length>0)continue;let i={id:U(),repoOwner:n.repoOwner??"",repoName:n.repoName??"",branch:n.branch??"",remoteFolder:n.remoteFolder??"",lastSyncState:n.lastSyncState,lastSyncAt:n.lastSyncAt,lastSyncError:n.lastSyncError};e.destinations=[i],delete n.repoOwner,delete n.repoName,delete n.branch,delete n.remoteFolder,delete n.lastSyncState,delete n.lastSyncAt,delete n.lastSyncError,t=!0}return t}healSettings(){let t=!1,e=this.settings;(!e.auth||typeof e.auth!="object")&&(e.auth={method:"none",token:"",provider:"github"},t=!0),e.auth.method!=="none"&&!e.auth.token&&(e.auth={method:"none",token:"",provider:e.auth.provider,instanceUrl:e.auth.instanceUrl},t=!0),(!e.auth.provider||e.auth.provider!=="github"&&e.auth.provider!=="forgejo")&&(e.auth.provider="github",t=!0),(typeof e.maxFileSizeBytes!="number"||!Number.isFinite(e.maxFileSizeBytes)||e.maxFileSizeBytes<1024)&&(e.maxFileSizeBytes=et.maxFileSizeBytes,t=!0),e.backupRetentionDays!==void 0&&(typeof e.backupRetentionDays!="number"||!Number.isFinite(e.backupRetentionDays)||e.backupRetentionDays<0)&&(e.backupRetentionDays=0,t=!0);let n=[".easy-git-backup/**",".claude/**",".DS_Store",".trash/**",`${this.app.vault.configDir}/**`,".git/**"];Array.isArray(e.excludedPaths)||(e.excludedPaths=[...et.excludedPaths],t=!0);for(let i of n)e.excludedPaths.includes(i)||(e.excludedPaths.push(i),t=!0);Array.isArray(e.mappings)||(e.mappings=[],t=!0);for(let i of e.mappings){if(i.id||(i.id=U(),t=!0),(typeof i.name!="string"||!i.name.trim())&&(i.name="Untitled mapping",t=!0),typeof i.vaultFolder=="string"){let r=i.vaultFolder.trim().replace(/^\/+|\/+$/g,"");r!==i.vaultFolder&&(i.vaultFolder=r,t=!0)}else i.vaultFolder="",t=!0;i.direction!=="push"&&i.direction!=="pull"&&i.direction!=="both"&&(i.direction="both",t=!0),!i.autoMode||typeof i.autoMode!="object"?(i.autoMode={kind:"off"},t=!0):i.autoMode.kind==="interval"?(typeof i.autoMode.minutes!="number"||!Number.isFinite(i.autoMode.minutes)||i.autoMode.minutes<1)&&(i.autoMode.minutes=15,t=!0):i.autoMode.kind==="onSave"?(typeof i.autoMode.debounceMs!="number"||!Number.isFinite(i.autoMode.debounceMs)||i.autoMode.debounceMs<500)&&(i.autoMode.debounceMs=1e4,t=!0):i.autoMode.kind!=="off"&&i.autoMode.kind!=="startup"&&(i.autoMode={kind:"off"},t=!0),Array.isArray(i.destinations)||(i.destinations=[],t=!0);let o=new Set;for(let r of i.destinations){(!r.id||o.has(r.id))&&(r.id=U(),t=!0),o.add(r.id);for(let l of["repoOwner","repoName","branch","remoteFolder"])typeof r[l]!="string"&&(r[l]="",t=!0);let a=r.remoteFolder.replace(/^\/+|\/+$/g,"");a!==r.remoteFolder&&(r.remoteFolder=a,t=!0)}}return t}mappingHealth(t){if(!t.destinations||t.destinations.length===0)return{ok:!1,reason:"No destinations configured. Edit to add one."};if(t.destinations.find(n=>!n.repoOwner||!n.repoName||!n.branch))return{ok:!1,reason:"Destination missing repo or branch. Edit to fix."};if(t.vaultFolder&&!this.app.vault.getFolderByPath(t.vaultFolder)){let n=t.vaultFolder.toLowerCase();if(!this.app.vault.getAllFolders(!0).find(o=>o.path.toLowerCase()===n))return{ok:!1,reason:`Vault folder "${t.vaultFolder}" not found. Edit to pick a new one.`}}return{ok:!0}}async saveSettings(){await this.saveData(this.settings)}refreshAutoSyncWiring(){for(let e of this.intervalHandles.values())window.clearInterval(e);this.intervalHandles.clear();for(let e of this.onSaveDebouncers.values())e.cancel();this.onSaveDebouncers.clear(),this.unregisterVaultListeners();let t=!1;for(let e of this.settings.mappings){let n=e.autoMode;if(n.kind==="interval"){let i=Math.max(1,n.minutes)*6e4,o=window.setInterval(()=>{this.syncMapping(e.id,"interval")},i);this.intervalHandles.set(e.id,o),this.registerInterval(o)}else if(n.kind==="onSave"){t=!0;let i=(0,C.debounce)(()=>void this.syncMapping(e.id,"on-save"),n.debounceMs,!0);this.onSaveDebouncers.set(e.id,{trigger:()=>i(),cancel:()=>i.cancel()})}}t&&this.registerVaultListeners(),this.statusBar?.refresh()}runStartupSyncs(){for(let t of this.settings.mappings)t.autoMode.kind==="startup"&&this.syncMapping(t.id,"startup")}registerVaultListeners(){let t=e=>this.maybeFireOnSave(e);this.modifyListener=this.app.vault.on("modify",t),this.createListener=this.app.vault.on("create",t),this.deleteListener=this.app.vault.on("delete",t),this.renameListener=this.app.vault.on("rename",e=>t(e)),this.registerEvent(this.modifyListener),this.registerEvent(this.createListener),this.registerEvent(this.deleteListener),this.registerEvent(this.renameListener)}unregisterVaultListeners(){this.modifyListener&&this.app.vault.offref(this.modifyListener),this.createListener&&this.app.vault.offref(this.createListener),this.deleteListener&&this.app.vault.offref(this.deleteListener),this.renameListener&&this.app.vault.offref(this.renameListener),this.modifyListener=void 0,this.createListener=void 0,this.deleteListener=void 0,this.renameListener=void 0}maybeFireOnSave(t){if(t instanceof C.TFile)for(let e of this.settings.mappings){if(e.autoMode.kind!=="onSave"||!Tn(t.path,e.vaultFolder))continue;let n=this.onSaveDebouncers.get(e.id);n&&n.trigger()}}async syncAll(t){for(let e of this.settings.mappings)await this.syncMapping(e.id,t)}async syncMapping(t,e){let n=this.settings.mappings.find(i=>i.id===t);if(n){if(this.syncing.has(t)){this.pendingAfterSync.add(t);return}this.syncing.add(t),this.statusBar?.refresh(),this.settingsTab?.refreshSyncStates();try{this.settings.debugLogging&&console.log(`[Easy Git] sync start (${e}) \u2014 ${n.name}`);let i=await this.engine.syncMapping(n);this.settings.debugLogging&&console.log("[Easy Git] sync results",i);for(let o of i)this.recordSyncResult(n,o,e);await this.saveSettings(),this.reportSyncResults(n,i)}finally{this.syncing.delete(t),this.statusBar?.refresh(),this.settingsTab?.refreshSyncStates(),this.pendingAfterSync.has(t)&&(this.pendingAfterSync.delete(t),window.setTimeout(()=>void this.syncMapping(t,"on-save"),0))}}}openSyncLog(){new pt(this.app,{entries:this.settings.syncLog??[],onClear:async()=>{this.settings.syncLog=[],await this.saveSettings()}}).open()}pickMappingToResetState(){if(this.settings.mappings.length===0){new C.Notice("Easy Git: no mappings configured.");return}new Z(this.app,this.settings.mappings,"Reset sync state for which mapping?",t=>{for(let e of t.destinations)e.lastSyncState=void 0,e.lastSyncAt=void 0,e.lastSyncError=void 0;this.saveSettings(),this.settingsTab?.refreshSyncStates(),this.statusBar?.refresh(),new C.Notice(`Easy Git: sync state cleared for "${t.name}". Next sync will re-scan from scratch.`)}).open()}recordSyncResult(t,e,n){let i=t.destinations.find(a=>a.id===e.destinationId),o={timestamp:Date.now(),mappingId:t.id,mappingName:t.name,destinationId:e.destinationId,destinationLabel:i?Y(i):"(unknown)",trigger:n,ok:e.ok,added:e.added,modified:e.modified,deleted:e.deleted,conflicts:e.conflicts.length,filesTouched:e.added+e.modified+e.deleted,changedPaths:e.changedPaths,error:e.error,durationMs:e.durationMs},r=this.settings.syncLog??[];r.unshift(o),r.length>Tt&&(r.length=Tt),this.settings.syncLog=r}reportSyncResults(t,e){let n=t.destinations.length>1;for(let i of e){let o=t.destinations.find(a=>a.id===i.destinationId),r=n&&o?` \u2192 ${Y(o)}`:"";if(!i.ok&&i.error)this.settings.showNotifications&&new C.Notice(`Easy Git (${t.name}${r}): ${i.error}`);else if(i.ok){if(this.settings.showNotifications){let d=i.added+i.modified+i.deleted===0?"up to date":`${i.added}+ ${i.modified}~ ${i.deleted}-`;new C.Notice(`Easy Git (${t.name}${r}): ${d}`)}i.skippedLarge&&i.skippedLarge.length>0&&this.settings.showNotifications&&new C.Notice(`Easy Git (${t.name}${r}): skipped ${i.skippedLarge.length} file(s) over size limit.`),i.unresolvedWikilinks&&i.unresolvedWikilinks>0&&this.settings.showNotifications&&new C.Notice(`Easy Git (${t.name}${r}): ${i.unresolvedWikilinks} unresolved wikilink(s) left untouched.`),i.excalidrawMissingCompanion&&i.excalidrawMissingCompanion>0&&this.settings.showNotifications&&new C.Notice(`Easy Git (${t.name}${r}): ${i.excalidrawMissingCompanion} Excalidraw drawing(s) have no .svg/.png companion. Enable "Auto-export SVG" in the Excalidraw plugin to render them on GitHub.`,8e3),i.autoResolvedConflicts&&i.autoResolvedConflicts>0&&this.settings.showNotifications&&new C.Notice(`Easy Git (${t.name}${r}): ${i.autoResolvedConflicts} conflict(s) auto-resolved \u2014 local was newer than last sync.`,6e3),i.mergedConflicts&&i.mergedConflicts>0&&this.settings.showNotifications&&new C.Notice(`Easy Git (${t.name}${r}): ${i.mergedConflicts} conflict(s) merged automatically via 3-way diff.`,6e3),i.backupsCreated&&i.backupsCreated>0&&this.settings.showNotifications&&new C.Notice(`Easy Git (${t.name}${r}): backed up ${i.backupsCreated} local file(s) to ${i.backupFolder??".easy-git-backup/"} before overwrite.`,8e3);let a=[];i.calloutsRewritten&&i.calloutsRewritten>0&&a.push(`${i.calloutsRewritten} callout${i.calloutsRewritten===1?"":"s"}`),i.highlightsRewritten&&i.highlightsRewritten>0&&a.push(`${i.highlightsRewritten} highlight${i.highlightsRewritten===1?"":"s"}`),i.mathMacrosRewritten&&i.mathMacrosRewritten>0&&a.push(`${i.mathMacrosRewritten} math macro${i.mathMacrosRewritten===1?"":"s"}`),a.length>0&&this.settings.showNotifications&&new C.Notice(`Easy Git (${t.name}${r}): rewrote ${a.join(", ")} for GitHub rendering (reversible on pull).`,6e3)}}}async runDirectionalOnce(t,e){let n=t.direction;t.direction=e;try{await this.syncMapping(t.id,"command")}finally{t.direction=n,await this.saveSettings()}}openMappingPicker(t,e){if(this.settings.mappings.length===0){new C.Notice("Easy Git: no mappings configured yet.");return}new Z(this.app,this.settings.mappings,t,e).open()}openSyncMenu(t){let e=new C.Menu;if(this.settings.mappings.length===0)e.addItem(n=>n.setTitle("No mappings configured").setIcon("info").setDisabled(!0));else{e.addItem(n=>n.setTitle("Sync all").setIcon("refresh-cw").onClick(()=>void this.syncAll("manual"))),e.addSeparator();for(let n of this.settings.mappings)e.addItem(i=>i.setTitle(`Sync: ${n.name}`).setIcon(Mn(n.direction)).onClick(()=>void this.syncMapping(n.id,"manual")))}e.addSeparator(),e.addItem(n=>n.setTitle("Open Easy Git settings").setIcon("settings").onClick(()=>this.openEasyGitSettings())),e.showAtMouseEvent(t)}async openConflictModal(t,e,n){let i=t.destinations.length>1?`${t.name} \u2192 ${Y(e)}`:t.name;return new Promise(o=>{new Et(this.app,i,n,r=>{if(!r.applied){o(null);return}o(r.resolutions)}).open()})}};function Mn(s){return s==="push"?"arrow-up":s==="pull"?"arrow-down":"arrow-up-down"}function Tn(s,t){let e=t.replace(/^\/+|\/+$/g,"");return e?s===e||s.startsWith(e+"/"):!0}