diff --git a/main.js b/main.js index b87e761..065deda 100644 --- a/main.js +++ b/main.js @@ -3,4 +3,4 @@ THIS IS A GENERATED/BUNDLED FILE BY ESBUILD if you want to view the source visit https://github.com/andrewboldi/obsidian-vault-sync */ -"use strict";var k=Object.defineProperty;var st=Object.getOwnPropertyDescriptor;var nt=Object.getOwnPropertyNames;var at=Object.prototype.hasOwnProperty;var rt=(t,s)=>{for(var e in s)k(t,e,{get:s[e],enumerable:!0})},it=(t,s,e,r)=>{if(s&&typeof s=="object"||typeof s=="function")for(let n of nt(s))!at.call(t,n)&&n!==e&&k(t,n,{get:()=>s[n],enumerable:!(r=st(s,n))||r.enumerable});return t};var ot=t=>it(k({},"__esModule",{value:!0}),t);var Rt={};rt(Rt,{default:()=>B});module.exports=ot(Rt);var E=require("obsidian");var $=require("obsidian"),_={pat:"",repoUrl:"",branch:"main",lastCommitSha:"",fileShaMap:{},autoSyncIntervalMinutes:0},C=class extends $.PluginSettingTab{constructor(s,e){super(s,e),this.plugin=e}display(){let{containerEl:s}=this;s.empty(),s.createEl("h2",{text:"Vault Sync (REST)"}),new $.Setting(s).setName("GitHub Personal Access Token").setDesc("Fine-grained or classic PAT with repo (contents: read/write) scope on the target repo.").addText(e=>{e.inputEl.type="password",e.setPlaceholder("ghp_\u2026").setValue(this.plugin.settings.pat).onChange(async r=>{this.plugin.settings.pat=r.trim(),await this.plugin.saveSettings()})}),new $.Setting(s).setName("Repository URL").setDesc("e.g. https://github.com/owner/repo").addText(e=>e.setPlaceholder("https://github.com/owner/repo").setValue(this.plugin.settings.repoUrl).onChange(async r=>{this.plugin.settings.repoUrl=r.trim(),await this.plugin.saveSettings()})),new $.Setting(s).setName("Branch").setDesc("Branch to sync (default: main)").addText(e=>e.setPlaceholder("main").setValue(this.plugin.settings.branch).onChange(async r=>{this.plugin.settings.branch=r.trim()||"main",await this.plugin.saveSettings()})),new $.Setting(s).setName("Auto-sync interval (minutes)").setDesc("Pull then push every N minutes. 0 disables. Recommended: 1-5.").addText(e=>e.setPlaceholder("0").setValue(String(this.plugin.settings.autoSyncIntervalMinutes??0)).onChange(async r=>{let n=Math.max(0,Math.floor(Number(r)||0));this.plugin.settings.autoSyncIntervalMinutes=n,await this.plugin.saveSettings(),this.plugin.scheduleAutoSync()})),new $.Setting(s).setName("Last commit SHA").setDesc("Set after a successful seed/pull. Read-only \u2014 used as the local sync base.").addText(e=>{e.setValue(this.plugin.settings.lastCommitSha||"(none)").setDisabled(!0)})}};var Y=require("obsidian");var y=require("obsidian"),ct="obsidian-vault-sync",w="https://api.github.com";function b(t){return{Authorization:`token ${t}`,"User-Agent":ct,Accept:"application/vnd.github+json","X-GitHub-Api-Version":"2022-11-28"}}function R(t){if(!t)throw new Error("Repository URL is empty");let s=t.trim().replace(/\.git$/,"").replace(/\/+$/,""),e=s.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+)$/i);if(e||(e=s.match(/^([^/\s]+)\/([^/\s]+)$/)),!e)throw new Error(`Cannot parse repo URL: ${t}`);return{owner:e[1],repo:e[2]}}async function P(t,s,e){let r=`${w}/repos/${t.owner}/${t.repo}/branches/${encodeURIComponent(s)}`,n=await(0,y.requestUrl)({url:r,method:"GET",headers:b(e),throw:!1});if(n.status<200||n.status>=300)throw new Error(`GitHub branch lookup failed: ${n.status} \u2014 ${n.text.slice(0,300)}`);let i=n.json?.commit?.sha;if(!i)throw new Error("GitHub response missing commit.sha");return i}async function I(t,s,e,r){let n=`${w}/repos/${t.owner}/${t.repo}/compare/${s}...${e}`,a=await(0,y.requestUrl)({url:n,method:"GET",headers:b(r),throw:!1});if(a.status<200||a.status>=300)throw new Error(`Compare failed: ${a.status} \u2014 ${(a.text||"").slice(0,300)}`);return a.json}async function j(t,s,e){let r=`${w}/repos/${t.owner}/${t.repo}/git/trees/${s}?recursive=1`,n=await(0,y.requestUrl)({url:r,method:"GET",headers:b(e),throw:!1});if(n.status<200||n.status>=300)throw new Error(`Tree fetch failed: ${n.status} \u2014 ${(n.text||"").slice(0,300)}`);let a=n.json;if(a.truncated)throw new Error("Repo tree exceeds GitHub's single-request limit (~100k files). Vault Sync needs pagination support \u2014 file an issue.");return a.tree}async function L(t,s,e){let r=`${w}/repos/${t.owner}/${t.repo}/git/commits/${s}`,n=await(0,y.requestUrl)({url:r,method:"GET",headers:b(e),throw:!1});if(n.status<200||n.status>=300)throw new Error(`Commit lookup failed: ${n.status}`);return n.json.tree.sha}async function D(t,s,e){let r=`${w}/repos/${t.owner}/${t.repo}/git/blobs`,n=new Uint8Array(s),a="",i=32768;for(let h=0;h=300)throw new Error(`Blob create failed: ${o.status} \u2014 ${(o.text||"").slice(0,300)}`);return o.json.sha}async function O(t,s,e,r){let n=`${w}/repos/${t.owner}/${t.repo}/git/trees`,a=await(0,y.requestUrl)({url:n,method:"POST",headers:{...b(r),"Content-Type":"application/json"},body:JSON.stringify({base_tree:s,tree:e}),throw:!1});if(a.status<200||a.status>=300)throw new Error(`Tree create failed: ${a.status} \u2014 ${(a.text||"").slice(0,300)}`);return a.json.sha}async function W(t,s,e,r,n){let a=`${w}/repos/${t.owner}/${t.repo}/git/commits`,i=await(0,y.requestUrl)({url:a,method:"POST",headers:{...b(n),"Content-Type":"application/json"},body:JSON.stringify({message:s,tree:e,parents:r}),throw:!1});if(i.status<200||i.status>=300)throw new Error(`Commit create failed: ${i.status} \u2014 ${(i.text||"").slice(0,300)}`);return i.json.sha}async function K(t,s,e,r){let n=`${w}/repos/${t.owner}/${t.repo}/git/refs/heads/${encodeURIComponent(s)}`,a=await(0,y.requestUrl)({url:n,method:"PATCH",headers:{...b(r),"Content-Type":"application/json"},body:JSON.stringify({sha:e,force:!1}),throw:!1});return{ok:a.status>=200&&a.status<300,status:a.status,message:(a.text||"").slice(0,300)}}async function x(t,s,e,r){let n=`${w}/repos/${t.owner}/${t.repo}/contents/${encodeURIComponent(s).replace(/%2F/g,"/")}?ref=${e}`,a=await(0,y.requestUrl)({url:n,method:"GET",headers:{...b(r),Accept:"application/vnd.github.raw"},throw:!1});if(a.status<200||a.status>=300)throw new Error(`File fetch failed (${s}): ${a.status} \u2014 ${(a.text||"").slice(0,200)}`);return a.arrayBuffer}function J(t){return t<1024?`${t} B`:t<1024*1024?`${(t/1024).toFixed(1)} KB`:t<1024*1024*1024?`${(t/(1024*1024)).toFixed(1)} MB`:`${(t/(1024*1024*1024)).toFixed(2)} GB`}async function q(t,s){if(!(!s||s==="/"||s==="."))try{await t.app.vault.adapter.mkdir(s)}catch{}}async function lt(t,s){let e=s.split("/");if(e.pop(),e.length===0)return;let r="";for(let n of e)n&&(r=r?`${r}/${n}`:n,await q(t,r))}async function ut(t,s,e){let r=0,n=[];for(let a=0;a{for(;;){let i=r++;if(i>=t.length)return;await e(t[i],i)}})());await Promise.all(n)}var ht=8,pt=10;async function z(t){let{pat:s,repoUrl:e,branch:r}=t.settings;if(!s)throw new Error("Set a GitHub PAT in Vault Sync settings.");if(!e)throw new Error("Set a repo URL in Vault Sync settings.");let n=R(e),a=new Y.Notice("Vault Sync: resolving branch\u2026",0),i;try{i=await P(n,r||"main",s)}catch(c){throw a.hide(),c}a.setMessage(`Vault Sync: listing ${n.owner}/${n.repo}@${i.slice(0,7)}\u2026`);let p;try{p=await j(n,i,s)}catch(c){throw a.hide(),c}let o=p.filter(c=>c.type==="tree"),h=p.filter(c=>c.type==="blob");a.setMessage(`Vault Sync: ${h.length} files, ${o.length} dirs \u2014 preparing\u2026`);for(let c of o)await q(t,c.path);let f=0,d=0,g={};await ut(h,ht,async c=>{let m=await x(n,c.path,i,s);await lt(t,c.path),await t.app.vault.adapter.writeBinary(c.path,m),g[c.path]=c.sha,f++,d+=m.byteLength,f%pt===0&&a.setMessage(`Vault Sync: ${f}/${h.length} files, ${J(d)}`)}),t.settings.lastCommitSha=i,t.settings.fileShaMap=g,await t.saveSettings(),a.setMessage(`Vault Sync: seed complete \u2014 ${f} files, ${J(d)} @ ${i.slice(0,7)}`),setTimeout(()=>a.hide(),6e3)}var G=require("obsidian");function mt(t){return t<1024?`${t} B`:t<1024*1024?`${(t/1024).toFixed(1)} KB`:`${(t/(1024*1024)).toFixed(1)} MB`}async function dt(t,s){if(!(!s||s==="/"||s==="."))try{await t.app.vault.adapter.mkdir(s)}catch{}}async function gt(t,s){let e=s.split("/");if(e.pop(),e.length===0)return;let r="";for(let n of e)n&&(r=r?`${r}/${n}`:n,await dt(t,r))}async function ft(t,s){try{await t.app.vault.adapter.remove(s)}catch{}}async function yt(t,s,e){let r=0,n=[];for(let a=0;a{for(;;){let i=r++;if(i>=t.length)return;await e(t[i])}})());await Promise.all(n)}var St=8;async function N(t,s={}){let{pat:e,repoUrl:r,branch:n,lastCommitSha:a,fileShaMap:i}=t.settings;if(!e)throw new Error("Set a GitHub PAT in Vault Sync settings.");if(!r)throw new Error("Set a repo URL in Vault Sync settings.");if(!a)throw new Error("No baseline commit recorded. Run 'Vault Sync: Seed from GitHub' first.");let p=R(r),o=s.silent?null:new G.Notice("Vault Sync: checking remote\u2026",0),h=await P(p,n||"main",e);if(h===a){o&&(o?.setMessage("Vault Sync: already up to date"),setTimeout(()=>o?.hide(),4e3));return}o?.setMessage(`Vault Sync: comparing ${a.slice(0,7)}\u2026${h.slice(0,7)}`);let f=await I(p,a,h,e);if(f.status==="diverged")throw o?.setMessage("Vault Sync: remote diverged from local baseline. Manual resolution needed (Phase 3 push will detect this)."),setTimeout(()=>o?.hide(),1e4),new Error(`Remote and local have diverged (ahead ${f.ahead_by}, behind ${f.behind_by}). Vault Sync can only fast-forward in Phase 2.`);let d=f.files??[];if(d.length===0){t.settings.lastCommitSha=h,await t.saveSettings(),o?.setMessage("Vault Sync: pull complete (no file changes)"),setTimeout(()=>o?.hide(),4e3);return}d.length===300&&new G.Notice("Vault Sync: compare returned exactly 300 files (GitHub's per-call cap). Some changes may be missing \u2014 re-seed if pull seems incomplete.",1e4);let g=[],c=[];for(let l of d)switch(l.status){case"added":case"modified":case"changed":case"copied":g.push(l);break;case"renamed":l.previous_filename&&c.push(l.previous_filename),g.push(l);break;case"removed":c.push(l.filename);break;case"unchanged":break}o?.setMessage(`Vault Sync: ${g.length} to fetch, ${c.length} to remove\u2026`);for(let l of c)await ft(t,l),delete i[l];let m=0,v=0;await yt(g,St,async l=>{let T=await x(p,l.filename,h,e);await gt(t,l.filename),await t.app.vault.adapter.writeBinary(l.filename,T),i[l.filename]=l.sha,m++,v+=T.byteLength,m%5===0&&o?.setMessage(`Vault Sync: ${m}/${g.length} files, ${mt(v)}`)}),t.settings.lastCommitSha=h,t.settings.fileShaMap=i,await t.saveSettings(),o?.setMessage(`Vault Sync: pull complete \u2014 ${g.length} updated, ${c.length} removed @ ${h.slice(0,7)}`),setTimeout(()=>o?.hide(),6e3)}var Q=require("obsidian");var wt=new Set([".obsidian/workspace.json",".obsidian/workspace-mobile.json",".obsidian/cache",".obsidian/appearance.json",".DS_Store","_GIT-DEBUG-ERROR.md"]),bt=[".trash/",".obsidian/plugins/"];function $t(t){if(wt.has(t)||t.endsWith(".tmp"))return!0;for(let s of bt)if(t.startsWith(s))return!0;return!!t.startsWith(".obsidian/workspace")}async function vt(t){let s=new TextEncoder().encode(`blob ${t.byteLength}\0`),e=new Uint8Array(s.byteLength+t.byteLength);e.set(s,0),e.set(new Uint8Array(t),s.byteLength);let r=await crypto.subtle.digest("SHA-1",e),n=new Uint8Array(r),a="";for(let i of n)a+=i.toString(16).padStart(2,"0");return a}function X(t){return t<1024?`${t} B`:t<1024*1024?`${(t/1024).toFixed(1)} KB`:`${(t/(1024*1024)).toFixed(1)} MB`}async function Tt(t){let s=[];async function e(r){let n=await t.app.vault.adapter.list(r||"/");for(let a of n.files)s.push(a);for(let a of n.folders)a===".trash"||a===".git"||await e(a)}return await e(""),s}async function F(t,s={}){let{pat:e,repoUrl:r,branch:n,lastCommitSha:a,fileShaMap:i}=t.settings;if(!e)throw new Error("Set a GitHub PAT in Vault Sync settings.");if(!r)throw new Error("Set a repo URL in Vault Sync settings.");if(!a)throw new Error("No baseline commit. Seed first.");let p=R(r),o=s.silent?null:new Q.Notice("Vault Sync: scanning local changes\u2026",0),h=await P(p,n||"main",e);if(h!==a)throw o?.hide(),new Error(`Remote advanced (${h.slice(0,7)} vs local baseline ${a.slice(0,7)}). Run 'Vault Sync: Pull from GitHub' first.`);let d=(await Tt(t)).filter(u=>!$t(u)),g=new Set(d);o?.setMessage(`Vault Sync: hashing ${d.length} files\u2026`);let c=[],m=[],v=0;for(let u of d){let S=await t.app.vault.adapter.readBinary(u),V=await vt(S),H=i[u];H?H!==V&&c.push({path:u,kind:"modify",content:S}):c.push({path:u,kind:"add",content:S}),v++,v%50===0&&o?.setMessage(`Vault Sync: hashed ${v}/${d.length}\u2026`)}for(let u of Object.keys(i))g.has(u)||m.push(u);if(c.length===0&&m.length===0){o?.setMessage("Vault Sync: nothing to push"),setTimeout(()=>o?.hide(),4e3);return}o?.setMessage(`Vault Sync: uploading ${c.length} blobs (${m.length} deletions)\u2026`);let l=[],T=0,M=0;for(let u of c){let S=await D(p,u.content,e);l.push({path:u.path,mode:"100644",type:"blob",sha:S}),T++,M+=u.content.byteLength,T%5===0&&o?.setMessage(`Vault Sync: uploaded ${T}/${c.length} (${X(M)})`)}for(let u of m)l.push({path:u,mode:"100644",type:"blob",sha:null});o?.setMessage("Vault Sync: creating tree\u2026");let Z=await L(p,a,e),tt=await O(p,Z,l,e);o?.setMessage("Vault Sync: creating commit\u2026");let et=`Vault Sync: ${c.length} changed, ${m.length} deleted from mobile`,U=await W(p,et,tt,[a],e);o?.setMessage("Vault Sync: updating branch\u2026");let A=await K(p,n||"main",U,e);if(!A.ok)throw o?.hide(),new Error(`Branch update failed (${A.status}): ${A.message}. Pull and retry.`);for(let u of c){let S=l.find(V=>V.path===u.path&&V.sha!==null);S&&S.sha&&(i[u.path]=S.sha)}for(let u of m)delete i[u];t.settings.lastCommitSha=U,t.settings.fileShaMap=i,await t.saveSettings(),o?.setMessage(`Vault Sync: pushed ${c.length} changes (${X(M)}) \u2192 ${U.slice(0,7)}`),setTimeout(()=>o?.hide(),6e3)}var B=class extends E.Plugin{constructor(){super(...arguments);this.autoSyncTimer=null;this.autoSyncRunning=!1;this.statusBar=null}async onload(){await this.loadSettings(),this.addSettingTab(new C(this.app,this)),this.statusBar=this.addStatusBarItem(),this.updateStatusBar("idle"),this.addRibbonIcon("refresh-cw","Vault Sync: Sync now",()=>{this.runWithErrorNotice("sync",()=>this.runSync(!1))}),this.addCommand({id:"seed-from-github",name:"Seed from GitHub",callback:()=>this.runWithErrorNotice("seed",()=>z(this))}),this.addCommand({id:"pull-from-github",name:"Pull from GitHub",callback:()=>this.runWithErrorNotice("pull",()=>N(this))}),this.addCommand({id:"push-to-github",name:"Push to GitHub",callback:()=>this.runWithErrorNotice("push",()=>F(this))}),this.addCommand({id:"sync-now",name:"Sync now (pull then push)",callback:()=>this.runWithErrorNotice("sync",()=>this.runSync(!1))}),this.scheduleAutoSync()}updateStatusBar(e,r){if(!this.statusBar)return;let n=this.settings.lastCommitSha?this.settings.lastCommitSha.slice(0,7):"(unseeded)",a=e==="syncing"?"\u27F3":e==="error"?"\u26A0":"\u2713";this.statusBar.setText(`${a} Vault Sync ${n}${r?` \u2014 ${r}`:""}`)}onunload(){this.autoSyncTimer!==null&&(window.clearInterval(this.autoSyncTimer),this.autoSyncTimer=null)}async runWithErrorNotice(e,r){try{await r()}catch(n){let a=n instanceof Error?n.message:String(n);console.error(`[Vault Sync] ${e} failed`,n),new E.Notice(`Vault Sync ${e} failed: ${a}`,1e4)}}async runSync(e){if(!this.autoSyncRunning){this.autoSyncRunning=!0,this.updateStatusBar("syncing");try{await N(this,{silent:e}),await F(this,{silent:e}),this.updateStatusBar("idle")}catch(r){throw this.updateStatusBar("error",r instanceof Error?r.message.slice(0,40):""),r}finally{this.autoSyncRunning=!1}}}scheduleAutoSync(){this.autoSyncTimer!==null&&(window.clearInterval(this.autoSyncTimer),this.autoSyncTimer=null);let e=this.settings.autoSyncIntervalMinutes??0;if(e<=0)return;let r=e*60*1e3;this.autoSyncTimer=window.setInterval(()=>{this.runSync(!0).catch(n=>{let a=n instanceof Error?n.message:String(n);console.error("[Vault Sync] auto-sync failed",n),new E.Notice(`Vault Sync auto-sync failed: ${a}`,1e4)})},r)}async loadSettings(){let e=await this.loadData()??{};this.settings=Object.assign({},_,e)}async saveSettings(){await this.saveData(this.settings)}}; +"use strict";var G=Object.defineProperty;var nt=Object.getOwnPropertyDescriptor;var rt=Object.getOwnPropertyNames;var at=Object.prototype.hasOwnProperty;var it=(t,n)=>{for(var e in n)G(t,e,{get:n[e],enumerable:!0})},ot=(t,n,e,a)=>{if(n&&typeof n=="object"||typeof n=="function")for(let s of rt(n))!at.call(t,s)&&s!==e&&G(t,s,{get:()=>n[s],enumerable:!(a=nt(n,s))||a.enumerable});return t};var ct=t=>ot(G({},"__esModule",{value:!0}),t);var Rt={};it(Rt,{default:()=>M});module.exports=ct(Rt);var E=require("obsidian");var v=require("obsidian"),j={pat:"",repoUrl:"",branch:"main",lastCommitSha:"",fileShaMap:{},autoSyncIntervalMinutes:0},x=class extends v.PluginSettingTab{constructor(n,e){super(n,e),this.plugin=e}display(){let{containerEl:n}=this;n.empty(),new v.Setting(n).setName("GitHub personal access token").setDesc("Fine-grained or classic PAT with repo (contents: read/write) scope on the target repo.").addText(e=>{e.inputEl.type="password",e.setPlaceholder("ghp_\u2026").setValue(this.plugin.settings.pat).onChange(async a=>{this.plugin.settings.pat=a.trim(),await this.plugin.saveSettings()})}),new v.Setting(n).setName("Repository URL").setDesc("For example, https://github.com/owner/repo").addText(e=>e.setPlaceholder("https://github.com/owner/repo").setValue(this.plugin.settings.repoUrl).onChange(async a=>{this.plugin.settings.repoUrl=a.trim(),await this.plugin.saveSettings()})),new v.Setting(n).setName("Branch").setDesc("Branch to sync (default: main)").addText(e=>e.setPlaceholder("main").setValue(this.plugin.settings.branch).onChange(async a=>{this.plugin.settings.branch=a.trim()||"main",await this.plugin.saveSettings()})),new v.Setting(n).setName("Auto-sync interval (minutes)").setDesc("Pull then push every N minutes. 0 disables. Recommended: 1-5.").addText(e=>e.setPlaceholder("0").setValue(String(this.plugin.settings.autoSyncIntervalMinutes??0)).onChange(async a=>{let s=Math.max(0,Math.floor(Number(a)||0));this.plugin.settings.autoSyncIntervalMinutes=s,await this.plugin.saveSettings(),this.plugin.scheduleAutoSync()})),new v.Setting(n).setName("Last commit SHA").setDesc("Set after a successful seed/pull. Read-only \u2014 used as the local sync base.").addText(e=>{e.setValue(this.plugin.settings.lastCommitSha||"(none)").setDisabled(!0)})}};var q=require("obsidian");var f=require("obsidian"),lt="obsidian-vault-sync",S="https://api.github.com";function b(t){return{Authorization:`token ${t}`,"User-Agent":lt,Accept:"application/vnd.github+json","X-GitHub-Api-Version":"2022-11-28"}}function T(t){if(!t)throw new Error("Repository URL is empty");let n=t.trim().replace(/\.git$/,"").replace(/\/+$/,""),e=n.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+)$/i);if(e||(e=n.match(/^([^/\s]+)\/([^/\s]+)$/)),!e)throw new Error(`Cannot parse repo URL: ${t}`);return{owner:e[1],repo:e[2]}}async function P(t,n,e){let a=`${S}/repos/${t.owner}/${t.repo}/branches/${encodeURIComponent(n)}`,s=await(0,f.requestUrl)({url:a,method:"GET",headers:b(e),throw:!1});if(s.status<200||s.status>=300)throw new Error(`GitHub branch lookup failed: ${s.status} \u2014 ${s.text.slice(0,300)}`);let i=s.json?.commit?.sha;if(!i)throw new Error("GitHub response missing commit.sha");return i}async function I(t,n,e,a){let s=`${S}/repos/${t.owner}/${t.repo}/compare/${n}...${e}`,r=await(0,f.requestUrl)({url:s,method:"GET",headers:b(a),throw:!1});if(r.status<200||r.status>=300)throw new Error(`Compare failed: ${r.status} \u2014 ${(r.text||"").slice(0,300)}`);return r.json}async function _(t,n,e){let a=`${S}/repos/${t.owner}/${t.repo}/git/trees/${n}?recursive=1`,s=await(0,f.requestUrl)({url:a,method:"GET",headers:b(e),throw:!1});if(s.status<200||s.status>=300)throw new Error(`Tree fetch failed: ${s.status} \u2014 ${(s.text||"").slice(0,300)}`);let r=s.json;if(r.truncated)throw new Error("Repo tree exceeds GitHub's single-request limit (~100k files). Vault Sync needs pagination support \u2014 file an issue.");return r.tree}async function O(t,n,e){let a=`${S}/repos/${t.owner}/${t.repo}/git/commits/${n}`,s=await(0,f.requestUrl)({url:a,method:"GET",headers:b(e),throw:!1});if(s.status<200||s.status>=300)throw new Error(`Commit lookup failed: ${s.status}`);return s.json.tree.sha}async function D(t,n,e){let a=`${S}/repos/${t.owner}/${t.repo}/git/blobs`,s=new Uint8Array(n),r="",i=32768;for(let u=0;u=300)throw new Error(`Blob create failed: ${o.status} \u2014 ${(o.text||"").slice(0,300)}`);return o.json.sha}async function W(t,n,e,a){let s=`${S}/repos/${t.owner}/${t.repo}/git/trees`,r=await(0,f.requestUrl)({url:s,method:"POST",headers:{...b(a),"Content-Type":"application/json"},body:JSON.stringify({base_tree:n,tree:e}),throw:!1});if(r.status<200||r.status>=300)throw new Error(`Tree create failed: ${r.status} \u2014 ${(r.text||"").slice(0,300)}`);return r.json.sha}async function J(t,n,e,a,s){let r=`${S}/repos/${t.owner}/${t.repo}/git/commits`,i=await(0,f.requestUrl)({url:r,method:"POST",headers:{...b(s),"Content-Type":"application/json"},body:JSON.stringify({message:n,tree:e,parents:a}),throw:!1});if(i.status<200||i.status>=300)throw new Error(`Commit create failed: ${i.status} \u2014 ${(i.text||"").slice(0,300)}`);return i.json.sha}async function K(t,n,e,a){let s=`${S}/repos/${t.owner}/${t.repo}/git/refs/heads/${encodeURIComponent(n)}`,r=await(0,f.requestUrl)({url:s,method:"PATCH",headers:{...b(a),"Content-Type":"application/json"},body:JSON.stringify({sha:e,force:!1}),throw:!1});return{ok:r.status>=200&&r.status<300,status:r.status,message:(r.text||"").slice(0,300)}}async function B(t,n,e,a){let s=`${S}/repos/${t.owner}/${t.repo}/contents/${encodeURIComponent(n).replace(/%2F/g,"/")}?ref=${e}`,r=await(0,f.requestUrl)({url:s,method:"GET",headers:{...b(a),Accept:"application/vnd.github.raw"},throw:!1});if(r.status<200||r.status>=300)throw new Error(`File fetch failed (${n}): ${r.status} \u2014 ${(r.text||"").slice(0,200)}`);return r.arrayBuffer}function Y(t){return t<1024?`${t} B`:t<1024*1024?`${(t/1024).toFixed(1)} KB`:t<1024*1024*1024?`${(t/(1024*1024)).toFixed(1)} MB`:`${(t/(1024*1024*1024)).toFixed(2)} GB`}async function z(t,n){if(!(!n||n==="/"||n==="."))try{await t.app.vault.adapter.mkdir(n)}catch{}}async function ut(t,n){let e=n.split("/");if(e.pop(),e.length===0)return;let a="";for(let s of e)s&&(a=a?`${a}/${s}`:s,await z(t,a))}async function ht(t,n,e){let a=0,s=[];for(let r=0;r{for(;;){let i=a++;if(i>=t.length)return;await e(t[i],i)}})());await Promise.all(s)}var pt=8,mt=10;async function X(t){let{pat:n,repoUrl:e,branch:a}=t.settings;if(!n)throw new Error("Set a GitHub PAT in Vault Sync settings.");if(!e)throw new Error("Set a repo URL in Vault Sync settings.");let s=T(e),r=new q.Notice("Resolving branch\u2026",0),i;try{i=await P(s,a||"main",n)}catch(c){throw r.hide(),c}r.setMessage(`Listing ${s.owner}/${s.repo}@${i.slice(0,7)}\u2026`);let p;try{p=await _(s,i,n)}catch(c){throw r.hide(),c}let o=p.filter(c=>c.type==="tree"),u=p.filter(c=>c.type==="blob");r.setMessage(`${u.length} files, ${o.length} dirs \u2014 preparing\u2026`);for(let c of o)await z(t,c.path);let g=0,$=0,d={};await ht(u,pt,async c=>{let m=await B(s,c.path,i,n);await ut(t,c.path),await t.app.vault.adapter.writeBinary(c.path,m),d[c.path]=c.sha,g++,$+=m.byteLength,g%mt===0&&r.setMessage(`${g}/${u.length} files, ${Y($)}`)}),t.settings.lastCommitSha=i,t.settings.fileShaMap=d,await t.saveSettings(),r.setMessage(`Seed complete \u2014 ${g} files, ${Y($)} @ ${i.slice(0,7)}`),setTimeout(()=>r.hide(),6e3)}var N=require("obsidian");function dt(t){return t<1024?`${t} B`:t<1024*1024?`${(t/1024).toFixed(1)} KB`:`${(t/(1024*1024)).toFixed(1)} MB`}async function gt(t,n){if(!(!n||n==="/"||n==="."))try{await t.app.vault.adapter.mkdir(n)}catch{}}async function ft(t,n){let e=n.split("/");if(e.pop(),e.length===0)return;let a="";for(let s of e)s&&(a=a?`${a}/${s}`:s,await gt(t,a))}async function yt(t,n){try{await t.app.vault.adapter.remove(n)}catch{}}async function wt(t,n,e){let a=0,s=[];for(let r=0;r{for(;;){let i=a++;if(i>=t.length)return;await e(t[i])}})());await Promise.all(s)}var St=8;async function F(t,n={}){let{pat:e,repoUrl:a,branch:s,lastCommitSha:r,fileShaMap:i}=t.settings;if(!e)throw new Error("Set a GitHub PAT in Vault Sync settings.");if(!a)throw new Error("Set a repo URL in Vault Sync settings.");if(!r)throw new Error("No baseline commit recorded. Run 'Seed from GitHub' first.");let p=T(a),o=n.silent?null:new N.Notice("Checking remote\u2026",0),u=await P(p,s||"main",e);if(u===r){o&&(o?.setMessage("Already up to date"),setTimeout(()=>o?.hide(),4e3));return}o?.setMessage(`Comparing ${r.slice(0,7)}\u2026${u.slice(0,7)}`);let g=await I(p,r,u,e);if(g.status==="diverged")throw o?.setMessage("Remote diverged from local baseline. Manual resolution needed (push will detect this)."),setTimeout(()=>o?.hide(),1e4),new Error(`Remote and local have diverged (ahead ${g.ahead_by}, behind ${g.behind_by}). Pull only supports fast-forward updates.`);let $=g.files??[];if($.length===0){t.settings.lastCommitSha=u,await t.saveSettings(),o?.setMessage("Pull complete (no file changes)"),setTimeout(()=>o?.hide(),4e3);return}$.length===300&&new N.Notice("Compare returned exactly 300 files (GitHub's per-call cap). Some changes may be missing \u2014 re-seed if pull seems incomplete.",1e4);let d=[],c=[];for(let h of $)switch(h.status){case"added":case"modified":case"changed":case"copied":d.push(h);break;case"renamed":h.previous_filename&&c.push(h.previous_filename),d.push(h);break;case"removed":c.push(h.filename);break;case"unchanged":break}o?.setMessage(`${d.length} to fetch, ${c.length} to remove\u2026`);for(let h of c)await yt(t,h),delete i[h];let m=0,y=0;await wt(d,St,async h=>{let R=await B(p,h.filename,u,e);await ft(t,h.filename),await t.app.vault.adapter.writeBinary(h.filename,R),i[h.filename]=h.sha,m++,y+=R.byteLength,m%5===0&&o?.setMessage(`${m}/${d.length} files, ${dt(y)}`)}),t.settings.lastCommitSha=u,t.settings.fileShaMap=i,await t.saveSettings(),o?.setMessage(`Pull complete \u2014 ${d.length} updated, ${c.length} removed @ ${u.slice(0,7)}`),setTimeout(()=>o?.hide(),6e3)}var Z=require("obsidian");function bt(t){let n=new Set([`${t}/workspace.json`,`${t}/workspace-mobile.json`,`${t}/cache`,`${t}/appearance.json`,".DS_Store","_GIT-DEBUG-ERROR.md"]),e=[".trash/",`${t}/plugins/`],a=`${t}/workspace`;return s=>{if(n.has(s)||s.endsWith(".tmp"))return!0;for(let r of e)if(s.startsWith(r))return!0;return!!s.startsWith(a)}}async function $t(t){let n=new TextEncoder().encode(`blob ${t.byteLength}\0`),e=new Uint8Array(n.byteLength+t.byteLength);e.set(n,0),e.set(new Uint8Array(t),n.byteLength);let a=await crypto.subtle.digest("SHA-1",e),s=new Uint8Array(a),r="";for(let i of s)r+=i.toString(16).padStart(2,"0");return r}function Q(t){return t<1024?`${t} B`:t<1024*1024?`${(t/1024).toFixed(1)} KB`:`${(t/(1024*1024)).toFixed(1)} MB`}async function vt(t){let n=[];async function e(a){let s=await t.app.vault.adapter.list(a||"/");for(let r of s.files)n.push(r);for(let r of s.folders)r===".trash"||r===".git"||await e(r)}return await e(""),n}async function H(t,n={}){let{pat:e,repoUrl:a,branch:s,lastCommitSha:r,fileShaMap:i}=t.settings;if(!e)throw new Error("Set a GitHub PAT in Vault Sync settings.");if(!a)throw new Error("Set a repo URL in Vault Sync settings.");if(!r)throw new Error("No baseline commit. Seed first.");let p=T(a),o=n.silent?null:new Z.Notice("Scanning local changes\u2026",0),u=await P(p,s||"main",e);if(u!==r)throw o?.hide(),new Error(`Remote advanced (${u.slice(0,7)} vs local baseline ${r.slice(0,7)}). Run 'Pull from GitHub' first.`);let g=bt(t.app.vault.configDir),d=(await vt(t)).filter(l=>!g(l)),c=new Set(d);o?.setMessage(`Hashing ${d.length} files\u2026`);let m=[],y=[],h=0;for(let l of d){let w=await t.app.vault.adapter.readBinary(l),C=await $t(w),L=i[l];L?L!==C&&m.push({path:l,kind:"modify",content:w}):m.push({path:l,kind:"add",content:w}),h++,h%50===0&&o?.setMessage(`Hashed ${h}/${d.length}\u2026`)}for(let l of Object.keys(i))c.has(l)||y.push(l);if(m.length===0&&y.length===0){o?.setMessage("Nothing to push"),setTimeout(()=>o?.hide(),4e3);return}o?.setMessage(`Uploading ${m.length} blobs (${y.length} deletions)\u2026`);let R=[],U=0,V=0;for(let l of m){let w=await D(p,l.content,e);R.push({path:l.path,mode:"100644",type:"blob",sha:w}),U++,V+=l.content.byteLength,U%5===0&&o?.setMessage(`Uploaded ${U}/${m.length} (${Q(V)})`)}for(let l of y)R.push({path:l,mode:"100644",type:"blob",sha:null});o?.setMessage("Creating tree\u2026");let tt=await O(p,r,e),et=await W(p,tt,R,e);o?.setMessage("Creating commit\u2026");let st=`Vault Sync: ${m.length} changed, ${y.length} deleted from mobile`,k=await J(p,st,et,[r],e);o?.setMessage("Updating branch\u2026");let A=await K(p,s||"main",k,e);if(!A.ok)throw o?.hide(),new Error(`Branch update failed (${A.status}): ${A.message}. Pull and retry.`);for(let l of m){let w=R.find(C=>C.path===l.path&&C.sha!==null);w&&w.sha&&(i[l.path]=w.sha)}for(let l of y)delete i[l];t.settings.lastCommitSha=k,t.settings.fileShaMap=i,await t.saveSettings(),o?.setMessage(`Pushed ${m.length} changes (${Q(V)}) \u2192 ${k.slice(0,7)}`),setTimeout(()=>o?.hide(),6e3)}var M=class extends E.Plugin{constructor(){super(...arguments);this.autoSyncTimer=null;this.autoSyncRunning=!1;this.statusBar=null}async onload(){await this.loadSettings(),this.addSettingTab(new x(this.app,this)),this.statusBar=this.addStatusBarItem(),this.updateStatusBar("idle"),this.addRibbonIcon("refresh-cw","Sync vault now",()=>{this.runWithErrorNotice("sync",()=>this.runSync(!1))}),this.addCommand({id:"seed-from-github",name:"Seed from GitHub",callback:()=>this.runWithErrorNotice("seed",()=>X(this))}),this.addCommand({id:"pull-from-github",name:"Pull from GitHub",callback:()=>this.runWithErrorNotice("pull",()=>F(this))}),this.addCommand({id:"push-to-github",name:"Push to GitHub",callback:()=>this.runWithErrorNotice("push",()=>H(this))}),this.addCommand({id:"sync-now",name:"Sync now (pull then push)",callback:()=>this.runWithErrorNotice("sync",()=>this.runSync(!1))}),this.scheduleAutoSync()}updateStatusBar(e,a){if(!this.statusBar)return;let s=this.settings.lastCommitSha?this.settings.lastCommitSha.slice(0,7):"(unseeded)",r=e==="syncing"?"\u27F3":e==="error"?"\u26A0":"\u2713";this.statusBar.setText(`${r} Vault Sync ${s}${a?` \u2014 ${a}`:""}`)}onunload(){this.autoSyncTimer!==null&&(window.clearInterval(this.autoSyncTimer),this.autoSyncTimer=null)}async runWithErrorNotice(e,a){try{await a()}catch(s){let r=s instanceof Error?s.message:String(s);console.error(`[Vault Sync] ${e} failed`,s),new E.Notice(`Vault Sync ${e} failed: ${r}`,1e4)}}async runSync(e){if(!this.autoSyncRunning){this.autoSyncRunning=!0,this.updateStatusBar("syncing");try{await F(this,{silent:e}),await H(this,{silent:e}),this.updateStatusBar("idle")}catch(a){throw this.updateStatusBar("error",a instanceof Error?a.message.slice(0,40):""),a}finally{this.autoSyncRunning=!1}}}scheduleAutoSync(){this.autoSyncTimer!==null&&(window.clearInterval(this.autoSyncTimer),this.autoSyncTimer=null);let e=this.settings.autoSyncIntervalMinutes??0;if(e<=0)return;let a=e*60*1e3;this.autoSyncTimer=window.setInterval(()=>{this.runSync(!0).catch(s=>{let r=s instanceof Error?s.message:String(s);console.error("[Vault Sync] auto-sync failed",s),new E.Notice(`Vault Sync auto-sync failed: ${r}`,1e4)})},a)}async loadSettings(){let e=await this.loadData()??{};this.settings=Object.assign({},j,e)}async saveSettings(){await this.saveData(this.settings)}}; diff --git a/manifest.json b/manifest.json index ff3adaa..9a14e16 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "vault-sync-rest", "name": "Vault Sync (REST)", - "version": "1.0.2", + "version": "1.0.3", "minAppVersion": "1.4.0", "description": "Two-way sync with a GitHub repo via REST API. Works on iOS for vaults of any size including images, where git-protocol plugins crash from WebView memory limits.", "author": "Andrew Boldi", diff --git a/src/main.ts b/src/main.ts index 663b532..606a6cb 100644 --- a/src/main.ts +++ b/src/main.ts @@ -21,7 +21,7 @@ export default class VaultSyncPlugin extends Plugin { this.statusBar = this.addStatusBarItem(); this.updateStatusBar("idle"); - this.addRibbonIcon("refresh-cw", "Vault Sync: Sync now", () => { + this.addRibbonIcon("refresh-cw", "Sync vault now", () => { void this.runWithErrorNotice("sync", () => this.runSync(false)); }); diff --git a/src/pull.ts b/src/pull.ts index c58af16..a0c1e38 100644 --- a/src/pull.ts +++ b/src/pull.ts @@ -18,7 +18,7 @@ async function ensureDir(plugin: VaultSyncPlugin, dirPath: string): Promise { try { await plugin.app.vault.adapter.remove(path); - } catch (_e) { + } catch { /* already gone */ } } @@ -78,35 +78,35 @@ export async function pullFromGitHub( if (!repoUrl) throw new Error("Set a repo URL in Vault Sync settings."); if (!lastCommitSha) { throw new Error( - "No baseline commit recorded. Run 'Vault Sync: Seed from GitHub' first." + "No baseline commit recorded. Run 'Seed from GitHub' first." ); } const ref = parseRepoUrl(repoUrl); const notice = opts.silent ? null - : new Notice("Vault Sync: checking remote…", 0); + : new Notice("Checking remote…", 0); const headSha = await getBranchSha(ref, branch || "main", pat); if (headSha === lastCommitSha) { if (notice) { - notice?.setMessage("Vault Sync: already up to date"); + notice?.setMessage("Already up to date"); setTimeout(() => notice?.hide(), 4000); } return; } - notice?.setMessage(`Vault Sync: comparing ${lastCommitSha.slice(0, 7)}…${headSha.slice(0, 7)}`); + notice?.setMessage(`Comparing ${lastCommitSha.slice(0, 7)}…${headSha.slice(0, 7)}`); const cmp = await compareCommits(ref, lastCommitSha, headSha, pat); if (cmp.status === "diverged") { notice?.setMessage( - "Vault Sync: remote diverged from local baseline. Manual resolution needed (Phase 3 push will detect this)." + "Remote diverged from local baseline. Manual resolution needed (push will detect this)." ); setTimeout(() => notice?.hide(), 10000); throw new Error( - `Remote and local have diverged (ahead ${cmp.ahead_by}, behind ${cmp.behind_by}). Vault Sync can only fast-forward in Phase 2.` + `Remote and local have diverged (ahead ${cmp.ahead_by}, behind ${cmp.behind_by}). Pull only supports fast-forward updates.` ); } @@ -114,14 +114,14 @@ export async function pullFromGitHub( if (files.length === 0) { plugin.settings.lastCommitSha = headSha; await plugin.saveSettings(); - notice?.setMessage("Vault Sync: pull complete (no file changes)"); + notice?.setMessage("Pull complete (no file changes)"); setTimeout(() => notice?.hide(), 4000); return; } if (files.length === 300) { new Notice( - "Vault Sync: compare returned exactly 300 files (GitHub's per-call cap). Some changes may be missing — re-seed if pull seems incomplete.", + "Compare returned exactly 300 files (GitHub's per-call cap). Some changes may be missing — re-seed if pull seems incomplete.", 10000 ); } @@ -150,7 +150,7 @@ export async function pullFromGitHub( } notice?.setMessage( - `Vault Sync: ${toFetch.length} to fetch, ${toRemove.length} to remove…` + `${toFetch.length} to fetch, ${toRemove.length} to remove…` ); // Apply removals first (cheap, sequential). @@ -171,7 +171,7 @@ export async function pullFromGitHub( bytes += content.byteLength; if (done % 5 === 0) { notice?.setMessage( - `Vault Sync: ${done}/${toFetch.length} files, ${formatBytes(bytes)}` + `${done}/${toFetch.length} files, ${formatBytes(bytes)}` ); } }); @@ -181,7 +181,7 @@ export async function pullFromGitHub( await plugin.saveSettings(); notice?.setMessage( - `Vault Sync: pull complete — ${toFetch.length} updated, ${toRemove.length} removed @ ${headSha.slice(0, 7)}` + `Pull complete — ${toFetch.length} updated, ${toRemove.length} removed @ ${headSha.slice(0, 7)}` ); setTimeout(() => notice?.hide(), 6000); } diff --git a/src/push.ts b/src/push.ts index 73b50fb..60dc916 100644 --- a/src/push.ts +++ b/src/push.ts @@ -11,30 +11,28 @@ import { type TreeUpdateEntry, } from "./github"; -const SKIP_PATHS = new Set([ - ".obsidian/workspace.json", - ".obsidian/workspace-mobile.json", - ".obsidian/cache", - ".obsidian/appearance.json", - ".DS_Store", - "_GIT-DEBUG-ERROR.md", -]); - -const SKIP_PREFIXES = [ - ".trash/", +function buildSkipFn(configDir: string): (path: string) => boolean { + const skipPaths = new Set([ + `${configDir}/workspace.json`, + `${configDir}/workspace-mobile.json`, + `${configDir}/cache`, + `${configDir}/appearance.json`, + ".DS_Store", + "_GIT-DEBUG-ERROR.md", + ]); // Plugin installs are per-device. Each Obsidian install pulls plugin code // from its own source (community store, BRAT, etc). Syncing plugin folders // bloats the repo and risks leaking credentials stored in plugin data.json. - // The list of *which* plugins to enable still syncs via .obsidian/community-plugins.json. - ".obsidian/plugins/", -]; - -function shouldSkip(path: string): boolean { - if (SKIP_PATHS.has(path)) return true; - if (path.endsWith(".tmp")) return true; - for (const p of SKIP_PREFIXES) if (path.startsWith(p)) return true; - if (path.startsWith(".obsidian/workspace")) return true; - return false; + // The list of *which* plugins to enable still syncs via community-plugins.json. + const skipPrefixes = [".trash/", `${configDir}/plugins/`]; + const workspacePrefix = `${configDir}/workspace`; + return (path: string) => { + if (skipPaths.has(path)) return true; + if (path.endsWith(".tmp")) return true; + for (const p of skipPrefixes) if (path.startsWith(p)) return true; + if (path.startsWith(workspacePrefix)) return true; + return false; + }; } async function gitBlobSha(content: ArrayBuffer): Promise { @@ -86,23 +84,24 @@ export async function pushToGitHub( const ref = parseRepoUrl(repoUrl); const notice = opts.silent ? null - : new Notice("Vault Sync: scanning local changes…", 0); + : new Notice("Scanning local changes…", 0); // 1. Detect divergence — refuse if remote moved. const remoteHead = await getBranchSha(ref, branch || "main", pat); if (remoteHead !== lastCommitSha) { notice?.hide(); throw new Error( - `Remote advanced (${remoteHead.slice(0, 7)} vs local baseline ${lastCommitSha.slice(0, 7)}). Run 'Vault Sync: Pull from GitHub' first.` + `Remote advanced (${remoteHead.slice(0, 7)} vs local baseline ${lastCommitSha.slice(0, 7)}). Run 'Pull from GitHub' first.` ); } // 2. Walk vault, hash every non-skipped file, compare to fileShaMap. + const shouldSkip = buildSkipFn(plugin.app.vault.configDir); const allPaths = await listAllVaultFiles(plugin); const localFiles = allPaths.filter((p) => !shouldSkip(p)); const localSet = new Set(localFiles); - notice?.setMessage(`Vault Sync: hashing ${localFiles.length} files…`); + notice?.setMessage(`Hashing ${localFiles.length} files…`); type Change = { path: string; kind: "add" | "modify"; content: ArrayBuffer }; const changes: Change[] = []; @@ -121,7 +120,7 @@ export async function pushToGitHub( scanned++; if (scanned % 50 === 0) { notice?.setMessage( - `Vault Sync: hashed ${scanned}/${localFiles.length}…` + `Hashed ${scanned}/${localFiles.length}…` ); } } @@ -131,14 +130,14 @@ export async function pushToGitHub( } if (changes.length === 0 && deletions.length === 0) { - notice?.setMessage("Vault Sync: nothing to push"); + notice?.setMessage("Nothing to push"); setTimeout(() => notice?.hide(), 4000); return; } // 3. Upload blobs for changed files (sequential to keep memory bounded for large files). notice?.setMessage( - `Vault Sync: uploading ${changes.length} blobs (${deletions.length} deletions)…` + `Uploading ${changes.length} blobs (${deletions.length} deletions)…` ); const treeEntries: TreeUpdateEntry[] = []; @@ -156,7 +155,7 @@ export async function pushToGitHub( bytes += c.content.byteLength; if (uploaded % 5 === 0) { notice?.setMessage( - `Vault Sync: uploaded ${uploaded}/${changes.length} (${formatBytes(bytes)})` + `Uploaded ${uploaded}/${changes.length} (${formatBytes(bytes)})` ); } } @@ -166,12 +165,12 @@ export async function pushToGitHub( } // 4. Build new tree on top of the last commit's tree. - notice?.setMessage("Vault Sync: creating tree…"); + notice?.setMessage("Creating tree…"); const baseTreeSha = await getCommitTreeSha(ref, lastCommitSha, pat); const newTreeSha = await createTree(ref, baseTreeSha, treeEntries, pat); // 5. Create commit. - notice?.setMessage("Vault Sync: creating commit…"); + notice?.setMessage("Creating commit…"); const message = `Vault Sync: ${changes.length} changed, ${deletions.length} deleted from mobile`; const newCommitSha = await createCommit( ref, @@ -182,7 +181,7 @@ export async function pushToGitHub( ); // 6. Update branch ref. - notice?.setMessage("Vault Sync: updating branch…"); + notice?.setMessage("Updating branch…"); const upd = await updateRef(ref, branch || "main", newCommitSha, pat); if (!upd.ok) { notice?.hide(); @@ -204,7 +203,7 @@ export async function pushToGitHub( await plugin.saveSettings(); notice?.setMessage( - `Vault Sync: pushed ${changes.length} changes (${formatBytes(bytes)}) → ${newCommitSha.slice(0, 7)}` + `Pushed ${changes.length} changes (${formatBytes(bytes)}) → ${newCommitSha.slice(0, 7)}` ); setTimeout(() => notice?.hide(), 6000); } diff --git a/src/seed.ts b/src/seed.ts index fc9e5e0..b60725d 100644 --- a/src/seed.ts +++ b/src/seed.ts @@ -19,7 +19,7 @@ async function ensureDir(plugin: VaultSyncPlugin, dirPath: string): Promise { if (!repoUrl) throw new Error("Set a repo URL in Vault Sync settings."); const ref = parseRepoUrl(repoUrl); - const notice = new Notice("Vault Sync: resolving branch…", 0); + const notice = new Notice("Resolving branch…", 0); let sha: string; try { @@ -83,7 +83,7 @@ export async function seedFromGitHub(plugin: VaultSyncPlugin): Promise { throw e; } - notice.setMessage(`Vault Sync: listing ${ref.owner}/${ref.repo}@${sha.slice(0, 7)}…`); + notice.setMessage(`Listing ${ref.owner}/${ref.repo}@${sha.slice(0, 7)}…`); let tree: TreeEntry[]; try { @@ -97,7 +97,7 @@ export async function seedFromGitHub(plugin: VaultSyncPlugin): Promise { const blobs = tree.filter((e) => e.type === "blob"); notice.setMessage( - `Vault Sync: ${blobs.length} files, ${dirs.length} dirs — preparing…` + `${blobs.length} files, ${dirs.length} dirs — preparing…` ); // Pre-create all directories sequentially (cheap, prevents race conditions). @@ -118,7 +118,7 @@ export async function seedFromGitHub(plugin: VaultSyncPlugin): Promise { byteCount += content.byteLength; if (fileCount % PROGRESS_EVERY === 0) { notice.setMessage( - `Vault Sync: ${fileCount}/${blobs.length} files, ${formatBytes(byteCount)}` + `${fileCount}/${blobs.length} files, ${formatBytes(byteCount)}` ); } }); @@ -128,7 +128,7 @@ export async function seedFromGitHub(plugin: VaultSyncPlugin): Promise { await plugin.saveSettings(); notice.setMessage( - `Vault Sync: seed complete — ${fileCount} files, ${formatBytes(byteCount)} @ ${sha.slice(0, 7)}` + `Seed complete — ${fileCount} files, ${formatBytes(byteCount)} @ ${sha.slice(0, 7)}` ); setTimeout(() => notice.hide(), 6000); } diff --git a/src/settings.ts b/src/settings.ts index 52f167b..43fccc3 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -31,10 +31,8 @@ export class VaultSyncSettingTab extends PluginSettingTab { const { containerEl } = this; containerEl.empty(); - containerEl.createEl("h2", { text: "Vault Sync (REST)" }); - new Setting(containerEl) - .setName("GitHub Personal Access Token") + .setName("GitHub personal access token") .setDesc( "Fine-grained or classic PAT with repo (contents: read/write) scope on the target repo." ) @@ -50,7 +48,7 @@ export class VaultSyncSettingTab extends PluginSettingTab { new Setting(containerEl) .setName("Repository URL") - .setDesc("e.g. https://github.com/owner/repo") + .setDesc("For example, https://github.com/owner/repo") .addText((text) => text .setPlaceholder("https://github.com/owner/repo")