diff --git a/main.js b/main.js index 02002be..048cf7d 100644 --- a/main.js +++ b/main.js @@ -1,149 +1,155 @@ -"use strict";var Tn=Object.defineProperty;var fl=Object.getOwnPropertyDescriptor;var Il=Object.getOwnPropertyNames;var ml=Object.prototype.hasOwnProperty;var $e=(A,r)=>()=>(A&&(r=A(A=0)),r);var wi=(A,r)=>{for(var e in r)Tn(A,e,{get:r[e],enumerable:!0})},Cl=(A,r,e,t)=>{if(r&&typeof r=="object"||typeof r=="function")for(let i of Il(r))!ml.call(A,i)&&i!==e&&Tn(A,i,{get:()=>r[i],enumerable:!(t=fl(r,i))||t.enumerable});return A};var Bl=A=>Cl(Tn({},"__esModule",{value:!0}),A);function Gn(A){return Array.isArray(A)?A.filter(r=>typeof r=="string"):typeof A=="string"?A.split(/[,\s]+/).filter(Boolean):[]}function Vt(A,r){let e=r.replace(/^#/,"");return Gn(A?.tags).some(t=>t.replace(/^#/,"")===e)}function Ln(A,r){let e=r.replace(/^#/,""),t=Gn(A.tags);t.some(i=>i.replace(/^#/,"")===e)||t.push(e),A.tags=t}function qn(A,r){let e=r.replace(/^#/,""),t=Gn(A.tags).filter(i=>i.replace(/^#/,"")!==e);t.length?A.tags=t:delete A.tags}function Wt(A){return A.split("/").some(r=>Si.has(r))}function da(A){return A.split("/").some(r=>r==="_archive"||r===".archive")}function We(A){let r=(A??"").trim();return!r||/^\[\[.*\]\]$/.test(r)?r:`[[${r}]]`}function Nt(A){let r=(A??"").trim(),e=r.match(/^\[\[(.*)\]\]$/);return e&&(r=e[1]),r.split("|")[0].split("#")[0].split("^")[0].trim()}function Aa(A){let r=A.match(/\.([^./]+)$/);return!!r&&Ql.has(r[1].toLowerCase())}function ha(A,r){if(!Array.isArray(r))return!1;for(let e of r){let t=(e??"").trim();if(t){if(t.length>2&&t.startsWith("/")&&t.endsWith("/"))try{if(new RegExp(t.slice(1,-1)).test(A))return!0}catch{}else if(A===t||A.startsWith(t.endsWith("/")?t:t+"/"))return!0}}return!1}function hs(A,r){let e=A.toLowerCase().split(/\s+/).filter(Boolean);if(e.length===0)return!0;let t=r.toLowerCase();return e.every(i=>t.includes(i))}function rt(A){if(typeof A!="string")return null;let r=A.replace(/^\[\[/,"").replace(/\]\]$/,""),[e,t]=r.split("|"),i=e.match(/_authors\/(.+?)-([a-z0-9]{4,12})(?:\.md)?$/i);if(!i)return null;let s=i[2],n=(t??"").trim()||i[1].replace(/-/g," ").trim();return{id:s,name:n}}function Mt(A){let r=A?.assignedTo,e=Array.isArray(r)?r:r!=null?[r]:[],t=[];for(let i of e){let s=rt(i);s&&t.push(s)}return t}var z,It,mt,xt,$t,zt,k,Ct,Si,Ql,le=$e(()=>{"use strict";z="stashpad-view",It="stashpad-panels",mt="stashpad-folder-panel",xt="stashpad-detail",$t="stashpad-trash",zt="stashpad-aggregate",k="__root__";Ct=["id","parent","created","modified","attachments","position","author","contributors","parentLink","children","due","assignedTo","assignedBy","pinned","pinnedAt","listPinned","listPinnedAt","imported","okfParent","okfChildren"],Si=new Set(["_attachments","_authors","_exports","_imports","_processed","_archive",".archive"]);Ql=new Set(["edtz"])});function Di(A,r){let e=A.replace(/\r\n/g,` +"use strict";var Vn=Object.defineProperty;var Pl=Object.getOwnPropertyDescriptor;var Hl=Object.getOwnPropertyNames;var Rl=Object.prototype.hasOwnProperty;var $e=(A,r)=>()=>(A&&(r=A(A=0)),r);var ki=(A,r)=>{for(var e in r)Vn(A,e,{get:r[e],enumerable:!0})},Ul=(A,r,e,t)=>{if(r&&typeof r=="object"||typeof r=="function")for(let i of Hl(r))!Rl.call(A,i)&&i!==e&&Vn(A,i,{get:()=>r[i],enumerable:!(t=Pl(r,i))||t.enumerable});return A};var Gl=A=>Ul(Vn({},"__esModule",{value:!0}),A);function _n(A){return Array.isArray(A)?A.filter(r=>typeof r=="string"):typeof A=="string"?A.split(/[,\s]+/).filter(Boolean):[]}function _t(A,r){let e=r.replace(/^#/,"");return _n(A?.tags).some(t=>t.replace(/^#/,"")===e)}function eo(A,r){let e=r.replace(/^#/,""),t=_n(A.tags);t.some(i=>i.replace(/^#/,"")===e)||t.push(e),A.tags=t}function to(A,r){let e=r.replace(/^#/,""),t=_n(A.tags).filter(i=>i.replace(/^#/,"")!==e);t.length?A.tags=t:delete A.tags}function Qr(A){return Bs.has(A)||Er.has(A)}function Ct(A){return`${(A||"").replace(/\/+$/,"")}/archive`}function ys(A){return(A||"").replace(/\/+$/,"").split("/").slice(1).includes("archive")}function Ht(A){return A.split("/").some((r,e)=>Bs.has(r)||e>0&&Er.has(r))}function vr(A){return A.split("/").some(r=>r==="_archive"||r===".archive")}function We(A){let r=(A??"").trim();return!r||/^\[\[.*\]\]$/.test(r)?r:`[[${r}]]`}function Rt(A){let r=(A??"").trim(),e=r.match(/^\[\[(.*)\]\]$/);return e&&(r=e[1]),r.split("|")[0].split("#")[0].split("^")[0].trim()}function Fr(A){let r=A.match(/\.([^./]+)$/);return!!r&&ql.has(r[1].toLowerCase())}function kr(A,r){if(!Array.isArray(r))return!1;for(let e of r){let t=(e??"").trim();if(t){if(t.length>2&&t.startsWith("/")&&t.endsWith("/"))try{if(new RegExp(t.slice(1,-1)).test(A))return!0}catch{}else if(A===t||A.startsWith(t.endsWith("/")?t:t+"/"))return!0}}return!1}function ws(A,r){let e=A.toLowerCase().split(/\s+/).filter(Boolean);if(e.length===0)return!0;let t=r.toLowerCase();return e.every(i=>t.includes(i))}function ct(A){if(typeof A!="string")return null;let r=A.replace(/^\[\[/,"").replace(/\]\]$/,""),[e,t]=r.split("|"),i=e.match(/_authors\/(.+?)-([a-z0-9]{4,12})(?:\.md)?$/i);if(!i)return null;let s=i[2],n=(t??"").trim()||i[1].replace(/-/g," ").trim();return{id:s,name:n}}function Ut(A){let r=A?.assignedTo,e=Array.isArray(r)?r:r!=null?[r]:[],t=[];for(let i of e){let s=ct(i);s&&t.push(s)}return t}var z,It,mt,Pt,Zt,jt,F,Ve,Bs,Er,ql,le=$e(()=>{"use strict";z="stashpad-view",It="stashpad-panels",mt="stashpad-folder-panel",Pt="stashpad-detail",Zt="stashpad-trash",jt="stashpad-aggregate",F="__root__";Ve=["id","parent","created","modified","attachments","position","author","contributors","parentLink","children","due","assignedTo","assignedBy","pinned","pinnedAt","listPinned","listPinnedAt","imported","okfParent","okfChildren"],Bs=new Set(["_attachments","_authors","_exports","_imports","_processed","_archive",".archive"]),Er=new Set(["archive","trash"]);ql=new Set(["edtz"])});function Ti(A,r){let e=A.replace(/\r\n/g,` `);if(r==="lines")return e.split(` `).map(n=>n.trim()).filter(Boolean);if(r==="paragraphs")return e.split(/\n[ \t]*\n+/).map(n=>n.trim()).filter(Boolean);let t=[],i=[],s=n=>/^#{1,6}\s/.test(n);for(let n of e.split(` `))s(n)&&i.some(o=>o.trim())&&(t.push(i.join(` `).trim()),i=[]),i.push(n);return i.some(n=>n.trim())&&t.push(i.join(` -`).trim()),t.filter(Boolean)}function Se(A,r,e){A.empty();try{(0,ga.setIcon)(A,r)}catch{}let t=A.querySelector("svg");t&&t.querySelector("path, line, circle, rect, polyline, polygon, ellipse")||(A.empty(),A.createSpan({cls:"stashpad-icon-fallback",text:e}))}function ua(A){if(A instanceof Element&&A.closest(".modal, .modal-container, .suggestion-container, .menu, .prompt"))return!0;let r=new Set([document]);A instanceof Element&&A.ownerDocument&&r.add(A.ownerDocument);for(let e of r)if(e.body?.querySelector(".modal-bg")||e.body?.querySelector(".modal-container .modal")||e.body?.querySelector(".suggestion-container")||e.body?.querySelector(".menu.mod-active"))return!0;return!1}function fa(A){let r=[],e=/^([ \t]*)(`{3,})[ \t]*([^\n`]*)\n([\s\S]*?)\n\1\2[ \t]*$/gm,t;for(;(t=e.exec(A))!=null;)r.push({lang:t[3].trim(),code:t[4]});return r}function xi(A){return A.split("/").map(r=>r.split(" ").map(e=>e&&/^[a-z]/.test(e)?e[0].toUpperCase()+e.slice(1):e).join(" ")).join("/")}function Ia(A,r,e){let t=new Set(r),i=A.filter(n=>!t.has(n)),s=A.findIndex(n=>t.has(n));if(s<0)return A.slice();switch(e){case"top":return[...r,...i];case"bottom":return[...i,...r];case"up":{let n=Math.max(0,s-1),o=i.slice();return o.splice(n,0,...r),o}case"down":{let n=(()=>{let c=-1;return A.forEach((d,h)=>{t.has(d)&&(c=h)}),c})(),o=0;for(let c=0;c0){let n=t[i-1];return(!/[a-z0-9]/.test(n)?600:500)-Math.min(i,99)}let s=0;for(let n=0;n({t,s:El(e,t.raw)})).filter(t=>t.s>=0).sort((t,i)=>i.s-t.s||i.t.count-t.t.count||t.t.label.localeCompare(i.t.label)).map(t=>t.t):r}var ga,bi,Tt,Pt,Ni=$e(()=>{"use strict";ga=require("obsidian"),bi={lines:"Each line",paragraphs:"Paragraphs (blank line)",headings:"Headings"};Tt="__stashpad:tagged__",Pt="__stashpad:untagged__"});function vl(A,r){return A>12?A>=24?0:A:r==="am"?A===12?0:A:A===12?12:A+12}function gs(A,r){A.addClass("stashpad-when-pop-time");let e=r.seedPeriod,t=A.createDiv({cls:"stashpad-when-time-display"}),i=t.createEl("input",{cls:"stashpad-when-time-field",attr:{type:"text",inputmode:"numeric",maxlength:"2"}});i.value=String(r.seedH),t.createSpan({cls:"stashpad-when-time-colon",text:":"});let s=t.createEl("input",{cls:"stashpad-when-time-field",attr:{type:"text",inputmode:"numeric",maxlength:"2"}});s.value=String(r.seedM).padStart(2,"0");let n=t.createDiv({cls:"stashpad-when-time-period"}),o=n.createEl("button",{cls:"stashpad-when-time-ampm",text:"AM"});o.type="button";let a=n.createEl("button",{cls:"stashpad-when-time-ampm",text:"PM"});a.type="button";let l=()=>{o.toggleClass("is-active",e==="am"),a.toggleClass("is-active",e==="pm")};l(),o.addEventListener("mousedown",B=>B.preventDefault()),a.addEventListener("mousedown",B=>B.preventDefault()),o.addEventListener("click",B=>{B.preventDefault(),e="am",l()}),a.addEventListener("click",B=>{B.preventDefault(),e="pm",l()});let c=i;i.addEventListener("focus",()=>{c=i,i.select()}),s.addEventListener("focus",()=>{c=s,s.select()});let d=()=>{let w=(parseInt(i.value||"0",10)||0)>12;o.toggleClass("is-disabled",w),a.toggleClass("is-disabled",w),o.disabled=w,a.disabled=w},h=B=>{let w=B.value.replace(/\D/g,"").slice(0,2);if(B.value=w,w===""){B===i&&d();return}let y=parseInt(w,10),E=B===i?24:59;y>E&&(B.value=String(E)),B===i&&d()};for(let B of[i,s])B.addEventListener("input",()=>h(B));d();let g=()=>{let B=parseInt(i.value||"12",10)||12,w=parseInt(s.value||"0",10)||0,y=B>12;r.onFinalize({hours24:vl(B,e),minutes:w,raw:{hh:B,mm:w,period:e,is24:y}}),r.close()};r.setOnEnter?.(g);let u=[i,s,o,a],p=(B,w)=>{let y=u.indexOf(B);if(y===-1)return;let E=(y+w+u.length)%u.length;u[E].focus()},f=B=>{B.addEventListener("keydown",w=>{w.key==="Enter"?(w.preventDefault(),w.stopPropagation(),g()):w.key==="Escape"?(w.preventDefault(),w.stopPropagation(),r.close()):w.key==="Tab"&&(w.preventDefault(),w.stopPropagation(),p(B,w.shiftKey?-1:1))})},I=A.createDiv({cls:"stashpad-when-time-pad"}),m=["1","2","3","4","5","6","7","8","9","backspace","0","insert"],C=null;for(let B of m){let w=I.createEl("button",{cls:"stashpad-when-time-padbtn"});w.type="button",B==="backspace"?(0,Ca.setIcon)(w,"delete"):B==="insert"?(w.setText("OK"),C=w):w.setText(B),B==="insert"&&w.addClass("is-go"),w.addEventListener("mousedown",y=>y.preventDefault()),w.addEventListener("click",y=>{if(y.preventDefault(),B==="insert"){g();return}if(B==="backspace"){c.value=c.value.slice(0,-1),h(c),c.focus();return}let E=c.selectionStart===0&&c.selectionEnd===c.value.length&&c.value.length>0,v=2,Q=E||c.value.length>=v?B:c.value+B;c.value=Q,h(c),c.focus(),c.setSelectionRange(c.value.length,c.value.length),c===i&&c.value.length>=v&&(s.focus(),s.select())})}C&&u.push(C);for(let B of u)f(B);i.focus(),i.select()}function Ba(A){let{hh:r,mm:e,period:t,is24:i}=A.raw;return i?`${r}:${String(e).padStart(2,"0")}`:`${r}:${String(e).padStart(2,"0")}${t}`}var Ca,Jn=$e(()=>{"use strict";Ca=require("obsidian")});function wa(A){let r=Math.floor(4294967296/A)*A,e=new Uint32Array(1),t;do crypto.getRandomValues(e),t=e[0];while(t>=r);return t%A}function On(A=5){let r=[];for(let e=0;e=1&&s.every(a=>/^[a-zA-Z]+$/.test(a))&&(i=Math.min(i,s.length*11)),Fl.has(A.toLowerCase())&&(i=Math.min(i,12));let n=i<36?0:i<56?1:i<76?2:3,o=["Weak \u2014 easily guessed","Fair","Good","Strong"][n];return{bits:i,level:n,label:o}}function kl(A){let r=2166136261;for(let e=0;e>>0).toString(16).padStart(8,"0")}function jt(A){let r="stashpad-",e=kl(A),t=64-r.length-1-e.length,i=A.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,Math.max(0,t)).replace(/-+$/g,"");return`${r}${i}-${e}`}var ya,Fl,us=$e(()=>{"use strict";ya="able acid acorn actor adept agile alarm album alert algae amber amble anchor angle ankle apple april arbor arc arctic arena armor arrow ascot aspen atlas atom attic auburn audio autumn award axis bacon badge bagel baker balmy bamboo banjo barge basil basin batch beach beam bean bear beaver begin belt bench berry birch bison black blade blaze bliss bloom blue board boat bold bolt bonus boost booth borax bother bottle boulder bounce bovine bowl brave bread breeze brick bridge brisk broad bronze brook broom brown brush bubble bucket bug bulb bundle burst cabin cable cacao cactus camel cameo candle canoe canvas canyon cargo carol carrot castle catch cedar cello chalk charm chart cheese cherry chess chime chirp cider cinder circle citrus clamp clay clever cliff cloak clock cloud clover coast cobra cocoa comet coral cosmos cotton couch cougar crane crater cream crest cricket crisp crown crumb crystal cube cumin curio dagger daisy dapper dawn deck deer delta denim depot desert dial diamond dingo ditch dock dodge dolphin domino donut draft dragon drift drum dune dusk eagle early earth easel ebony echo eclipse edge eel eagle elbow elder ember emboss emerald ember emu engine ermine ether ever fable falcon fancy farm fawn feather felt fennel fern ferry fiber fiddle field fig finch flame flask flax fleet flint float flock flora flute focus foam forest fossil fox frame frost fudge garden gecko ginger glacier glade glass glide globe glory glove gnat goat golden gopher gourd grape grass gravel grove guava gull hammer hamlet harbor hatch hazel heron hickory hippo honey hornet hostel ivory ivy jade jaguar jasmine jelly jetty jewel jolly jungle juniper kayak kelp kettle kiwi koala ladle lagoon lamp lantern larch lark lasso lava ledge lemon lentil leopard lever lilac lime linen lizard llama lobby locket lodge lotus lunar lupine lynx maple marble marsh meadow medal melon mellow mentor mesa metro mimic mint mirror mocha morsel moss mottle mural muslin nacho nectar nest noble nomad noodle nougat nutmeg oasis oat ocean ochre olive onyx opal orbit orca otter oval owl oxide oyster pace paddle palm panda pantry papaya parka parsley pasta pearl pebble pelican pepper pewter phantom piano picnic pigeon pilot pine pixel plank plaza plum pocket pollen pond poplar poppy portal potato pottery prairie prism puffin pumice quail quartz quill quilt quince rabbit radish raft rapid raven reed reef relish ribbon ridge rind ripple river robin rocket rope rover ruby rudder rust saffron sage salmon sandal sash satin sauce scout sedge sequin shadow shale shark shell shore shrub silk silo silver siren sketch slate sleet slope sloth smoke snail sonar sorrel spark sparrow spice spire sprig spruce squid stable stamp starling steam stem stilt stone stork storm stove straw stream stucco summit sundae sunset swan sweater syrup tabby talon tango tapir tassel tawny teal tempo thicket thimble thistle thorn thunder tidal tiger timber toffee token topaz torch totem trail tulip tundra turtle tusk twig umber unicorn valley vanilla velvet vermil violet viper vista vivid vortex waffle walnut warbler wasp water weasel wharf wheat whisk willow window winter wisp wombat wonder woven yam yarn yeast yew yodel yonder zebra zenith zephyr zigzag zinc zircon".split(/\s+/).filter(Boolean).filter((A,r,e)=>e.indexOf(A)===r);Fl=new Set(["password","passw0rd","password1","123456","12345678","123456789","qwerty","abc123","letmein","welcome","admin","iloveyou","monkey","dragon","sunshine","princess","football","baseball","trustno1","000000","111111","secret"])});var fs={};wi(fs,{newId:()=>Z});function Z(A=6){let r="",e=new Uint8Array(A);crypto.getRandomValues(e);for(let t=0;t{"use strict";Qa="abcdefghijkmnpqrstuvwxyz23456789"});var qi={};wi(qi,{BreadcrumbLevelsModal:()=>Ui,ColorPickerModal:()=>Rt,ConfirmDeleteModal:()=>ei,ConfirmModal:()=>ie,CustomColorModal:()=>Is,DueDatePickerModal:()=>Ke,EncryptionPasswordModal:()=>de,ExportStashModal:()=>ti,ImportDupChoiceModal:()=>Gi,ImportLogModal:()=>$n,LogModal:()=>_t,NotificationHistoryModal:()=>Ri,OkfExportModal:()=>Li,SplitNoteModal:()=>Ti,StashPasswordModal:()=>Pi,TypeToConfirmModal:()=>Hi,buildAssigneePicker:()=>va,parseAdjustMinutes:()=>Ea});function Ea(A){let r=/^\s*(\d+)\s*(m|h|d|w)\s*$/i.exec(A);if(!r)return null;let e=parseInt(r[1],10);if(!Number.isFinite(e)||e<=0)return null;let t={m:1,h:60,d:1440,w:10080}[r[2].toLowerCase()]??1;return e*t}function va(A,r){let e=[...r.initial],t=r.knownAuthors,i=A.createDiv({cls:"stashpad-assign"});i.createDiv({cls:"stashpad-assign-label",text:"Assign to"});let s=i.createDiv({cls:"stashpad-assign-chips"}),n=i.createDiv({cls:"stashpad-assign-input-wrap"}),o=n.createEl("input",{type:"text",cls:"stashpad-assign-input",attr:{placeholder:"Add a person \u2014 type a name\u2026"}}),a=n.createDiv({cls:"stashpad-assign-suggest"});a.setCssStyles({display:"none"});let l=()=>r.onChange([...e]),c=()=>{s.empty(),e.length===0&&s.createSpan({cls:"stashpad-assign-empty",text:"No one yet"});for(let g of e){let u=s.createSpan({cls:"stashpad-assign-chip"});u.createSpan({cls:"stashpad-assign-chip-name",text:g.name});let p=u.createSpan({cls:"stashpad-assign-chip-x",text:"\xD7"});p.title=`Remove ${g.name}`,p.onclick=()=>{e=e.filter(f=>f.id!==g.id),l(),c()}}},d=g=>{g.name.trim()&&(e.some(u=>u.id===g.id)||e.push(g),o.value="",a.setCssStyles({display:"none"}),l(),c(),o.focus())},h=()=>{let g=o.value.trim();a.empty();let u=new Set(e.map(I=>I.id)),f=t.filter(I=>!u.has(I.id)&&hs(g,I.name)).slice(0,6).map(I=>({label:I.name,onPick:()=>d(I)}));if(g&&!t.some(I=>I.name.toLowerCase()===g.toLowerCase())&&f.push({label:`Create \u201C${g}\u201D`,onPick:()=>d({id:Z(6),name:g})}),f.length===0){a.setCssStyles({display:"none"});return}a.setCssStyles({display:""});for(let I of f){let m=a.createDiv({cls:"stashpad-assign-suggest-item",text:I.label});m.onmousedown=C=>{C.preventDefault(),I.onPick()}}};o.addEventListener("input",h),o.addEventListener("focus",h),o.addEventListener("keydown",g=>{if(g.key==="Enter"){g.preventDefault();let u=a.querySelector(".stashpad-assign-suggest-item");u&&u.dispatchEvent(new MouseEvent("mousedown"))}else g.key==="Escape"&&a.style.display!=="none"&&(g.stopPropagation(),a.setCssStyles({display:"none"}))}),o.addEventListener("blur",()=>{window.setTimeout(()=>{a.setCssStyles({display:"none"})},120)}),c()}var O,Sl,Ht,_t,ei,Ti,ti,Pi,de,Hi,Is,Mi,Rt,ie,Ui,Ke,Ut,Ri,$n,Gi,Li,Be=$e(()=>{"use strict";O=require("obsidian");Ni();Jn();le();us();qe();Sl=O.moment,Ht=class Ht extends O.Modal{constructor(e,t,i){super(e);this.text=t;this.jsonlPath=i;this.events=[];this.visible=[];this.shownCount=0;this.typeFilter=null;this.listEl=null;this.footerEl=null;this.countEl=null;this.filterSelEl=null}onOpen(){this.contentEl.empty(),this.titleEl.setText("Stashpad log"),this.modalEl.addClass("stashpad-log-modal"),this.events=[];for(let l of this.text.trim().split(/\r?\n/))if(l)try{this.events.push(JSON.parse(l))}catch{}this.events.reverse();let e=this.contentEl.createDiv({cls:"stashpad-log-toolbar"});this.countEl=e.createSpan({cls:"stashpad-log-count"}),this.updateCount(),this.filterSelEl=e.createEl("select",{cls:"stashpad-log-type-filter"}),this.filterSelEl.onchange=()=>this.setTypeFilter(this.filterSelEl.value||null),this.refreshTypeFilter();let t=e.createEl("button",{text:"Reveal JSONL"});t.onclick=()=>this.shellAct("reveal");let i=e.createEl("button",{text:"Open in default app"});i.onclick=()=>this.shellAct("open");let s=e.createEl("button",{text:"Copy raw JSONL"}),n=null;s.onclick=async()=>{await navigator.clipboard.writeText(this.text),s.setText("Copied"),n!=null&&window.clearTimeout(n),n=window.setTimeout(()=>{s.setText("Copy raw JSONL"),n=null},1500)};let o=e.createEl("button",{text:"Export copy"});o.onclick=()=>this.exportCopy();let a=e.createEl("button",{cls:"mod-warning",text:"Clear log"});a.onclick=()=>this.clearLog(),this.listEl=this.contentEl.createDiv({cls:"stashpad-log-list"}),this.refreshList(),this.footerEl=this.contentEl.createDiv({cls:"stashpad-log-footer"}),this.renderFooter()}refreshList(){if(this.listEl){if(this.visible=this.typeFilter?this.events.filter(e=>e.type===this.typeFilter):this.events.slice(),this.shownCount=0,this.listEl.empty(),!this.visible.length){this.listEl.createDiv({cls:"stashpad-log-empty",text:this.typeFilter?`No "${this.typeFilter}" events.`:"No events yet."}),this.updateCount();return}this.appendMore(Ht.PAGE)}}setTypeFilter(e){(this.typeFilter??null)!==(e??null)&&(this.typeFilter=e,this.refreshList(),this.renderFooter())}refreshTypeFilter(){if(!this.filterSelEl)return;let e=this.filterSelEl;e.empty();let t=new Map;for(let n of this.events)t.set(n.type,(t.get(n.type)??0)+1);let i=[...t.entries()].sort((n,o)=>o[1]-n[1]||n[0].localeCompare(o[0])),s=e.createEl("option",{text:`All types (${this.events.length})`});s.value="";for(let[n,o]of i){let a=e.createEl("option",{text:`${n} (${o})`});a.value=n}this.typeFilter&&!t.has(this.typeFilter)&&(this.typeFilter=null),e.value=this.typeFilter??""}updateCount(){if(!this.countEl)return;let e=this.visible.length,t=this.typeFilter?`${e} ${this.typeFilter} event${e===1?"":"s"}`:`${e} event${e===1?"":"s"}`;this.shownCount===0||this.shownCount>=e?this.countEl.setText(t):this.countEl.setText(`Showing ${this.shownCount} of ${t}`)}appendMore(e){if(!this.listEl)return;let t=Math.min(this.visible.length,this.shownCount+e);for(let i=this.shownCount;i{this.appendMore(Ht.PAGE),this.renderFooter()},e>Ht.PAGE){let i=this.footerEl.createEl("button",{text:`Load all (${e})`});i.onclick=()=>{this.appendMore(e),this.renderFooter()}}}shellAct(e){try{let t=this.app.vault.adapter.getFullPath?.(this.jsonlPath);if(!t)throw new Error("no full path");let{shell:i}=window.require("electron");e==="reveal"?i.showItemInFolder(t):i.openPath(t)}catch(t){new O.Notice(`Couldn't ${e}: ${t.message}`)}}async exportCopy(){try{let e=(0,O.moment)().format("YYYY-MM-DD_HHmmss"),t=this.jsonlPath.replace(/\/[^/]+$/,"")||"",i=t?`${t}/${e}-log.jsonl`:`${e}-log.jsonl`;return await this.app.vault.adapter.write(i,this.text),new O.Notice(`Exported log \u2192 ${i}`),i}catch(e){return new O.Notice(`Export failed: ${e.message}`),null}}clearLog(){new ie(this.app,"Clear log?","A timestamped copy will be saved alongside the active log before it's truncated.","Export & clear",async e=>{if(!(!e||!await this.exportCopy())){try{await this.app.vault.adapter.write(this.jsonlPath,"")}catch(i){new O.Notice(`Clear failed: ${i.message}`);return}this.text="",this.events=[],this.typeFilter=null,this.shownCount=0,this.refreshTypeFilter(),this.refreshList(),this.footerEl&&this.footerEl.empty(),new O.Notice("Log cleared.")}}).open()}renderEvent(e){let t=document.createElement("div");t.className=`stashpad-log-row stashpad-log-${e.type}`;let i=(0,O.moment)(e.ts).isValid()?(0,O.moment)(e.ts).format("YYYY-MM-DD HH:mm:ss"):e.ts,s=document.createElement("span");s.className="stashpad-log-ts",s.textContent=i,t.appendChild(s);let n=document.createElement("span");n.className="stashpad-log-type",n.textContent=e.type,t.appendChild(n);let o=document.createElement("span");o.className="stashpad-log-author",o.textContent=e.author?e.author:"\u2014",e.author||o.classList.add("is-empty"),t.appendChild(o);let a=document.createElement("span");return a.className="stashpad-log-msg",a.textContent=this.describe(e),t.appendChild(a),t}describe(e){let t=e.payload??{};switch(e.type){case"create":return`Created "${t.path??e.id}" under ${t.parent??"?"}`;case"delete":{let i=Array.isArray(t.attachmentsRemoved)?t.attachmentsRemoved.length:0,s=t.mergedInto?` (merged into ${t.mergedInto})`:"";return`Deleted ${e.id}${s}${i?` \u2014 also removed ${i} attachment${i===1?"":"s"}`:""}`}case"missing":return`Missing: ${t.lastPath??e.id}`;case"parent_change":{let i=t.reason?` (${t.reason})`:"";return`Moved ${e.id} from ${t.from??"null"} \u2192 ${t.to??"null"}${i}`}case"rename":return`Renamed ${t.from??"?"} \u2192 ${t.to??"?"}`;case"reorder":{let i=t.count??1,s=t.dir??"?",n=t.parent??e.id??"?";return`Reordered ${i} note${i===1?"":"s"} (${s}) under ${n}`}case"complete":{let i=t.count??1,s=t.undo?" (undo)":t.redo?" (redo)":"";return`Marked ${i} note${i===1?"":"s"} complete${s}`}case"uncomplete":{let i=t.count??1,s=t.undo?" (undo)":t.redo?" (redo)":"";return`Unmarked ${i} note${i===1?"":"s"}${s}`}case"stash_export":{let i=t.noteCount??"?";return`Exported ${i} note${i===1?"":"s"} \u2192 ${t.path??"?"}`}case"stash_import":{let i=t.noteCount??"?",s=[];t.attachmentsWritten&&s.push(`${t.attachmentsWritten} attachment${t.attachmentsWritten===1?"":"s"}`),t.collisionsRenamed&&s.push(`${t.collisionsRenamed} renamed`);let n=s.length?` (${s.join(", ")})`:"";return`Imported ${i} note${i===1?"":"s"} from ${t.from??"?"} \u2192 ${t.into??"?"}${n}`}case"attachment_add":return`Added attachment ${t.path??e.id}`;case"attachment_remove":return`Removed attachment ${t.path??e.id}`;case"palette_color_add":return`Added palette color ${t.color??e.id}`;case"palette_color_remove":return`Removed palette color ${t.color??e.id}`;default:return JSON.stringify(t)}}onClose(){this.contentEl.empty()}};Ht.PAGE=100;_t=Ht,ei=class extends O.Modal{constructor(e,t,i,s,n,o){super(e);this.noteTitle=t;this.descendantCount=i;this.attachmentCount=s;this.offerAttachmentDelete=n;this.onChoose=o}onOpen(){this.modalEl?.addClass("stashpad-compact-modal"),this.contentEl.empty(),this.titleEl.setText(`Delete "${this.noteTitle}"?`);let e=[];this.descendantCount>0&&e.push(`${this.descendantCount} descendant note${this.descendantCount===1?"":"s"} will also be deleted.`),this.attachmentCount>0&&e.push(`${this.attachmentCount} attachment${this.attachmentCount===1?"":"s"} found across these notes.`),this.contentEl.createEl("p",{text:e.join(" ")});let t=this.offerAttachmentDelete&&this.attachmentCount>0;if(this.offerAttachmentDelete&&this.attachmentCount>0){let o=this.contentEl.createEl("label",{cls:"stashpad-modal-check"}),a=o.createEl("input",{type:"checkbox"});a.checked=t,a.onchange=()=>{t=a.checked},o.createSpan({text:" Also delete attachments"})}let i=this.contentEl.createDiv({cls:"stashpad-modal-btns"}),s=i.createEl("button",{text:"Cancel"});s.onclick=()=>this.close();let n=i.createEl("button",{cls:"mod-warning",text:"Delete"});n.onclick=()=>{this.close(),this.onChoose(t)}}onClose(){this.contentEl.empty()}},Ti=class extends O.Modal{constructor(e,t,i,s,n){super(e);this.body=t;this.onSplitAtLine=i;this.onSplitAtChar=s;this.onSplitMany=n;this.mode="line";this.cursorTextarea=null;this.lines=t.replace(/\r\n/g,` +`).trim()),t.filter(Boolean)}function be(A,r,e){A.empty();try{(0,br.setIcon)(A,r)}catch{}let t=A.querySelector("svg");t&&t.querySelector("path, line, circle, rect, polyline, polygon, ellipse")||(A.empty(),A.createSpan({cls:"stashpad-icon-fallback",text:e}))}function Dr(A){if(A instanceof Element&&A.closest(".modal, .modal-container, .suggestion-container, .menu, .prompt"))return!0;let r=new Set([document]);A instanceof Element&&A.ownerDocument&&r.add(A.ownerDocument);for(let e of r)if(e.body?.querySelector(".modal-bg")||e.body?.querySelector(".modal-container .modal")||e.body?.querySelector(".suggestion-container")||e.body?.querySelector(".menu.mod-active"))return!0;return!1}function xr(A){let r=[],e=/^([ \t]*)(`{3,})[ \t]*([^\n`]*)\n([\s\S]*?)\n\1\2[ \t]*$/gm,t;for(;(t=e.exec(A))!=null;)r.push({lang:t[3].trim(),code:t[4]});return r}function Pi(A){return A.split("/").map(r=>r.split(" ").map(e=>e&&/^[a-z]/.test(e)?e[0].toUpperCase()+e.slice(1):e).join(" ")).join("/")}function Nr(A,r,e){let t=new Set(r),i=A.filter(n=>!t.has(n)),s=A.findIndex(n=>t.has(n));if(s<0)return A.slice();switch(e){case"top":return[...r,...i];case"bottom":return[...i,...r];case"up":{let n=Math.max(0,s-1),o=i.slice();return o.splice(n,0,...r),o}case"down":{let n=(()=>{let c=-1;return A.forEach((d,h)=>{t.has(d)&&(c=h)}),c})(),o=0;for(let c=0;c0){let n=t[i-1];return(!/[a-z0-9]/.test(n)?600:500)-Math.min(i,99)}let s=0;for(let n=0;n({t,s:Kl(e,t.raw)})).filter(t=>t.s>=0).sort((t,i)=>i.s-t.s||i.t.count-t.t.count||t.t.label.localeCompare(i.t.label)).map(t=>t.t):r}var br,Mi,Gt,Lt,Hi=$e(()=>{"use strict";br=require("obsidian"),Mi={lines:"Each line",paragraphs:"Paragraphs (blank line)",headings:"Headings"};Gt="__stashpad:tagged__",Lt="__stashpad:untagged__"});function Jl(A,r){return A===0?0:A>12?A>=24?0:A:r==="am"?A===12?0:A:A===12?12:A+12}function Qs(A,r){A.addClass("stashpad-when-pop-time");let e=r.seedPeriod,t=A.createDiv({cls:"stashpad-when-time-display"}),i=t.createEl("input",{cls:"stashpad-when-time-field",attr:{type:"text",inputmode:"numeric",maxlength:"2"}});i.value=String(r.seedH),t.createSpan({cls:"stashpad-when-time-colon",text:":"});let s=t.createEl("input",{cls:"stashpad-when-time-field",attr:{type:"text",inputmode:"numeric",maxlength:"2"}});s.value=String(r.seedM).padStart(2,"0");let n=t.createDiv({cls:"stashpad-when-time-period"}),o=n.createEl("button",{cls:"stashpad-when-time-ampm",text:"AM"});o.type="button";let a=n.createEl("button",{cls:"stashpad-when-time-ampm",text:"PM"});a.type="button";let l=()=>{o.toggleClass("is-active",e==="am"),a.toggleClass("is-active",e==="pm")};l(),o.addEventListener("mousedown",B=>B.preventDefault()),a.addEventListener("mousedown",B=>B.preventDefault()),o.addEventListener("click",B=>{B.preventDefault(),e="am",l()}),a.addEventListener("click",B=>{B.preventDefault(),e="pm",l()});let c=i;i.addEventListener("focus",()=>{c=i,i.select()}),s.addEventListener("focus",()=>{c=s,s.select()});let d=()=>{let w=(parseInt(i.value||"0",10)||0)>12;o.toggleClass("is-disabled",w),a.toggleClass("is-disabled",w),o.disabled=w,a.disabled=w},h=B=>{let w=B.value.replace(/\D/g,"").slice(0,2);if(B.value=w,w===""){B===i&&d();return}let y=parseInt(w,10),v=B===i?24:59;y>v&&(B.value=String(v)),B===i&&d()};for(let B of[i,s])B.addEventListener("input",()=>h(B));d();let g=()=>{let B=parseInt(i.value,10),w=Number.isNaN(B)?12:B,y=parseInt(s.value||"0",10)||0,v=w>12||w===0;r.onFinalize({hours24:Jl(w,e),minutes:y,raw:{hh:w,mm:y,period:e,is24:v}}),r.close()};r.setOnEnter?.(g);let f=[i,s,o,a],p=(B,w)=>{let y=f.indexOf(B);if(y===-1)return;let v=(y+w+f.length)%f.length;f[v].focus()},u=B=>{B.addEventListener("keydown",w=>{w.key==="Enter"?(w.preventDefault(),w.stopPropagation(),g()):w.key==="Escape"?(w.preventDefault(),w.stopPropagation(),r.close()):w.key==="Tab"&&(w.preventDefault(),w.stopPropagation(),p(B,w.shiftKey?-1:1))})},I=A.createDiv({cls:"stashpad-when-time-pad"}),m=["1","2","3","4","5","6","7","8","9","backspace","0","insert"],C=null;for(let B of m){let w=I.createEl("button",{cls:"stashpad-when-time-padbtn"});w.type="button",B==="backspace"?(0,Tr.setIcon)(w,"delete"):B==="insert"?(w.setText("OK"),C=w):w.setText(B),B==="insert"&&w.addClass("is-go"),w.addEventListener("mousedown",y=>y.preventDefault()),w.addEventListener("click",y=>{if(y.preventDefault(),B==="insert"){g();return}if(B==="backspace"){c.value=c.value.slice(0,-1),h(c),c.focus();return}let v=c.selectionStart===0&&c.selectionEnd===c.value.length&&c.value.length>0,E=2,Q=v||c.value.length>=E?B:c.value+B;c.value=Q,h(c),c.focus(),c.setSelectionRange(c.value.length,c.value.length),c===i&&c.value.length>=E&&(s.focus(),s.select())})}C&&f.push(C);for(let B of f)u(B);i.focus(),i.select()}function Pr(A){let{hh:r,mm:e,period:t,is24:i}=A.raw;return i?`${r}:${String(e).padStart(2,"0")}`:`${r}:${String(e).padStart(2,"0")}${t}`}var Tr,so=$e(()=>{"use strict";Tr=require("obsidian")});function Rr(A){let r=Math.floor(4294967296/A)*A,e=new Uint32Array(1),t;do crypto.getRandomValues(e),t=e[0];while(t>=r);return t%A}function no(A=5){let r=[];for(let e=0;e=1&&s.every(a=>/^[a-zA-Z]+$/.test(a))&&(i=Math.min(i,s.length*11)),$l.has(A.toLowerCase())&&(i=Math.min(i,12));let n=i<36?0:i<56?1:i<76?2:3,o=["Weak \u2014 easily guessed","Fair","Good","Strong"][n];return{bits:i,level:n,label:o}}function Yl(A){let r=2166136261;for(let e=0;e>>0).toString(16).padStart(8,"0")}function ti(A){let r="stashpad-",e=Yl(A),t=64-r.length-1-e.length,i=A.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,Math.max(0,t)).replace(/-+$/g,"");return`${r}${i}-${e}`}var Hr,$l,vs=$e(()=>{"use strict";Hr="able acid acorn actor adept agile alarm album alert algae amber amble anchor angle ankle apple april arbor arc arctic arena armor arrow ascot aspen atlas atom attic auburn audio autumn award axis bacon badge bagel baker balmy bamboo banjo barge basil basin batch beach beam bean bear beaver begin belt bench berry birch bison black blade blaze bliss bloom blue board boat bold bolt bonus boost booth borax bother bottle boulder bounce bovine bowl brave bread breeze brick bridge brisk broad bronze brook broom brown brush bubble bucket bug bulb bundle burst cabin cable cacao cactus camel cameo candle canoe canvas canyon cargo carol carrot castle catch cedar cello chalk charm chart cheese cherry chess chime chirp cider cinder circle citrus clamp clay clever cliff cloak clock cloud clover coast cobra cocoa comet coral cosmos cotton couch cougar crane crater cream crest cricket crisp crown crumb crystal cube cumin curio dagger daisy dapper dawn deck deer delta denim depot desert dial diamond dingo ditch dock dodge dolphin domino donut draft dragon drift drum dune dusk eagle early earth easel ebony echo eclipse edge eel eagle elbow elder ember emboss emerald ember emu engine ermine ether ever fable falcon fancy farm fawn feather felt fennel fern ferry fiber fiddle field fig finch flame flask flax fleet flint float flock flora flute focus foam forest fossil fox frame frost fudge garden gecko ginger glacier glade glass glide globe glory glove gnat goat golden gopher gourd grape grass gravel grove guava gull hammer hamlet harbor hatch hazel heron hickory hippo honey hornet hostel ivory ivy jade jaguar jasmine jelly jetty jewel jolly jungle juniper kayak kelp kettle kiwi koala ladle lagoon lamp lantern larch lark lasso lava ledge lemon lentil leopard lever lilac lime linen lizard llama lobby locket lodge lotus lunar lupine lynx maple marble marsh meadow medal melon mellow mentor mesa metro mimic mint mirror mocha morsel moss mottle mural muslin nacho nectar nest noble nomad noodle nougat nutmeg oasis oat ocean ochre olive onyx opal orbit orca otter oval owl oxide oyster pace paddle palm panda pantry papaya parka parsley pasta pearl pebble pelican pepper pewter phantom piano picnic pigeon pilot pine pixel plank plaza plum pocket pollen pond poplar poppy portal potato pottery prairie prism puffin pumice quail quartz quill quilt quince rabbit radish raft rapid raven reed reef relish ribbon ridge rind ripple river robin rocket rope rover ruby rudder rust saffron sage salmon sandal sash satin sauce scout sedge sequin shadow shale shark shell shore shrub silk silo silver siren sketch slate sleet slope sloth smoke snail sonar sorrel spark sparrow spice spire sprig spruce squid stable stamp starling steam stem stilt stone stork storm stove straw stream stucco summit sundae sunset swan sweater syrup tabby talon tango tapir tassel tawny teal tempo thicket thimble thistle thorn thunder tidal tiger timber toffee token topaz torch totem trail tulip tundra turtle tusk twig umber unicorn valley vanilla velvet vermil violet viper vista vivid vortex waffle walnut warbler wasp water weasel wharf wheat whisk willow window winter wisp wombat wonder woven yam yarn yeast yew yodel yonder zebra zenith zephyr zigzag zinc zircon".split(/\s+/).filter(Boolean).filter((A,r,e)=>e.indexOf(A)===r);$l=new Set(["password","passw0rd","password1","123456","12345678","123456789","qwerty","abc123","letmein","welcome","admin","iloveyou","monkey","dragon","sunshine","princess","football","baseball","trustno1","000000","111111","secret"])});var Fs={};ki(Fs,{newId:()=>Z});function Z(A=6){let r="",e=new Uint8Array(A);crypto.getRandomValues(e);for(let t=0;t{"use strict";Ur="abcdefghijkmnpqrstuvwxyz23456789"});var Yi={};ki(Yi,{BreadcrumbLevelsModal:()=>Oi,ColorPickerModal:()=>Kt,ConfirmDeleteModal:()=>si,ConfirmModal:()=>ie,CustomColorModal:()=>ks,DueDatePickerModal:()=>Oe,EncryptionPasswordModal:()=>de,ExportStashModal:()=>ni,ImportDupChoiceModal:()=>Ki,ImportLogModal:()=>ro,LogModal:()=>ii,NotificationHistoryModal:()=>qi,OkfExportModal:()=>Ji,ReEncryptReviewModal:()=>$i,SplitNoteModal:()=>Ui,StashPasswordModal:()=>Gi,TypeToConfirmModal:()=>Li,buildAssigneePicker:()=>Lr,parseAdjustMinutes:()=>Gr});function Gr(A){let r=/^\s*(\d+)\s*(m|h|d|w)\s*$/i.exec(A);if(!r)return null;let e=parseInt(r[1],10);if(!Number.isFinite(e)||e<=0)return null;let t={m:1,h:60,d:1440,w:10080}[r[2].toLowerCase()]??1;return e*t}function Lr(A,r){let e=[...r.initial],t=r.knownAuthors,i=A.createDiv({cls:"stashpad-assign"});i.createDiv({cls:"stashpad-assign-label",text:"Assign to"});let s=i.createDiv({cls:"stashpad-assign-chips"}),n=i.createDiv({cls:"stashpad-assign-input-wrap"}),o=n.createEl("input",{type:"text",cls:"stashpad-assign-input",attr:{placeholder:"Add a person \u2014 type a name\u2026"}}),a=n.createDiv({cls:"stashpad-assign-suggest"});a.setCssStyles({display:"none"});let l=()=>r.onChange([...e]),c=()=>{s.empty(),e.length===0&&s.createSpan({cls:"stashpad-assign-empty",text:"No one yet"});for(let g of e){let f=s.createSpan({cls:"stashpad-assign-chip"});f.createSpan({cls:"stashpad-assign-chip-name",text:g.name});let p=f.createSpan({cls:"stashpad-assign-chip-x",text:"\xD7"});p.title=`Remove ${g.name}`,p.onclick=()=>{e=e.filter(u=>u.id!==g.id),l(),c()}}},d=g=>{g.name.trim()&&(e.some(f=>f.id===g.id)||e.push(g),o.value="",a.setCssStyles({display:"none"}),l(),c(),o.focus())},h=()=>{let g=o.value.trim();a.empty();let f=new Set(e.map(I=>I.id)),u=t.filter(I=>!f.has(I.id)&&ws(g,I.name)).slice(0,6).map(I=>({label:I.name,onPick:()=>d(I)}));if(g&&!t.some(I=>I.name.toLowerCase()===g.toLowerCase())&&u.push({label:`Create \u201C${g}\u201D`,onPick:()=>d({id:Z(6),name:g})}),u.length===0){a.setCssStyles({display:"none"});return}a.setCssStyles({display:""});for(let I of u){let m=a.createDiv({cls:"stashpad-assign-suggest-item",text:I.label});m.onmousedown=C=>{C.preventDefault(),I.onPick()}}};o.addEventListener("input",h),o.addEventListener("focus",h),o.addEventListener("keydown",g=>{if(g.key==="Enter"){g.preventDefault();let f=a.querySelector(".stashpad-assign-suggest-item");f&&f.dispatchEvent(new MouseEvent("mousedown"))}else g.key==="Escape"&&a.style.display!=="none"&&(g.stopPropagation(),a.setCssStyles({display:"none"}))}),o.addEventListener("blur",()=>{window.setTimeout(()=>{a.setCssStyles({display:"none"})},120)}),c()}var J,zl,Ot,ii,si,Ui,ni,Gi,de,Li,ks,Ri,Kt,ie,Oi,Oe,qt,qi,ro,Ki,Ji,$i,ye=$e(()=>{"use strict";J=require("obsidian");Hi();so();le();vs();Le();zl=J.moment,Ot=class Ot extends J.Modal{constructor(e,t,i){super(e);this.text=t;this.jsonlPath=i;this.events=[];this.visible=[];this.shownCount=0;this.typeFilter=null;this.listEl=null;this.footerEl=null;this.countEl=null;this.filterSelEl=null}onOpen(){this.contentEl.empty(),this.titleEl.setText("Stashpad log"),this.modalEl.addClass("stashpad-log-modal"),this.events=[];for(let l of this.text.trim().split(/\r?\n/))if(l)try{this.events.push(JSON.parse(l))}catch{}this.events.reverse();let e=this.contentEl.createDiv({cls:"stashpad-log-toolbar"});this.countEl=e.createSpan({cls:"stashpad-log-count"}),this.updateCount(),this.filterSelEl=e.createEl("select",{cls:"stashpad-log-type-filter"}),this.filterSelEl.onchange=()=>this.setTypeFilter(this.filterSelEl.value||null),this.refreshTypeFilter();let t=e.createEl("button",{text:"Reveal JSONL"});t.onclick=()=>this.shellAct("reveal");let i=e.createEl("button",{text:"Open in default app"});i.onclick=()=>this.shellAct("open");let s=e.createEl("button",{text:"Copy raw JSONL"}),n=null;s.onclick=async()=>{await navigator.clipboard.writeText(this.text),s.setText("Copied"),n!=null&&window.clearTimeout(n),n=window.setTimeout(()=>{s.setText("Copy raw JSONL"),n=null},1500)};let o=e.createEl("button",{text:"Export copy"});o.onclick=()=>this.exportCopy();let a=e.createEl("button",{cls:"mod-warning",text:"Clear log"});a.onclick=()=>this.clearLog(),this.listEl=this.contentEl.createDiv({cls:"stashpad-log-list"}),this.refreshList(),this.footerEl=this.contentEl.createDiv({cls:"stashpad-log-footer"}),this.renderFooter()}refreshList(){if(this.listEl){if(this.visible=this.typeFilter?this.events.filter(e=>e.type===this.typeFilter):this.events.slice(),this.shownCount=0,this.listEl.empty(),!this.visible.length){this.listEl.createDiv({cls:"stashpad-log-empty",text:this.typeFilter?`No "${this.typeFilter}" events.`:"No events yet."}),this.updateCount();return}this.appendMore(Ot.PAGE)}}setTypeFilter(e){(this.typeFilter??null)!==(e??null)&&(this.typeFilter=e,this.refreshList(),this.renderFooter())}refreshTypeFilter(){if(!this.filterSelEl)return;let e=this.filterSelEl;e.empty();let t=new Map;for(let n of this.events)t.set(n.type,(t.get(n.type)??0)+1);let i=[...t.entries()].sort((n,o)=>o[1]-n[1]||n[0].localeCompare(o[0])),s=e.createEl("option",{text:`All types (${this.events.length})`});s.value="";for(let[n,o]of i){let a=e.createEl("option",{text:`${n} (${o})`});a.value=n}this.typeFilter&&!t.has(this.typeFilter)&&(this.typeFilter=null),e.value=this.typeFilter??""}updateCount(){if(!this.countEl)return;let e=this.visible.length,t=this.typeFilter?`${e} ${this.typeFilter} event${e===1?"":"s"}`:`${e} event${e===1?"":"s"}`;this.shownCount===0||this.shownCount>=e?this.countEl.setText(t):this.countEl.setText(`Showing ${this.shownCount} of ${t}`)}appendMore(e){if(!this.listEl)return;let t=Math.min(this.visible.length,this.shownCount+e);for(let i=this.shownCount;i{this.appendMore(Ot.PAGE),this.renderFooter()},e>Ot.PAGE){let i=this.footerEl.createEl("button",{text:`Load all (${e})`});i.onclick=()=>{this.appendMore(e),this.renderFooter()}}}shellAct(e){try{let t=this.app.vault.adapter.getFullPath?.(this.jsonlPath);if(!t)throw new Error("no full path");let{shell:i}=window.require("electron");e==="reveal"?i.showItemInFolder(t):i.openPath(t)}catch(t){new J.Notice(`Couldn't ${e}: ${t.message}`)}}async exportCopy(){try{let e=(0,J.moment)().format("YYYY-MM-DD_HHmmss"),t=this.jsonlPath.replace(/\/[^/]+$/,"")||"",i=t?`${t}/${e}-log.jsonl`:`${e}-log.jsonl`;return await this.app.vault.adapter.write(i,this.text),new J.Notice(`Exported log \u2192 ${i}`),i}catch(e){return new J.Notice(`Export failed: ${e.message}`),null}}clearLog(){new ie(this.app,"Clear log?","A timestamped copy will be saved alongside the active log before it's truncated.","Export & clear",async e=>{if(!(!e||!await this.exportCopy())){try{await this.app.vault.adapter.write(this.jsonlPath,"")}catch(i){new J.Notice(`Clear failed: ${i.message}`);return}this.text="",this.events=[],this.typeFilter=null,this.shownCount=0,this.refreshTypeFilter(),this.refreshList(),this.footerEl&&this.footerEl.empty(),new J.Notice("Log cleared.")}},"Cancel",!0).open()}renderEvent(e){let t=document.createElement("div");t.className=`stashpad-log-row stashpad-log-${e.type}`;let i=(0,J.moment)(e.ts).isValid()?(0,J.moment)(e.ts).format("YYYY-MM-DD HH:mm:ss"):e.ts,s=document.createElement("span");s.className="stashpad-log-ts",s.textContent=i,t.appendChild(s);let n=document.createElement("span");n.className="stashpad-log-type",n.textContent=e.type,t.appendChild(n);let o=document.createElement("span");o.className="stashpad-log-author",o.textContent=e.author?e.author:"\u2014",e.author||o.classList.add("is-empty"),t.appendChild(o);let a=document.createElement("span");return a.className="stashpad-log-msg",a.textContent=this.describe(e),t.appendChild(a),t}describe(e){let t=e.payload??{};switch(e.type){case"create":return`Created "${t.path??e.id}" under ${t.parent??"?"}`;case"delete":{let i=Array.isArray(t.attachmentsRemoved)?t.attachmentsRemoved.length:0,s=t.mergedInto?` (merged into ${t.mergedInto})`:"";return`Deleted ${e.id}${s}${i?` \u2014 also removed ${i} attachment${i===1?"":"s"}`:""}`}case"missing":return`Missing: ${t.lastPath??e.id}`;case"parent_change":{let i=t.reason?` (${t.reason})`:"";return`Moved ${e.id} from ${t.from??"null"} \u2192 ${t.to??"null"}${i}`}case"rename":return`Renamed ${t.from??"?"} \u2192 ${t.to??"?"}`;case"reorder":{let i=t.count??1,s=t.dir??"?",n=t.parent??e.id??"?";return`Reordered ${i} note${i===1?"":"s"} (${s}) under ${n}`}case"complete":{let i=t.count??1,s=t.undo?" (undo)":t.redo?" (redo)":"";return`Marked ${i} note${i===1?"":"s"} complete${s}`}case"uncomplete":{let i=t.count??1,s=t.undo?" (undo)":t.redo?" (redo)":"";return`Unmarked ${i} note${i===1?"":"s"}${s}`}case"stash_export":{let i=t.noteCount??"?";return`Exported ${i} note${i===1?"":"s"} \u2192 ${t.path??"?"}`}case"stash_import":{let i=t.noteCount??"?",s=[];t.attachmentsWritten&&s.push(`${t.attachmentsWritten} attachment${t.attachmentsWritten===1?"":"s"}`),t.collisionsRenamed&&s.push(`${t.collisionsRenamed} renamed`);let n=s.length?` (${s.join(", ")})`:"";return`Imported ${i} note${i===1?"":"s"} from ${t.from??"?"} \u2192 ${t.into??"?"}${n}`}case"attachment_add":return`Added attachment ${t.path??e.id}`;case"attachment_remove":return`Removed attachment ${t.path??e.id}`;case"palette_color_add":return`Added palette color ${t.color??e.id}`;case"palette_color_remove":return`Removed palette color ${t.color??e.id}`;default:return JSON.stringify(t)}}onClose(){this.contentEl.empty()}};Ot.PAGE=100;ii=Ot,si=class extends J.Modal{constructor(e,t,i,s,n,o){super(e);this.noteTitle=t;this.descendantCount=i;this.attachmentCount=s;this.offerAttachmentDelete=n;this.onChoose=o}onOpen(){this.modalEl?.addClass("stashpad-compact-modal"),this.contentEl.empty(),this.titleEl.setText(`Delete "${this.noteTitle}"?`);let e=[];this.descendantCount>0&&e.push(`${this.descendantCount} descendant note${this.descendantCount===1?"":"s"} will also be deleted.`),this.attachmentCount>0&&e.push(`${this.attachmentCount} attachment${this.attachmentCount===1?"":"s"} found across these notes.`),this.contentEl.createEl("p",{text:e.join(" ")});let t=this.offerAttachmentDelete&&this.attachmentCount>0;if(this.offerAttachmentDelete&&this.attachmentCount>0){let o=this.contentEl.createEl("label",{cls:"stashpad-modal-check"}),a=o.createEl("input",{type:"checkbox"});a.checked=t,a.onchange=()=>{t=a.checked},o.createSpan({text:" Also delete attachments"})}let i=this.contentEl.createDiv({cls:"stashpad-modal-btns"}),s=i.createEl("button",{text:"Cancel"});s.onclick=()=>this.close();let n=i.createEl("button",{cls:"mod-warning",text:"Delete"});n.onclick=()=>{this.close(),this.onChoose(t)}}onClose(){this.contentEl.empty()}},Ui=class extends J.Modal{constructor(e,t,i,s,n){super(e);this.body=t;this.onSplitAtLine=i;this.onSplitAtChar=s;this.onSplitMany=n;this.mode="line";this.cursorTextarea=null;this.lines=t.replace(/\r\n/g,` `).split(` -`),this.lineCursorIdx=Math.max(1,Math.min(this.lines.length-1,Math.floor(this.lines.length/2))),this.lines.length<2&&(this.mode="cursor")}onOpen(){this.titleEl.setText("Split note"),this.modalEl.addClass("stashpad-split-modal"),this.render(),this.scope.register([],"Tab",e=>{this.lines.length<2||(e.preventDefault(),this.mode=this.mode==="line"?"cursor":"line",this.render())}),this.scope.register([],"Enter",e=>{this.mode==="line"&&(e.preventDefault(),this.commitLine())}),this.scope.register(["Mod"],"Enter",e=>{this.mode==="cursor"&&(e.preventDefault(),this.commitCursor())}),this.scope.register([],"ArrowUp",e=>{this.mode==="line"&&(e.preventDefault(),this.lineCursorIdx=Math.max(1,this.lineCursorIdx-1),this.render())}),this.scope.register([],"ArrowDown",e=>{this.mode==="line"&&(e.preventDefault(),this.lineCursorIdx=Math.min(this.lines.length-1,this.lineCursorIdx+1),this.render())})}commitLine(){let e=this.lineCursorIdx;this.close(),this.onSplitAtLine(e)}commitCursor(){let e=this.cursorTextarea;if(!e)return;let t=e.selectionStart;if(t<=0||t>=e.value.length){new O.Notice("Move the cursor inside the text \u2014 neither end can be empty.");return}this.close(),this.onSplitAtChar(t)}render(){this.contentEl.empty();let e=this.contentEl.createDiv({cls:"stashpad-split-quick"});e.createSpan({cls:"stashpad-split-quick-label",text:"Split by:"}),["lines","paragraphs","headings"].forEach(o=>{let a=Di(this.body,o),l=e.createEl("button",{cls:"stashpad-split-quick-btn",text:`${bi[o]} (${a.length})`});l.disabled=a.length<2,l.onmousedown=c=>c.preventDefault(),l.onclick=()=>{let c=Di(this.body,o);if(c.length<2){new O.Notice("That delimiter wouldn't split this note.");return}this.close(),this.onSplitMany(c)}});let t=this.contentEl.createDiv({cls:"stashpad-split-toggle-bar"});if(this.lines.length>=2){let o=t.createEl("button",{text:"Line split",cls:"stashpad-split-mode-btn"});this.mode==="line"&&o.addClass("is-active"),o.onclick=()=>{this.mode="line",this.render()}}let i=t.createEl("button",{text:"Cursor split",cls:"stashpad-split-mode-btn"});this.mode==="cursor"&&i.addClass("is-active"),i.onclick=()=>{this.mode="cursor",this.render()};let s=t.createEl("button",{text:"Split",cls:"stashpad-split-confirm-btn mod-cta"});s.onmousedown=o=>o.preventDefault(),s.onclick=()=>{this.mode==="line"?this.commitLine():this.commitCursor()},this.mode==="line"?this.renderLineMode():this.renderCursorMode();let n=this.contentEl.createDiv({cls:"stashpad-split-help"});O.Platform.isMobile?n.setText(this.mode==="line"?"Tap a line to position the divider, then Split.":"Tap inside the text to position the cursor, then Split."):n.setText(this.mode==="line"?"\u2191/\u2193 pick split line \xB7 Enter or Split confirm \xB7 Tab \u2192 cursor mode \xB7 Esc cancel \xB7 Children stay with the first part":"Click or arrow to position cursor \xB7 Mod+Enter or Split confirm \xB7 Tab \u2192 line mode \xB7 Esc cancel \xB7 Children stay with the first part")}renderLineMode(){let e=this.contentEl.createDiv({cls:"stashpad-split-list"}),t=null;for(let i=0;i{let n=Math.max(1,Math.min(this.lines.length-1,i+1));n!==this.lineCursorIdx&&(this.lineCursorIdx=n,this.render())}}t&&window.requestAnimationFrame(()=>t.scrollIntoView({block:"center"}))}renderCursorMode(){let t=this.contentEl.createDiv({cls:"stashpad-split-cursor-wrap"}).createEl("textarea",{cls:"stashpad-split-cursor-ta"});t.value=this.body,t.readOnly=!1,this.cursorTextarea=t;let i=parseFloat(getComputedStyle(t).lineHeight)||22,s=O.Platform.isMobile?3:12,n=2,o=()=>{t.setCssStyles({height:"auto"});let a=Math.min(t.scrollHeight,i*s+16);t.setCssStyles({height:`${Math.max(a,i*n+16)}px`})};requestAnimationFrame(()=>{o();let a=Math.floor(t.value.length/2);t.focus(),t.setSelectionRange(a,a)}),t.addEventListener("input",o)}onClose(){this.cursorTextarea=null,this.contentEl.empty()}},ti=class extends O.Modal{constructor(e,t,i,s,n){super(e);this.defaultBaseName=t;this.noteCount=i;this.onConfirm=s;this.kdfProbe=n;this.delivered=!1}onOpen(){this.contentEl.empty(),this.titleEl.setText("Export to .stash"),this.modalEl.addClass("stashpad-export-modal"),this.contentEl.createEl("p",{cls:"stashpad-export-desc",text:`${this.noteCount} note${this.noteCount===1?"":"s"} will be bundled into a single .stash file.`});let e=this.contentEl.createDiv({cls:"stashpad-export-field"});e.createEl("label",{cls:"stashpad-export-label",text:"File name"});let t=e.createEl("input",{type:"text"});t.addClass("stashpad-export-name"),t.value=this.defaultBaseName;let i=this.contentEl.createEl("div",{cls:"stashpad-export-preview"}),s=this.contentEl.createDiv({cls:"stashpad-export-encrypt"}),n=s.createDiv({cls:"stashpad-export-toggle"}),o=n.createEl("input",{type:"checkbox"});o.id="stashpad-export-encrypt-cb";let a=n.createEl("label",{text:"Encrypt with a password"});a.htmlFor=o.id;let l=s.createDiv({cls:"stashpad-export-pw-area"});l.setCssStyles({display:"none"});let c=[],d=K=>{let W=l.createDiv({cls:"stashpad-export-pw-row"}),$=W.createEl("input",{type:"password"});$.addClass("stashpad-export-name"),$.placeholder=K;let Y=W.createEl("button",{cls:"stashpad-export-copy"}),_=()=>{let ce=$.value.length===0;Y.setText(ce?"Paste":"Copy"),Y.toggleClass("is-paste",ce),Y.setAttr("aria-label",`${ce?"Paste into":"Copy"} ${K.toLowerCase()}`)};return Y.onclick=async ce=>{if(ce.preventDefault(),$.value.length===0)try{let ae=(await navigator.clipboard?.readText())?.trim();if(!ae){new O.Notice("Clipboard is empty.");return}$.value=ae,$.dispatchEvent(new Event("input")),new O.Notice("Pasted from clipboard.")}catch{new O.Notice("Couldn't read the clipboard.")}else navigator.clipboard?.writeText($.value).then(()=>new O.Notice("Passphrase copied to clipboard."),()=>new O.Notice("Couldn't access the clipboard."))},_(),c.push(_),$},h=d("Password"),g=d("Confirm password"),u=l.createDiv({cls:"stashpad-export-strength"}),p=u.createDiv({cls:"stashpad-strength-bar"}),f=[0,1,2,3].map(()=>p.createDiv({cls:"stashpad-strength-seg"})),I=u.createEl("span",{cls:"stashpad-strength-label"}),m=l.createDiv({cls:"stashpad-export-genrow"}),C=m.createEl("button",{cls:"stashpad-export-gen",text:"Generate strong passphrase"}),B=m.createEl("button",{cls:"stashpad-export-show",text:"Show"}),w=l.createEl("div",{cls:"stashpad-export-pw-hint"}),y=l.createEl("div",{cls:"stashpad-export-pw-suite"});y.setText("Encryption: AES-256-GCM. Checking key-derivation suite for this device\u2026"),this.kdfProbe?this.kdfProbe().then(K=>{y.toggleClass("is-weak",!K),y.setText(K?"Encryption: Argon2id + AES-256-GCM \u2014 the strongest suite (used on this device).":"\u26A0\uFE0F Argon2id can't run on this device, so this export will use the weaker PBKDF2 (600k) + AES-256-GCM fallback.")}).catch(()=>{y.setText("Encryption: AES-256-GCM with a password-derived key.")}):y.setText("Encryption: Argon2id + AES-256-GCM (falls back to PBKDF2 if Argon2 can't run here).");let E=this.app.secretStorage,v=l.createDiv({cls:"stashpad-export-remember"}),Q=v.createEl("input",{type:"checkbox"});Q.id="stashpad-export-remember-cb";let S=v.createEl("label",{text:"Remember in this vault (this device) \u2014 skips the prompt when you re-import here."});S.htmlFor=Q.id;let x=l.createDiv({cls:"stashpad-export-remember-note"});x.setText("Saved only in this device's keychain \u2014 it doesn't sync to your other devices and isn't shared with anyone you send this file to. Keep the passphrase somewhere safe if you'll open this export elsewhere."),x.setCssStyles({display:"none"}),Q.onchange=()=>{x.setCssStyles({display:Q.checked?"":"none"})},E||v.setCssStyles({display:"none"});let H=()=>{let K=t.value.trim()||this.defaultBaseName;return o.checked?`${K}-encrypted`:K},G=()=>i.setText(`Saves as: ${H()}-.stash`);t.oninput=G;let N=this.contentEl.createDiv({cls:"stashpad-export-footer"}),U=N.createEl("button",{text:"Cancel"});U.onclick=()=>this.close();let R=N.createEl("button",{cls:"mod-cta",text:"Export"}),D=()=>{let K=Yn(h.value);u.style.visibility=h.value?"visible":"hidden",f.forEach((W,$)=>W.toggleClass("is-on",h.value!==""&&$<=K.level)),p.dataset.level=String(K.level),I.setText(K.label)},b=()=>{let K=o.checked;l.setCssStyles({display:K?"":"none"});let W=!0;K&&(h.value?h.value!==g.value?(w.setText("Passwords don't match."),w.addClass("is-error"),W=!1):(w.setText("\u26A0\uFE0F If you lose this password, the export can't be recovered."),w.removeClass("is-error")):(w.setText("Enter a password to encrypt this export."),w.removeClass("is-error"),W=!1)),D(),c.forEach($=>$()),R.disabled=!W,R.toggleClass("is-disabled",!W),G()},P=!1,q=K=>{P=K,h.type=g.type=K?"text":"password",B.setText(K?"Hide":"Show")};B.onclick=K=>{K.preventDefault(),q(!P)},C.onclick=K=>{K.preventDefault(),h.value=g.value=On(),q(!1),new O.Notice("Passphrase generated (hidden) \u2014 Show to view, or Copy to save it."),b()},o.onchange=b,h.oninput=b,g.oninput=b,b();let L=()=>this.commit(H(),o.checked?h.value:null,o.checked&&Q.checked);R.onclick=L,this.scope.register([],"Enter",K=>{K.preventDefault(),R.disabled||L()}),requestAnimationFrame(()=>{t.focus(),t.select()})}commit(e,t,i){let s=e.trim()||this.defaultBaseName,n=t&&t.length?t:null;this.delivered=!0,this.close(),this.onConfirm(s,n,!!n&&i)}onClose(){this.contentEl.empty()}},Pi=class extends O.Modal{constructor(e,t,i,s){super(e);this.errorMsg=t;this.allowLater=i;this.onResult=s;this.delivered=!1}onOpen(){this.contentEl.empty(),this.titleEl.setText("Encrypted .stash"),this.modalEl.addClass("stashpad-export-modal"),this.contentEl.createEl("p",{cls:"stashpad-export-desc",text:"This export is password-protected. Enter its password to import it."}),this.errorMsg&&this.contentEl.createEl("div",{cls:"stashpad-export-error",text:this.errorMsg});let e=this.contentEl.createEl("input",{type:"password"});e.addClass("stashpad-export-name"),e.placeholder="Password";let t=this.contentEl.createDiv({cls:"stashpad-export-footer"});if(t.createEl("button",{text:"Cancel"}).onclick=()=>this.close(),this.allowLater){let s=t.createEl("button",{text:"Remind me later"});s.title="Don't import now \u2014 surface the reminder again later.",s.onclick=()=>this.finish({kind:"later"})}let i=t.createEl("button",{cls:"mod-cta",text:"Decrypt & import"});i.onclick=()=>this.submit(e.value),this.scope.register([],"Enter",s=>{s.preventDefault(),this.submit(e.value)}),requestAnimationFrame(()=>e.focus())}submit(e){e&&this.finish({kind:"password",value:e})}finish(e){this.delivered=!0,this.close(),this.onResult(e)}onClose(){this.delivered||(this.delivered=!0,this.onResult({kind:"cancel"})),this.contentEl.empty()}},de=class extends O.Modal{constructor(e,t){super(e);this.opts=t;this.succeeded=!1}onOpen(){let{mode:e}=this.opts;this.contentEl.empty(),this.modalEl.addClass("stashpad-export-modal","stashpad-encryption-modal"),this.titleEl.setText(this.opts.title??(e==="setup"?"Set up encryption password":e==="change"?"Change encryption password":"Unlock encryption")),this.contentEl.createDiv({cls:"stashpad-beta-row"}).createEl("span",{cls:"stashpad-beta-badge",text:"BETA"}),this.opts.intro?this.contentEl.createEl("p",{cls:"stashpad-export-desc",text:this.opts.intro}):e==="setup"&&this.contentEl.createEl("p",{cls:"stashpad-export-desc",text:"This single password protects everything you encrypt in this vault. It is stored only on this device. There is NO recovery \u2014 if you lose it, anything you've encrypted is gone for good."});let t=[],i=f=>{let I=this.contentEl.createDiv({cls:"stashpad-export-pw-row stashpad-encryption-row"}),m=I.createEl("input",{type:"password"});m.addClass("stashpad-export-name","stashpad-encryption-field"),m.placeholder=f;let C=I.createEl("button",{cls:"stashpad-export-copy"}),B=()=>{let w=m.value.length===0;C.setText(w?"Paste":"Copy"),C.toggleClass("is-paste",w),C.setAttr("aria-label",`${w?"Paste into":"Copy"} ${f.toLowerCase()}`)};return C.onclick=async w=>{if(w.preventDefault(),m.value.length===0)try{let y=(await navigator.clipboard?.readText())?.trim();if(!y){new O.Notice("Clipboard is empty.");return}m.value=y,m.dispatchEvent(new Event("input")),new O.Notice("Pasted from clipboard.")}catch{new O.Notice("Couldn't read the clipboard.")}else navigator.clipboard?.writeText(m.value).then(()=>new O.Notice("Copied to clipboard."),()=>new O.Notice("Couldn't access the clipboard."))},m.addEventListener("input",B),B(),t.push(B),m},s=null,n=null,o=null;if((e==="unlock"||e==="change")&&(s=i("Current password")),(e==="setup"||e==="change")&&(n=i(e==="change"?"New password":"Password"),o=i("Confirm password")),n){let f=this.contentEl.createDiv({cls:"stashpad-export-strength"}),I=f.createDiv({cls:"stashpad-strength-bar"}),m=[0,1,2,3].map(()=>I.createDiv({cls:"stashpad-strength-seg"})),C=f.createEl("span",{cls:"stashpad-strength-label"}),B=this.contentEl.createDiv({cls:"stashpad-encryption-counter"}),w=()=>{let Q=n.value,S=Yn(Q);m.forEach((x,H)=>x.toggleClass("is-on",H0&&Q.length<6?" \u2014 use at least 6":""}`),B.toggleClass("is-weak",Q.length>0&&Q.length<6)};n.addEventListener("input",w),w();let y=this.contentEl.createDiv({cls:"stashpad-export-genrow"});y.createEl("button",{cls:"stashpad-export-gen",text:"Generate strong passphrase"}).onclick=Q=>{Q.preventDefault();let S=On(5);n.value=S,o&&(o.value=S),w(),t.forEach(x=>x()),new O.Notice("Generated \u2014 copy it somewhere safe; there's no recovery."),navigator.clipboard?.writeText(S).catch(()=>{})};let E=y.createEl("button",{cls:"stashpad-export-show",text:"Show"});E.onclick=Q=>{Q.preventDefault();let S=n.type==="password";n.type=S?"text":"password",o&&(o.type=S?"text":"password"),E.setText(S?"Hide":"Show")};let v=this.contentEl.createEl("div",{cls:"stashpad-export-pw-suite"});v.setText("Encryption: AES-256-GCM. Checking key-derivation suite\u2026"),this.opts.kdfProbe?this.opts.kdfProbe().then(Q=>{v.toggleClass("is-weak",!Q),v.setText(Q?"Encryption: Argon2id + AES-256-GCM \u2014 the strongest suite (used on this device).":"\u26A0\uFE0F Argon2id can't run here, so this will use the weaker PBKDF2 (600k) + AES-256-GCM fallback.")}).catch(()=>v.setText("Encryption: AES-256-GCM with a password-derived key.")):v.setText("Encryption: Argon2id + AES-256-GCM (PBKDF2 fallback if Argon2 can't run here).")}let a=null,l=this.app.secretStorage;if(this.opts.offerKeychain&&l){let f=this.contentEl.createDiv({cls:"stashpad-export-remember"});a=f.createEl("input",{type:"checkbox"}),a.id="stashpad-enc-remember";let I=f.createEl("label",{text:"Remember on this device (keychain) \u2014 auto-unlock here without re-typing."});I.htmlFor=a.id;let m=this.contentEl.createDiv({cls:"stashpad-export-remember-note"});m.setText("Stored only in this device's keychain \u2014 doesn't sync to your other devices. Anyone with access to this unlocked device + keychain could decrypt."),m.setCssStyles({display:"none"}),a.onchange=()=>{m.setCssStyles({display:a.checked?"":"none"})}}let c=this.contentEl.createEl("div",{cls:"stashpad-export-error"});c.setCssStyles({display:"none"});let d=f=>{c.setText(f),c.setCssStyles({display:""})},h=this.contentEl.createDiv({cls:"stashpad-export-footer"});h.createEl("button",{text:"Cancel"}).onclick=()=>this.close();let g=h.createEl("button",{cls:"mod-cta",text:e==="setup"?"Set up":e==="change"?"Change":"Unlock"}),u=!1,p=async()=>{if(u)return;let f=s?.value??void 0,I=n?.value??void 0;if((e==="unlock"||e==="change")&&!f){d("Enter your current password.");return}if(e==="setup"||e==="change"){if(!I){d("Enter a password.");return}if(I.length<6){d("Use at least 6 characters.");return}if(I!==o?.value){d("Passwords don't match.");return}}u=!0,g.disabled=!0,c.setCssStyles({display:"none"});let m=g.textContent;g.setText("Working\u2026");try{let C=await this.opts.onSubmit({current:f,next:I,remember:!!a?.checked});if(C){d(C),u=!1,g.disabled=!1,g.setText(m??"OK");return}this.succeeded=!0,this.close()}catch(C){d(`Failed: ${C.message}`),u=!1,g.disabled=!1,g.setText(m??"OK")}};g.onclick=()=>void p(),this.scope.register([],"Enter",f=>{f.preventDefault(),p()}),requestAnimationFrame(()=>(s??n)?.focus())}onClose(){this.contentEl.empty(),this.succeeded||this.opts.onCancel?.()}},Hi=class extends O.Modal{constructor(e,t){super(e);this.opts=t}onOpen(){this.contentEl.empty(),this.modalEl.addClass("stashpad-export-modal","stashpad-encryption-modal"),this.titleEl.setText(this.opts.title),this.contentEl.createEl("p",{cls:"stashpad-export-desc",text:this.opts.body});let e=null;this.opts.requirePassword&&(this.contentEl.createEl("p",{cls:"stashpad-export-desc"}).setText("Enter your encryption password:"),e=this.contentEl.createEl("input",{type:"password"}),e.addClass("stashpad-export-name","stashpad-encryption-field"),e.placeholder="Password"),this.contentEl.createEl("p",{cls:"stashpad-export-desc"}).setText(`Type "${this.opts.phrase}" to confirm.`);let t=this.contentEl.createEl("input",{type:"text"});t.addClass("stashpad-export-name","stashpad-encryption-field"),t.placeholder=this.opts.phrase;let i=this.contentEl.createEl("div",{cls:"stashpad-export-error"});i.setCssStyles({display:"none"});let s=this.contentEl.createDiv({cls:"stashpad-export-footer"});s.createEl("button",{text:"Cancel"}).onclick=()=>this.close();let n=s.createEl("button",{cls:"mod-cta mod-warning",text:this.opts.confirmText}),o=()=>t.value.trim().toLowerCase()===this.opts.phrase.trim().toLowerCase(),a=()=>{n.disabled=!o()||!!this.opts.requirePassword&&!e?.value};t.addEventListener("input",a),e?.addEventListener("input",a),a();let l=!1,c=async()=>{if(!(l||!o())){if(this.opts.requirePassword&&(l=!0,n.disabled=!0,i.setCssStyles({display:"none"}),!await this.opts.requirePassword(e.value))){i.setText("Wrong password."),i.setCssStyles({display:""}),l=!1,a();return}this.close(),await this.opts.onConfirm()}};n.onclick=()=>void c(),this.scope.register([],"Enter",d=>{d.preventDefault(),c()}),requestAnimationFrame(()=>(e??t).focus())}onClose(){this.contentEl.empty()}},Is=class extends O.Modal{constructor(e,t,i){super(e);this.onResult=i;this.delivered=!1;this.value=t&&/^#[0-9a-f]{6}$/i.test(t)?t:"#888888"}onOpen(){this.contentEl.empty(),this.titleEl.setText("Custom color"),this.modalEl.addClass("stashpad-custom-color-modal");let e=this.contentEl.createDiv({cls:"stashpad-custom-color-row"}),t=e.createDiv({cls:"stashpad-custom-color-preview"});t.style.background=this.value;let i=e.createEl("input",{type:"color"});i.value=this.value,i.addClass("stashpad-custom-color-wheel"),t.onclick=()=>i.click();let s=e.createEl("input",{type:"text"});s.addClass("stashpad-custom-color-hex"),s.placeholder="#RRGGBB",s.value=this.value,s.maxLength=7;let n=d=>{let h=d.startsWith("#")?d:"#"+d;/^#[0-9a-f]{6}$/i.test(h)&&(this.value=h,t.style.background=h,i.value=h,s.value!==h&&(s.value=h))};i.oninput=()=>n(i.value),s.oninput=()=>n(s.value);let o=this.contentEl.createDiv({cls:"stashpad-color-footer"}),a=o.createEl("button",{text:"Cancel"});a.onclick=()=>{this.delivered=!0,this.close()};let l=o.createEl("button",{cls:"mod-cta",text:"Save"});l.title="Apply this color to the selection (without adding to your palette).",l.onclick=()=>this.commit({addToPalette:!1});let c=o.createEl("button",{cls:"mod-cta",text:"Add to palette"});c.title="Apply and save this color so it appears as a tile next time.",c.onclick=()=>this.commit({addToPalette:!0}),this.scope.register([],"Enter",d=>{d.preventDefault(),this.commit({addToPalette:!1})}),this.scope.register(["Mod"],"Enter",d=>{d.preventDefault(),this.commit({addToPalette:!0})}),requestAnimationFrame(()=>s.focus())}commit(e){this.delivered=!0,this.close(),this.onResult(this.value,e)}onClose(){this.delivered||(this.delivered=!0,this.onResult(this.value,{addToPalette:!1})),this.contentEl.empty()}},Mi=class Mi extends O.Modal{constructor(e,t,i,s,n){super(e);this.currentColor=t;this.customPalette=i;this.onPick=s;this.onDeleteCustom=n;this.focusIdx=-1;this.items=[]}onOpen(){this.contentEl.empty(),this.titleEl.setText("Pick a color"),this.modalEl.addClass("stashpad-color-modal");let e=this.contentEl.createDiv({cls:"stashpad-color-grid"});this.items=[];let t=e.createDiv({cls:"stashpad-color-tile stashpad-color-none"});t.title="No color",t.onclick=()=>this.activate(0),this.items.push({kind:"none",color:null,el:t});let i=[...Mi.DEFAULT_PALETTE,...this.customPalette];for(let a of i){let l=e.createDiv({cls:"stashpad-color-tile"});l.style.background=a,l.title=a;let c=!Mi.DEFAULT_PALETTE.some(h=>h.toLowerCase()===a.toLowerCase()),d=this.items.length;if(l.onclick=()=>this.activate(d),c&&this.onDeleteCustom){let h=l.createSpan({cls:"stashpad-color-tile-del",text:"\xD7"});h.title="Remove from palette",h.onclick=g=>{g.stopPropagation(),this.handleDelete(a)}}this.items.push({kind:c?"custom":"preset",color:a,el:l})}let s=e.createDiv({cls:"stashpad-color-tile stashpad-color-add"});s.title="Custom color\u2026";let n=this.items.length;s.onclick=()=>this.activate(n),this.items.push({kind:"add",color:null,el:s});let o=this.items.findIndex(a=>this.currentColor===null?a.kind==="none":this.currentColor?a.color!==null&&a.color.toLowerCase()===this.currentColor.toLowerCase():!1);this.focusIdx=o>=0?o:0,this.refreshActive(),this.scope.register([],"ArrowRight",a=>{a.preventDefault(),this.moveFocus(1)}),this.scope.register([],"ArrowLeft",a=>{a.preventDefault(),this.moveFocus(-1)}),this.scope.register([],"ArrowDown",a=>{a.preventDefault(),this.moveFocus(this.columns())}),this.scope.register([],"ArrowUp",a=>{a.preventDefault(),this.moveFocus(-this.columns())}),this.scope.register([],"Enter",a=>{a.preventDefault(),this.activate(this.focusIdx)}),requestAnimationFrame(()=>this.modalEl.focus())}activate(e){if(e<0||e>=this.items.length)return;this.focusIdx=e,this.refreshActive();let t=this.items[e];if(t.kind==="add"){this.close(),new Is(this.app,this.currentColor,(i,s)=>{this.onPick(i,s)}).open();return}this.close(),this.onPick(t.color,{addToPalette:!1})}columns(){if(!this.items.length)return 1;let e=this.items[0].el.parentElement;if(!e)return 1;let t=e.getBoundingClientRect(),i=this.items[0].el.getBoundingClientRect();if(i.width<=0)return 1;let s=i.top,n=0;for(let o of this.items){let a=o.el.getBoundingClientRect();if(Math.abs(a.top-s)<1)n++;else break}return Math.max(1,n)}async handleDelete(e){if(this.onDeleteCustom)try{let t=await this.onDeleteCustom(e);this.customPalette=Array.isArray(t)?t:this.customPalette.filter(i=>i.toLowerCase()!==e.toLowerCase()),this.onOpen()}catch(t){console.warn("Stashpad: palette delete failed",t)}}moveFocus(e){if(!this.items.length)return;let t=Math.max(0,Math.min(this.items.length-1,this.focusIdx+e));this.focusIdx=t,this.refreshActive()}refreshActive(){for(let e=0;e{this.didChoose=!0,this.close(),this.onChoose(!1)};let s=t.createEl("button",{cls:"mod-cta",text:this.confirmText});s.onclick=()=>{this.didChoose=!0,this.close(),this.onChoose(!0)},requestAnimationFrame(()=>s.focus())}onClose(){this.contentEl.empty(),this.didChoose||(this.didChoose=!0,this.onChoose(!1))}},Ui=class extends O.Modal{constructor(e,t,i,s={}){super(e);this.levels=t;this.onPick=i;this.opts=s}onOpen(){this.modalEl?.addClass("stashpad-compact-modal"),this.modalEl?.addClass("stashpad-breadcrumb-modal"),this.contentEl.empty(),this.titleEl.setText("Jump to level");let e=this.contentEl.createDiv({cls:"stashpad-bc-levels"});for(let t of this.levels){let i=e.createDiv({cls:"stashpad-bc-level-row"});if(t.isCurrent&&i.addClass("is-current"),i.createSpan({cls:"stashpad-bc-level-num",text:String(t.level)}),i.createSpan({cls:"stashpad-bc-level-label",text:t.label}),!t.isCurrent){i.setAttribute("role","button"),i.setAttribute("tabindex","0");let s=()=>{this.close(),this.onPick(t.id)};i.onclick=s,i.onkeydown=n=>{(n.key==="Enter"||n.key===" ")&&(n.preventDefault(),s())}}this.opts.onContext&&(i.oncontextmenu=s=>{s.preventDefault(),this.opts.onContext(t.id,s,i,()=>this.close())},this.opts.attachLongPress?.(i,()=>this.opts.onContext(t.id,null,i,()=>this.close())))}}onClose(){this.contentEl.empty()}};Ke=class extends O.Modal{constructor(e,t,i,s={}){super(e);this.current=t;this.onPick=i;this.opts=s;this.didChoose=!1;this.assignees=[];this.tinyClosePopover=null;this.assignees=[...s.currentAssignees??[]]}onOpen(){this.modalEl?.addClass("stashpad-compact-modal"),this.contentEl.empty(),this.titleEl.setText(this.opts.title??"Set due date");let e=null;if(this.current){let B=Date.parse(this.current);Number.isNaN(B)||(e=new Date(B))}let t=this.contentEl.createDiv({cls:"stashpad-due-picker"}),i=t.createDiv({cls:"stashpad-due-fields"}),s=i.createDiv({cls:"stashpad-due-field"}),n=s.createSpan({cls:"stashpad-due-field-icon"});(0,O.setIcon)(n,"calendar");let o=s.createEl("input",{type:"date",cls:"stashpad-due-date"}),a=s.createSpan({cls:"stashpad-due-clear",attr:{"aria-label":"Clear date"}});(0,O.setIcon)(a,"x"),a.onclick=()=>{o.value="",o.focus()};let l=i.createDiv({cls:"stashpad-due-field"}),c=l.createSpan({cls:"stashpad-due-field-icon"});(0,O.setIcon)(c,"clock");let d=l.createEl("input",{type:"time",cls:"stashpad-due-time"}),h=l.createSpan({cls:"stashpad-due-clear",attr:{"aria-label":"Clear time"}});(0,O.setIcon)(h,"x"),h.onclick=()=>{d.value="",d.focus()},n.onclick=()=>{try{o.showPicker?.()}catch{}},c.onclick=()=>this.openTimeNumpad(c,d),e&&(o.value=this.toDateValue(e),d.value=this.toTimeValue(e));let g=(this.opts.quickAdjusts??[]).map(B=>({raw:B,min:Ea(B)})).filter(B=>B.min!=null);if(g.length>0){let B=1,w=t.createDiv({cls:"stashpad-due-quickadjust"}),y=w.createEl("button",{cls:"stashpad-due-adjust-flip",attr:{type:"button"}}),E=()=>{y.setText(B>0?"+":"\u2212"),y.toggleClass("is-minus",B<0),y.title=B>0?"Adding time (click to subtract)":"Subtracting time (click to add)"};E(),y.onclick=()=>{B=-B,E()};let v=Q=>{let S=new Date,x,H,G;if(o.value){let[D,b,P]=o.value.split("-").map(q=>parseInt(q,10));x=D,H=b-1,G=P}else x=S.getFullYear(),H=S.getMonth(),G=S.getDate();let N,U;if(d.value){let[D,b]=d.value.split(":").map(P=>parseInt(P,10));N=D,U=b}else N=S.getHours(),U=S.getMinutes();let R=new Date(x,H,G,N,U,0,0);R.setMinutes(R.getMinutes()+B*Q),o.value=this.toDateValue(R),d.value=this.toTimeValue(R)};for(let Q of g){let S=w.createEl("button",{cls:"stashpad-due-adjust-btn",text:Q.raw,attr:{type:"button"}});S.onclick=()=>v(Q.min)}}this.opts.hideAssignees||this.renderAssignSection(t);let u=t.createDiv({cls:"stashpad-due-grid"}),p=(B,w)=>{let y=u.createEl("button",{cls:"stashpad-due-btn stashpad-due-preset",text:B});y.onclick=()=>{let E=w();o.value=this.toDateValue(E),d.value||(d.value=this.toTimeValue(E))}},f=B=>(B.setHours(9,0,0,0),B);p("Today",()=>f(this.startOfTodayLocal())),p("Tomorrow",()=>{let B=this.startOfTodayLocal();return B.setDate(B.getDate()+1),f(B)}),p("Next week",()=>{let B=this.startOfTodayLocal();return B.setDate(B.getDate()+7),f(B)});let I=u.createEl("button",{cls:"stashpad-due-btn",text:"Clear"});I.onclick=()=>{o.value="",d.value="",o.focus()};let m=u.createEl("button",{cls:"stashpad-due-btn",text:"Cancel"});m.onclick=()=>{this.didChoose=!0,this.close()};let C=u.createEl("button",{cls:"stashpad-due-btn mod-cta",text:"Set"});C.onclick=()=>{if(!o.value){this.didChoose=!0,this.close(),this.onPick({iso:null,assignees:this.assignees});return}let[B,w,y]=o.value.split("-").map(S=>parseInt(S,10)),E=9,v=0;if(d.value){let[S,x]=d.value.split(":").map(H=>parseInt(H,10));E=S,v=x}let Q=new Date(B,w-1,y,E,v,0,0);this.didChoose=!0,this.close(),this.onPick({iso:Q.toISOString(),assignees:this.assignees})},requestAnimationFrame(()=>o.focus())}onClose(){this.tinyClosePopover?.(),this.contentEl.empty(),this.didChoose}renderAssignSection(e){va(e,{knownAuthors:this.opts.knownAuthors??[],initial:this.assignees,onChange:t=>{this.assignees=t}})}openTimeNumpad(e,t){this.tinyClosePopover?.();let i=e.ownerDocument??document,s=i.defaultView??window,n=9,o=0;if(t.value){let[I,m]=t.value.split(":").map(C=>parseInt(C,10));Number.isFinite(I)&&(n=I),Number.isFinite(m)&&(o=m)}else{let I=new Date;n=I.getHours(),o=I.getMinutes()}let a=n>=12?"pm":"am",l=n===0?12:n>12?n-12:n,d=(this.modalEl??i.body).createDiv({cls:"stashpad-when-popover stashpad-due-time-pop"});d.setCssStyles({position:"fixed",zIndex:"9999"});let h=null,g=()=>{d.remove(),i.removeEventListener("mousedown",u,!0),i.removeEventListener("keydown",p,!0),this.tinyClosePopover===g&&(this.tinyClosePopover=null)},u=I=>{!d.contains(I.target)&&I.target!==e&&!e.contains(I.target)&&g()},p=I=>{I.key==="Escape"?(I.preventDefault(),I.stopPropagation(),g()):I.key==="Enter"&&h&&(I.preventDefault(),I.stopPropagation(),h())};this.tinyClosePopover=g,gs(d,{seedH:l,seedM:o,seedPeriod:a,close:g,setOnEnter:I=>{h=I},onFinalize:I=>{t.value=`${String(I.hours24).padStart(2,"0")}:${String(I.minutes).padStart(2,"0")}`}});let f=e.getBoundingClientRect();d.setCssStyles({left:`${Math.max(8,Math.min(f.left,s.innerWidth-220))}px`,top:`${f.bottom+4}px`}),setTimeout(()=>{i.addEventListener("mousedown",u,!0),i.addEventListener("keydown",p,!0),d.querySelector(".stashpad-when-time-field")?.focus()},0)}startOfTodayLocal(){let e=new Date;return e.setHours(0,0,0,0),e}toDateValue(e){let t=e.getFullYear(),i=String(e.getMonth()+1).padStart(2,"0"),s=String(e.getDate()).padStart(2,"0");return`${t}-${i}-${s}`}toTimeValue(e){let t=String(e.getHours()).padStart(2,"0"),i=String(e.getMinutes()).padStart(2,"0");return`${t}:${i}`}},Ut=class Ut extends O.Modal{constructor(e,t,i,s=null,n){super(e);this.service=t;this.openLog=i;this.currentAuthorId=s;this.getNoteAuthorIds=n;this.records=[];this.visible=[];this.shownCount=0;this.categoryFilter=null;this.listEl=null;this.footerEl=null;this.countEl=null;this.filterSelEl=null;this.unsubscribe=null;this.authorFilter="all";this.authorSelEl=null}onOpen(){this.contentEl.empty(),this.titleEl.setText("Stashpad notification history"),this.modalEl.addClass("stashpad-log-modal"),this.modalEl.addClass("stashpad-notif-history-modal"),this.records=this.service.recent();let e=this.contentEl.createDiv({cls:"stashpad-log-toolbar"});if(this.countEl=e.createSpan({cls:"stashpad-log-count"}),this.updateCount(),this.filterSelEl=e.createEl("select",{cls:"stashpad-log-type-filter"}),this.filterSelEl.onchange=()=>this.setCategoryFilter(this.filterSelEl.value||null),this.refreshCategoryFilter(),this.authorSelEl=e.createEl("select",{cls:"stashpad-log-type-filter stashpad-notif-author-filter"}),this.authorSelEl.onchange=()=>this.setAuthorFilter(this.authorSelEl.value||"all"),this.refreshAuthorFilter(),this.openLog){let i=e.createEl("button",{text:"Open log"});i.title="Open the per-folder Stashpad log for the most recent notification's folder.",i.onclick=()=>{let s=this.records.find(n=>!!n.folder);this.openLog?.(s?.folder)}}let t=e.createEl("button",{cls:"mod-warning",text:"Clear history"});t.onclick=()=>{new ie(this.app,"Clear notification history?",`This will permanently remove all ${this.records.length} stored notifications from the history. The current toasts on screen are unaffected. This can't be undone.`,"Clear history",i=>{i&&this.service.clearHistory()}).open()},this.listEl=this.contentEl.createDiv({cls:"stashpad-log-list"}),this.refreshList(),this.footerEl=this.contentEl.createDiv({cls:"stashpad-log-footer"}),this.renderFooter(),this.unsubscribe=this.service.onChange(()=>{this.records=this.service.recent(),this.refreshCategoryFilter(),this.refreshAuthorFilter(),this.refreshList(),this.renderFooter()})}setAuthorFilter(e){this.authorFilter!==e&&(this.authorFilter=e,this.refreshList(),this.renderFooter())}refreshAuthorFilter(){if(!this.authorSelEl)return;let e=this.authorSelEl;e.empty(),e.createEl("option",{text:"All authors"}).value="all",this.currentAuthorId&&(e.createEl("option",{text:"Me"}).value="me"),e.createEl("option",{text:"Cross-author"}).value="cross";let t=new Set;for(let s of this.records)s.authorId&&s.authorId!==this.currentAuthorId&&t.add(s.authorId);if(t.size>0){let s=e.createEl("option",{text:"\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"});s.disabled=!0;for(let n of[...t].sort())e.createEl("option",{text:n}).value=n}new Set(["all","cross",...this.currentAuthorId?["me"]:[],...t]).has(this.authorFilter)||(this.authorFilter="all"),e.value=this.authorFilter}isCrossAuthor(e){let t=e.authorId??null;if(!t)return!1;for(let i of e.affectedAuthorIds??[])if(i&&i!==t)return!0;if(!this.getNoteAuthorIds)return!1;for(let i of e.affectedIds){let s=this.getNoteAuthorIds(i);for(let n of s)if(n&&n!==t)return!0}return!1}onClose(){this.unsubscribe?.(),this.unsubscribe=null,this.contentEl.empty()}setCategoryFilter(e){(this.categoryFilter??null)!==(e??null)&&(this.categoryFilter=e,this.refreshList(),this.renderFooter())}refreshCategoryFilter(){if(!this.filterSelEl)return;let e=this.filterSelEl;e.empty();let t=new Map;for(let n of this.records)t.set(n.category,(t.get(n.category)??0)+1);let i=[...t.entries()].sort((n,o)=>o[1]-n[1]||n[0].localeCompare(o[0])),s=e.createEl("option",{text:`All categories (${this.records.length})`});s.value="";for(let[n,o]of i){let a=e.createEl("option",{text:`${n} (${o})`});a.value=n}this.categoryFilter&&!t.has(this.categoryFilter)&&(this.categoryFilter=null),e.value=this.categoryFilter??""}refreshList(){if(this.listEl){if(this.visible=this.records.filter(e=>{if(this.categoryFilter&&e.category!==this.categoryFilter)return!1;switch(this.authorFilter){case"all":return!0;case"me":return!!this.currentAuthorId&&e.authorId===this.currentAuthorId;case"cross":return this.isCrossAuthor(e);default:return e.authorId===this.authorFilter}}),this.shownCount=0,this.listEl.empty(),!this.visible.length){this.listEl.createDiv({cls:"stashpad-log-empty",text:this.categoryFilter?`No "${this.categoryFilter}" notifications.`:"No notifications yet."}),this.updateCount();return}this.appendMore(Ut.PAGE)}}appendMore(e){if(!this.listEl)return;let t=Math.min(this.visible.length,this.shownCount+e);for(let i=this.shownCount;i0){let c=i.createDiv({cls:"stashpad-notif-actions-snapshot"});for(let d of t.actionLabels){let h=c.createSpan({cls:"stashpad-notif-action-chip"});h.setText(d),h.title="Action button was shown on the original toast (handler not retained)."}}}updateCount(){if(!this.countEl)return;let e=this.visible.length,t=this.categoryFilter?`${e} ${this.categoryFilter} notification${e===1?"":"s"}`:`${e} notification${e===1?"":"s"}`;this.shownCount===0||this.shownCount>=e?this.countEl.setText(t):this.countEl.setText(`Showing ${this.shownCount} of ${t}`)}renderFooter(){if(!this.footerEl)return;this.footerEl.empty();let e=this.visible.length-this.shownCount;if(e<=0)return;let t=this.footerEl.createEl("button",{text:`Load ${Math.min(Ut.PAGE,e)} more`});if(t.onclick=()=>{this.appendMore(Ut.PAGE),this.renderFooter()},e>Ut.PAGE){let i=this.footerEl.createEl("button",{text:`Load all (${e})`});i.onclick=()=>{this.appendMore(e),this.renderFooter()}}}};Ut.PAGE=100;Ri=Ut,$n=class extends O.Modal{constructor(e,t){super(e);this.entries=t}onOpen(){if(this.contentEl.empty(),this.titleEl.setText("Stashpad import log"),this.entries.length===0){this.contentEl.createDiv({cls:"stashpad-log-empty",text:"Nothing imported yet."});return}let e=this.contentEl.createDiv({cls:"stashpad-import-log-list"});for(let t of this.entries){let i=e.createDiv({cls:"stashpad-import-log-row"}),s=(0,O.moment)(t.ts).format("YYYY-MM-DD HH:mm");i.createSpan({cls:"stashpad-import-log-when",text:s});let n=t.kind==="folder"?"folder":t.kind==="md"?"note":"file";i.createSpan({cls:`stashpad-import-log-kind is-${t.kind}`,text:n}),i.createSpan({cls:"stashpad-import-log-name",text:t.originalName});let o=[t.folder.split("/").pop()||t.folder];t.notePaths.length>1&&o.push(`${t.notePaths.length} notes`),i.createSpan({cls:"stashpad-import-log-meta",text:o.join(" \xB7 ")})}}onClose(){this.contentEl.empty()}},Gi=class extends O.Modal{constructor(e,t,i){super(e);this.message=t;this.onChoose=i;this.chose=!1}onOpen(){this.modalEl?.addClass("stashpad-compact-modal"),this.contentEl.empty(),this.titleEl.setText("Possible duplicate import");let e=this.contentEl.createDiv({cls:"stashpad-confirm-body"});for(let o of this.message.split(` -`))e.createDiv({cls:"stashpad-confirm-line",text:o});let t=this.contentEl.createDiv({cls:"stashpad-modal-btns"}),i=t.createEl("button",{text:"Skip duplicates"});i.onclick=()=>{this.chose=!0,this.close(),this.onChoose("skip")};let s=t.createEl("button",{text:"Replace existing"});s.onclick=()=>{this.chose=!0,this.close(),this.onChoose("replace")};let n=t.createEl("button",{cls:"mod-cta",text:"Import anyway"});n.onclick=()=>{this.chose=!0,this.close(),this.onChoose("anyway")}}onClose(){this.chose||this.onChoose("skip"),this.contentEl.empty()}},Li=class extends O.Modal{constructor(e,t,i,s){super(e);this.noteCount=i;this.onConfirm=s;this.base=t}onOpen(){this.contentEl.empty(),this.modalEl.addClass("stashpad-export-modal"),this.titleEl.setText("Export as OKF"),this.contentEl.createEl("p",{cls:"stashpad-export-desc",text:`Export ${this.noteCount} note${this.noteCount===1?"":"s"} as an Open Knowledge Format bundle. Pick one or more formats.`});let e=this.contentEl.createEl("input",{type:"text"});e.addClass("stashpad-export-name"),e.value=this.base,e.placeholder="Export name";let t=(l,c)=>{let d=this.contentEl.createDiv({cls:"stashpad-okf-fmt"}),h=d.createEl("input",{type:"checkbox"});return h.checked=c,d.createEl("label",{text:l}),h},i=t(".zip \u2014 OKF bundle (portable)",!0),s=t(".tar.gz \u2014 OKF bundle (tarball)",!1),n=t(".stash \u2014 Stashpad format (re-importable)",!1),o=this.contentEl.createDiv({cls:"stashpad-export-footer"});o.createEl("button",{text:"Cancel"}).onclick=()=>this.close();let a=o.createEl("button",{cls:"mod-cta",text:"Export"});a.onclick=()=>{if(!i.checked&&!s.checked&&!n.checked){new O.Notice("Pick at least one format.");return}this.close(),this.onConfirm(e.value.trim()||this.base,{zip:i.checked,targz:s.checked,stash:n.checked})},requestAnimationFrame(()=>e.focus())}onClose(){this.contentEl.empty()}}});function be(A,r=Ze){let e=r instanceof Set?r:new Set(r.map(o=>o.toLowerCase())),t=(A.split(/\r?\n/).find(o=>o.trim().length>0)??"").trim();if(!t)return"Untitled";let s=t.replace(/['‘-‛ʼ]/g,"").replace(/[^A-Za-z0-9\s]+/g," ").split(/\s+/).filter(o=>o&&!e.has(o.toLowerCase())).map(o=>o.length>=2&&/^[A-Z0-9]+$/.test(o)?o:o.charAt(0).toUpperCase()+o.slice(1).toLowerCase()),n="";for(let o of s){let a=n?`${n}-${o}`:o;if(a.length>50)break;n=a}return n||"Untitled"}function De(A,r){return`${A}-${r}.md`}function yt(A){let r=A.match(/-([a-z0-9]{4,12})$/);return r?r[1]:null}function ws(A,r){let e=A.lastIndexOf(".");return e>0?`${A.slice(0,e)}-${r}${A.slice(e)}`:`${A}-${r}`}function ka(A){let r=A.match(/^([0-9a-z]{7,9})-(.+)$/);if(!r)return null;let e=parseInt(r[1],36);return!Number.isFinite(e)||e<142e10||e>Date.now()+864e5?null:{stamp:r[1],rest:r[2]}}var Ze,si=$e(()=>{"use strict";Ze=["a","an","the","and","or","but","if","then","else","of","in","on","at","to","for","with","by","from","as","is","are","was","were","be","been","being","i","you","he","she","it","we","they","this","that","these","those","my","your","our","their","do","does","did","so","just","very","really","im"]});var vs={};wi(vs,{JdBuildConfirmModal:()=>Oi,buildJdIndexNotes:()=>Xn,buildJdIndexPreview:()=>Qs,buildJdPreviewNotice:()=>Es,scanForJdNotes:()=>ni});function Wn(A,r){if(/^\d+-\d+$/.test(A))return null;if(A.includes(".")){let t=A.split(".").slice(0,-1).join(".");return r.has(t)?t:Wn(t,r)}if(/^\d+$/.test(A)){let e=parseInt(A,10);for(let t of r){let i=t.match(/^(\d+)-(\d+)$/);if(!i)continue;let s=parseInt(i[1],10),n=parseInt(i[2],10);if(e>=s&&e<=n)return t}return null}return null}function ni(A,r,e){let t=A.vault.getMarkdownFiles(),i=Nl(t,e),s=e.jdIndexIncludeStashpadFolders===!0,n=s?new Set:new Set(r.discoverStashpadFolders()),o=u=>s?!1:Array.from(n).some(p=>u===p||u.startsWith(p+"/")),a=[],l=[],c=[];for(let u of i){let p=u.parent?.path?.replace(/\/+$/,"")??"";if(o(p)){c.push(u);continue}let f=u.basename.match(Sa);f?a.push({prefix:f[1],segments:f[1].split("."),title:f[2],file:u,folder:null}):l.push(u)}let d=(e.jdIndexScope??"vault")==="folder"?(e.jdIndexScopeFolder??"").trim().replace(/^\/+|\/+$/g,""):"",h=u=>{let p=u.path.replace(/\/+$/,"");if(!(d&&p&&p!==d&&!p.startsWith(d+"/"))){if(p&&!o(p)){let f=u.name.match(Sa);f&&a.push({prefix:f[1],segments:f[1].split("."),title:f[2],file:null,folder:u})}}for(let f of u.children)f instanceof fe.TFolder&&h(f)};h(A.vault.getRoot());let g=e.jdIndexSort??"natural";return a.sort((u,p)=>Ml(u,p,g)),{indexed:a,nonIndex:l,skippedStashpadNotes:c}}async function Qs(A,r,e){let t=Zn(e.jdIndexStashpadFolder??"");if(!t)return{...ni(A,r,e),previewPath:null,error:"no-dest"};let i=ni(A,r,e),s=xl(A,t);if(!s)return{...i,previewPath:null,error:"no-home"};let n=Tl(i.indexed,i.nonIndex,i.skippedStashpadNotes),a=(await A.vault.read(s)).match(/^---\r?\n[\s\S]*?\r?\n---\r?\n/),l=a?`${a[0]}${n}`:n;return await A.vault.modify(s,l),{...i,previewPath:s.path}}function xl(A,r){let e=A.vault.getAbstractFileByPath(r);if(!(e instanceof fe.TFolder))return null;for(let t of e.children){if(!(t instanceof fe.TFile)||t.extension!=="md")continue;if((A.metadataCache.getFileCache(t)?.frontmatter??{}).id===k)return t}return null}async function Xn(A,r,e){let t=Zn(e.jdIndexStashpadFolder??"");if(!t)return{created:0,updated:0,skipped:0,destFolder:"",error:"no-dest"};if(!new Set(r.discoverStashpadFolders()).has(t))return{created:0,updated:0,skipped:0,destFolder:t,error:"dest-not-stashpad"};let s=ni(A,r,e),n=new Map;for(let I of s.indexed)n.set(I.prefix,I);for(let I of s.indexed)if(I.prefix.includes("."))for(let m=1;m{l.push(I);for(let m of a.get(I)??[])c(m)};for(let I of o)c(I);let d=new Map,h=A.vault.getAbstractFileByPath(t);if(h instanceof fe.TFolder)for(let I of h.children){if(!(I instanceof fe.TFile)||I.extension!=="md")continue;let m=A.metadataCache.getFileCache(I)?.frontmatter??{};typeof m.jdPrefix=="string"&&d.set(m.jdPrefix,I)}let g=new Map,u=0,p=0,f=0;for(let I of l){let m=n.get(I)??null,C=Wn(I,new Set(n.keys())),B=C?g.get(C)??k:k,w=m?`${I} ${m.title}`:I,y=m?.file?`[[${m.file.basename}]]`:"",E=y?`${y} +`),this.lineCursorIdx=Math.max(1,Math.min(this.lines.length-1,Math.floor(this.lines.length/2))),this.lines.length<2&&(this.mode="cursor")}onOpen(){this.titleEl.setText("Split note"),this.modalEl.addClass("stashpad-split-modal"),this.render(),this.scope.register([],"Tab",e=>{this.lines.length<2||(e.preventDefault(),this.mode=this.mode==="line"?"cursor":"line",this.render())}),this.scope.register([],"Enter",e=>{this.mode==="line"&&(e.preventDefault(),this.commitLine())}),this.scope.register(["Mod"],"Enter",e=>{this.mode==="cursor"&&(e.preventDefault(),this.commitCursor())}),this.scope.register([],"ArrowUp",e=>{this.mode==="line"&&(e.preventDefault(),this.lineCursorIdx=Math.max(1,this.lineCursorIdx-1),this.render())}),this.scope.register([],"ArrowDown",e=>{this.mode==="line"&&(e.preventDefault(),this.lineCursorIdx=Math.min(this.lines.length-1,this.lineCursorIdx+1),this.render())})}commitLine(){let e=this.lineCursorIdx;this.close(),this.onSplitAtLine(e)}commitCursor(){let e=this.cursorTextarea;if(!e)return;let t=e.selectionStart;if(t<=0||t>=e.value.length){new J.Notice("Move the cursor inside the text \u2014 neither end can be empty.");return}this.close(),this.onSplitAtChar(t)}render(){this.contentEl.empty();let e=this.contentEl.createDiv({cls:"stashpad-split-quick"});e.createSpan({cls:"stashpad-split-quick-label",text:"Split by:"}),["lines","paragraphs","headings"].forEach(o=>{let a=Ti(this.body,o),l=e.createEl("button",{cls:"stashpad-split-quick-btn",text:`${Mi[o]} (${a.length})`});l.disabled=a.length<2,l.onmousedown=c=>c.preventDefault(),l.onclick=()=>{let c=Ti(this.body,o);if(c.length<2){new J.Notice("That delimiter wouldn't split this note.");return}this.close(),this.onSplitMany(c)}});let t=this.contentEl.createDiv({cls:"stashpad-split-toggle-bar"});if(this.lines.length>=2){let o=t.createEl("button",{text:"Line split",cls:"stashpad-split-mode-btn"});this.mode==="line"&&o.addClass("is-active"),o.onclick=()=>{this.mode="line",this.render()}}let i=t.createEl("button",{text:"Cursor split",cls:"stashpad-split-mode-btn"});this.mode==="cursor"&&i.addClass("is-active"),i.onclick=()=>{this.mode="cursor",this.render()};let s=t.createEl("button",{text:"Split",cls:"stashpad-split-confirm-btn mod-cta"});s.onmousedown=o=>o.preventDefault(),s.onclick=()=>{this.mode==="line"?this.commitLine():this.commitCursor()},this.mode==="line"?this.renderLineMode():this.renderCursorMode();let n=this.contentEl.createDiv({cls:"stashpad-split-help"});J.Platform.isMobile?n.setText(this.mode==="line"?"Tap a line to position the divider, then Split.":"Tap inside the text to position the cursor, then Split."):n.setText(this.mode==="line"?"\u2191/\u2193 pick split line \xB7 Enter or Split confirm \xB7 Tab \u2192 cursor mode \xB7 Esc cancel \xB7 Children stay with the first part":"Click or arrow to position cursor \xB7 Mod+Enter or Split confirm \xB7 Tab \u2192 line mode \xB7 Esc cancel \xB7 Children stay with the first part")}renderLineMode(){let e=this.contentEl.createDiv({cls:"stashpad-split-list"}),t=null;for(let i=0;i{let n=Math.max(1,Math.min(this.lines.length-1,i+1));n!==this.lineCursorIdx&&(this.lineCursorIdx=n,this.render())}}t&&window.requestAnimationFrame(()=>t.scrollIntoView({block:"center"}))}renderCursorMode(){let t=this.contentEl.createDiv({cls:"stashpad-split-cursor-wrap"}).createEl("textarea",{cls:"stashpad-split-cursor-ta"});t.value=this.body,t.readOnly=!1,this.cursorTextarea=t;let i=parseFloat(getComputedStyle(t).lineHeight)||22,s=J.Platform.isMobile?3:12,n=2,o=()=>{t.setCssStyles({height:"auto"});let a=Math.min(t.scrollHeight,i*s+16);t.setCssStyles({height:`${Math.max(a,i*n+16)}px`})};requestAnimationFrame(()=>{o();let a=Math.floor(t.value.length/2);t.focus(),t.setSelectionRange(a,a)}),t.addEventListener("input",o)}onClose(){this.cursorTextarea=null,this.contentEl.empty()}},ni=class extends J.Modal{constructor(e,t,i,s,n){super(e);this.defaultBaseName=t;this.noteCount=i;this.onConfirm=s;this.kdfProbe=n;this.delivered=!1}onOpen(){this.contentEl.empty(),this.titleEl.setText("Export to .stash"),this.modalEl.addClass("stashpad-export-modal"),this.contentEl.createEl("p",{cls:"stashpad-export-desc",text:`${this.noteCount} note${this.noteCount===1?"":"s"} will be bundled into a single .stash file.`});let e=this.contentEl.createDiv({cls:"stashpad-export-field"});e.createEl("label",{cls:"stashpad-export-label",text:"File name"});let t=e.createEl("input",{type:"text"});t.addClass("stashpad-export-name"),t.value=this.defaultBaseName;let i=this.contentEl.createEl("div",{cls:"stashpad-export-preview"}),s=this.contentEl.createDiv({cls:"stashpad-export-encrypt"}),n=s.createDiv({cls:"stashpad-export-toggle"}),o=n.createEl("input",{type:"checkbox"});o.id="stashpad-export-encrypt-cb";let a=n.createEl("label",{text:"Encrypt with a password"});a.htmlFor=o.id;let l=s.createDiv({cls:"stashpad-export-pw-area"});l.setCssStyles({display:"none"});let c=[],d=q=>{let V=l.createDiv({cls:"stashpad-export-pw-row"}),Y=V.createEl("input",{type:"password"});Y.addClass("stashpad-export-name"),Y.placeholder=q;let $=V.createEl("button",{cls:"stashpad-export-copy"}),_=()=>{let ce=Y.value.length===0;$.setText(ce?"Paste":"Copy"),$.toggleClass("is-paste",ce),$.setAttr("aria-label",`${ce?"Paste into":"Copy"} ${q.toLowerCase()}`)};return $.onclick=async ce=>{if(ce.preventDefault(),Y.value.length===0)try{let re=(await navigator.clipboard?.readText())?.trim();if(!re){new J.Notice("Clipboard is empty.");return}Y.value=re,Y.dispatchEvent(new Event("input")),new J.Notice("Pasted from clipboard.")}catch{new J.Notice("Couldn't read the clipboard.")}else navigator.clipboard?.writeText(Y.value).then(()=>new J.Notice("Passphrase copied to clipboard."),()=>new J.Notice("Couldn't access the clipboard."))},_(),c.push(_),Y},h=d("Password"),g=d("Confirm password"),f=l.createDiv({cls:"stashpad-export-strength"}),p=f.createDiv({cls:"stashpad-strength-bar"}),u=[0,1,2,3].map(()=>p.createDiv({cls:"stashpad-strength-seg"})),I=f.createEl("span",{cls:"stashpad-strength-label"}),m=l.createDiv({cls:"stashpad-export-genrow"}),C=m.createEl("button",{cls:"stashpad-export-gen",text:"Generate strong passphrase"}),B=m.createEl("button",{cls:"stashpad-export-show",text:"Show"}),w=l.createEl("div",{cls:"stashpad-export-pw-hint"}),y=l.createEl("div",{cls:"stashpad-export-pw-suite"});y.setText("Encryption: AES-256-GCM. Checking key-derivation suite for this device\u2026"),this.kdfProbe?this.kdfProbe().then(q=>{y.toggleClass("is-weak",!q),y.setText(q?"Encryption: Argon2id + AES-256-GCM \u2014 the strongest suite (used on this device).":"\u26A0\uFE0F Argon2id can't run on this device, so this export will use the weaker PBKDF2 (600k) + AES-256-GCM fallback.")}).catch(()=>{y.setText("Encryption: AES-256-GCM with a password-derived key.")}):y.setText("Encryption: Argon2id + AES-256-GCM (falls back to PBKDF2 if Argon2 can't run here).");let v=this.app.secretStorage,E=l.createDiv({cls:"stashpad-export-remember"}),Q=E.createEl("input",{type:"checkbox"});Q.id="stashpad-export-remember-cb";let S=E.createEl("label",{text:"Remember in this vault (this device) \u2014 skips the prompt when you re-import here."});S.htmlFor=Q.id;let x=l.createDiv({cls:"stashpad-export-remember-note"});x.setText("Saved only in this device's keychain \u2014 it doesn't sync to your other devices and isn't shared with anyone you send this file to. Keep the passphrase somewhere safe if you'll open this export elsewhere."),x.setCssStyles({display:"none"}),Q.onchange=()=>{x.setCssStyles({display:Q.checked?"":"none"})},v||E.setCssStyles({display:"none"});let H=()=>{let q=t.value.trim()||this.defaultBaseName;return o.checked?`${q}-encrypted`:q},G=()=>i.setText(`Saves as: ${H()}-.stash`);t.oninput=G;let N=this.contentEl.createDiv({cls:"stashpad-export-footer"}),R=N.createEl("button",{text:"Cancel"});R.onclick=()=>this.close();let U=N.createEl("button",{cls:"mod-cta",text:"Export"}),D=()=>{let q=oo(h.value);f.style.visibility=h.value?"visible":"hidden",u.forEach((V,Y)=>V.toggleClass("is-on",h.value!==""&&Y<=q.level)),p.dataset.level=String(q.level),I.setText(q.label)},b=()=>{let q=o.checked;l.setCssStyles({display:q?"":"none"});let V=!0;q&&(h.value?h.value!==g.value?(w.setText("Passwords don't match."),w.addClass("is-error"),V=!1):(w.setText("\u26A0\uFE0F If you lose this password, the export can't be recovered."),w.removeClass("is-error")):(w.setText("Enter a password to encrypt this export."),w.removeClass("is-error"),V=!1)),D(),c.forEach(Y=>Y()),U.disabled=!V,U.toggleClass("is-disabled",!V),G()},P=!1,O=q=>{P=q,h.type=g.type=q?"text":"password",B.setText(q?"Hide":"Show")};B.onclick=q=>{q.preventDefault(),O(!P)},C.onclick=q=>{q.preventDefault(),h.value=g.value=no(),O(!1),new J.Notice("Passphrase generated (hidden) \u2014 Show to view, or Copy to save it."),b()},o.onchange=b,h.oninput=b,g.oninput=b,b();let L=()=>this.commit(H(),o.checked?h.value:null,o.checked&&Q.checked);U.onclick=L,this.scope.register([],"Enter",q=>{q.preventDefault(),U.disabled||L()}),requestAnimationFrame(()=>{t.focus(),t.select()})}commit(e,t,i){let s=e.trim()||this.defaultBaseName,n=t&&t.length?t:null;this.delivered=!0,this.close(),this.onConfirm(s,n,!!n&&i)}onClose(){this.contentEl.empty()}},Gi=class extends J.Modal{constructor(e,t,i,s){super(e);this.errorMsg=t;this.allowLater=i;this.onResult=s;this.delivered=!1}onOpen(){this.contentEl.empty(),this.titleEl.setText("Encrypted .stash"),this.modalEl.addClass("stashpad-export-modal"),this.contentEl.createEl("p",{cls:"stashpad-export-desc",text:"This export is password-protected. Enter its password to import it."}),this.errorMsg&&this.contentEl.createEl("div",{cls:"stashpad-export-error",text:this.errorMsg});let e=this.contentEl.createEl("input",{type:"password"});e.addClass("stashpad-export-name"),e.placeholder="Password";let t=this.contentEl.createDiv({cls:"stashpad-export-footer"});if(t.createEl("button",{text:"Cancel"}).onclick=()=>this.close(),this.allowLater){let s=t.createEl("button",{text:"Remind me later"});s.title="Don't import now \u2014 surface the reminder again later.",s.onclick=()=>this.finish({kind:"later"})}let i=t.createEl("button",{cls:"mod-cta",text:"Decrypt & import"});i.onclick=()=>this.submit(e.value),this.scope.register([],"Enter",s=>{s.preventDefault(),this.submit(e.value)}),requestAnimationFrame(()=>e.focus())}submit(e){e&&this.finish({kind:"password",value:e})}finish(e){this.delivered=!0,this.close(),this.onResult(e)}onClose(){this.delivered||(this.delivered=!0,this.onResult({kind:"cancel"})),this.contentEl.empty()}},de=class extends J.Modal{constructor(e,t){super(e);this.opts=t;this.succeeded=!1}onOpen(){let{mode:e}=this.opts;this.contentEl.empty(),this.modalEl.addClass("stashpad-export-modal","stashpad-encryption-modal"),this.titleEl.setText(this.opts.title??(e==="setup"?"Set up encryption password":e==="change"?"Change encryption password":"Unlock encryption")),this.contentEl.createDiv({cls:"stashpad-beta-row"}).createEl("span",{cls:"stashpad-beta-badge",text:"BETA"}),this.opts.intro?this.contentEl.createEl("p",{cls:"stashpad-export-desc",text:this.opts.intro}):e==="setup"&&this.contentEl.createEl("p",{cls:"stashpad-export-desc",text:"This single password protects everything you encrypt in this vault. It is stored only on this device. There is NO recovery \u2014 if you lose it, anything you've encrypted is gone for good."});let t=[],i=u=>{let I=this.contentEl.createDiv({cls:"stashpad-export-pw-row stashpad-encryption-row"}),m=I.createEl("input",{type:"password"});m.addClass("stashpad-export-name","stashpad-encryption-field"),m.placeholder=u;let C=I.createEl("button",{cls:"stashpad-export-copy"}),B=()=>{let w=m.value.length===0;C.setText(w?"Paste":"Copy"),C.toggleClass("is-paste",w),C.setAttr("aria-label",`${w?"Paste into":"Copy"} ${u.toLowerCase()}`)};return C.onclick=async w=>{if(w.preventDefault(),m.value.length===0)try{let y=(await navigator.clipboard?.readText())?.trim();if(!y){new J.Notice("Clipboard is empty.");return}m.value=y,m.dispatchEvent(new Event("input")),new J.Notice("Pasted from clipboard.")}catch{new J.Notice("Couldn't read the clipboard.")}else navigator.clipboard?.writeText(m.value).then(()=>new J.Notice("Copied to clipboard."),()=>new J.Notice("Couldn't access the clipboard."))},m.addEventListener("input",B),B(),t.push(B),m},s=null,n=null,o=null;if((e==="unlock"||e==="change")&&(s=i("Current password")),(e==="setup"||e==="change")&&(n=i(e==="change"?"New password":"Password"),o=i("Confirm password")),n){let u=this.contentEl.createDiv({cls:"stashpad-export-strength"}),I=u.createDiv({cls:"stashpad-strength-bar"}),m=[0,1,2,3].map(()=>I.createDiv({cls:"stashpad-strength-seg"})),C=u.createEl("span",{cls:"stashpad-strength-label"}),B=this.contentEl.createDiv({cls:"stashpad-encryption-counter"}),w=()=>{let Q=n.value,S=oo(Q);m.forEach((x,H)=>x.toggleClass("is-on",H0&&Q.length<6?" \u2014 use at least 6":""}`),B.toggleClass("is-weak",Q.length>0&&Q.length<6)};n.addEventListener("input",w),w();let y=this.contentEl.createDiv({cls:"stashpad-export-genrow"});y.createEl("button",{cls:"stashpad-export-gen",text:"Generate strong passphrase"}).onclick=Q=>{Q.preventDefault();let S=no(5);n.value=S,o&&(o.value=S),w(),t.forEach(x=>x()),new J.Notice("Generated \u2014 copy it somewhere safe; there's no recovery."),navigator.clipboard?.writeText(S).catch(()=>{})};let v=y.createEl("button",{cls:"stashpad-export-show",text:"Show"});v.onclick=Q=>{Q.preventDefault();let S=n.type==="password";n.type=S?"text":"password",o&&(o.type=S?"text":"password"),v.setText(S?"Hide":"Show")};let E=this.contentEl.createEl("div",{cls:"stashpad-export-pw-suite"});E.setText("Encryption: AES-256-GCM. Checking key-derivation suite\u2026"),this.opts.kdfProbe?this.opts.kdfProbe().then(Q=>{E.toggleClass("is-weak",!Q),E.setText(Q?"Encryption: Argon2id + AES-256-GCM \u2014 the strongest suite (used on this device).":"\u26A0\uFE0F Argon2id can't run here, so this will use the weaker PBKDF2 (600k) + AES-256-GCM fallback.")}).catch(()=>E.setText("Encryption: AES-256-GCM with a password-derived key.")):E.setText("Encryption: Argon2id + AES-256-GCM (PBKDF2 fallback if Argon2 can't run here).")}let a=null,l=this.app.secretStorage;if(this.opts.offerKeychain&&l){let u=this.contentEl.createDiv({cls:"stashpad-export-remember"});a=u.createEl("input",{type:"checkbox"}),a.id="stashpad-enc-remember";let I=u.createEl("label",{text:"Remember on this device (keychain) \u2014 auto-unlock here without re-typing."});I.htmlFor=a.id;let m=this.contentEl.createDiv({cls:"stashpad-export-remember-note"});m.setText("Stored only in this device's keychain \u2014 doesn't sync to your other devices. Anyone with access to this unlocked device + keychain could decrypt."),m.setCssStyles({display:"none"}),a.onchange=()=>{m.setCssStyles({display:a.checked?"":"none"})}}let c=this.contentEl.createEl("div",{cls:"stashpad-export-error"});c.setCssStyles({display:"none"});let d=u=>{c.setText(u),c.setCssStyles({display:""})},h=this.contentEl.createDiv({cls:"stashpad-export-footer"});h.createEl("button",{text:"Cancel"}).onclick=()=>this.close();let g=h.createEl("button",{cls:"mod-cta",text:e==="setup"?"Set up":e==="change"?"Change":"Unlock"}),f=!1,p=async()=>{if(f)return;let u=s?.value??void 0,I=n?.value??void 0;if((e==="unlock"||e==="change")&&!u){d("Enter your current password.");return}if(e==="setup"||e==="change"){if(!I){d("Enter a password.");return}if(I.length<6){d("Use at least 6 characters.");return}if(I!==o?.value){d("Passwords don't match.");return}}f=!0,g.disabled=!0,c.setCssStyles({display:"none"});let m=g.textContent;g.setText("Working\u2026");try{let C=await this.opts.onSubmit({current:u,next:I,remember:!!a?.checked});if(C){d(C),f=!1,g.disabled=!1,g.setText(m??"OK");return}this.succeeded=!0,this.close()}catch(C){d(`Failed: ${C.message}`),f=!1,g.disabled=!1,g.setText(m??"OK")}};g.onclick=()=>void p(),this.scope.register([],"Enter",u=>{u.preventDefault(),p()}),requestAnimationFrame(()=>(s??n)?.focus())}onClose(){this.contentEl.empty(),this.succeeded||this.opts.onCancel?.()}},Li=class extends J.Modal{constructor(e,t){super(e);this.opts=t}onOpen(){this.contentEl.empty(),this.modalEl.addClass("stashpad-export-modal","stashpad-encryption-modal"),this.titleEl.setText(this.opts.title),this.contentEl.createEl("p",{cls:"stashpad-export-desc",text:this.opts.body});let e=null;this.opts.requirePassword&&(this.contentEl.createEl("p",{cls:"stashpad-export-desc"}).setText("Enter your encryption password:"),e=this.contentEl.createEl("input",{type:"password"}),e.addClass("stashpad-export-name","stashpad-encryption-field"),e.placeholder="Password"),this.contentEl.createEl("p",{cls:"stashpad-export-desc"}).setText(`Type "${this.opts.phrase}" to confirm.`);let t=this.contentEl.createEl("input",{type:"text"});t.addClass("stashpad-export-name","stashpad-encryption-field"),t.placeholder=this.opts.phrase;let i=this.contentEl.createEl("div",{cls:"stashpad-export-error"});i.setCssStyles({display:"none"});let s=this.contentEl.createDiv({cls:"stashpad-export-footer"});s.createEl("button",{text:"Cancel"}).onclick=()=>this.close();let n=s.createEl("button",{cls:"mod-cta mod-warning",text:this.opts.confirmText}),o=()=>t.value.trim().toLowerCase()===this.opts.phrase.trim().toLowerCase(),a=()=>{n.disabled=!o()||!!this.opts.requirePassword&&!e?.value};t.addEventListener("input",a),e?.addEventListener("input",a),a();let l=!1,c=async()=>{if(!(l||!o())){if(this.opts.requirePassword&&(l=!0,n.disabled=!0,i.setCssStyles({display:"none"}),!await this.opts.requirePassword(e.value))){i.setText("Wrong password."),i.setCssStyles({display:""}),l=!1,a();return}this.close(),await this.opts.onConfirm()}};n.onclick=()=>void c(),this.scope.register([],"Enter",d=>{d.preventDefault(),c()}),requestAnimationFrame(()=>(e??t).focus())}onClose(){this.contentEl.empty()}},ks=class extends J.Modal{constructor(e,t,i){super(e);this.onResult=i;this.delivered=!1;this.touched=!1;this.value=t&&/^#[0-9a-f]{6}$/i.test(t)?t:"#888888"}onOpen(){this.contentEl.empty(),this.titleEl.setText("Custom color"),this.modalEl.addClass("stashpad-custom-color-modal");let e=this.contentEl.createDiv({cls:"stashpad-custom-color-row"}),t=e.createDiv({cls:"stashpad-custom-color-preview"});t.style.background=this.value;let i=e.createEl("input",{type:"color"});i.value=this.value,i.addClass("stashpad-custom-color-wheel"),t.onclick=()=>i.click();let s=e.createEl("input",{type:"text"});s.addClass("stashpad-custom-color-hex"),s.placeholder="#RRGGBB",s.value=this.value,s.maxLength=7;let n=d=>{let h=d.startsWith("#")?d:"#"+d;/^#[0-9a-f]{6}$/i.test(h)&&(this.touched=!0,this.value=h,t.style.background=h,i.value=h,s.value!==h&&(s.value=h))};i.oninput=()=>n(i.value),s.oninput=()=>n(s.value);let o=this.contentEl.createDiv({cls:"stashpad-color-footer"}),a=o.createEl("button",{text:"Cancel"});a.onclick=()=>{this.delivered=!0,this.close()};let l=o.createEl("button",{cls:"mod-cta",text:"Save"});l.title="Apply this color to the selection (without adding to your palette).",l.onclick=()=>this.commit({addToPalette:!1});let c=o.createEl("button",{cls:"mod-cta",text:"Add to palette"});c.title="Apply and save this color so it appears as a tile next time.",c.onclick=()=>this.commit({addToPalette:!0}),this.scope.register([],"Enter",d=>{d.preventDefault(),this.commit({addToPalette:!1})}),this.scope.register(["Mod"],"Enter",d=>{d.preventDefault(),this.commit({addToPalette:!0})}),requestAnimationFrame(()=>s.focus())}commit(e){this.delivered=!0,this.close(),this.onResult(this.value,e)}onClose(){!this.delivered&&this.touched&&(this.delivered=!0,this.onResult(this.value,{addToPalette:!1})),this.contentEl.empty()}},Ri=class Ri extends J.Modal{constructor(e,t,i,s,n){super(e);this.currentColor=t;this.customPalette=i;this.onPick=s;this.onDeleteCustom=n;this.focusIdx=-1;this.scopeBound=!1;this.items=[]}onOpen(){this.contentEl.empty(),this.titleEl.setText("Pick a color"),this.modalEl.addClass("stashpad-color-modal");let e=this.contentEl.createDiv({cls:"stashpad-color-grid"});this.items=[];let t=e.createDiv({cls:"stashpad-color-tile stashpad-color-none"});t.title="No color",t.onclick=()=>this.activate(0),this.items.push({kind:"none",color:null,el:t});let i=[...Ri.DEFAULT_PALETTE,...this.customPalette];for(let a of i){let l=e.createDiv({cls:"stashpad-color-tile"});l.style.background=a,l.title=a;let c=!Ri.DEFAULT_PALETTE.some(h=>h.toLowerCase()===a.toLowerCase()),d=this.items.length;if(l.onclick=()=>this.activate(d),c&&this.onDeleteCustom){let h=l.createSpan({cls:"stashpad-color-tile-del",text:"\xD7"});h.title="Remove from palette",h.onclick=g=>{g.stopPropagation(),this.handleDelete(a)}}this.items.push({kind:c?"custom":"preset",color:a,el:l})}let s=e.createDiv({cls:"stashpad-color-tile stashpad-color-add"});s.title="Custom color\u2026";let n=this.items.length;s.onclick=()=>this.activate(n),this.items.push({kind:"add",color:null,el:s});let o=this.items.findIndex(a=>this.currentColor===null?a.kind==="none":this.currentColor?a.color!==null&&a.color.toLowerCase()===this.currentColor.toLowerCase():!1);this.focusIdx=o>=0?o:0,this.refreshActive(),this.scopeBound||(this.scope.register([],"ArrowRight",a=>{a.preventDefault(),this.moveFocus(1)}),this.scope.register([],"ArrowLeft",a=>{a.preventDefault(),this.moveFocus(-1)}),this.scope.register([],"ArrowDown",a=>{a.preventDefault(),this.moveFocus(this.columns())}),this.scope.register([],"ArrowUp",a=>{a.preventDefault(),this.moveFocus(-this.columns())}),this.scope.register([],"Enter",a=>{a.preventDefault(),this.activate(this.focusIdx)}),this.scopeBound=!0),requestAnimationFrame(()=>this.modalEl.focus())}activate(e){if(e<0||e>=this.items.length)return;this.focusIdx=e,this.refreshActive();let t=this.items[e];if(t.kind==="add"){this.close(),new ks(this.app,this.currentColor,(i,s)=>{this.onPick(i,s)}).open();return}this.close(),this.onPick(t.color,{addToPalette:!1})}columns(){if(!this.items.length)return 1;let e=this.items[0].el.parentElement;if(!e)return 1;let t=e.getBoundingClientRect(),i=this.items[0].el.getBoundingClientRect();if(i.width<=0)return 1;let s=i.top,n=0;for(let o of this.items){let a=o.el.getBoundingClientRect();if(Math.abs(a.top-s)<1)n++;else break}return Math.max(1,n)}async handleDelete(e){if(this.onDeleteCustom)try{let t=await this.onDeleteCustom(e);this.customPalette=Array.isArray(t)?t:this.customPalette.filter(i=>i.toLowerCase()!==e.toLowerCase()),this.onOpen()}catch(t){console.warn("Stashpad: palette delete failed",t)}}moveFocus(e){if(!this.items.length)return;let t=Math.max(0,Math.min(this.items.length-1,this.focusIdx+e));this.focusIdx=t,this.refreshActive()}refreshActive(){for(let e=0;e{this.didChoose=!0,this.close(),this.onChoose(!1)};let s=t.createEl("button",{cls:"mod-cta",text:this.confirmText});s.onclick=()=>{this.didChoose=!0,this.close(),this.onChoose(!0)},requestAnimationFrame(()=>(this.dangerous?i:s).focus())}onClose(){this.contentEl.empty(),this.didChoose||(this.didChoose=!0,this.onChoose(!1))}},Oi=class extends J.Modal{constructor(e,t,i,s={}){super(e);this.levels=t;this.onPick=i;this.opts=s}onOpen(){this.modalEl?.addClass("stashpad-compact-modal"),this.modalEl?.addClass("stashpad-breadcrumb-modal"),this.contentEl.empty(),this.titleEl.setText("Jump to level");let e=this.contentEl.createDiv({cls:"stashpad-bc-levels"});for(let t of this.levels){let i=e.createDiv({cls:"stashpad-bc-level-row"});if(t.isCurrent&&i.addClass("is-current"),i.createSpan({cls:"stashpad-bc-level-num",text:String(t.level)}),i.createSpan({cls:"stashpad-bc-level-label",text:t.label}),!t.isCurrent){i.setAttribute("role","button"),i.setAttribute("tabindex","0");let s=()=>{this.close(),this.onPick(t.id)};i.onclick=s,i.onkeydown=n=>{(n.key==="Enter"||n.key===" ")&&(n.preventDefault(),s())}}this.opts.onContext&&(i.oncontextmenu=s=>{s.preventDefault(),this.opts.onContext(t.id,s,i,()=>this.close())},this.opts.attachLongPress?.(i,()=>this.opts.onContext(t.id,null,i,()=>this.close())))}}onClose(){this.contentEl.empty()}};Oe=class extends J.Modal{constructor(e,t,i,s={}){super(e);this.current=t;this.onPick=i;this.opts=s;this.didChoose=!1;this.assignees=[];this.tinyClosePopover=null;this.assignees=[...s.currentAssignees??[]]}onOpen(){this.modalEl?.addClass("stashpad-compact-modal"),this.contentEl.empty(),this.titleEl.setText(this.opts.title??"Set due date");let e=null;if(this.current){let E=Date.parse(this.current);Number.isNaN(E)||(e=new Date(E))}let t=this.contentEl.createDiv({cls:"stashpad-due-picker"}),i=t.createDiv({cls:"stashpad-due-fields"}),s=i.createDiv({cls:"stashpad-due-field"}),n=s.createSpan({cls:"stashpad-due-field-icon"});(0,J.setIcon)(n,"calendar");let o=s.createEl("input",{type:"date",cls:"stashpad-due-date"}),a=s.createSpan({cls:"stashpad-due-clear",attr:{"aria-label":"Clear date"}});(0,J.setIcon)(a,"x"),a.onclick=()=>{o.value="",o.focus()};let l=i.createDiv({cls:"stashpad-due-field"}),c=l.createSpan({cls:"stashpad-due-field-icon"});(0,J.setIcon)(c,"clock");let d=l.createEl("input",{type:"time",cls:"stashpad-due-time"}),h=l.createSpan({cls:"stashpad-due-clear",attr:{"aria-label":"Clear time"}});(0,J.setIcon)(h,"x"),h.onclick=()=>{d.value="",d.focus()},n.onclick=()=>{try{o.showPicker?.()}catch{}},c.onclick=()=>this.openTimeNumpad(c,d),e&&(o.value=this.toDateValue(e),d.value=this.toTimeValue(e));let g=(this.opts.quickAdjusts??[]).map(E=>({raw:E,min:Gr(E)})).filter(E=>E.min!=null);if(g.length>0){let E=1,Q=t.createDiv({cls:"stashpad-due-quickadjust"}),S=Q.createEl("button",{cls:"stashpad-due-adjust-flip",attr:{type:"button"}}),x=()=>{S.setText(E>0?"+":"\u2212"),S.toggleClass("is-minus",E<0),S.title=E>0?"Adding time (click to subtract)":"Subtracting time (click to add)"};x(),S.onclick=()=>{E=-E,x()};let H=G=>{let N=new Date,R,U,D;if(o.value){let[L,q,V]=o.value.split("-").map(Y=>parseInt(Y,10));R=L,U=q-1,D=V}else R=N.getFullYear(),U=N.getMonth(),D=N.getDate();let b,P;if(d.value){let[L,q]=d.value.split(":").map(V=>parseInt(V,10));b=L,P=q}else b=N.getHours(),P=N.getMinutes();let O=new Date(R,U,D,b,P,0,0);O.setMinutes(O.getMinutes()+E*G),o.value=this.toDateValue(O),d.value=this.toTimeValue(O)};for(let G of g){let N=Q.createEl("button",{cls:"stashpad-due-adjust-btn",text:G.raw,attr:{type:"button"}});N.onclick=()=>H(G.min)}}this.opts.hideAssignees||this.renderAssignSection(t);let f=t.createDiv({cls:"stashpad-due-grid"}),p=(E,Q)=>{let S=f.createEl("button",{cls:"stashpad-due-btn stashpad-due-preset",text:E});S.onclick=()=>{let x=Q();o.value=this.toDateValue(x),d.value||(d.value=this.toTimeValue(x))}},u=E=>(E.setHours(9,0,0,0),E);p("Today",()=>u(this.startOfTodayLocal())),p("Tomorrow",()=>{let E=this.startOfTodayLocal();return E.setDate(E.getDate()+1),u(E)}),p("Next week",()=>{let E=this.startOfTodayLocal();return E.setDate(E.getDate()+7),u(E)});let I=f.createEl("button",{cls:"stashpad-due-btn",text:"Clear"});I.onclick=()=>{o.value="",d.value="",o.focus()};let m=null,C=null,B=null;if(this.opts.showRecurrence){let E=t.createEl("details",{cls:"stashpad-due-recur"});(this.opts.currentRepeat||this.opts.currentAutoDoneAfter||this.opts.currentRemindEvery)&&(E.open=!0),E.createEl("summary",{text:"\u{1F501} Repeat & reminders"});let Q=(S,x,H)=>{let G=E.createDiv({cls:"stashpad-due-recur-row"});G.createEl("label",{text:S});let N=G.createEl("input",{type:"text",attr:{placeholder:x}});return H&&(N.value=H),N};m=Q("Repeat",'e.g. "every weekday", "every 30 days when done"',this.opts.currentRepeat),C=Q("Auto-complete after",'e.g. "1d" \u2014 mark done once this overdue',this.opts.currentAutoDoneAfter),B=Q("Remind every",'e.g. "2h" \u2014 re-notify until done',this.opts.currentRemindEvery)}let w=()=>this.opts.showRecurrence?{repeat:m.value.trim(),autoDoneAfter:C.value.trim(),remindEvery:B.value.trim()}:{},y=f.createEl("button",{cls:"stashpad-due-btn",text:"Cancel"});y.onclick=()=>{this.didChoose=!0,this.close()};let v=f.createEl("button",{cls:"stashpad-due-btn mod-cta",text:"Set"});v.onclick=()=>{if(!o.value){this.didChoose=!0,this.close(),this.onPick({iso:null,assignees:this.assignees,...w()});return}let[E,Q,S]=o.value.split("-").map(N=>parseInt(N,10)),x=9,H=0;if(d.value){let[N,R]=d.value.split(":").map(U=>parseInt(U,10));x=N,H=R}let G=new Date(E,Q-1,S,x,H,0,0);this.didChoose=!0,this.close(),this.onPick({iso:G.toISOString(),assignees:this.assignees,...w()})},requestAnimationFrame(()=>o.focus())}onClose(){this.tinyClosePopover?.(),this.contentEl.empty(),this.didChoose}renderAssignSection(e){Lr(e,{knownAuthors:this.opts.knownAuthors??[],initial:this.assignees,onChange:t=>{this.assignees=t}})}openTimeNumpad(e,t){this.tinyClosePopover?.();let i=e.ownerDocument??document,s=i.defaultView??window,n=9,o=0;if(t.value){let[I,m]=t.value.split(":").map(C=>parseInt(C,10));Number.isFinite(I)&&(n=I),Number.isFinite(m)&&(o=m)}else{let I=new Date;n=I.getHours(),o=I.getMinutes()}let a=n>=12?"pm":"am",l=n===0?12:n>12?n-12:n,d=(this.modalEl??i.body).createDiv({cls:"stashpad-when-popover stashpad-due-time-pop"});d.setCssStyles({position:"fixed",zIndex:"9999"});let h=null,g=()=>{d.remove(),i.removeEventListener("mousedown",f,!0),i.removeEventListener("keydown",p,!0),this.tinyClosePopover===g&&(this.tinyClosePopover=null)},f=I=>{!d.contains(I.target)&&I.target!==e&&!e.contains(I.target)&&g()},p=I=>{I.key==="Escape"?(I.preventDefault(),I.stopPropagation(),g()):I.key==="Enter"&&h&&(I.preventDefault(),I.stopPropagation(),h())};this.tinyClosePopover=g,Qs(d,{seedH:l,seedM:o,seedPeriod:a,close:g,setOnEnter:I=>{h=I},onFinalize:I=>{t.value=`${String(I.hours24).padStart(2,"0")}:${String(I.minutes).padStart(2,"0")}`}});let u=e.getBoundingClientRect();d.setCssStyles({left:`${Math.max(8,Math.min(u.left,s.innerWidth-220))}px`,top:`${u.bottom+4}px`}),setTimeout(()=>{i.addEventListener("mousedown",f,!0),i.addEventListener("keydown",p,!0),d.querySelector(".stashpad-when-time-field")?.focus()},0)}startOfTodayLocal(){let e=new Date;return e.setHours(0,0,0,0),e}toDateValue(e){let t=e.getFullYear(),i=String(e.getMonth()+1).padStart(2,"0"),s=String(e.getDate()).padStart(2,"0");return`${t}-${i}-${s}`}toTimeValue(e){let t=String(e.getHours()).padStart(2,"0"),i=String(e.getMinutes()).padStart(2,"0");return`${t}:${i}`}},qt=class qt extends J.Modal{constructor(e,t,i,s=null,n){super(e);this.service=t;this.openLog=i;this.currentAuthorId=s;this.getNoteAuthorIds=n;this.records=[];this.visible=[];this.shownCount=0;this.categoryFilter=null;this.listEl=null;this.footerEl=null;this.countEl=null;this.filterSelEl=null;this.unsubscribe=null;this.authorFilter="all";this.authorSelEl=null}onOpen(){this.contentEl.empty(),this.titleEl.setText("Stashpad notification history"),this.modalEl.addClass("stashpad-log-modal"),this.modalEl.addClass("stashpad-notif-history-modal"),this.records=this.service.recent();let e=this.contentEl.createDiv({cls:"stashpad-log-toolbar"});if(this.countEl=e.createSpan({cls:"stashpad-log-count"}),this.updateCount(),this.filterSelEl=e.createEl("select",{cls:"stashpad-log-type-filter"}),this.filterSelEl.onchange=()=>this.setCategoryFilter(this.filterSelEl.value||null),this.refreshCategoryFilter(),this.authorSelEl=e.createEl("select",{cls:"stashpad-log-type-filter stashpad-notif-author-filter"}),this.authorSelEl.onchange=()=>this.setAuthorFilter(this.authorSelEl.value||"all"),this.refreshAuthorFilter(),this.openLog){let i=e.createEl("button",{text:"Open log"});i.title="Open the per-folder Stashpad log for the most recent notification's folder.",i.onclick=()=>{let s=this.records.find(n=>!!n.folder);this.openLog?.(s?.folder)}}let t=e.createEl("button",{cls:"mod-warning",text:"Clear history"});t.onclick=()=>{new ie(this.app,"Clear notification history?",`This will permanently remove all ${this.records.length} stored notifications from the history. The current toasts on screen are unaffected. This can't be undone.`,"Clear history",i=>{i&&this.service.clearHistory()},"Cancel",!0).open()},this.listEl=this.contentEl.createDiv({cls:"stashpad-log-list"}),this.refreshList(),this.footerEl=this.contentEl.createDiv({cls:"stashpad-log-footer"}),this.renderFooter(),this.unsubscribe=this.service.onChange(()=>{this.records=this.service.recent(),this.refreshCategoryFilter(),this.refreshAuthorFilter(),this.refreshList(),this.renderFooter()})}setAuthorFilter(e){this.authorFilter!==e&&(this.authorFilter=e,this.refreshList(),this.renderFooter())}refreshAuthorFilter(){if(!this.authorSelEl)return;let e=this.authorSelEl;e.empty(),e.createEl("option",{text:"All authors"}).value="all",this.currentAuthorId&&(e.createEl("option",{text:"Me"}).value="me"),e.createEl("option",{text:"Cross-author"}).value="cross";let t=new Set;for(let s of this.records)s.authorId&&s.authorId!==this.currentAuthorId&&t.add(s.authorId);if(t.size>0){let s=e.createEl("option",{text:"\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"});s.disabled=!0;for(let n of[...t].sort())e.createEl("option",{text:n}).value=n}new Set(["all","cross",...this.currentAuthorId?["me"]:[],...t]).has(this.authorFilter)||(this.authorFilter="all"),e.value=this.authorFilter}isCrossAuthor(e){let t=e.authorId??null;if(!t)return!1;for(let i of e.affectedAuthorIds??[])if(i&&i!==t)return!0;if(!this.getNoteAuthorIds)return!1;for(let i of e.affectedIds){let s=this.getNoteAuthorIds(i);for(let n of s)if(n&&n!==t)return!0}return!1}onClose(){this.unsubscribe?.(),this.unsubscribe=null,this.contentEl.empty()}setCategoryFilter(e){(this.categoryFilter??null)!==(e??null)&&(this.categoryFilter=e,this.refreshList(),this.renderFooter())}refreshCategoryFilter(){if(!this.filterSelEl)return;let e=this.filterSelEl;e.empty();let t=new Map;for(let n of this.records)t.set(n.category,(t.get(n.category)??0)+1);let i=[...t.entries()].sort((n,o)=>o[1]-n[1]||n[0].localeCompare(o[0])),s=e.createEl("option",{text:`All categories (${this.records.length})`});s.value="";for(let[n,o]of i){let a=e.createEl("option",{text:`${n} (${o})`});a.value=n}this.categoryFilter&&!t.has(this.categoryFilter)&&(this.categoryFilter=null),e.value=this.categoryFilter??""}refreshList(){if(this.listEl){if(this.visible=this.records.filter(e=>{if(this.categoryFilter&&e.category!==this.categoryFilter)return!1;switch(this.authorFilter){case"all":return!0;case"me":return!!this.currentAuthorId&&e.authorId===this.currentAuthorId;case"cross":return this.isCrossAuthor(e);default:return e.authorId===this.authorFilter}}),this.shownCount=0,this.listEl.empty(),!this.visible.length){this.listEl.createDiv({cls:"stashpad-log-empty",text:this.categoryFilter?`No "${this.categoryFilter}" notifications.`:"No notifications yet."}),this.updateCount();return}this.appendMore(qt.PAGE)}}appendMore(e){if(!this.listEl)return;let t=Math.min(this.visible.length,this.shownCount+e);for(let i=this.shownCount;i0){let c=i.createDiv({cls:"stashpad-notif-actions-snapshot"});for(let d of t.actionLabels){let h=c.createSpan({cls:"stashpad-notif-action-chip"});h.setText(d),h.title="Action button was shown on the original toast (handler not retained)."}}}updateCount(){if(!this.countEl)return;let e=this.visible.length,t=this.categoryFilter?`${e} ${this.categoryFilter} notification${e===1?"":"s"}`:`${e} notification${e===1?"":"s"}`;this.shownCount===0||this.shownCount>=e?this.countEl.setText(t):this.countEl.setText(`Showing ${this.shownCount} of ${t}`)}renderFooter(){if(!this.footerEl)return;this.footerEl.empty();let e=this.visible.length-this.shownCount;if(e<=0)return;let t=this.footerEl.createEl("button",{text:`Load ${Math.min(qt.PAGE,e)} more`});if(t.onclick=()=>{this.appendMore(qt.PAGE),this.renderFooter()},e>qt.PAGE){let i=this.footerEl.createEl("button",{text:`Load all (${e})`});i.onclick=()=>{this.appendMore(e),this.renderFooter()}}}};qt.PAGE=100;qi=qt,ro=class extends J.Modal{constructor(e,t){super(e);this.entries=t}onOpen(){if(this.contentEl.empty(),this.titleEl.setText("Stashpad import log"),this.entries.length===0){this.contentEl.createDiv({cls:"stashpad-log-empty",text:"Nothing imported yet."});return}let e=this.contentEl.createDiv({cls:"stashpad-import-log-list"});for(let t of this.entries){let i=e.createDiv({cls:"stashpad-import-log-row"}),s=(0,J.moment)(t.ts).format("YYYY-MM-DD HH:mm");i.createSpan({cls:"stashpad-import-log-when",text:s});let n=t.kind==="folder"?"folder":t.kind==="md"?"note":"file";i.createSpan({cls:`stashpad-import-log-kind is-${t.kind}`,text:n}),i.createSpan({cls:"stashpad-import-log-name",text:t.originalName});let o=[t.folder.split("/").pop()||t.folder];t.notePaths.length>1&&o.push(`${t.notePaths.length} notes`),i.createSpan({cls:"stashpad-import-log-meta",text:o.join(" \xB7 ")})}}onClose(){this.contentEl.empty()}},Ki=class extends J.Modal{constructor(e,t,i){super(e);this.message=t;this.onChoose=i;this.chose=!1}onOpen(){this.modalEl?.addClass("stashpad-compact-modal"),this.contentEl.empty(),this.titleEl.setText("Possible duplicate import");let e=this.contentEl.createDiv({cls:"stashpad-confirm-body"});for(let o of this.message.split(` +`))e.createDiv({cls:"stashpad-confirm-line",text:o});let t=this.contentEl.createDiv({cls:"stashpad-modal-btns"}),i=t.createEl("button",{text:"Skip duplicates"});i.onclick=()=>{this.chose=!0,this.close(),this.onChoose("skip")};let s=t.createEl("button",{text:"Replace existing"});s.onclick=()=>{this.chose=!0,this.close(),this.onChoose("replace")};let n=t.createEl("button",{cls:"mod-cta",text:"Import anyway"});n.onclick=()=>{this.chose=!0,this.close(),this.onChoose("anyway")}}onClose(){this.chose||this.onChoose("skip"),this.contentEl.empty()}},Ji=class extends J.Modal{constructor(e,t,i,s){super(e);this.noteCount=i;this.onConfirm=s;this.base=t}onOpen(){this.contentEl.empty(),this.modalEl.addClass("stashpad-export-modal"),this.titleEl.setText("Export as OKF"),this.contentEl.createEl("p",{cls:"stashpad-export-desc",text:`Export ${this.noteCount} note${this.noteCount===1?"":"s"} as an Open Knowledge Format bundle. Pick one or more formats.`});let e=this.contentEl.createEl("input",{type:"text"});e.addClass("stashpad-export-name"),e.value=this.base,e.placeholder="Export name";let t=(l,c)=>{let d=this.contentEl.createDiv({cls:"stashpad-okf-fmt"}),h=d.createEl("input",{type:"checkbox"});return h.checked=c,d.createEl("label",{text:l}),h},i=t(".zip \u2014 OKF bundle (portable)",!0),s=t(".tar.gz \u2014 OKF bundle (tarball)",!1),n=t(".stash \u2014 Stashpad format (re-importable)",!1),o=this.contentEl.createDiv({cls:"stashpad-export-footer"});o.createEl("button",{text:"Cancel"}).onclick=()=>this.close();let a=o.createEl("button",{cls:"mod-cta",text:"Export"});a.onclick=()=>{if(!i.checked&&!s.checked&&!n.checked){new J.Notice("Pick at least one format.");return}this.close(),this.onConfirm(e.value.trim()||this.base,{zip:i.checked,targz:s.checked,stash:n.checked})},requestAnimationFrame(()=>e.focus())}onClose(){this.contentEl.empty()}},$i=class extends J.Modal{constructor(e,t,i){super(e);this.items=t;this.onConfirm=i}onOpen(){this.titleEl.setText("Re-encrypt everything applicable?");let{contentEl:e}=this;e.createEl("p",{cls:"setting-item-description",text:"These notes should be encrypted (they were unlocked/restored, or their folder is set to encrypt) but are currently plaintext. Untick anything you want to leave readable."});let t=e.createDiv({cls:"stashpad-reenc-list"}),i=[];this.items.forEach(o=>{let a=t.createEl("label",{cls:"stashpad-reenc-row"}),l=a.createEl("input",{type:"checkbox"});l.checked=!0,i.push(l);let c=a.createDiv({cls:"stashpad-reenc-body"});c.createDiv({cls:"stashpad-reenc-label",text:o.label}),c.createDiv({cls:"stashpad-reenc-detail",text:o.detail})});let s=e.createDiv({cls:"modal-button-container"}),n=s.createEl("button",{cls:"mod-cta",text:"Re-encrypt checked"});n.onclick=()=>{let o=i.map((a,l)=>a.checked?l:-1).filter(a=>a>=0);this.close(),o.length&&this.onConfirm(o)},s.createEl("button",{text:"Cancel"}).onclick=()=>this.close()}onClose(){this.contentEl.empty()}}});function De(A,r=je){let e=r instanceof Set?r:new Set(r.map(o=>o.toLowerCase())),t=(A.split(/\r?\n/).find(o=>o.trim().length>0)??"").trim();if(!t)return"Untitled";let s=t.replace(/['‘-‛ʼ]/g,"").replace(/[^A-Za-z0-9\s]+/g," ").split(/\s+/).filter(o=>o&&!e.has(o.toLowerCase())).map(o=>o.length>=2&&/^[A-Z0-9]+$/.test(o)?o:o.charAt(0).toUpperCase()+o.slice(1).toLowerCase()),n="";for(let o of s){let a=n?`${n}-${o}`:o;if(a.length>50)break;n=a}return n||"Untitled"}function xe(A,r){return`${A}-${r}.md`}function wt(A){let r=A.match(/-([abcdefghijkmnpqrstuvwxyz23456789]{6})$/);return r?r[1]:null}function Ns(A,r){let e=A.lastIndexOf(".");return e>0?`${A.slice(0,e)}-${r}${A.slice(e)}`:`${A}-${r}`}function qr(A){let r=A.match(/^([0-9a-z]{7,9})-(.+)$/);if(!r)return null;let e=parseInt(r[1],36);return!Number.isFinite(e)||e<142e10||e>Date.now()+864e5?null:{stamp:r[1],rest:r[2]}}var je,ri=$e(()=>{"use strict";je=["a","an","the","and","or","but","if","then","else","of","in","on","at","to","for","with","by","from","as","is","are","was","were","be","been","being","i","you","he","she","it","we","they","this","that","these","those","my","your","our","their","do","does","did","so","just","very","really","im"]});var Ps={};ki(Ps,{JdBuildConfirmModal:()=>Wi,buildJdIndexNotes:()=>Ao,buildJdIndexPreview:()=>Ms,buildJdPreviewNotice:()=>Ts,scanForJdNotes:()=>ai});function co(A,r){if(/^\d+-\d+$/.test(A))return null;if(A.includes(".")){let t=A.split(".").slice(0,-1).join(".");return r.has(t)?t:co(t,r)}if(/^\d+$/.test(A)){let e=parseInt(A,10);for(let t of r){let i=t.match(/^(\d+)-(\d+)$/);if(!i)continue;let s=parseInt(i[1],10),n=parseInt(i[2],10);if(e>=s&&e<=n)return t}return null}return null}function ai(A,r,e){let t=A.vault.getMarkdownFiles(),i=Zl(t,e),s=e.jdIndexIncludeStashpadFolders===!0,n=s?new Set:new Set(r.discoverStashpadFolders()),o=f=>s?!1:Array.from(n).some(p=>f===p||f.startsWith(p+"/")),a=[],l=[],c=[];for(let f of i){let p=f.parent?.path?.replace(/\/+$/,"")??"";if(o(p)){c.push(f);continue}let u=f.basename.match(Kr);u?a.push({prefix:u[1],segments:u[1].split("."),title:u[2],file:f,folder:null}):l.push(f)}let d=(e.jdIndexScope??"vault")==="folder"?(e.jdIndexScopeFolder??"").trim().replace(/^\/+|\/+$/g,""):"",h=f=>{let p=f.path.replace(/\/+$/,"");if(!(d&&p&&p!==d&&!p.startsWith(d+"/"))){if(p&&!o(p)){let u=f.name.match(Kr);u&&a.push({prefix:u[1],segments:u[1].split("."),title:u[2],file:null,folder:f})}}for(let u of f.children)u instanceof Ie.TFolder&&h(u)};h(A.vault.getRoot());let g=e.jdIndexSort??"natural";return a.sort((f,p)=>jl(f,p,g)),{indexed:a,nonIndex:l,skippedStashpadNotes:c}}async function Ms(A,r,e){let t=ho(e.jdIndexStashpadFolder??"");if(!t)return{...ai(A,r,e),previewPath:null,error:"no-dest"};let i=ai(A,r,e),s=Xl(A,t);if(!s)return{...i,previewPath:null,error:"no-home"};let n=_l(i.indexed,i.nonIndex,i.skippedStashpadNotes),a=(await A.vault.read(s)).match(/^---\r?\n[\s\S]*?\r?\n---\r?\n/),l=a?`${a[0]}${n}`:n;return await A.vault.modify(s,l),{...i,previewPath:s.path}}function Xl(A,r){let e=A.vault.getAbstractFileByPath(r);if(!(e instanceof Ie.TFolder))return null;for(let t of e.children){if(!(t instanceof Ie.TFile)||t.extension!=="md")continue;if((A.metadataCache.getFileCache(t)?.frontmatter??{}).id===F)return t}return null}async function Ao(A,r,e){let t=ho(e.jdIndexStashpadFolder??"");if(!t)return{created:0,updated:0,skipped:0,destFolder:"",error:"no-dest"};if(!new Set(r.discoverStashpadFolders()).has(t))return{created:0,updated:0,skipped:0,destFolder:t,error:"dest-not-stashpad"};let s=ai(A,r,e),n=new Map;for(let I of s.indexed)n.set(I.prefix,I);for(let I of s.indexed)if(I.prefix.includes("."))for(let m=1;m{l.push(I);for(let m of a.get(I)??[])c(m)};for(let I of o)c(I);let d=new Map,h=A.vault.getAbstractFileByPath(t);if(h instanceof Ie.TFolder)for(let I of h.children){if(!(I instanceof Ie.TFile)||I.extension!=="md")continue;let m=A.metadataCache.getFileCache(I)?.frontmatter??{};typeof m.jdPrefix=="string"&&d.set(m.jdPrefix,I)}let g=new Map,f=0,p=0,u=0;for(let I of l){let m=n.get(I)??null,C=co(I,new Set(n.keys())),B=C?g.get(C)??F:F,w=m?`${I} ${m.title}`:I,y=m?.file?`[[${m.file.basename}]]`:"",v=y?`${y} `:`${w} -`,v=d.get(I);if(v){try{let Q=null;await A.fileManager.processFrontMatter(v,G=>{G.jdPrefix=I,B&&(G.parent=B),(!G.id||typeof G.id!="string")&&(G.id=Z()),G.created||(G.created=new Date().toISOString()),G.attachments||(G.attachments=[]),Q=G.id});let x=(await A.vault.read(v)).match(/^---\r?\n[\s\S]*?\r?\n---\r?\n/),H=x?`${x[0]}${E}`:E;await A.vault.modify(v,H),Q&&g.set(I,Q),p++}catch(Q){console.error("[stashpad] buildJdIndexNotes: update failed",Q),f++}continue}try{let Q=Z();g.set(I,Q);let S=be(w,e.slugStopWords),x=De(S,Q),H=`${t}/${x}`,G=["---",`id: ${Q}`,`parent: ${B}`,`created: "${new Date().toISOString()}"`,"attachments: []",`jdPrefix: "${I}"`,"---",""].join(` -`);await A.vault.create(H,G+E),u++}catch(Q){console.error("[stashpad] buildJdIndexNotes: create failed for",I,Q),f++}}return{created:u,updated:p,skipped:f,destFolder:t}}function Zn(A){return(A||"").trim().replace(/^\/+|\/+$/g,"")}function Nl(A,r){if((r.jdIndexScope??"vault")==="vault")return A;let e=Zn(r.jdIndexScopeFolder??"");return e?A.filter(t=>{let i=t.parent?.path?.replace(/\/+$/,"")??"";return i===e||i.startsWith(e+"/")}):A}function Ml(A,r,e){if(e==="created"){let t=A.file?.stat?.ctime??0,i=r.file?.stat?.ctime??0;if(t!==i)return t-i}return Da(A.segments,r.segments)}function ba(A,r){let e=A.match(/^(\d+)-(\d+)$/),t=r.match(/^(\d+)-(\d+)$/),i=e?[e[1]]:A.split("."),s=t?[t[1]]:r.split(".");return Da(i,s)}function Da(A,r){let e=Math.min(A.length,r.length);for(let t=0;t{s.set(o,a);for(let l of t.get(o)??[])n(l,a+1)};for(let o of i)n(o,0);return{roots:i,childrenOf:t,depthOf:s}}function Tl(A,r,e){let t=[],i=s=>"#".repeat(Math.min(s+1,6));if(t.push("# Indexed"),t.push(""),A.length===0){let s=e.length>0?`_No notes matched. ${e.length} note${e.length===1?" was":"s were"} excluded because they live inside a Stashpad folder \u2014 toggle "Include Stashpad folders in scan" in Settings \u2192 JD Index Builder if you want them included._`:"_No notes matched the JD-prefix convention. Check Scope and Designated folder in Settings \u2192 JD Index Builder._";t.push(s)}else{let s=new Map;for(let c of A)s.set(c.prefix,c);for(let c of A)if(c.prefix.includes("."))for(let d=1;d{let d=s.get(c),h=a.get(c)??0,g=" ".repeat(h),u=i(h),p=d?.folder?` ${d.title} _(folder)_`:(d?.file,"");d?.file?t.push(`${g}- ${u} [[${d.file.basename}|${d.prefix} ${d.title}]]`):d?.folder?t.push(`${g}- ${u} ${d.prefix}${p}`):t.push(`${g}- ${u} ${c}`);for(let f of o.get(c)??[])l(f)};for(let c of n)l(c)}if(r.length>0){t.push(""),t.push(`# Non-indexed (${r.length})`),t.push(""),t.push("_These notes don't match the JD-prefix convention. Rename them to ` ` and re-run to include them._"),t.push("");for(let s of r)t.push(`- # [[${s.basename}]] \xB7 \`${s.path}\``)}if(e.length>0){let s=new Map;for(let o of e){let a=o.parent?.path?.replace(/\/+$/,"")??"";s.set(a,(s.get(a)??0)+1)}let n=Array.from(s.entries()).sort((o,a)=>o[0].localeCompare(a[0]));t.push(""),t.push(`# Excluded folders (${n.length})`),t.push(""),t.push(`_Stashpad folders are excluded by default so the index doesn't reference itself. Toggle "Include Stashpad folders in scan" in Settings \u2192 JD Index Builder to include them._`),t.push("");for(let[o,a]of n)t.push(`- # All ${a} file${a===1?"":"s"} in \`${o||"(vault root)"}\``)}return t.join(` +`,E=d.get(I);if(E){try{let Q=null;await A.fileManager.processFrontMatter(E,G=>{G.jdPrefix=I,B&&(G.parent=B),(!G.id||typeof G.id!="string")&&(G.id=Z()),G.created||(G.created=new Date().toISOString()),G.attachments||(G.attachments=[]),Q=G.id});let x=(await A.vault.read(E)).match(/^---\r?\n[\s\S]*?\r?\n---\r?\n/),H=x?`${x[0]}${v}`:v;await A.vault.modify(E,H),Q&&g.set(I,Q),p++}catch(Q){console.error("[stashpad] buildJdIndexNotes: update failed",Q),u++}continue}try{let Q=Z();g.set(I,Q);let S=De(w,e.slugStopWords),x=xe(S,Q),H=`${t}/${x}`,G=["---",`id: ${Q}`,`parent: ${B}`,`created: "${new Date().toISOString()}"`,"attachments: []",`jdPrefix: "${I}"`,"---",""].join(` +`);await A.vault.create(H,G+v),f++}catch(Q){console.error("[stashpad] buildJdIndexNotes: create failed for",I,Q),u++}}return{created:f,updated:p,skipped:u,destFolder:t}}function ho(A){return(A||"").trim().replace(/^\/+|\/+$/g,"")}function Zl(A,r){if((r.jdIndexScope??"vault")==="vault")return A;let e=ho(r.jdIndexScopeFolder??"");return e?A.filter(t=>{let i=t.parent?.path?.replace(/\/+$/,"")??"";return i===e||i.startsWith(e+"/")}):A}function jl(A,r,e){if(e==="created"){let t=A.file?.stat?.ctime??0,i=r.file?.stat?.ctime??0;if(t!==i)return t-i}return $r(A.segments,r.segments)}function Jr(A,r){let e=A.match(/^(\d+)-(\d+)$/),t=r.match(/^(\d+)-(\d+)$/),i=e?[e[1]]:A.split("."),s=t?[t[1]]:r.split(".");return $r(i,s)}function $r(A,r){let e=Math.min(A.length,r.length);for(let t=0;t<e;t++){let i=A[t],s=r[t],n=/^\d+$/.test(i),o=/^\d+$/.test(s);if(n&&o){let a=parseInt(i,10)-parseInt(s,10);if(a!==0)return a}else{if(n&&!o)return-1;if(!n&&o)return 1;{let a=i.localeCompare(s,void 0,{numeric:!0,sensitivity:"base"});if(a!==0)return a}}}return A.length-r.length}function Yr(A){let r=new Set(A.keys()),e=new Map,t=new Map;for(let o of r){let a=co(o,r);if(e.set(o,a),a!==null){let l=t.get(a)??[];l.push(o),t.set(a,l)}}let i=[];for(let o of r)e.get(o)===null&&i.push(o);i.sort(Jr);for(let o of t.values())o.sort(Jr);let s=new Map,n=(o,a)=>{s.set(o,a);for(let l of t.get(o)??[])n(l,a+1)};for(let o of i)n(o,0);return{roots:i,childrenOf:t,depthOf:s}}function _l(A,r,e){let t=[],i=s=>"#".repeat(Math.min(s+1,6));if(t.push("# Indexed"),t.push(""),A.length===0){let s=e.length>0?`_No notes matched. ${e.length} note${e.length===1?" was":"s were"} excluded because they live inside a Stashpad folder \u2014 toggle "Include Stashpad folders in scan" in Settings \u2192 JD Index Builder if you want them included._`:"_No notes matched the JD-prefix convention. Check Scope and Designated folder in Settings \u2192 JD Index Builder._";t.push(s)}else{let s=new Map;for(let c of A)s.set(c.prefix,c);for(let c of A)if(c.prefix.includes("."))for(let d=1;d<c.segments.length;d++){let h=c.segments.slice(0,d).join(".");s.has(h)||s.set(h,null)}let{roots:n,childrenOf:o,depthOf:a}=Yr(s),l=c=>{let d=s.get(c),h=a.get(c)??0,g=" ".repeat(h),f=i(h),p=d?.folder?` ${d.title} _(folder)_`:(d?.file,"");d?.file?t.push(`${g}- ${f} [[${d.file.basename}|${d.prefix} ${d.title}]]`):d?.folder?t.push(`${g}- ${f} ${d.prefix}${p}`):t.push(`${g}- ${f} ${c}`);for(let u of o.get(c)??[])l(u)};for(let c of n)l(c)}if(r.length>0){t.push(""),t.push(`# Non-indexed (${r.length})`),t.push(""),t.push("_These notes don't match the JD-prefix convention. Rename them to `<prefix> <title>` and re-run to include them._"),t.push("");for(let s of r)t.push(`- # [[${s.basename}]] \xB7 \`${s.path}\``)}if(e.length>0){let s=new Map;for(let o of e){let a=o.parent?.path?.replace(/\/+$/,"")??"";s.set(a,(s.get(a)??0)+1)}let n=Array.from(s.entries()).sort((o,a)=>o[0].localeCompare(a[0]));t.push(""),t.push(`# Excluded folders (${n.length})`),t.push(""),t.push(`_Stashpad folders are excluded by default so the index doesn't reference itself. Toggle "Include Stashpad folders in scan" in Settings \u2192 JD Index Builder to include them._`),t.push("");for(let[o,a]of n)t.push(`- # All ${a} file${a===1?"":"s"} in \`${o||"(vault root)"}\``)}return t.join(` `)+` -`}function Es(A,r){let e=document.createDocumentFragment();e.createEl("div",{text:"Home note updated"}).setCssStyles({fontWeight:"600",marginBottom:"6px"});let i=e.createEl("ul");i.setCssStyles({margin:"0 0 8px 0",paddingLeft:"18px"}),i.createEl("li",{text:`${r.indexed.length} indexed`}),i.createEl("li",{text:`${r.nonIndex.length+r.skippedStashpadNotes.length} excluded`});let s=e.createEl("button",{text:"Open",cls:"mod-cta"});s.setCssStyles({padding:"2px 10px",fontSize:"var(--font-ui-smaller)"});let n;return s.onclick=async o=>{if(o.stopPropagation(),r.previewPath){let a=A.vault.getAbstractFileByPath(r.previewPath);a instanceof fe.TFile&&await A.workspace.getLeaf("tab").openFile(a)}n?.hide()},n=new fe.Notice(e,0),n}var fe,Sa,Oi,Yi=$e(()=>{"use strict";fe=require("obsidian");qe();si();le();Sa=/^(\d+-\d+|\d+|[A-Za-z0-9]+(?:\.[A-Za-z0-9]+)+)\s+(.+)$/;Oi=class extends fe.Modal{constructor(e,t,i,s,n){super(e);this.plugin=t;this.settings=i;this.indexedCount=s;this.onBuild=n;this.LARGE_BUILD=50;this.previewRan=!1;this.previewPath=null}onOpen(){let{contentEl:e,titleEl:t}=this,i=!this.settings.jdIndexHasBuilt,s=this.indexedCount>this.LARGE_BUILD;t.setText(i?"Build JD index \u2014 first time?":"Build JD index");let n=this.settings.jdIndexStashpadFolder,o=e.createEl("p"),a=`${this.indexedCount} note${this.indexedCount===1?"":"s"}`;i?(o.appendText(`Stashpad is about to create ${a} (plus synthetic parents as needed) inside "`),o.createEl("code",{text:n}),o.appendText('". Existing notes with the same '),o.createEl("code",{text:"jdPrefix"}),o.appendText(" are updated, never deleted \u2014 but if the prefix detection picks up notes you didn't mean to index, you'll end up with a lot of unwanted notes."),e.createEl("p").setText("Running Preview first writes a single Markdown file showing exactly what would be built (and what wouldn't), so you can sanity-check before committing.")):s?(o.appendText(`You're about to create ${this.indexedCount} notes in "`),o.createEl("code",{text:n}),o.appendText(`". That's a big batch \u2014 if anything looks off, Preview the single-file output first.`)):(o.appendText('Build the JD index into "'),o.createEl("code",{text:n}),o.appendText(`"? Stashpad will create / update ${a}. Existing notes with matching `),o.createEl("code",{text:"jdPrefix"}),o.appendText(" are updated in place.")),this.previewRan&&this.previewPath&&e.createEl("p",{cls:"setting-item-description"}).setText(`\u2713 Preview written to home note (${this.previewPath}). Open it before building if you haven't.`);let l=new fe.Setting(e);(i||s)&&l.addButton(c=>{c.setButtonText(this.previewRan?"Re-run preview":"Run preview first"),c.setCta(),c.onClick(async()=>{try{let d=await Qs(this.app,this.plugin,this.settings);if(d.error==="no-dest"){new fe.Notice("Set a Designated Stashpad folder for Index first.",5e3),this.close();return}if(d.error==="no-home"){new fe.Notice(`"${this.settings.jdIndexStashpadFolder}" has no Stashpad home note. Open the folder in Stashpad first.`,7e3),this.close();return}this.previewRan=!0,this.previewPath=d.previewPath,Es(this.app,d),this.contentEl.empty(),this.titleEl.empty(),this.onOpen()}catch(d){new fe.Notice(`Preview failed: ${d?.message??d}`,8e3)}})}),l.addButton(c=>{c.setButtonText(i?"Build anyway":"Build"),!i&&!s&&c.setCta(),c.onClick(async()=>{this.close(),await this.onBuild()})}),l.addButton(c=>{c.setButtonText("Cancel"),c.onClick(()=>this.close())})}onClose(){this.contentEl.empty()}}});function Pc(A,r){return Tc(A,r||{},0,0)}function Hc(A,r){return bc(A,{i:2},r&&r.out,r&&r.dictionary)}function _s(A,r){if(r){for(var e=new Ae(A.length),t=0;t<A.length;++t)e[t]=A.charCodeAt(t);return e}if(fr)return fr.encode(A);for(var i=A.length,s=new Ae(A.length+(A.length>>1)),n=0,o=function(c){s[n++]=c},t=0;t<i;++t){if(n+5>s.length){var a=new Ae(n+8+(i-t<<1));a.set(s),s=a}var l=A.charCodeAt(t);l<128||r?o(l):l<2048?(o(192|l>>6),o(128|l&63)):l>55295&&l<57344?(l=65536+(l&1047552)|A.charCodeAt(++t)&1023,o(240|l>>18),o(128|l>>12&63),o(128|l>>6&63),o(128|l&63)):(o(224|l>>12),o(128|l>>6&63),o(128|l&63))}return ns(s,0,n)}function qo(A,r){if(r){for(var e="",t=0;t<A.length;t+=16384)e+=String.fromCharCode.apply(null,A.subarray(t,t+16384));return e}else{if(Uo)return Uo.decode(A);var i=Rc(A),s=i.s,e=i.r;return e.length&&we(8),s}}function Fr(A,r){r||(r={});var e={},t=[];vr(A,"",e,r);var i=0,s=0;for(var n in e){var o=e[n],a=o[0],l=o[1],c=l.level==0?0:8,d=_s(n),h=d.length,g=l.comment,u=g&&_s(g),p=u&&u.length,f=Ro(l.extra);h>65535&&we(11);var I=c?Pc(a,l):a,m=I.length,C=Mc();C.p(a),t.push(Er(l,{size:a.length,crc:C.d(),c:I,f:d,m:u,u:h!=n.length||u&&g.length!=p,o:i,compression:c})),i+=30+h+f+m,s+=76+2*(h+f)+(p||0)+m}for(var B=new Ae(s+22),w=i,y=s-i,E=0;E<t.length;++E){var d=t[E];Ir(B,d.o,d,d.f,d.u,d.c.length);var v=30+d.f.length+Ro(d.extra);B.set(d.c,d.o+v),Ir(B,i,d,d.f,d.u,d.c.length,d.o,d.m),i+=16+v+(d.m?d.m.length:0)}return Kc(B,i,t.length,y,w),B}function kr(A,r){for(var e={},t=A.length-22;Re(A,t)!=101010256;--t)(!t||A.length-t>65558)&&we(13);var i=it(A,t+8);if(!i)return{};var s=Re(A,t+16),n=Re(A,t-20)==117853008;if(n){var o=Re(A,t-12);n=Re(A,o)==101075792,n&&(i=Re(A,o+32),s=Re(A,o+48))}for(var a=r&&r.filter,l=0;l<i;++l){var c=Lc(A,s,n),d=c[0],h=c[1],g=c[2],u=c[3],p=c[4],f=c[5],I=Gc(A,f);s=p,(!a||a({name:u,size:h,originalSize:g,compression:d}))&&(d?d==8?e[u]=Hc(A.subarray(I,I+h),{out:new Ae(g)}):we(14,"unknown compression type "+d):e[u]=ns(A,I,I+h))}return e}var Ae,Me,Go,en,tn,Mo,mr,Cr,Br,To,yr,Qc,pr,Po,gt,j,st,Ft,j,j,j,j,ss,j,Ec,vc,Fc,kc,bo,Ye,Do,Lo,ns,Sc,we,bc,ut,ts,xo,Ho,gr,is,wr,ur,Dc,Qr,xc,Nc,Mc,Tc,Er,it,Re,No,Ie,vr,fr,Uo,Uc,Rc,Gc,Lc,qc,Ro,Ir,Kc,Sr=$e(()=>{Ae=Uint8Array,Me=Uint16Array,Go=Int32Array,en=new Ae([0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0,0]),tn=new Ae([0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,0,0]),Mo=new Ae([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]),mr=function(A,r){for(var e=new Me(31),t=0;t<31;++t)e[t]=r+=1<<A[t-1];for(var i=new Go(e[30]),t=1;t<30;++t)for(var s=e[t];s<e[t+1];++s)i[s]=s-e[t]<<5|t;return{b:e,r:i}},Cr=mr(en,2),Br=Cr.b,To=Cr.r;Br[28]=258,To[258]=28;yr=mr(tn,0),Qc=yr.b,pr=yr.r,Po=new Me(32768);for(j=0;j<32768;++j)gt=(j&43690)>>1|(j&21845)<<1,gt=(gt&52428)>>2|(gt&13107)<<2,gt=(gt&61680)>>4|(gt&3855)<<4,Po[j]=((gt&65280)>>8|(gt&255)<<8)>>1;st=function(A,r,e){for(var t=A.length,i=0,s=new Me(r);i<t;++i)A[i]&&++s[A[i]-1];var n=new Me(r);for(i=1;i<r;++i)n[i]=n[i-1]+s[i-1]<<1;var o;if(e){o=new Me(1<<r);var a=15-r;for(i=0;i<t;++i)if(A[i])for(var l=i<<4|A[i],c=r-A[i],d=n[A[i]-1]++<<c,h=d|(1<<c)-1;d<=h;++d)o[Po[d]>>a]=l}else for(o=new Me(t),i=0;i<t;++i)A[i]&&(o[i]=Po[n[A[i]-1]++]>>15-A[i]);return o},Ft=new Ae(288);for(j=0;j<144;++j)Ft[j]=8;for(j=144;j<256;++j)Ft[j]=9;for(j=256;j<280;++j)Ft[j]=7;for(j=280;j<288;++j)Ft[j]=8;ss=new Ae(32);for(j=0;j<32;++j)ss[j]=5;Ec=st(Ft,9,0),vc=st(Ft,9,1),Fc=st(ss,5,0),kc=st(ss,5,1),bo=function(A){for(var r=A[0],e=1;e<A.length;++e)A[e]>r&&(r=A[e]);return r},Ye=function(A,r,e){var t=r/8|0;return(A[t]|A[t+1]<<8)>>(r&7)&e},Do=function(A,r){var e=r/8|0;return(A[e]|A[e+1]<<8|A[e+2]<<16)>>(r&7)},Lo=function(A){return(A+7)/8|0},ns=function(A,r,e){return(r==null||r<0)&&(r=0),(e==null||e>A.length)&&(e=A.length),new Ae(A.subarray(r,e))},Sc=["unexpected EOF","invalid block type","invalid length/literal","invalid distance","stream finished","no stream handler",,"no callback","invalid UTF-8 data","extra field too long","date not in range 1980-2099","filename too long","stream finishing","invalid zip data"],we=function(A,r,e){var t=new Error(r||Sc[A]);if(t.code=A,Error.captureStackTrace&&Error.captureStackTrace(t,we),!e)throw t;return t},bc=function(A,r,e,t){var i=A.length,s=t?t.length:0;if(!i||r.f&&!r.l)return e||new Ae(0);var n=!e,o=n||r.i!=2,a=r.i;n&&(e=new Ae(i*3));var l=function(pe){var He=e.length;if(pe>He){var ne=new Ae(Math.max(He*2,pe));ne.set(e),e=ne}},c=r.f||0,d=r.p||0,h=r.b||0,g=r.l,u=r.d,p=r.m,f=r.n,I=i*8;do{if(!g){c=Ye(A,d,1);var m=Ye(A,d+1,3);if(d+=3,m)if(m==1)g=vc,u=kc,p=9,f=5;else if(m==2){var y=Ye(A,d,31)+257,E=Ye(A,d+10,15)+4,v=y+Ye(A,d+5,31)+1;d+=14;for(var Q=new Ae(v),S=new Ae(19),x=0;x<E;++x)S[Mo[x]]=Ye(A,d+x*3,7);d+=E*3;for(var H=bo(S),G=(1<<H)-1,N=st(S,H,1),x=0;x<v;){var U=N[Ye(A,d,G)];d+=U&15;var C=U>>4;if(C<16)Q[x++]=C;else{var R=0,D=0;for(C==16?(D=3+Ye(A,d,3),d+=2,R=Q[x-1]):C==17?(D=3+Ye(A,d,7),d+=3):C==18&&(D=11+Ye(A,d,127),d+=7);D--;)Q[x++]=R}}var b=Q.subarray(0,y),P=Q.subarray(y);p=bo(b),f=bo(P),g=st(b,p,1),u=st(P,f,1)}else we(1);else{var C=Lo(d)+4,B=A[C-4]|A[C-3]<<8,w=C+B;if(w>i){a&&we(0);break}o&&l(h+B),e.set(A.subarray(C,w),h),r.b=h+=B,r.p=d=w*8,r.f=c;continue}if(d>I){a&&we(0);break}}o&&l(h+131072);for(var q=(1<<p)-1,L=(1<<f)-1,K=d;;K=d){var R=g[Do(A,d)&q],W=R>>4;if(d+=R&15,d>I){a&&we(0);break}if(R||we(2),W<256)e[h++]=W;else if(W==256){K=d,g=null;break}else{var $=W-254;if(W>264){var x=W-257,Y=en[x];$=Ye(A,d,(1<<Y)-1)+Br[x],d+=Y}var _=u[Do(A,d)&L],ce=_>>4;_||we(3),d+=_&15;var P=Qc[ce];if(ce>3){var Y=tn[ce];P+=Do(A,d)&(1<<Y)-1,d+=Y}if(d>I){a&&we(0);break}o&&l(h+131072);var ae=h+$;if(h<P){var se=s-P,te=Math.min(P,ae);for(se+h<0&&we(3);h<te;++h)e[h]=t[se+h]}for(;h<ae;++h)e[h]=e[h-P]}}r.l=g,r.p=K,r.b=h,r.f=c,g&&(c=1,r.m=p,r.d=u,r.n=f)}while(!c);return h!=e.length&&n?ns(e,0,h):e.subarray(0,h)},ut=function(A,r,e){e<<=r&7;var t=r/8|0;A[t]|=e,A[t+1]|=e>>8},ts=function(A,r,e){e<<=r&7;var t=r/8|0;A[t]|=e,A[t+1]|=e>>8,A[t+2]|=e>>16},xo=function(A,r){for(var e=[],t=0;t<A.length;++t)A[t]&&e.push({s:t,f:A[t]});var i=e.length,s=e.slice();if(!i)return{t:Qr,l:0};if(i==1){var n=new Ae(e[0].s+1);return n[e[0].s]=1,{t:n,l:1}}e.sort(function(w,y){return w.f-y.f}),e.push({s:-1,f:25001});var o=e[0],a=e[1],l=0,c=1,d=2;for(e[0]={s:-1,f:o.f+a.f,l:o,r:a};c!=i-1;)o=e[e[l].f<e[d].f?l++:d++],a=e[l!=c&&e[l].f<e[d].f?l++:d++],e[c++]={s:-1,f:o.f+a.f,l:o,r:a};for(var h=s[0].s,t=1;t<i;++t)s[t].s>h&&(h=s[t].s);var g=new Me(h+1),u=Ho(e[c-1],g,0);if(u>r){var t=0,p=0,f=u-r,I=1<<f;for(s.sort(function(y,E){return g[E.s]-g[y.s]||y.f-E.f});t<i;++t){var m=s[t].s;if(g[m]>r)p+=I-(1<<u-g[m]),g[m]=r;else break}for(p>>=f;p>0;){var C=s[t].s;g[C]<r?p-=1<<r-g[C]++-1:++t}for(;t>=0&&p;--t){var B=s[t].s;g[B]==r&&(--g[B],++p)}u=r}return{t:new Ae(g),l:u}},Ho=function(A,r,e){return A.s==-1?Math.max(Ho(A.l,r,e+1),Ho(A.r,r,e+1)):r[A.s]=e},gr=function(A){for(var r=A.length;r&&!A[--r];);for(var e=new Me(++r),t=0,i=A[0],s=1,n=function(a){e[t++]=a},o=1;o<=r;++o)if(A[o]==i&&o!=r)++s;else{if(!i&&s>2){for(;s>138;s-=138)n(32754);s>2&&(n(s>10?s-11<<5|28690:s-3<<5|12305),s=0)}else if(s>3){for(n(i),--s;s>6;s-=6)n(8304);s>2&&(n(s-3<<5|8208),s=0)}for(;s--;)n(i);s=1,i=A[o]}return{c:e.subarray(0,t),n:r}},is=function(A,r){for(var e=0,t=0;t<r.length;++t)e+=A[t]*r[t];return e},wr=function(A,r,e){var t=e.length,i=Lo(r+2);A[i]=t&255,A[i+1]=t>>8,A[i+2]=A[i]^255,A[i+3]=A[i+1]^255;for(var s=0;s<t;++s)A[i+s+4]=e[s];return(i+4+t)*8},ur=function(A,r,e,t,i,s,n,o,a,l,c){ut(r,c++,e),++i[256];for(var d=xo(i,15),h=d.t,g=d.l,u=xo(s,15),p=u.t,f=u.l,I=gr(h),m=I.c,C=I.n,B=gr(p),w=B.c,y=B.n,E=new Me(19),v=0;v<m.length;++v)++E[m[v]&31];for(var v=0;v<w.length;++v)++E[w[v]&31];for(var Q=xo(E,7),S=Q.t,x=Q.l,H=19;H>4&&!S[Mo[H-1]];--H);var G=l+5<<3,N=is(i,Ft)+is(s,ss)+n,U=is(i,h)+is(s,p)+n+14+3*H+is(E,S)+2*E[16]+3*E[17]+7*E[18];if(a>=0&&G<=N&&G<=U)return wr(r,c,A.subarray(a,a+l));var R,D,b,P;if(ut(r,c,1+(U<N)),c+=2,U<N){R=st(h,g,0),D=h,b=st(p,f,0),P=p;var q=st(S,x,0);ut(r,c,C-257),ut(r,c+5,y-1),ut(r,c+10,H-4),c+=14;for(var v=0;v<H;++v)ut(r,c+3*v,S[Mo[v]]);c+=3*H;for(var L=[m,w],K=0;K<2;++K)for(var W=L[K],v=0;v<W.length;++v){var $=W[v]&31;ut(r,c,q[$]),c+=S[$],$>15&&(ut(r,c,W[v]>>5&127),c+=W[v]>>12)}}else R=Ec,D=Ft,b=Fc,P=ss;for(var v=0;v<o;++v){var Y=t[v];if(Y>255){var $=Y>>18&31;ts(r,c,R[$+257]),c+=D[$+257],$>7&&(ut(r,c,Y>>23&31),c+=en[$]);var _=Y&31;ts(r,c,b[_]),c+=P[_],_>3&&(ts(r,c,Y>>5&8191),c+=tn[_])}else ts(r,c,R[Y]),c+=D[Y]}return ts(r,c,R[256]),c+D[256]},Dc=new Go([65540,131080,131088,131104,262176,1048704,1048832,2114560,2117632]),Qr=new Ae(0),xc=function(A,r,e,t,i,s){var n=s.z||A.length,o=new Ae(t+n+5*(1+Math.ceil(n/7e3))+i),a=o.subarray(t,o.length-i),l=s.l,c=(s.r||0)&7;if(r){c&&(a[0]=s.r>>3);for(var d=Dc[r-1],h=d>>13,g=d&8191,u=(1<<e)-1,p=s.p||new Me(32768),f=s.h||new Me(u+1),I=Math.ceil(e/3),m=2*I,C=function(Ee){return(A[Ee]^A[Ee+1]<<I^A[Ee+2]<<m)&u},B=new Go(25e3),w=new Me(288),y=new Me(32),E=0,v=0,Q=s.i||0,S=0,x=s.w||0,H=0;Q+2<n;++Q){var G=C(Q),N=Q&32767,U=f[G];if(p[N]=U,f[G]=N,x<=Q){var R=n-Q;if((E>7e3||S>24576)&&(R>423||!l)){c=ur(A,a,0,B,w,y,v,S,H,Q-H,c),S=E=v=0,H=Q;for(var D=0;D<286;++D)w[D]=0;for(var D=0;D<30;++D)y[D]=0}var b=2,P=0,q=g,L=N-U&32767;if(R>2&&G==C(Q-L))for(var K=Math.min(h,R)-1,W=Math.min(32767,Q),$=Math.min(258,R);L<=W&&--q&&N!=U;){if(A[Q+b]==A[Q+b-L]){for(var Y=0;Y<$&&A[Q+Y]==A[Q+Y-L];++Y);if(Y>b){if(b=Y,P=L,Y>K)break;for(var _=Math.min(L,Y-2),ce=0,D=0;D<_;++D){var ae=Q-L+D&32767,se=p[ae],te=ae-se&32767;te>ce&&(ce=te,U=ae)}}}N=U,U=p[N],L+=N-U&32767}if(P){B[S++]=268435456|To[b]<<18|pr[P];var pe=To[b]&31,He=pr[P]&31;v+=en[pe]+tn[He],++w[257+pe],++y[He],x=Q+b,++E}else B[S++]=A[Q],++w[A[Q]]}}for(Q=Math.max(Q,x);Q<n;++Q)B[S++]=A[Q],++w[A[Q]];c=ur(A,a,l,B,w,y,v,S,H,Q-H,c),l||(s.r=c&7|a[c/8|0]<<3,c-=7,s.h=f,s.p=p,s.i=Q,s.w=x)}else{for(var Q=s.w||0;Q<n+l;Q+=65535){var ne=Q+65535;ne>=n&&(a[c/8|0]=l,ne=n),c=wr(a,c+1,A.subarray(Q,ne))}s.i=n}return ns(o,0,t+Lo(c)+i)},Nc=function(){for(var A=new Int32Array(256),r=0;r<256;++r){for(var e=r,t=9;--t;)e=(e&1&&-306674912)^e>>>1;A[r]=e}return A}(),Mc=function(){var A=-1;return{p:function(r){for(var e=A,t=0;t<r.length;++t)e=Nc[e&255^r[t]]^e>>>8;A=e},d:function(){return~A}}},Tc=function(A,r,e,t,i){if(!i&&(i={l:1},r.dictionary)){var s=r.dictionary.subarray(-32768),n=new Ae(s.length+A.length);n.set(s),n.set(A,s.length),A=n,i.w=s.length}return xc(A,r.level==null?6:r.level,r.mem==null?i.l?Math.ceil(Math.max(8,Math.min(13,Math.log(A.length)))*1.5):20:12+r.mem,e,t,i)},Er=function(A,r){var e={};for(var t in A)e[t]=A[t];for(var t in r)e[t]=r[t];return e},it=function(A,r){return A[r]|A[r+1]<<8},Re=function(A,r){return(A[r]|A[r+1]<<8|A[r+2]<<16|A[r+3]<<24)>>>0},No=function(A,r){return Re(A,r)+Re(A,r+4)*4294967296},Ie=function(A,r,e){for(;e;++r)A[r]=e,e>>>=8};vr=function(A,r,e,t){for(var i in A){var s=A[i],n=r+i,o=t;Array.isArray(s)&&(o=Er(t,s[1]),s=s[0]),ArrayBuffer.isView(s)?e[n]=[s,o]:(e[n+="/"]=[new Ae(0),o],vr(s,n,e,t))}},fr=typeof TextEncoder<"u"&&new TextEncoder,Uo=typeof TextDecoder<"u"&&new TextDecoder,Uc=0;try{Uo.decode(Qr,{stream:!0}),Uc=1}catch{}Rc=function(A){for(var r="",e=0;;){var t=A[e++],i=(t>127)+(t>223)+(t>239);if(e+i>A.length)return{s:r,r:ns(A,e-1)};i?i==3?(t=((t&15)<<18|(A[e++]&63)<<12|(A[e++]&63)<<6|A[e++]&63)-65536,r+=String.fromCharCode(55296|t>>10,56320|t&1023)):i&1?r+=String.fromCharCode((t&31)<<6|A[e++]&63):r+=String.fromCharCode((t&15)<<12|(A[e++]&63)<<6|A[e++]&63):r+=String.fromCharCode(t)}};Gc=function(A,r){return r+30+it(A,r+26)+it(A,r+28)},Lc=function(A,r,e){var t=it(A,r+28),i=it(A,r+30),s=qo(A.subarray(r+46,r+46+t),!(it(A,r+8)&2048)),n=r+46+t,o=qc(A,n,i,e,Re(A,r+20),Re(A,r+24),Re(A,r+42)),a=o[0],l=o[1],c=o[2];return[it(A,r+10),a,l,s,n+i+it(A,r+32),c]},qc=function(A,r,e,t,i,s,n){var o=i==4294967295,a=s==4294967295,l=n==4294967295,c=r+e,d=o+a+l;if(t&&d){for(;r+4<c;r+=4+it(A,r+2))if(it(A,r)==1)return[o?No(A,r+4+8*a):i,a?No(A,r+4):s,l?No(A,r+4+8*(a+o)):n,1];t<2&&we(13)}return[i,s,n,0]},Ro=function(A){var r=0;if(A)for(var e in A){var t=A[e].length;t>65535&&we(9),r+=t+4}return r},Ir=function(A,r,e,t,i,s,n,o){var a=t.length,l=e.extra,c=o&&o.length,d=Ro(l);Ie(A,r,n!=null?33639248:67324752),r+=4,n!=null&&(A[r++]=20,A[r++]=e.os),A[r]=20,r+=2,A[r++]=e.flag<<1|(s<0&&8),A[r++]=i&&8,A[r++]=e.compression&255,A[r++]=e.compression>>8;var h=new Date(e.mtime==null?Date.now():e.mtime),g=h.getFullYear()-1980;if((g<0||g>119)&&we(10),Ie(A,r,g<<25|h.getMonth()+1<<21|h.getDate()<<16|h.getHours()<<11|h.getMinutes()<<5|h.getSeconds()>>1),r+=4,s!=-1&&(Ie(A,r,e.crc),Ie(A,r+4,s<0?-s-2:s),Ie(A,r+8,e.size)),Ie(A,r+12,a),Ie(A,r+14,d),r+=16,n!=null&&(Ie(A,r,c),Ie(A,r+6,e.attrs),Ie(A,r+10,n),r+=14),A.set(t,r),r+=a,d)for(var u in l){var p=l[u],f=p.length;Ie(A,r,+u),Ie(A,r+2,f),A.set(p,r+4),r+=4+f}return c&&(A.set(o,r),r+=c),r},Kc=function(A,r,e,t,i){Ie(A,r,101010256),Ie(A,r+8,e),Ie(A,r+10,e),Ie(A,r+12,t),Ie(A,r+16,i)}});var br={};wi(br,{bytesToStr:()=>sn,unzipFiles:()=>mi,zipFiles:()=>Ii});function Jc(A){return typeof A=="string"?_s(A):A instanceof Uint8Array?A:new Uint8Array(A)}function Ii(A,r=6){let e={};for(let t of A)e[t.name]=[Jc(t.data),{level:r}];return Promise.resolve(Fr(e,{level:r}))}function sn(A){return qo(A)}function mi(A){let r=A instanceof Uint8Array?A:new Uint8Array(A);return Promise.resolve(kr(r))}var nn=$e(()=>{"use strict";Sr()});var Ad={};wi(Ad,{default:()=>Nn});module.exports=Bl(Ad);var T=require("obsidian");var Qi="sheet-group",at="sheet-order",Yt="sheet-final",na="sheet-label",Ei="sheet-origin",vi="forked-from",ze="fork-siblings",Hn="forked-at",Un=[Qi,at,Yt,na,Ei,vi,ze,Hn];function Ve(A){if(!A)return null;let r=A[Qi];return typeof r=="string"&&r.trim()?r:null}function Pn(A){let r=A?.[at];return typeof r=="number"?r:null}function Fi(A){return Ve(A)!==null&&Pn(A)!==null}function ki(A){return A?.[Yt]===!0}function oa(A){return A?.[Ei]===!0}function yl(A){let r=A?.[vi];return typeof r=="string"&&r.trim()?r:null}function As(A){return typeof A!="string"?null:A.replace(/^\[\[/,"").replace(/\]\]$/,"").split("|")[0].split("#")[0].trim()||null}function aa(A){return As(yl(A))}function wl(A){let r=A?.[na];return typeof r=="string"&&r.trim()?r:null}function ra(){let A=Date.now().toString(36),r=Math.floor(Math.random()*1e6).toString(36);return`s-${A}-${r}`}function Ce(A,r){return r.file?A.metadataCache.getFileCache(r.file)?.frontmatter:null}function Rn(A,r){return r.slice().sort((e,t)=>{let i=Pn(Ce(A,e)),s=Pn(Ce(A,t));if(i!=null&&s!=null&&i!==s)return i-s;if(i!=null&&s==null)return-1;if(i==null&&s!=null)return 1;let n=e.created??"",o=t.created??"";return n!==o?n<o?-1:1:e.id<t.id?-1:e.id>t.id?1:0})}function la(A,r){if(r.length===0)return null;let e=Rn(A,r);return e.find(t=>ki(Ce(A,t)))??e[0]}function ca(A,r,e){let t=wl(Ce(A,r));return t||e}le();var Fe=require("obsidian");var oe=require("obsidian");le();function ps(A){let r=(A.dateDisplayTimezone||"").trim();return r?{timeZone:r}:{}}function lt(A,r){try{return new Intl.DateTimeFormat(void 0,r).format(new Date(A))}catch{let{timeZone:e,...t}=r;return new Intl.DateTimeFormat(void 0,t).format(new Date(A))}}function ct(A,r){let e=ps(r);switch(r.dateDisplayFormat??"locale"){case"iso":return pa(A,r,!0);case"us":return lt(A,{...e,month:"numeric",day:"numeric",year:"numeric",hour:"numeric",minute:"2-digit"});case"eu":return lt(A,{...e,day:"numeric",month:"numeric",year:"numeric",hour:"2-digit",minute:"2-digit",hourCycle:"h23"});case"long":return lt(A,{...e,dateStyle:"full",timeStyle:"short"});case"locale":default:return lt(A,{...e,dateStyle:"medium",timeStyle:"short"})}}function Xt(A,r){let e=ps(r);switch(r.dateDisplayFormat??"locale"){case"iso":return pa(A,r,!1);case"us":return lt(A,{...e,month:"numeric",day:"numeric",year:"numeric"});case"eu":return lt(A,{...e,day:"numeric",month:"numeric",year:"numeric"});case"long":return lt(A,{...e,dateStyle:"full"});case"locale":default:{let t=new Date,i=new Date(A).getFullYear()===t.getFullYear();return lt(A,i?{...e,month:"short",day:"numeric"}:{...e,month:"short",day:"numeric",year:"numeric"})}}}function Zt(A,r){let e=ps(r),t=r.dateDisplayFormat==="iso"||r.dateDisplayFormat==="eu";return lt(A,{...e,hour:t?"2-digit":"numeric",minute:"2-digit",...t?{hourCycle:"h23"}:{}})}function pa(A,r,e){let t=ps(r),i=new Intl.DateTimeFormat("en-CA",{...t,year:"numeric",month:"2-digit",day:"2-digit",...e?{hour:"2-digit",minute:"2-digit",hourCycle:"h23"}:{}}).formatToParts(new Date(A)),s=o=>i.find(a=>a.type===o)?.value??"",n=`${s("year")}-${s("month")}-${s("day")}`;return e?`${n} ${s("hour")}:${s("minute")}`:n}Be();le();function zn(A){return A.basename.replace(/-[a-z0-9]{4,12}$/,"").replace(/-/g," ").trim()||A.basename}function ms(A,r){let e=new Set(r.discoverStashpadFolders()),t=[];for(let i of A.vault.getMarkdownFiles()){let s=i.parent?.path?.replace(/\/+$/,"")??"";if(!e.has(s))continue;let n=A.metadataCache.getFileCache(i)?.frontmatter??{},o=typeof n.id=="string"?n.id:null;if(!o||o===k)continue;let a=n.completed===!0,l=Vt(n,"task")||n.task===!0||n.completed!==void 0,c=typeof n.due=="string"||typeof n.due=="number"?String(n.due):null,d=null;if(c){let h=Date.parse(c);Number.isNaN(h)||(d=h)}!l&&!a&&d==null&&!c||t.push({file:i,folder:s,id:o,title:zn(i),task:l,completed:a,due:d,dueRaw:c,color:typeof n.color=="string"?n.color:null,assignedTo:Mt(n),assignedBy:rt(n.assignedBy),author:rt(n.author)})}return t}var Fa=require("obsidian");var Xe=require("obsidian");Be();var bl=0,Vn=[{key:"overdue",label:"Overdue",icon:"alert-circle"},{key:"today",label:"Due today",icon:"calendar-clock"},{key:"upcoming",label:"Upcoming",icon:"calendar"},{key:"nodate",label:"No date",icon:"inbox"},{key:"completed",label:"Completed",icon:"check-circle-2"}];function Cs(){return{assign:"all",person:"",folder:"all",status:"all"}}function Ki(A,r,e,t,i){let s=()=>Ki(A,r,e,t,i);A.empty(),A.addClass("stashpad-task-triage");let n=ms(r,e),o=(e.settings.authorId??"").trim(),a=b=>!!o&&b.assignedTo.some(P=>P.id===o),l=b=>{switch(t.assign){case"mine":return a(b);case"others":return b.assignedTo.length>0&&!a(b);case"byme":return!!o&&b.assignedBy?.id===o;case"unassigned":return b.assignedTo.length===0;default:return!0}},c=b=>!t.person||b.assignedTo.some(P=>P.id===t.person)||b.author?.id===t.person||b.assignedBy?.id===t.person,d=new Map;for(let b of n){for(let P of b.assignedTo)d.set(P.id,P.name);b.assignedBy&&d.set(b.assignedBy.id,b.assignedBy.name),b.author&&d.set(b.author.id,b.author.name)}let h=[...d.entries()].map(([b,P])=>({id:b,name:P})).sort((b,P)=>b.name.localeCompare(P.name)),g=[...new Set(n.map(b=>b.folder))].sort((b,P)=>b.localeCompare(P));t.folder!=="all"&&!g.includes(t.folder)&&(t.folder="all");let u=b=>t.folder==="all"||b.folder===t.folder,p=A.createDiv({cls:"stashpad-triage-filters"}),f=p.createEl("button",{cls:"stashpad-triage-reset",attr:{"aria-label":"Reset filters"}});(0,Xe.setIcon)(f,"rotate-ccw"),f.onclick=()=>{t.assign="all",t.person="",t.folder="all",s()};let I=p.createEl("select",{cls:"stashpad-triage-select"}),m={all:"Everyone",mine:"Assigned to me",others:"Assigned to others",byme:"Assigned by me",unassigned:"Unassigned"};for(let b of["all","mine","others","byme","unassigned"]){let P=I.createEl("option",{text:m[b],value:b});t.assign===b&&(P.selected=!0)}I.onchange=()=>{t.assign=I.value,s()};let C=p.createEl("select",{cls:"stashpad-triage-select"}),B=C.createEl("option",{text:"All folders",value:"all"});t.folder==="all"&&(B.selected=!0);for(let b of g){let P=C.createEl("option",{text:b.split("/").pop()||b,value:b});t.folder===b&&(P.selected=!0)}if(C.onchange=()=>{t.folder=C.value,s()},h.length>0){let b=new Map;for(let Y of h)b.has(Y.name)||b.set(Y.name,Y.id);let P=`stashpad-task-authors-${++bl}`,q=p.createDiv({cls:"stashpad-triage-authorwrap"}),L=q.createEl("input",{cls:"stashpad-triage-authorsearch",attr:{type:"text",list:P,placeholder:"Any person\u2026",spellcheck:"false"}}),K=q.createEl("button",{cls:"stashpad-triage-authorclear",attr:{"aria-label":"Clear person filter"}});(0,Xe.setIcon)(K,"x"),K.onclick=()=>{L.value="",t.person?(t.person="",s()):L.focus()};let W=q.createEl("datalist");W.id=P;for(let Y of h)W.createEl("option",{value:Y.name});t.person&&(L.value=d.get(t.person)??"");let $=()=>{let Y=L.value.trim();if(Y===""){t.person&&(t.person="",s());return}let _=b.get(Y);_&&t.person!==_&&(t.person=_,s())};L.onchange=$,L.addEventListener("keydown",Y=>{Y.key==="Enter"&&$()})}let w=Date.now(),y=new Date;y.setHours(0,0,0,0);let E=y.getTime(),v=E+864e5,Q=n.filter(b=>l(b)&&c(b)&&u(b)),S={overdue:[],today:[],upcoming:[],nodate:[],completed:[]};for(let b of Q){if(b.completed){S.completed.push(b);continue}if(b.due==null){S.nodate.push(b);continue}b.due<E?S.overdue.push(b):b.due<v?S.today.push(b):S.upcoming.push(b)}let x=(b,P)=>b.due==null&&P.due==null?b.title.localeCompare(P.title):b.due==null?1:P.due==null?-1:b.due-P.due,H=A.createDiv({cls:"stashpad-triage-chips"}),G=(b,P,q)=>{let L=H.createEl("button",{cls:"stashpad-triage-chip"});t.status===b&&L.addClass("is-active"),L.createSpan({text:P}),L.createSpan({cls:"stashpad-triage-chip-count",text:String(q)}),L.onclick=()=>{t.status=b,s()}};G("all","All",Q.length);for(let b of Vn)G(b.key,b.label,S[b.key].length);if(Q.length===0){A.createDiv({cls:"stashpad-tasks-empty"}).setText(n.length===0?"No tasks yet \u2014 press G on a note to make it a task, or D to give it a due date.":"No tasks match the current filters.");return}let N=async b=>{try{await r.fileManager.processFrontMatter(b.file,P=>{P.completed=P.completed!==!0})}catch(P){new Xe.Notice(`Couldn't update task: ${P.message}`);return}s()},U=b=>{let P=b.dueRaw??(b.due!=null?new Date(b.due).toISOString():null);new Ke(r,P,q=>{r.fileManager.processFrontMatter(b.file,L=>{q.iso===null?delete L.due:(L.due=q.iso,L.task=!0)}).then(s).catch(L=>new Xe.Notice(`Couldn't snooze: ${L.message}`))},{title:"Snooze \u2014 reschedule",hideAssignees:!0,quickAdjusts:e.settings.dueQuickAdjusts}).open()},R=t.status==="all"?Vn:Vn.filter(b=>b.key===t.status),D=!1;for(let b of R){let P=S[b.key];if(P.length===0)continue;if(D=!0,P.sort(b.key==="completed"?(L,K)=>x(K,L):x),t.status==="all"){let L=A.createDiv({cls:`stashpad-review-section is-${b.key}`});(0,Xe.setIcon)(L.createSpan({cls:"stashpad-review-section-icon"}),b.icon),L.createSpan({cls:"stashpad-review-section-name",text:b.label}),L.createSpan({cls:"stashpad-review-section-count",text:String(P.length)})}let q=A.createDiv({cls:"stashpad-review-list"});for(let L of P)Dl(q,L,w,e,{toggleCompleted:N,snooze:U,onOpen:i.onOpen})}D||A.createDiv({cls:"stashpad-tasks-empty"}).setText("Nothing in this view.")}function Dl(A,r,e,t,i){let s=A.createDiv({cls:"stashpad-review-row"});r.completed&&s.addClass("is-completed");let n=s.createSpan({cls:"stashpad-review-check"});(0,Xe.setIcon)(n,r.completed?"check-square":"square"),r.color&&(n.style.color=r.color),n.title=r.completed?"Mark not done":"Mark done",n.onclick=()=>i.toggleCompleted(r);let o=s.createDiv({cls:"stashpad-review-main"}),a=o.createDiv({cls:"stashpad-review-title",text:r.title});a.onclick=()=>i.onOpen(r.folder,r.id);let l=o.createDiv({cls:"stashpad-review-meta"});if(l.createSpan({cls:"stashpad-review-folder",text:r.folder.split("/").pop()||r.folder}),r.author&&l.createSpan({cls:"stashpad-review-author",text:`by ${r.author.name}`}),r.due!=null){let g=r.due<e&&!r.completed,u=r.due>=e-864e5&&r.due<e+864e5,p=l.createSpan({cls:"stashpad-review-due",text:u?Zt(r.due,t.settings):Xt(r.due,t.settings)});g&&p.addClass("is-overdue")}else r.dueRaw?l.createSpan({cls:"stashpad-review-due",text:r.dueRaw}):l.createSpan({cls:"stashpad-review-due is-none",text:"no date"});for(let g of r.assignedTo)l.createSpan({cls:"stashpad-review-assignee",text:g.name});let c=s.createDiv({cls:"stashpad-review-actions"}),d=c.createEl("button",{cls:"stashpad-review-btn"});(0,Xe.setIcon)(d,"alarm-clock"),d.title="Snooze \u2014 reschedule",d.onclick=()=>i.snooze(r);let h=c.createEl("button",{cls:"stashpad-review-btn"});(0,Xe.setIcon)(h,"arrow-right"),h.title="Open note",h.onclick=()=>i.onOpen(r.folder,r.id)}var ii=class extends Fa.Modal{constructor(e,t){super(e);this.plugin=t;this.state=Cs()}onOpen(){this.modalEl.addClass("stashpad-task-review-modal"),this.titleEl.setText("Daily task review"),Ki(this.contentEl,this.app,this.plugin,this.state,{onOpen:(e,t)=>{this.plugin.revealNoteByRef(e,t),this.close()}})}onClose(){this.contentEl.empty()}};function Bt(A,r,e){let t=A.createSpan({cls:"stashpad-count-badge"});e&&t.addClass("is-expanded"),t.setText(r>99?"99+":String(r))}var Ji={pinned:{label:"Pinned",icon:"pin"},shared:{label:"Shared",icon:"users"},tasks:{label:"Tasks",icon:"check-circle-2"}},Bs=class extends oe.ItemView{constructor(e,t){super(e);this.plugin=t;this.activePanel="pinned";this.taskFilter="all";this.taskAssignFilter="all";this.taskFolderFilter="all";this.expanded=new Set;this.renderTimer=null;this.sharedAuthorFilter="all";this.sharedContribOnly=!1}setActivePanel(e){this.activePanel=e,this.containerEl.isConnected&&this.render()}getViewType(){return It}getDisplayText(){return"Stashpad panels"}getIcon(){return"panel-left"}async onOpen(){this.render(),this.registerEvent(this.app.metadataCache.on("changed",()=>this.scheduleRender())),this.registerEvent(this.app.vault.on("rename",()=>this.scheduleRender())),this.registerEvent(this.app.vault.on("delete",()=>this.scheduleRender())),this.registerEvent(this.app.workspace.on("active-leaf-change",e=>{e&&e.view.getViewType()===z&&this.scheduleRender()}))}scheduleRender(){this.renderTimer==null&&(this.renderTimer=window.setTimeout(()=>{this.renderTimer=null,this.containerEl.isConnected&&this.render()},80))}render(){let e=this.contentEl;e.empty(),e.addClass("stashpad-panels-root");let t=e.createDiv({cls:"stashpad-panels-globals"}),i=t.createEl("button",{cls:"stashpad-panels-global-btn"});(0,oe.setIcon)(i.createSpan({cls:"stashpad-panels-global-btn-icon"}),"search"),i.createSpan({cls:"stashpad-panels-global-btn-text",text:"Search"}),i.onclick=()=>this.openSearchFromPanel();let s=t.createEl("button",{cls:"stashpad-panels-global-btn"});(0,oe.setIcon)(s.createSpan({cls:"stashpad-panels-global-btn-icon"}),"folder-tree"),s.createSpan({cls:"stashpad-panels-global-btn-text",text:"Folder Switcher"}),s.onclick=()=>this.plugin.openFolderPicker();let n=t.createDiv({cls:"stashpad-panels-globals-row"}),o=n.createEl("button",{cls:"stashpad-panels-global-btn"});(0,oe.setIcon)(o.createSpan({cls:"stashpad-panels-global-btn-icon"}),"scroll-text"),o.createSpan({cls:"stashpad-panels-global-btn-text",text:"Log"}),o.onclick=()=>this.openLogFromPanel();let a=n.createEl("button",{cls:"stashpad-panels-global-btn"});(0,oe.setIcon)(a.createSpan({cls:"stashpad-panels-global-btn-icon"}),"bell"),a.createSpan({cls:"stashpad-panels-global-btn-text",text:"Notifications"}),a.onclick=()=>this.openNotificationsFromPanel();let l=e.createDiv({cls:"stashpad-panels-bar"});for(let d of Object.keys(Ji)){let h=Ji[d],g=l.createEl("button",{cls:"stashpad-panels-bar-btn"});(0,oe.setIcon)(g.createSpan({cls:"stashpad-panels-bar-btn-icon"}),h.icon),g.createSpan({cls:"stashpad-panels-bar-btn-text",text:h.label}),this.activePanel===d&&g.addClass("is-active"),g.onclick=()=>{this.activePanel!==d&&(this.activePanel=d,this.render())}}let c=e.createDiv({cls:"stashpad-panels-body"});this.activePanel==="pinned"?this.renderPinnedPanel(c):this.activePanel==="shared"?this.renderSharedPanel(c):this.activePanel==="tasks"&&this.renderTasksPanel(c)}openPinnedOptionsMenu(e){let t=this.plugin.settings.folderPanelPinnedGrouping??"pin-order",i=new oe.Menu;i.addItem(s=>s.setTitle("Sort by pin order").setChecked(t==="pin-order").onClick(()=>void this.setPinnedGrouping("pin-order"))),i.addItem(s=>s.setTitle("Group by folder").setChecked(t==="folder").onClick(()=>void this.setPinnedGrouping("folder"))),i.showAtMouseEvent(e)}async setPinnedGrouping(e){(this.plugin.settings.folderPanelPinnedGrouping??"pin-order")!==e&&(this.plugin.settings.folderPanelPinnedGrouping=e,await this.plugin.saveSettings(),this.render())}renderPinnedPanel(e){let t=e.createDiv({cls:"stashpad-panel-pinned"});(this.plugin.settings.folderPanelPinnedGrouping??"pin-order")!=="folder"&&t.addClass("is-flat");let i=t.createDiv({cls:"stashpad-pinned-headrow"}),s=i.createEl("button",{cls:"stashpad-pinned-row stashpad-pinned-home"}),n=s.createSpan({cls:"stashpad-pinned-icon"});(0,oe.setIcon)(n,"home"),s.createSpan({cls:"stashpad-pinned-label",text:"Home"}),s.onclick=()=>this.openHomeFromPanel();let o=i.createEl("button",{cls:"stashpad-folderpanel-iconbtn stashpad-pinned-opts"});(0,oe.setIcon)(o,"list"),o.setAttr("aria-label","Pinned view options"),o.onclick=g=>{g.stopPropagation(),this.openPinnedOptionsMenu(g)};let a=this.plugin.listPinnedNotes();if(a.length===0){t.createDiv({cls:"stashpad-pinned-empty"}).setText("No pinned notes yet \u2014 right-click a note and choose \u201CPin to sidebar.\u201D");return}if((this.plugin.settings.folderPanelPinnedGrouping??"pin-order")!=="folder"){a.forEach((g,u)=>this.renderPinnedRow(t,g,u));return}let c=new Map;a.forEach((g,u)=>{let p=c.get(g.folder);p||(p=[],c.set(g.folder,p)),p.push({pin:g,idx:u})});let d=this.plugin.lastActiveStashpadLeaf?.view?.noteFolder,h=Array.from(c.keys());d&&c.has(d)&&(h.splice(h.indexOf(d),1),h.unshift(d));for(let g of h){let u=g.split("/").pop()||g,p=t.createDiv({cls:"stashpad-pinned-group-header"});g===d&&p.addClass("is-active-folder"),p.createSpan({cls:"stashpad-pinned-group-name",text:u});let f=c.get(g)??[];for(let{pin:I,idx:m}of f)this.renderPinnedRow(t,I,m)}}renderPinnedRow(e,t,i){let s=this.findFileFor(t);if(!s)return;let n=this.app.metadataCache.getFileCache(s)?.frontmatter??{},o=this.titleFromFile(s),a=typeof n.color=="string"?n.color:null,l=n.completed===!0,c=this.childrenOf(t.folder,t.id).length,d=c>0,h=this.expanded.has(`${t.folder}|${t.id}`),g=e.createDiv({cls:"stashpad-pinned-row"});a&&g.addClass("has-color"),l&&g.addClass("is-completed"),g.draggable=!0,g.dataset.pinIdx=String(i),g.addEventListener("dragstart",m=>{m.dataTransfer?.setData("text/plain",String(i)),m.dataTransfer&&(m.dataTransfer.effectAllowed="move"),g.addClass("is-dragging")}),g.addEventListener("dragend",()=>g.removeClass("is-dragging")),g.addEventListener("dragover",m=>{m.preventDefault(),m.dataTransfer&&(m.dataTransfer.dropEffect="move");let C=g.getBoundingClientRect(),B=m.clientY-C.top<C.height/2;g.toggleClass("drop-before",B),g.toggleClass("drop-after",!B)}),g.addEventListener("dragleave",()=>{g.removeClass("drop-before"),g.removeClass("drop-after")}),g.addEventListener("drop",m=>{m.preventDefault(),g.removeClass("drop-before"),g.removeClass("drop-after");let C=parseInt(m.dataTransfer?.getData("text/plain")??"",10);if(!Number.isFinite(C)||C===i)return;let B=g.getBoundingClientRect(),w=m.clientY-B.top<B.height/2;this.reorderPin(C,w?i:i+1)});let u=g.createSpan({cls:"stashpad-pinned-toggle"});d&&(Bt(u,c,h),u.onclick=m=>{m.stopPropagation();let C=`${t.folder}|${t.id}`;this.expanded.has(C)?this.expanded.delete(C):this.expanded.add(C),this.render()});let p=g.createSpan({cls:"stashpad-pinned-icon"});(0,oe.setIcon)(p,d?"folder-tree":"file-text"),a&&(p.style.color=a);let f=g.createSpan({cls:"stashpad-pinned-label",text:o});f.onclick=()=>this.openPinFromPanel(t);let I=t.folder.split("/").pop()||t.folder;if(g.createSpan({cls:"stashpad-pinned-folder",text:I}),g.oncontextmenu=m=>{m.preventDefault();let C=new oe.Menu;C.addItem(B=>B.setTitle("Unpin from sidebar").setIcon("pin-off").onClick(()=>{this.plugin.unpinNote(t)})),C.showAtMouseEvent(m)},d&&h){let m=e.createDiv({cls:"stashpad-pinned-children"});this.renderPinnedSubtree(m,t.folder,t.id,1)}}async reorderPin(e,t){let i=this.plugin.listPinnedNotes();if(e<0||e>=i.length)return;let s=i[e],n=i.filter((d,h)=>h!==e),o=Math.max(0,Math.min(t>e?t-1:t,n.length)),a=n[o-1],l=n[o],c;!a&&!l?c=Date.now():a?l?c=(a.pinnedAt+l.pinnedAt)/2:c=a.pinnedAt+1e3:c=l.pinnedAt-1e3;try{await this.app.fileManager.processFrontMatter(s.file,d=>{d.pinnedAt=c})}catch(d){console.warn("[Stashpad] pin reorder failed",d)}this.render()}renderPinnedSubtree(e,t,i,s){let n=this.childrenOf(t,i);for(let o of n){let a=this.app.metadataCache.getFileCache(o)?.frontmatter??{},l=typeof a.id=="string"?a.id:null;if(!l)continue;let c=typeof a.color=="string"?a.color:null,d=a.completed===!0,h=this.childrenOf(t,l).length,g=h>0,u=this.expanded.has(`${t}|${l}`),p=e.createDiv({cls:"stashpad-pinned-subrow"});d&&p.addClass("is-completed"),p.style.paddingLeft=`${s*16}px`;let f=p.createSpan({cls:"stashpad-pinned-toggle"});g&&(Bt(f,h,u),f.onclick=C=>{C.stopPropagation();let B=`${t}|${l}`;this.expanded.has(B)?this.expanded.delete(B):this.expanded.add(B),this.render()});let I=p.createSpan({cls:"stashpad-pinned-icon"});(0,oe.setIcon)(I,"file-text"),c&&(I.style.color=c);let m=p.createSpan({cls:"stashpad-pinned-label",text:this.titleFromFile(o)});m.onclick=()=>this.openPinFromPanel({folder:t,id:l}),g&&u&&this.renderPinnedSubtree(e,t,l,s+1)}}findFileFor(e){for(let t of this.app.vault.getMarkdownFiles()){if((t.parent?.path?.replace(/\/+$/,"")??"")!==e.folder)continue;if(this.app.metadataCache.getFileCache(t)?.frontmatter?.id===e.id)return t}return null}childrenOf(e,t){let i=[];for(let s of this.app.vault.getMarkdownFiles()){if((s.parent?.path?.replace(/\/+$/,"")??"")!==e)continue;let o=this.app.metadataCache.getFileCache(s)?.frontmatter;if(!o||typeof o.id!="string")continue;let a=o.parent;if(a===t||t===k&&(a==null||a===k)){if(o.id===k)continue;i.push(s)}}return i.sort((s,n)=>{let o=this.app.metadataCache.getFileCache(s)?.frontmatter,a=this.app.metadataCache.getFileCache(n)?.frontmatter,l=o?.created??"",c=a?.created??"";return l.localeCompare(c)}),i}titleFromFile(e){return zn(e)}async openSearchFromPanel(){let e=await this.resolveTargetStashpad();e&&typeof e.openSearchModal=="function"&&e.openSearchModal()}async openLogFromPanel(){let e=this.app.vault.adapter,t=this.plugin.pluginPrivatePath("log.jsonl");if(!await e.exists(t)){new oe.Notice("No log yet \u2014 make some changes first.");return}let i=await e.read(t),{LogModal:s}=await Promise.resolve().then(()=>(Be(),qi));new s(this.app,i,t).open()}openNotificationsFromPanel(){this.app.commands?.executeCommandById?.("stashpad:stashpad-open-notification-history")}async openHomeFromPanel(){let e=await this.resolveTargetStashpad();e&&typeof e.navigateTo=="function"&&e.navigateTo(k)}async openPinFromPanel(e){await this.plugin.revealNoteByRef(e.folder,e.id)}async resolveTargetStashpad(){let e=this.plugin.lastActiveStashpadLeaf;if(e&&e.view.getViewType()===z)return this.app.workspace.revealLeaf(e),e.view;let t=this.findActiveStashpad();if(t)return t;let i=this.app.workspace.getLeavesOfType(z);return i.length>0?(this.app.workspace.revealLeaf(i[0]),i[0].view):(await this.plugin.activateView({reveal:!0}),this.findActiveStashpad())}findActiveStashpad(){let e=this.app.workspace.activeLeaf;return e&&e.view.getViewType()===z?e.view:null}renderSharedPanel(e){let t=e.createDiv({cls:"stashpad-panel-shared"}),i=(this.plugin.settings.authorId??"").trim();if(!i){t.createDiv({cls:"stashpad-shared-empty"}).setText("Set an author name in Stashpad settings to populate Shared.");return}let s=this.collectSharedNotes(i),n=new Map;for(let d of s){let h=d.authorId;h&&!n.has(h)&&n.set(h,d.authorDisplay||h)}let o=t.createDiv({cls:"stashpad-shared-filters"}),a=(d,h,g)=>{let u=o.createEl("button",{cls:"stashpad-shared-chip",text:d});return h&&u.addClass("is-active"),u.onclick=g,u};if(a("All",this.sharedAuthorFilter==="all",()=>{this.sharedAuthorFilter="all",this.render()}),a("Mine",this.sharedAuthorFilter==="mine",()=>{this.sharedAuthorFilter="mine",this.render()}),a("Others",this.sharedAuthorFilter==="others",()=>{this.sharedAuthorFilter="others",this.render()}),n.size>1){let d=o.createEl("select",{cls:"stashpad-shared-author-select"}),h=d.createEl("option",{text:"Any author"});h.value="__any__";for(let[u,p]of n){let f=d.createEl("option",{text:p});f.value=u}let g=["all","mine","others"].includes(this.sharedAuthorFilter)?"__any__":this.sharedAuthorFilter;d.value=g,d.onchange=()=>{let u=d.value;u==="__any__"?this.sharedAuthorFilter="all":this.sharedAuthorFilter=u,this.render()}}let l=o.createEl("button",{cls:"stashpad-shared-chip",text:"Has contributors"});this.sharedContribOnly&&l.addClass("is-active"),l.onclick=()=>{this.sharedContribOnly=!this.sharedContribOnly,this.render()};let c=s.filter(d=>{if(this.sharedContribOnly&&d.contributorCount===0)return!1;switch(this.sharedAuthorFilter){case"all":return!0;case"mine":return d.authorId===i;case"others":return d.authorId!==i;default:return d.authorId===this.sharedAuthorFilter}});if(c.length===0){t.createDiv({cls:"stashpad-shared-empty"}).setText("No shared notes match the current filters.");return}for(let d of c){let h=t.createDiv({cls:"stashpad-pinned-row stashpad-shared-row"}),g=h.createSpan({cls:"stashpad-pinned-icon"});(0,oe.setIcon)(g,"users"),d.color&&(g.style.color=d.color);let u=h.createSpan({cls:"stashpad-pinned-label",text:d.title});u.onclick=()=>this.openSharedFromPanel(d.folder,d.id);let p=d.folder.split("/").pop()||d.folder;h.createSpan({cls:"stashpad-pinned-folder",text:p}),d.authorDisplay&&h.createSpan({cls:"stashpad-shared-meta"}).setText(d.authorId===i?`you \xB7 ${d.contributorCount} contributor${d.contributorCount===1?"":"s"}`:`by ${d.authorDisplay}${d.contributorCount>0?` \xB7 ${d.contributorCount} contributor${d.contributorCount===1?"":"s"}`:""}`)}}collectSharedNotes(e){let t=this.plugin.discoverStashpadFolders(),i=new Set(t),s=new Map;for(let o of this.app.vault.getMarkdownFiles()){let a=o.parent?.path?.replace(/\/+$/,"")??"";if(!i.has(a))continue;let l=this.app.metadataCache.getFileCache(o)?.frontmatter??{};l.id===k&&s.set(a,this.extractAuthorId(l.author))}let n=[];for(let o of this.app.vault.getMarkdownFiles()){let a=o.parent?.path?.replace(/\/+$/,"")??"";if(!i.has(a)||a.endsWith("/_authors")||o.path.includes("/_authors/"))continue;let l=this.app.metadataCache.getFileCache(o)?.frontmatter??{};if(typeof l.id!="string"||l.id===k)continue;let c=this.extractAuthorId(l.author),d=Array.isArray(l.contributors)?l.contributors.filter(C=>typeof C=="string"):[],h=s.get(a)??null;if(!(d.length>0||h===e&&(c!==null&&c!==e)))continue;let I=this.titleFromFile(o),m=typeof l.color=="string"?l.color:null;n.push({file:o,folder:a,id:l.id,title:I,color:m,authorId:c,authorDisplay:this.extractAuthorDisplay(l.author)||(c??""),contributorCount:d.length})}return n.sort((o,a)=>{let l=this.app.metadataCache.getFileCache(o.file)?.frontmatter??{},c=this.app.metadataCache.getFileCache(a.file)?.frontmatter??{},d=l.modified??l.created??"";return(c.modified??c.created??"").localeCompare(d)}),n}extractAuthorId(e){if(typeof e!="string")return null;let t=e.match(/-([a-z0-9]{4,12})(?:\.md)?(?:\||\]\])/i);return t?t[1]:null}extractAuthorDisplay(e){if(typeof e!="string")return"";let t=e.match(/\|([^\]]+)\]\]/);if(t)return t[1].trim();let i=e.match(/_authors\/([^\]|]+)-[a-z0-9]{4,12}/i);return i?i[1].replace(/[-_]/g," ").trim():""}async openSharedFromPanel(e,t){await this.plugin.revealNoteByRef(e,t)}renderTasksPanel(e){let t=e.createDiv({cls:"stashpad-panel-tasks"}),s=t.createDiv({cls:"stashpad-task-review-bar"}).createEl("button",{cls:"stashpad-task-review-open"});(0,oe.setIcon)(s.createSpan({cls:"stashpad-btn-icon"}),"maximize-2"),s.createSpan({text:"Daily review"}),s.onclick=()=>new ii(this.app,this.plugin).open();let n=this.collectTasks();if(n.length===0){t.createDiv({cls:"stashpad-tasks-empty"}).setText("No tasks yet \u2014 press G on a note to mark it a task, or D to give it a due date.");return}let o=(this.plugin.settings.authorId??"").trim(),a=D=>!!o&&D.assignedTo.some(b=>b.id===o),l=D=>{let b=this.taskAssignFilter;if(b.startsWith("person:")){let P=b.slice(7);return D.assignedTo.some(q=>q.id===P)}switch(b){case"mine":return a(D);case"others":return D.assignedTo.length>0&&!a(D);case"byme":return!!o&&D.assignedBy?.id===o;case"unassigned":return D.assignedTo.length===0;default:return!0}},c=new Map;for(let D of n){for(let b of D.assignedTo)b.id!==o&&c.set(b.id,b.name);D.assignedBy&&D.assignedBy.id!==o&&c.set(D.assignedBy.id,D.assignedBy.name)}let d=[...c.entries()].map(([D,b])=>({id:D,name:b})).sort((D,b)=>D.name.localeCompare(b.name)),h=t.createDiv({cls:"stashpad-task-assign-bar"});h.createSpan({cls:"stashpad-task-assign-label",text:"Assignment"});let g=h.createEl("select",{cls:"stashpad-task-assign-select"}),u=D=>{let b={all:"Everyone",mine:"Assigned to me",others:"Assigned to others",byme:"Assigned by me",unassigned:"Unassigned"};return D.startsWith("person:")?c.get(D.slice(7))??"Person":b[D]??D},p=D=>{let b=g.createEl("option",{text:u(D),value:D});this.taskAssignFilter===D&&(b.selected=!0)};for(let D of["all","mine","others","byme","unassigned"])p(D);if(d.length>0){let D=g.createEl("optgroup");D.setAttr("label","By person");for(let b of d){let P=D.createEl("option",{text:b.name,value:`person:${b.id}`});this.taskAssignFilter===`person:${b.id}`&&(P.selected=!0)}}g.onchange=()=>{this.taskAssignFilter=g.value,this.render()};let f=[...new Set(n.map(D=>D.folder))].sort((D,b)=>D.localeCompare(b));this.taskFolderFilter!=="all"&&!f.includes(this.taskFolderFilter)&&(this.taskFolderFilter="all");let I=t.createDiv({cls:"stashpad-task-assign-bar"});I.createSpan({cls:"stashpad-task-assign-label",text:"Folder"});let m=I.createEl("select",{cls:"stashpad-task-assign-select"}),C=m.createEl("option",{text:"All folders",value:"all"});this.taskFolderFilter==="all"&&(C.selected=!0);for(let D of f){let b=m.createEl("option",{text:D.split("/").pop()||D,value:D});this.taskFolderFilter===D&&(b.selected=!0)}m.onchange=()=>{this.taskFolderFilter=m.value,this.render()};let B=D=>this.taskFolderFilter==="all"||D.folder===this.taskFolderFilter,w=n.filter(D=>l(D)&&B(D));if(w.length===0){t.createDiv({cls:"stashpad-tasks-empty"}).setText("No tasks match the current filters.");return}let y=new Date;y.setHours(0,0,0,0);let E=y.getTime(),v=E+24*60*60*1e3,Q={overdue:[],today:[],upcoming:[],nodate:[],completed:[]};for(let D of w){if(D.completed){Q.completed.push(D);continue}if(D.due==null){Q.nodate.push(D);continue}D.due<E?Q.overdue.push(D):D.due<v?Q.today.push(D):Q.upcoming.push(D)}let S=(D,b)=>D.due==null&&b.due==null?D.title.localeCompare(b.title):D.due==null?1:b.due==null?-1:D.due-b.due,x=[{key:"overdue",label:"Overdue",icon:"alert-circle"},{key:"today",label:"Due today",icon:"calendar-clock"},{key:"upcoming",label:"Upcoming",icon:"calendar"},{key:"nodate",label:"No date",icon:"inbox"},{key:"completed",label:"Completed",icon:"check-circle-2"}],H=t.createDiv({cls:"stashpad-task-filters"}),G=w.length,N=(D,b,P)=>{let q=H.createEl("button",{cls:"stashpad-task-filter"});this.taskFilter===D&&q.addClass("is-active"),q.createSpan({cls:"stashpad-task-filter-label",text:b}),q.createSpan({cls:"stashpad-task-filter-count",text:String(P)}),q.onclick=()=>{this.taskFilter=D,this.render()}};N("all","All",G),N("overdue","Overdue",Q.overdue.length),N("today","Today",Q.today.length),N("upcoming","Upcoming",Q.upcoming.length),N("nodate","No date",Q.nodate.length),N("completed","Done",Q.completed.length);let U=this.taskFilter==="all"?x:x.filter(D=>D.key===this.taskFilter),R=!1;for(let D of U){let b=Q[D.key];if(b.length!==0){if(R=!0,b.sort(D.key==="completed"?(P,q)=>S(q,P):S),this.taskFilter==="all"){let P=t.createDiv({cls:`stashpad-task-section-header is-${D.key}`});(0,oe.setIcon)(P.createSpan({cls:"stashpad-task-section-icon"}),D.icon),P.createSpan({cls:"stashpad-task-section-name",text:D.label}),P.createSpan({cls:"stashpad-task-section-count",text:String(b.length)})}for(let P of b)this.renderTaskRow(t,P,D.key==="today")}}if(!R&&this.taskFilter!=="all"){t.createDiv({cls:"stashpad-tasks-empty"}).setText(`Nothing in "${U[0]?.label??this.taskFilter}".`);return}R||t.createDiv({cls:"stashpad-tasks-empty"}).setText("No tasks to show.")}renderTaskRow(e,t,i){let s=e.createDiv({cls:"stashpad-pinned-row stashpad-task-row"});t.color&&s.addClass("has-color"),t.completed&&s.addClass("is-completed");let n=s.createSpan({cls:"stashpad-pinned-icon stashpad-task-checkbox"});(0,oe.setIcon)(n,t.completed?"check-square":"square"),t.color&&(n.style.color=t.color),n.title=t.completed?"Mark not done":"Mark done",n.onclick=l=>{l.stopPropagation(),this.toggleTaskCompleted(t)};let o=s.createSpan({cls:"stashpad-pinned-label",text:t.title});if(o.onclick=()=>this.openTaskFromPanel(t.folder,t.id),s.createSpan({cls:"stashpad-task-folder",text:t.folder.split("/").pop()||t.folder}),t.due!=null){let l=s.createSpan({cls:"stashpad-task-due",text:this.formatDueShort(t.due,i)});t.due<Date.now()&&!t.completed&&l.addClass("is-overdue")}else t.dueRaw&&s.createSpan({cls:"stashpad-task-due",text:t.dueRaw});if(t.assignedTo.length>0){let l=(this.plugin.settings.authorId??"").trim(),c=s.createSpan({cls:"stashpad-task-assignees"});for(let d of t.assignedTo){let h=c.createSpan({cls:"stashpad-task-assignee"});l&&d.id===l&&h.addClass("is-me"),h.setText(this.initials(d.name)),h.title=l&&d.id===l?`${d.name} (you)`:d.name}}let a=s.createEl("button",{cls:"stashpad-task-snooze"});(0,oe.setIcon)(a,"alarm-clock"),a.title="Snooze \u2014 reschedule due date",a.onclick=l=>{l.stopPropagation(),this.snoozeTask(t)},s.oncontextmenu=l=>{l.preventDefault();let c=new oe.Menu;c.addItem(d=>d.setTitle("Open").setIcon("arrow-right").onClick(()=>{this.openTaskFromPanel(t.folder,t.id)})),c.addItem(d=>d.setTitle("Snooze (reschedule)\u2026").setIcon("alarm-clock").onClick(()=>{this.snoozeTask(t)})),c.showAtMouseEvent(l)}}snoozeTask(e){let t=e.dueRaw??(e.due!=null?new Date(e.due).toISOString():null);new Ke(this.app,t,i=>{this.app.fileManager.processFrontMatter(e.file,s=>{i.iso===null?delete s.due:(s.due=i.iso,s.task=!0)}).then(()=>this.scheduleRender()).catch(s=>new oe.Notice(`Couldn't snooze: ${s.message}`))},{title:"Snooze \u2014 reschedule",hideAssignees:!0,quickAdjusts:this.plugin.settings.dueQuickAdjusts}).open()}formatDueShort(e,t){return t?Zt(e,this.plugin.settings):Xt(e,this.plugin.settings)}async openTaskFromPanel(e,t){await this.plugin.revealNoteByRef(e,t)}async toggleTaskCompleted(e){try{await this.app.fileManager.processFrontMatter(e.file,t=>{t.completed=t.completed!==!0})}catch(t){new oe.Notice(`Couldn't update task: ${t.message}`);return}this.scheduleRender()}initials(e){let t=e.trim().split(/\s+/).filter(Boolean);return t.length===0?"?":t.length===1?t[0].slice(0,2).toUpperCase():(t[0][0]+t[t.length-1][0]).toUpperCase()}collectTasks(){return ms(this.app,this.plugin)}};async function ys(A){let r=A.workspace.getLeavesOfType(It);if(r.length>0){A.workspace.revealLeaf(r[0]);return}let e=A.workspace.getLeftLeaf(!1);if(!e){new oe.Notice("Stashpad: couldn't open the panels view.");return}await e.setViewState({type:It,active:!0}),A.workspace.revealLeaf(e)}var sr=require("obsidian");le();var M=require("obsidian");Yi();var Fs=require("obsidian");le();var oi=class extends Fs.AbstractInputSuggest{constructor(e,t){super(e,t);this.inputEl=t}getSuggestions(e){let t=e.toLowerCase().split(/\s+/).filter(Boolean),i=o=>t.every(a=>o.toLowerCase().includes(a)),s=[],n=o=>{if(!(o.path!=="/"&&Si.has(o.name))){o.path!=="/"&&i(o.path)&&s.push(o);for(let a of o.children)a instanceof Fs.TFolder&&n(a)}};return n(this.app.vault.getRoot()),s.slice(0,100)}renderSuggestion(e,t){t.setText(e.path)}selectSuggestion(e){this.setValue(e.path),this.inputEl.dispatchEvent(new Event("input",{bubbles:!0})),this.close()}};var ai=require("obsidian"),ks=class A extends ai.AbstractInputSuggest{constructor(e,t){super(e,t);this.inputEl=t}static norm(e){return e.replace(/^lucide-/,"")}getSuggestions(e){let t=e.toLowerCase().split(/\s+/).filter(Boolean),i=new Set,s=[];for(let a of(0,ai.getIconIds)()){let l=A.norm(a);i.has(l)||(i.add(l),s.push(l))}let n=a=>t.every(l=>a.toLowerCase().includes(l)),o=t.length?s.filter(n):s;return o.sort(),o.slice(0,t.length?50:300)}renderSuggestion(e,t){t.addClass("stashpad-icon-suggest-item");let i=t.createSpan({cls:"stashpad-icon-suggest-icon"});(0,ai.setIcon)(i,e),t.createSpan({cls:"stashpad-icon-suggest-label",text:e})}selectSuggestion(e){this.setValue(e),this.inputEl.dispatchEvent(new Event("input",{bubbles:!0})),this.close()}};le();Be();var Gt=require("obsidian"),jn={create:{label:"Create",desc:"Confirmations after a new note is created."},edit:{label:"Edit",desc:"Edit-related toasts (currently rare)."},delete:{label:"Delete",desc:"Confirmations after deleting one or more notes."},move:{label:"Move",desc:"After reparenting or moving notes between folders."},merge:{label:"Merge",desc:"After combining multiple notes into one."},split:{label:"Split",desc:"After splitting a note in two."},clone:{label:"Clone",desc:"After duplicating notes or subtrees."},complete:{label:"Complete",desc:"When a note is marked complete."},uncomplete:{label:"Uncomplete",desc:"When a note's complete mark is removed."},export:{label:"Export",desc:".stash exports \u2014 success + the action buttons."},import:{label:"Import",desc:".stash imports (both manual and the drop-folder auto-import)."},attachment:{label:"Attachment",desc:"Attachment add / remove notifications."},color:{label:"Color",desc:"Per-note color changes."},reorder:{label:"Reorder",desc:"Drag-reorder and keyboard moveUp/Down/Top/Bottom."},multiplayer:{label:"Multiplayer",desc:"Cross-author activity (someone else touched your notes or vice versa)."},reminder:{label:"Reminder",desc:"Task due-date reminders \u2014 surfaced when a task comes due (fired at launch / periodically)."},system:{label:"System",desc:"Plumbing toasts: backfill progress, integrity warnings, errors not tied to a verb."}},Pl={info:4e3,success:4e3,warning:6e3,error:1e4},Na=5e3,Ss=class{constructor(r){this.app=r;this.history=[];this.nextId=1;this.historyLimit=Na;this.changeListeners=new Set;this.muted=new Set;this.defaultAuthorId=null}setDefaultAuthorId(r){this.defaultAuthorId=r||null}show(r){let e=r.kind??"info",t=r.category??"system",i={id:this.nextId++,ts:Date.now(),message:r.message,kind:e,category:t,authorId:r.authorId??this.defaultAuthorId??void 0,affectedIds:r.affectedIds?r.affectedIds.slice():[],affectedPaths:r.affectedPaths?r.affectedPaths.slice():[],affectedAuthorIds:r.affectedAuthorIds?r.affectedAuthorIds.slice():[],folder:r.folder,actionLabels:(r.actions??[]).map(o=>o.label)};if(this.pushHistory(i),this.muted.has(t))return null;let s=this.buildContent(r,e),n=r.duration??Pl[e];return new Gt.Notice(s,n)}onChange(r){return this.changeListeners.add(r),()=>this.changeListeners.delete(r)}recent(){return this.history.slice().reverse()}clearHistory(){this.history=[],this.emit()}setMuted(r,e){e?this.muted.add(r):this.muted.delete(r)}isMuted(r){return this.muted.has(r)}loadMutedFromList(r){this.muted=new Set(r)}mutedList(){return Array.from(this.muted)}loadHistory(r){let e=this.applyLimit(r.slice());this.history=e;let t=0;for(let i of e)i.id>t&&(t=i.id);this.nextId=Math.max(this.nextId,t+1),this.emit()}setHistoryLimit(r){this.historyLimit=Number.isFinite(r)?r:Na,this.history=this.applyLimit(this.history),this.emit()}applyLimit(r){return this.historyLimit<=0||r.length<=this.historyLimit?r:r.slice(r.length-this.historyLimit)}pushHistory(r){this.history.push(r),this.history=this.applyLimit(this.history),this.emit()}emit(){for(let r of this.changeListeners)try{r()}catch(e){console.warn("[Stashpad] notification listener failed",e)}}buildContent(r,e){let t=document.createDocumentFragment(),i=document.createElement("div");i.className=`stashpad-notice stashpad-notice-${e}`;let s=document.createElement("div");s.className="stashpad-notice-message";let n=r.message.split(/(`[^`\n]+`)/);for(let o of n)if(o.length>1&&o.startsWith("`")&&o.endsWith("`")){let a=document.createElement("code");a.textContent=o.slice(1,-1),s.appendChild(a)}else o.length>0&&s.appendChild(document.createTextNode(o));if(i.appendChild(s),r.actions&&r.actions.length>0){let o=document.createElement("div");o.className="stashpad-notice-actions";for(let a of r.actions){let l=document.createElement("button");l.className="stashpad-notice-action",l.textContent=a.label,l.addEventListener("click",c=>{if(c.stopPropagation(),Promise.resolve().then(()=>a.onClick()).catch(d=>console.warn("[Stashpad] notification action failed",d)),!a.keepOpen){let d=l.closest(".notice");d&&d.parentElement&&d.parentElement.removeChild(d)}}),o.appendChild(l)}i.appendChild(o)}return t.appendChild(i),t}};function bs(A,r,e){let t=A.vault.getAbstractFileByPath(r);if(!(t instanceof Gt.TFile))return[];let i=[{label:"Reveal",keepOpen:!0,onClick:()=>{let s=A.workspace.getLeavesOfType("file-explorer")[0];if(!s)return;A.workspace.revealLeaf(s),s.view?.revealInFolder?.(t)}}];if(!e){let s=Gt.Platform.isMacOS?"Finder":Gt.Platform.isWin?"File Explorer":"file manager";i.push({label:`Show in ${s}`,keepOpen:!0,onClick:()=>{try{let n=window.require?.("electron")?.shell,a=A.vault.adapter?.getFullPath?.(r);a&&n?.showItemInFolder&&n.showItemInFolder(a)}catch(n){console.warn("[Stashpad] showItemInFolder failed",n)}}})}return i}var Ma=require("obsidian"),_n=Ma.Platform.isMacOS;function $i(A){if(!A)return"(none)";let r=A.replace(/\bMod\b/g,_n?"Cmd":"Ctrl");return _n&&(r=r.replace(/\bAlt\b/g,"Option")),r}function Ta(A,r,e={allowSingleKey:!0}){let t=A.placeholder,i=A.value;A.placeholder="Press a key\u2026 (Backspace to cancel)",A.value="",A.classList.add("is-recording");let s=!1,n=()=>{A.placeholder=t,A.classList.remove("is-recording"),s||(A.value=i),A.removeEventListener("keydown",o,!0),A.removeEventListener("blur",a)},o=l=>{if(l.key==="Control"||l.key==="Shift"||l.key==="Alt"||l.key==="Meta"||l.key==="OS")return;let c=!!l.code&&(/^Key[A-Z]$/.test(l.code)||/^Digit\d$/.test(l.code));if((l.key==="Dead"||l.key==="Process"||l.key==="Unidentified")&&!c)return;if(l.preventDefault(),l.stopPropagation(),l.key==="Backspace"&&!l.metaKey&&!l.ctrlKey&&!l.altKey&&!l.shiftKey){n();return}let d=[];_n?(l.metaKey&&d.push("Mod"),l.ctrlKey&&d.push("Ctrl")):l.ctrlKey&&d.push("Mod"),l.altKey&&d.push("Alt"),l.shiftKey&&d.push("Shift");let h=Hl(l.key,l.code);if(!h||d.length===0&&!e.allowSingleKey)return;d.push(h);let g=d.join("+");s=!0,n(),r(g)},a=()=>n();return A.addEventListener("keydown",o,!0),A.addEventListener("blur",a),n}function Hl(A,r){if(!A)return"";if(r){let e=/^Key([A-Z])$/.exec(r);if(e)return e[1];let t=/^Digit(\d)$/.exec(r);if(t)return t[1]}return A.length===1?A.toUpperCase():A}si();qe();function Lt(A,r,e,t){function i(s){return s instanceof e?s:new e(function(n){n(s)})}return new(e||(e=Promise))(function(s,n){function o(c){try{l(t.next(c))}catch(d){n(d)}}function a(c){try{l(t.throw(c))}catch(d){n(d)}}function l(c){c.done?s(c.value):i(c.value).then(o,a)}l((t=t.apply(A,r||[])).next())})}var re=class{constructor(){this.mutex=Promise.resolve()}lock(){let r=()=>{};return this.mutex=this.mutex.then(()=>new Promise(r)),new Promise(e=>{r=e})}dispatch(r){return Lt(this,void 0,void 0,function*(){let e=yield this.lock();try{return yield Promise.resolve(r())}finally{e()}})}},eo;function Ul(){return typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:global}var no=Ul(),to=(eo=no.Buffer)!==null&&eo!==void 0?eo:null,Rl=no.TextEncoder?new no.TextEncoder:null;function La(A,r){return(A&15)+(A>>6|A>>3&8)<<4|(r&15)+(r>>6|r>>3&8)}function qa(A,r){let e=r.length>>1;for(let t=0;t<e;t++){let i=t<<1;A[t]=La(r.charCodeAt(i),r.charCodeAt(i+1))}}function Gl(A,r){if(A.length!==r.length*2)return!1;for(let e=0;e<r.length;e++){let t=e<<1;if(r[e]!==La(A.charCodeAt(t),A.charCodeAt(t+1)))return!1}return!0}var Pa=87,Ha=48;function oo(A,r,e){let t=0;for(let i=0;i<e;i++){let s=r[i]>>>4;A[t++]=s>9?s+Pa:s+Ha,s=r[i]&15,A[t++]=s>9?s+Pa:s+Ha}return String.fromCharCode.apply(null,A)}var At=to!==null?A=>{if(typeof A=="string"){let r=to.from(A,"utf8");return new Uint8Array(r.buffer,r.byteOffset,r.length)}if(to.isBuffer(A))return new Uint8Array(A.buffer,A.byteOffset,A.length);if(ArrayBuffer.isView(A))return new Uint8Array(A.buffer,A.byteOffset,A.byteLength);throw new Error("Invalid data type!")}:A=>{if(typeof A=="string")return Rl.encode(A);if(ArrayBuffer.isView(A))return new Uint8Array(A.buffer,A.byteOffset,A.byteLength);throw new Error("Invalid data type!")},Je="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",Vi=new Uint8Array(256);for(let A=0;A<Je.length;A++)Vi[Je.charCodeAt(A)]=A;function Ua(A,r=!0){let e=A.length,t=e%3,i=[],s=e-t;for(let n=0;n<s;n+=3){let o=(A[n]<<16&16711680)+(A[n+1]<<8&65280)+(A[n+2]&255),a=Je.charAt(o>>18&63)+Je.charAt(o>>12&63)+Je.charAt(o>>6&63)+Je.charAt(o&63);i.push(a)}if(t===1){let n=A[e-1],o=Je.charAt(n>>2),a=Je.charAt(n<<4&63);i.push(`${o}${a}`),r&&i.push("==")}else if(t===2){let n=(A[e-2]<<8)+A[e-1],o=Je.charAt(n>>10),a=Je.charAt(n>>4&63),l=Je.charAt(n<<2&63);i.push(`${o}${a}${l}`),r&&i.push("=")}return i.join("")}function Ll(A){let r=Math.floor(A.length*.75),e=A.length;return A[e-1]==="="&&(r-=1,A[e-2]==="="&&(r-=1)),r}function ql(A){let r=Ll(A),e=A.length,t=new Uint8Array(r),i=0;for(let s=0;s<e;s+=4){let n=Vi[A.charCodeAt(s)],o=Vi[A.charCodeAt(s+1)],a=Vi[A.charCodeAt(s+2)],l=Vi[A.charCodeAt(s+3)];t[i]=n<<2|o>>4,i+=1,t[i]=(o&15)<<4|a>>2,i+=1,t[i]=(a&3)<<6|l&63,i+=1}return t}var Ds=16*1024,zi=4,Kl=new re,io=new Map;function Ka(A,r){return Lt(this,void 0,void 0,function*(){let e=null,t=null,i=!1;if(typeof WebAssembly>"u")throw new Error("WebAssembly is not supported in this environment!");let s=(y,E=0)=>{t.set(y,E)},n=()=>t,o=()=>e.exports,a=y=>{e.exports.Hash_SetMemorySize(y);let E=e.exports.Hash_GetBuffer(),v=e.exports.memory.buffer;t=new Uint8Array(v,E,y)},l=()=>new DataView(e.exports.memory.buffer).getUint32(e.exports.STATE_SIZE,!0),c=Kl.dispatch(()=>Lt(this,void 0,void 0,function*(){if(!io.has(A.name)){let E=ql(A.data),v=WebAssembly.compile(E);io.set(A.name,v)}let y=yield io.get(A.name);e=yield WebAssembly.instantiate(y,{})})),d=()=>Lt(this,void 0,void 0,function*(){e||(yield c);let y=e.exports.Hash_GetBuffer(),E=e.exports.memory.buffer;t=new Uint8Array(E,y,Ds)}),h=(y=null)=>{i=!0,e.exports.Hash_Init(y)},g=y=>{let E=0;for(;E<y.length;){let v=y.subarray(E,E+Ds);E+=v.length,t.set(v),e.exports.Hash_Update(v.length)}},u=y=>{if(!i)throw new Error("update() called before init()");let E=At(y);g(E)},p=new Uint8Array(r*2),f=(y,E=null)=>{if(!i)throw new Error("digest() called before init()");return i=!1,e.exports.Hash_Final(E),y==="binary"?t.slice(0,r):oo(p,t,r)},I=()=>{if(!i)throw new Error("save() can only be called after init() and before digest()");let y=e.exports.Hash_GetState(),E=l(),v=e.exports.memory.buffer,Q=new Uint8Array(v,y,E),S=new Uint8Array(zi+E);return qa(S,A.hash),S.set(Q,zi),S},m=y=>{if(!(y instanceof Uint8Array))throw new Error("load() expects an Uint8Array generated by save()");let E=e.exports.Hash_GetState(),v=l(),Q=zi+v,S=e.exports.memory.buffer;if(y.length!==Q)throw new Error(`Bad state length (expected ${Q} bytes, got ${y.length})`);if(!Gl(A.hash,y.subarray(0,zi)))throw new Error("This state was written by an incompatible hash implementation");let x=y.subarray(zi);new Uint8Array(S,E,v).set(x),i=!0},C=y=>typeof y=="string"?y.length<Ds/4:y.byteLength<Ds,B=C;switch(A.name){case"argon2":case"scrypt":B=()=>!0;break;case"blake2b":case"blake2s":B=(y,E)=>E<=512&&C(y);break;case"blake3":B=(y,E)=>E===0&&C(y);break;case"xxhash64":case"xxhash3":case"xxhash128":case"crc64":B=()=>!1;break}let w=(y,E=null,v=null)=>{if(!B(y,E))return h(E),u(y),f("hex",v);let Q=At(y);return t.set(Q),e.exports.Hash_Calculate(Q.length,E,v),oo(p,t,r)};return yield d(),{getMemory:n,writeMemory:s,getExports:o,setMemorySize:a,init:h,update:u,digest:f,save:I,load:m,calculate:w,hashLength:r}})}var oA=new re;var Jl="argon2",Ol="AGFzbQEAAAABKQVgAX8Bf2AAAX9gEH9/f39/f39/f39/f39/f38AYAR/f39/AGACf38AAwYFAAECAwQFBgEBAoCAAgYIAX8BQZCoBAsHQQQGbWVtb3J5AgASSGFzaF9TZXRNZW1vcnlTaXplAAAOSGFzaF9HZXRCdWZmZXIAAQ5IYXNoX0NhbGN1bGF0ZQAECvEyBVgBAn9BACEBAkAgAEEAKAKICCICRg0AAkAgACACayIAQRB2IABBgIB8cSAASWoiAEAAQX9HDQBB/wHADwtBACEBQQBBACkDiAggAEEQdK18NwOICAsgAcALcAECfwJAQQAoAoAIIgANAEEAPwBBEHQiADYCgAhBACgCiAgiAUGAgCBGDQACQEGAgCAgAWsiAEEQdiAAQYCAfHEgAElqIgBAAEF/Rw0AQQAPC0EAQQApA4gIIABBEHStfDcDiAhBACgCgAghAAsgAAvcDgECfiAAIAQpAwAiECAAKQMAIhF8IBFCAYZC/v///x+DIBBC/////w+DfnwiEDcDACAMIBAgDCkDAIVCIIkiEDcDACAIIBAgCCkDACIRfCARQgGGQv7///8fgyAQQv////8Pg358IhA3AwAgBCAQIAQpAwCFQiiJIhA3AwAgACAQIAApAwAiEXwgEEL/////D4MgEUIBhkL+////H4N+fCIQNwMAIAwgECAMKQMAhUIwiSIQNwMAIAggECAIKQMAIhF8IBBC/////w+DIBFCAYZC/v///x+DfnwiEDcDACAEIBAgBCkDAIVCAYk3AwAgASAFKQMAIhAgASkDACIRfCARQgGGQv7///8fgyAQQv////8Pg358IhA3AwAgDSAQIA0pAwCFQiCJIhA3AwAgCSAQIAkpAwAiEXwgEUIBhkL+////H4MgEEL/////D4N+fCIQNwMAIAUgECAFKQMAhUIoiSIQNwMAIAEgECABKQMAIhF8IBBC/////w+DIBFCAYZC/v///x+DfnwiEDcDACANIBAgDSkDAIVCMIkiEDcDACAJIBAgCSkDACIRfCAQQv////8PgyARQgGGQv7///8fg358IhA3AwAgBSAQIAUpAwCFQgGJNwMAIAIgBikDACIQIAIpAwAiEXwgEUIBhkL+////H4MgEEL/////D4N+fCIQNwMAIA4gECAOKQMAhUIgiSIQNwMAIAogECAKKQMAIhF8IBFCAYZC/v///x+DIBBC/////w+DfnwiEDcDACAGIBAgBikDAIVCKIkiEDcDACACIBAgAikDACIRfCAQQv////8PgyARQgGGQv7///8fg358IhA3AwAgDiAQIA4pAwCFQjCJIhA3AwAgCiAQIAopAwAiEXwgEEL/////D4MgEUIBhkL+////H4N+fCIQNwMAIAYgECAGKQMAhUIBiTcDACADIAcpAwAiECADKQMAIhF8IBFCAYZC/v///x+DIBBC/////w+DfnwiEDcDACAPIBAgDykDAIVCIIkiEDcDACALIBAgCykDACIRfCARQgGGQv7///8fgyAQQv////8Pg358IhA3AwAgByAQIAcpAwCFQiiJIhA3AwAgAyAQIAMpAwAiEXwgEEL/////D4MgEUIBhkL+////H4N+fCIQNwMAIA8gECAPKQMAhUIwiSIQNwMAIAsgECALKQMAIhF8IBBC/////w+DIBFCAYZC/v///x+DfnwiEDcDACAHIBAgBykDAIVCAYk3AwAgACAFKQMAIhAgACkDACIRfCARQgGGQv7///8fgyAQQv////8Pg358IhA3AwAgDyAQIA8pAwCFQiCJIhA3AwAgCiAQIAopAwAiEXwgEUIBhkL+////H4MgEEL/////D4N+fCIQNwMAIAUgECAFKQMAhUIoiSIQNwMAIAAgECAAKQMAIhF8IBBC/////w+DIBFCAYZC/v///x+DfnwiEDcDACAPIBAgDykDAIVCMIkiEDcDACAKIBAgCikDACIRfCAQQv////8PgyARQgGGQv7///8fg358IhA3AwAgBSAQIAUpAwCFQgGJNwMAIAEgBikDACIQIAEpAwAiEXwgEUIBhkL+////H4MgEEL/////D4N+fCIQNwMAIAwgECAMKQMAhUIgiSIQNwMAIAsgECALKQMAIhF8IBFCAYZC/v///x+DIBBC/////w+DfnwiEDcDACAGIBAgBikDAIVCKIkiEDcDACABIBAgASkDACIRfCAQQv////8PgyARQgGGQv7///8fg358IhA3AwAgDCAQIAwpAwCFQjCJIhA3AwAgCyAQIAspAwAiEXwgEEL/////D4MgEUIBhkL+////H4N+fCIQNwMAIAYgECAGKQMAhUIBiTcDACACIAcpAwAiECACKQMAIhF8IBFCAYZC/v///x+DIBBC/////w+DfnwiEDcDACANIBAgDSkDAIVCIIkiEDcDACAIIBAgCCkDACIRfCARQgGGQv7///8fgyAQQv////8Pg358IhA3AwAgByAQIAcpAwCFQiiJIhA3AwAgAiAQIAIpAwAiEXwgEEL/////D4MgEUIBhkL+////H4N+fCIQNwMAIA0gECANKQMAhUIwiSIQNwMAIAggECAIKQMAIhF8IBBC/////w+DIBFCAYZC/v///x+DfnwiEDcDACAHIBAgBykDAIVCAYk3AwAgAyAEKQMAIhAgAykDACIRfCARQgGGQv7///8fgyAQQv////8Pg358IhA3AwAgDiAQIA4pAwCFQiCJIhA3AwAgCSAQIAkpAwAiEXwgEUIBhkL+////H4MgEEL/////D4N+fCIQNwMAIAQgECAEKQMAhUIoiSIQNwMAIAMgECADKQMAIhF8IBBC/////w+DIBFCAYZC/v///x+DfnwiEDcDACAOIBAgDikDAIVCMIkiEDcDACAJIBAgCSkDACIRfCAQQv////8PgyARQgGGQv7///8fg358IhA3AwAgBCAQIAQpAwCFQgGJNwMAC98aAQN/QQAhBEEAIAIpAwAgASkDAIU3A5AIQQAgAikDCCABKQMIhTcDmAhBACACKQMQIAEpAxCFNwOgCEEAIAIpAxggASkDGIU3A6gIQQAgAikDICABKQMghTcDsAhBACACKQMoIAEpAyiFNwO4CEEAIAIpAzAgASkDMIU3A8AIQQAgAikDOCABKQM4hTcDyAhBACACKQNAIAEpA0CFNwPQCEEAIAIpA0ggASkDSIU3A9gIQQAgAikDUCABKQNQhTcD4AhBACACKQNYIAEpA1iFNwPoCEEAIAIpA2AgASkDYIU3A/AIQQAgAikDaCABKQNohTcD+AhBACACKQNwIAEpA3CFNwOACUEAIAIpA3ggASkDeIU3A4gJQQAgAikDgAEgASkDgAGFNwOQCUEAIAIpA4gBIAEpA4gBhTcDmAlBACACKQOQASABKQOQAYU3A6AJQQAgAikDmAEgASkDmAGFNwOoCUEAIAIpA6ABIAEpA6ABhTcDsAlBACACKQOoASABKQOoAYU3A7gJQQAgAikDsAEgASkDsAGFNwPACUEAIAIpA7gBIAEpA7gBhTcDyAlBACACKQPAASABKQPAAYU3A9AJQQAgAikDyAEgASkDyAGFNwPYCUEAIAIpA9ABIAEpA9ABhTcD4AlBACACKQPYASABKQPYAYU3A+gJQQAgAikD4AEgASkD4AGFNwPwCUEAIAIpA+gBIAEpA+gBhTcD+AlBACACKQPwASABKQPwAYU3A4AKQQAgAikD+AEgASkD+AGFNwOICkEAIAIpA4ACIAEpA4AChTcDkApBACACKQOIAiABKQOIAoU3A5gKQQAgAikDkAIgASkDkAKFNwOgCkEAIAIpA5gCIAEpA5gChTcDqApBACACKQOgAiABKQOgAoU3A7AKQQAgAikDqAIgASkDqAKFNwO4CkEAIAIpA7ACIAEpA7AChTcDwApBACACKQO4AiABKQO4AoU3A8gKQQAgAikDwAIgASkDwAKFNwPQCkEAIAIpA8gCIAEpA8gChTcD2ApBACACKQPQAiABKQPQAoU3A+AKQQAgAikD2AIgASkD2AKFNwPoCkEAIAIpA+ACIAEpA+AChTcD8ApBACACKQPoAiABKQPoAoU3A/gKQQAgAikD8AIgASkD8AKFNwOAC0EAIAIpA/gCIAEpA/gChTcDiAtBACACKQOAAyABKQOAA4U3A5ALQQAgAikDiAMgASkDiAOFNwOYC0EAIAIpA5ADIAEpA5ADhTcDoAtBACACKQOYAyABKQOYA4U3A6gLQQAgAikDoAMgASkDoAOFNwOwC0EAIAIpA6gDIAEpA6gDhTcDuAtBACACKQOwAyABKQOwA4U3A8ALQQAgAikDuAMgASkDuAOFNwPIC0EAIAIpA8ADIAEpA8ADhTcD0AtBACACKQPIAyABKQPIA4U3A9gLQQAgAikD0AMgASkD0AOFNwPgC0EAIAIpA9gDIAEpA9gDhTcD6AtBACACKQPgAyABKQPgA4U3A/ALQQAgAikD6AMgASkD6AOFNwP4C0EAIAIpA/ADIAEpA/ADhTcDgAxBACACKQP4AyABKQP4A4U3A4gMQQAgAikDgAQgASkDgASFNwOQDEEAIAIpA4gEIAEpA4gEhTcDmAxBACACKQOQBCABKQOQBIU3A6AMQQAgAikDmAQgASkDmASFNwOoDEEAIAIpA6AEIAEpA6AEhTcDsAxBACACKQOoBCABKQOoBIU3A7gMQQAgAikDsAQgASkDsASFNwPADEEAIAIpA7gEIAEpA7gEhTcDyAxBACACKQPABCABKQPABIU3A9AMQQAgAikDyAQgASkDyASFNwPYDEEAIAIpA9AEIAEpA9AEhTcD4AxBACACKQPYBCABKQPYBIU3A+gMQQAgAikD4AQgASkD4ASFNwPwDEEAIAIpA+gEIAEpA+gEhTcD+AxBACACKQPwBCABKQPwBIU3A4ANQQAgAikD+AQgASkD+ASFNwOIDUEAIAIpA4AFIAEpA4AFhTcDkA1BACACKQOIBSABKQOIBYU3A5gNQQAgAikDkAUgASkDkAWFNwOgDUEAIAIpA5gFIAEpA5gFhTcDqA1BACACKQOgBSABKQOgBYU3A7ANQQAgAikDqAUgASkDqAWFNwO4DUEAIAIpA7AFIAEpA7AFhTcDwA1BACACKQO4BSABKQO4BYU3A8gNQQAgAikDwAUgASkDwAWFNwPQDUEAIAIpA8gFIAEpA8gFhTcD2A1BACACKQPQBSABKQPQBYU3A+ANQQAgAikD2AUgASkD2AWFNwPoDUEAIAIpA+AFIAEpA+AFhTcD8A1BACACKQPoBSABKQPoBYU3A/gNQQAgAikD8AUgASkD8AWFNwOADkEAIAIpA/gFIAEpA/gFhTcDiA5BACACKQOABiABKQOABoU3A5AOQQAgAikDiAYgASkDiAaFNwOYDkEAIAIpA5AGIAEpA5AGhTcDoA5BACACKQOYBiABKQOYBoU3A6gOQQAgAikDoAYgASkDoAaFNwOwDkEAIAIpA6gGIAEpA6gGhTcDuA5BACACKQOwBiABKQOwBoU3A8AOQQAgAikDuAYgASkDuAaFNwPIDkEAIAIpA8AGIAEpA8AGhTcD0A5BACACKQPIBiABKQPIBoU3A9gOQQAgAikD0AYgASkD0AaFNwPgDkEAIAIpA9gGIAEpA9gGhTcD6A5BACACKQPgBiABKQPgBoU3A/AOQQAgAikD6AYgASkD6AaFNwP4DkEAIAIpA/AGIAEpA/AGhTcDgA9BACACKQP4BiABKQP4BoU3A4gPQQAgAikDgAcgASkDgAeFNwOQD0EAIAIpA4gHIAEpA4gHhTcDmA9BACACKQOQByABKQOQB4U3A6APQQAgAikDmAcgASkDmAeFNwOoD0EAIAIpA6AHIAEpA6AHhTcDsA9BACACKQOoByABKQOoB4U3A7gPQQAgAikDsAcgASkDsAeFNwPAD0EAIAIpA7gHIAEpA7gHhTcDyA9BACACKQPAByABKQPAB4U3A9APQQAgAikDyAcgASkDyAeFNwPYD0EAIAIpA9AHIAEpA9AHhTcD4A9BACACKQPYByABKQPYB4U3A+gPQQAgAikD4AcgASkD4AeFNwPwD0EAIAIpA+gHIAEpA+gHhTcD+A9BACACKQPwByABKQPwB4U3A4AQQQAgAikD+AcgASkD+AeFNwOIEEGQCEGYCEGgCEGoCEGwCEG4CEHACEHICEHQCEHYCEHgCEHoCEHwCEH4CEGACUGICRACQZAJQZgJQaAJQagJQbAJQbgJQcAJQcgJQdAJQdgJQeAJQegJQfAJQfgJQYAKQYgKEAJBkApBmApBoApBqApBsApBuApBwApByApB0ApB2ApB4ApB6ApB8ApB+ApBgAtBiAsQAkGQC0GYC0GgC0GoC0GwC0G4C0HAC0HIC0HQC0HYC0HgC0HoC0HwC0H4C0GADEGIDBACQZAMQZgMQaAMQagMQbAMQbgMQcAMQcgMQdAMQdgMQeAMQegMQfAMQfgMQYANQYgNEAJBkA1BmA1BoA1BqA1BsA1BuA1BwA1ByA1B0A1B2A1B4A1B6A1B8A1B+A1BgA5BiA4QAkGQDkGYDkGgDkGoDkGwDkG4DkHADkHIDkHQDkHYDkHgDkHoDkHwDkH4DkGAD0GIDxACQZAPQZgPQaAPQagPQbAPQbgPQcAPQcgPQdAPQdgPQeAPQegPQfAPQfgPQYAQQYgQEAJBkAhBmAhBkAlBmAlBkApBmApBkAtBmAtBkAxBmAxBkA1BmA1BkA5BmA5BkA9BmA8QAkGgCEGoCEGgCUGoCUGgCkGoCkGgC0GoC0GgDEGoDEGgDUGoDUGgDkGoDkGgD0GoDxACQbAIQbgIQbAJQbgJQbAKQbgKQbALQbgLQbAMQbgMQbANQbgNQbAOQbgOQbAPQbgPEAJBwAhByAhBwAlByAlBwApByApBwAtByAtBwAxByAxBwA1ByA1BwA5ByA5BwA9ByA8QAkHQCEHYCEHQCUHYCUHQCkHYCkHQC0HYC0HQDEHYDEHQDUHYDUHQDkHYDkHQD0HYDxACQeAIQegIQeAJQegJQeAKQegKQeALQegLQeAMQegMQeANQegNQeAOQegOQeAPQegPEAJB8AhB+AhB8AlB+AlB8ApB+ApB8AtB+AtB8AxB+AxB8A1B+A1B8A5B+A5B8A9B+A8QAkGACUGICUGACkGICkGAC0GIC0GADEGIDEGADUGIDUGADkGIDkGAD0GID0GAEEGIEBACAkACQCADRQ0AA0AgACAEaiIDIAIgBGoiBSkDACABIARqIgYpAwCFIARBkAhqKQMAhSADKQMAhTcDACADQQhqIgMgBUEIaikDACAGQQhqKQMAhSAEQZgIaikDAIUgAykDAIU3AwAgBEEQaiIEQYAIRw0ADAILC0EAIQQDQCAAIARqIgMgAiAEaiIFKQMAIAEgBGoiBikDAIUgBEGQCGopAwCFNwMAIANBCGogBUEIaikDACAGQQhqKQMAhSAEQZgIaikDAIU3AwAgBEEQaiIEQYAIRw0ACwsL5QcMBX8BfgR/An4BfwF+AX8Bfgd/AX4DfwF+AkBBACgCgAgiAiABQQp0aiIDKAIIIAFHDQAgAygCDCEEIAMoAgAhBUEAIAMoAhQiBq03A7gQQQAgBK0iBzcDsBBBACAFIAEgBUECdG4iCGwiCUECdK03A6gQAkACQAJAAkAgBEUNAEF/IQogBUUNASAIQQNsIQsgCEECdCIErSEMIAWtIQ0gBkF/akECSSEOQgAhDwNAQQAgDzcDkBAgD6chEEIAIRFBACEBA0BBACARNwOgECAPIBGEUCIDIA5xIRIgBkEBRiAPUCITIAZBAkYgEUICVHFxciEUQX8gAUEBakEDcSAIbEF/aiATGyEVIAEgEHIhFiABIAhsIRcgA0EBdCEYQgAhGQNAQQBCADcDwBBBACAZNwOYECAYIQECQCASRQ0AQQBCATcDwBBBkBhBkBBBkCBBABADQZAYQZAYQZAgQQAQA0ECIQELAkAgASAITw0AIAQgGaciGmwgF2ogAWohAwNAIANBACAEIAEbQQAgEVAiGxtqQX9qIRwCQAJAIBQNAEEAKAKACCICIBxBCnQiHGohCgwBCwJAIAFB/wBxIgINAEEAQQApA8AQQgF8NwPAEEGQGEGQEEGQIEEAEANBkBhBkBhBkCBBABADCyAcQQp0IRwgAkEDdEGQGGohCkEAKAKACCECCyACIANBCnRqIAIgHGogAiAKKQMAIh1CIIinIAVwIBogFhsiHCAEbCABIAFBACAZIBytUSIcGyIKIBsbIBdqIAogC2ogExsgAUUgHHJrIhsgFWqtIB1C/////w+DIh0gHX5CIIggG61+QiCIfSAMgqdqQQp0akEBEAMgA0EBaiEDIAggAUEBaiIBRw0ACwsgGUIBfCIZIA1SDQALIBFCAXwiEachASARQgRSDQALIA9CAXwiDyAHUg0AC0EAKAKACCECCyAJQQx0QYB4aiEXIAVBf2oiCkUNAgwBC0EAQgM3A6AQQQAgBEF/aq03A5AQQYB4IRcLIAIgF2ohGyAIQQx0IQhBACEcA0AgCCAcQQFqIhxsQYB4aiEEQQAhAQNAIBsgAWoiAyADKQMAIAIgBCABamopAwCFNwMAIANBCGoiAyADKQMAIAIgBCABQQhyamopAwCFNwMAIAFBCGohAyABQRBqIQEgA0H4B0kNAAsgHCAKRw0ACwsgAiAXaiEbQXghAQNAIAIgAWoiA0EIaiAbIAFqIgRBCGopAwA3AwAgA0EQaiAEQRBqKQMANwMAIANBGGogBEEYaikDADcDACADQSBqIARBIGopAwA3AwAgAUEgaiIBQfgHSQ0ACwsL",Yl="e4cdc523",$l={name:Jl,data:Ol,hash:Yl},zl="blake2b",Vl="AGFzbQEAAAABEQRgAAF/YAJ/fwBgAX8AYAAAAwoJAAECAwECAgABBQQBAQICBg4CfwFBsIsFC38AQYAICwdwCAZtZW1vcnkCAA5IYXNoX0dldEJ1ZmZlcgAACkhhc2hfRmluYWwAAwlIYXNoX0luaXQABQtIYXNoX1VwZGF0ZQAGDUhhc2hfR2V0U3RhdGUABw5IYXNoX0NhbGN1bGF0ZQAIClNUQVRFX1NJWkUDAQrTOAkFAEGACQvrAgIFfwF+AkAgAUEBSA0AAkACQAJAIAFBgAFBACgC4IoBIgJrIgNKDQAgASEEDAELQQBBADYC4IoBAkAgAkH/AEoNACACQeCJAWohBSAAIQRBACEGA0AgBSAELQAAOgAAIARBAWohBCAFQQFqIQUgAyAGQQFqIgZB/wFxSg0ACwtBAEEAKQPAiQEiB0KAAXw3A8CJAUEAQQApA8iJASAHQv9+Vq18NwPIiQFB4IkBEAIgACADaiEAAkAgASADayIEQYEBSA0AIAIgAWohBQNAQQBBACkDwIkBIgdCgAF8NwPAiQFBAEEAKQPIiQEgB0L/flatfDcDyIkBIAAQAiAAQYABaiEAIAVBgH9qIgVBgAJLDQALIAVBgH9qIQQMAQsgBEEATA0BC0EAIQUDQCAFQQAoAuCKAWpB4IkBaiAAIAVqLQAAOgAAIAQgBUEBaiIFQf8BcUoNAAsLQQBBACgC4IoBIARqNgLgigELC78uASR+QQBBACkD0IkBQQApA7CJASIBQQApA5CJAXwgACkDICICfCIDhULr+obav7X2wR+FQiCJIgRCq/DT9K/uvLc8fCIFIAGFQiiJIgYgA3wgACkDKCIBfCIHIASFQjCJIgggBXwiCSAGhUIBiSIKQQApA8iJAUEAKQOoiQEiBEEAKQOIiQF8IAApAxAiA3wiBYVCn9j52cKR2oKbf4VCIIkiC0K7zqqm2NDrs7t/fCIMIASFQiiJIg0gBXwgACkDGCIEfCIOfCAAKQNQIgV8Ig9BACkDwIkBQQApA6CJASIQQQApA4CJASIRfCAAKQMAIgZ8IhKFQtGFmu/6z5SH0QCFQiCJIhNCiJLznf/M+YTqAHwiFCAQhUIoiSIVIBJ8IAApAwgiEHwiFiAThUIwiSIXhUIgiSIYQQApA9iJAUEAKQO4iQEiE0EAKQOYiQF8IAApAzAiEnwiGYVC+cL4m5Gjs/DbAIVCIIkiGkLx7fT4paf9p6V/fCIbIBOFQiiJIhwgGXwgACkDOCITfCIZIBqFQjCJIhogG3wiG3wiHSAKhUIoiSIeIA98IAApA1giCnwiDyAYhUIwiSIYIB18Ih0gDiALhUIwiSIOIAx8Ih8gDYVCAYkiDCAWfCAAKQNAIgt8Ig0gGoVCIIkiFiAJfCIaIAyFQiiJIiAgDXwgACkDSCIJfCIhIBaFQjCJIhYgGyAchUIBiSIMIAd8IAApA2AiB3wiDSAOhUIgiSIOIBcgFHwiFHwiFyAMhUIoiSIbIA18IAApA2giDHwiHCAOhUIwiSIOIBd8IhcgG4VCAYkiGyAZIBQgFYVCAYkiFHwgACkDcCINfCIVIAiFQiCJIhkgH3wiHyAUhUIoiSIUIBV8IAApA3giCHwiFXwgDHwiIoVCIIkiI3wiJCAbhUIoiSIbICJ8IBJ8IiIgFyAYIBUgGYVCMIkiFSAffCIZIBSFQgGJIhQgIXwgDXwiH4VCIIkiGHwiFyAUhUIoiSIUIB98IAV8Ih8gGIVCMIkiGCAXfCIXIBSFQgGJIhR8IAF8IiEgFiAafCIWIBUgHSAehUIBiSIaIBx8IAl8IhyFQiCJIhV8Ih0gGoVCKIkiGiAcfCAIfCIcIBWFQjCJIhWFQiCJIh4gGSAOIBYgIIVCAYkiFiAPfCACfCIPhUIgiSIOfCIZIBaFQiiJIhYgD3wgC3wiDyAOhUIwiSIOIBl8Ihl8IiAgFIVCKIkiFCAhfCAEfCIhIB6FQjCJIh4gIHwiICAiICOFQjCJIiIgJHwiIyAbhUIBiSIbIBx8IAp8IhwgDoVCIIkiDiAXfCIXIBuFQiiJIhsgHHwgE3wiHCAOhUIwiSIOIBkgFoVCAYkiFiAffCAQfCIZICKFQiCJIh8gFSAdfCIVfCIdIBaFQiiJIhYgGXwgB3wiGSAfhUIwiSIfIB18Ih0gFoVCAYkiFiAVIBqFQgGJIhUgD3wgBnwiDyAYhUIgiSIYICN8IhogFYVCKIkiFSAPfCADfCIPfCAHfCIihUIgiSIjfCIkIBaFQiiJIhYgInwgBnwiIiAjhUIwiSIjICR8IiQgFoVCAYkiFiAOIBd8Ig4gDyAYhUIwiSIPICAgFIVCAYkiFCAZfCAKfCIXhUIgiSIYfCIZIBSFQiiJIhQgF3wgC3wiF3wgBXwiICAPIBp8Ig8gHyAOIBuFQgGJIg4gIXwgCHwiGoVCIIkiG3wiHyAOhUIoiSIOIBp8IAx8IhogG4VCMIkiG4VCIIkiISAdIB4gDyAVhUIBiSIPIBx8IAF8IhWFQiCJIhx8Ih0gD4VCKIkiDyAVfCADfCIVIByFQjCJIhwgHXwiHXwiHiAWhUIoiSIWICB8IA18IiAgIYVCMIkiISAefCIeIBogFyAYhUIwiSIXIBl8IhggFIVCAYkiFHwgCXwiGSAchUIgiSIaICR8IhwgFIVCKIkiFCAZfCACfCIZIBqFQjCJIhogHSAPhUIBiSIPICJ8IAR8Ih0gF4VCIIkiFyAbIB98Iht8Ih8gD4VCKIkiDyAdfCASfCIdIBeFQjCJIhcgH3wiHyAPhUIBiSIPIBsgDoVCAYkiDiAVfCATfCIVICOFQiCJIhsgGHwiGCAOhUIoiSIOIBV8IBB8IhV8IAx8IiKFQiCJIiN8IiQgD4VCKIkiDyAifCAHfCIiICOFQjCJIiMgJHwiJCAPhUIBiSIPIBogHHwiGiAVIBuFQjCJIhUgHiAWhUIBiSIWIB18IAR8IhuFQiCJIhx8Ih0gFoVCKIkiFiAbfCAQfCIbfCABfCIeIBUgGHwiFSAXIBogFIVCAYkiFCAgfCATfCIYhUIgiSIXfCIaIBSFQiiJIhQgGHwgCXwiGCAXhUIwiSIXhUIgiSIgIB8gISAVIA6FQgGJIg4gGXwgCnwiFYVCIIkiGXwiHyAOhUIoiSIOIBV8IA18IhUgGYVCMIkiGSAffCIffCIhIA+FQiiJIg8gHnwgBXwiHiAghUIwiSIgICF8IiEgGyAchUIwiSIbIB18IhwgFoVCAYkiFiAYfCADfCIYIBmFQiCJIhkgJHwiHSAWhUIoiSIWIBh8IBJ8IhggGYVCMIkiGSAfIA6FQgGJIg4gInwgAnwiHyAbhUIgiSIbIBcgGnwiF3wiGiAOhUIoiSIOIB98IAZ8Ih8gG4VCMIkiGyAafCIaIA6FQgGJIg4gFSAXIBSFQgGJIhR8IAh8IhUgI4VCIIkiFyAcfCIcIBSFQiiJIhQgFXwgC3wiFXwgBXwiIoVCIIkiI3wiJCAOhUIoiSIOICJ8IAh8IiIgGiAgIBUgF4VCMIkiFSAcfCIXIBSFQgGJIhQgGHwgCXwiGIVCIIkiHHwiGiAUhUIoiSIUIBh8IAZ8IhggHIVCMIkiHCAafCIaIBSFQgGJIhR8IAR8IiAgGSAdfCIZIBUgISAPhUIBiSIPIB98IAN8Ih2FQiCJIhV8Ih8gD4VCKIkiDyAdfCACfCIdIBWFQjCJIhWFQiCJIiEgFyAbIBkgFoVCAYkiFiAefCABfCIZhUIgiSIbfCIXIBaFQiiJIhYgGXwgE3wiGSAbhUIwiSIbIBd8Ihd8Ih4gFIVCKIkiFCAgfCAMfCIgICGFQjCJIiEgHnwiHiAiICOFQjCJIiIgJHwiIyAOhUIBiSIOIB18IBJ8Ih0gG4VCIIkiGyAafCIaIA6FQiiJIg4gHXwgC3wiHSAbhUIwiSIbIBcgFoVCAYkiFiAYfCANfCIXICKFQiCJIhggFSAffCIVfCIfIBaFQiiJIhYgF3wgEHwiFyAYhUIwiSIYIB98Ih8gFoVCAYkiFiAVIA+FQgGJIg8gGXwgCnwiFSAchUIgiSIZICN8IhwgD4VCKIkiDyAVfCAHfCIVfCASfCIihUIgiSIjfCIkIBaFQiiJIhYgInwgBXwiIiAjhUIwiSIjICR8IiQgFoVCAYkiFiAbIBp8IhogFSAZhUIwiSIVIB4gFIVCAYkiFCAXfCADfCIXhUIgiSIZfCIbIBSFQiiJIhQgF3wgB3wiF3wgAnwiHiAVIBx8IhUgGCAaIA6FQgGJIg4gIHwgC3wiGoVCIIkiGHwiHCAOhUIoiSIOIBp8IAR8IhogGIVCMIkiGIVCIIkiICAfICEgFSAPhUIBiSIPIB18IAZ8IhWFQiCJIh18Ih8gD4VCKIkiDyAVfCAKfCIVIB2FQjCJIh0gH3wiH3wiISAWhUIoiSIWIB58IAx8Ih4gIIVCMIkiICAhfCIhIBogFyAZhUIwiSIXIBt8IhkgFIVCAYkiFHwgEHwiGiAdhUIgiSIbICR8Ih0gFIVCKIkiFCAafCAJfCIaIBuFQjCJIhsgHyAPhUIBiSIPICJ8IBN8Ih8gF4VCIIkiFyAYIBx8Ihh8IhwgD4VCKIkiDyAffCABfCIfIBeFQjCJIhcgHHwiHCAPhUIBiSIPIBggDoVCAYkiDiAVfCAIfCIVICOFQiCJIhggGXwiGSAOhUIoiSIOIBV8IA18IhV8IA18IiKFQiCJIiN8IiQgD4VCKIkiDyAifCAMfCIiICOFQjCJIiMgJHwiJCAPhUIBiSIPIBsgHXwiGyAVIBiFQjCJIhUgISAWhUIBiSIWIB98IBB8IhiFQiCJIh18Ih8gFoVCKIkiFiAYfCAIfCIYfCASfCIhIBUgGXwiFSAXIBsgFIVCAYkiFCAefCAHfCIZhUIgiSIXfCIbIBSFQiiJIhQgGXwgAXwiGSAXhUIwiSIXhUIgiSIeIBwgICAVIA6FQgGJIg4gGnwgAnwiFYVCIIkiGnwiHCAOhUIoiSIOIBV8IAV8IhUgGoVCMIkiGiAcfCIcfCIgIA+FQiiJIg8gIXwgBHwiISAehUIwiSIeICB8IiAgGCAdhUIwiSIYIB98Ih0gFoVCAYkiFiAZfCAGfCIZIBqFQiCJIhogJHwiHyAWhUIoiSIWIBl8IBN8IhkgGoVCMIkiGiAcIA6FQgGJIg4gInwgCXwiHCAYhUIgiSIYIBcgG3wiF3wiGyAOhUIoiSIOIBx8IAN8IhwgGIVCMIkiGCAbfCIbIA6FQgGJIg4gFSAXIBSFQgGJIhR8IAt8IhUgI4VCIIkiFyAdfCIdIBSFQiiJIhQgFXwgCnwiFXwgBHwiIoVCIIkiI3wiJCAOhUIoiSIOICJ8IAl8IiIgGyAeIBUgF4VCMIkiFSAdfCIXIBSFQgGJIhQgGXwgDHwiGYVCIIkiHXwiGyAUhUIoiSIUIBl8IAp8IhkgHYVCMIkiHSAbfCIbIBSFQgGJIhR8IAN8Ih4gGiAffCIaIBUgICAPhUIBiSIPIBx8IAd8IhyFQiCJIhV8Ih8gD4VCKIkiDyAcfCAQfCIcIBWFQjCJIhWFQiCJIiAgFyAYIBogFoVCAYkiFiAhfCATfCIahUIgiSIYfCIXIBaFQiiJIhYgGnwgDXwiGiAYhUIwiSIYIBd8Ihd8IiEgFIVCKIkiFCAefCAFfCIeICCFQjCJIiAgIXwiISAiICOFQjCJIiIgJHwiIyAOhUIBiSIOIBx8IAt8IhwgGIVCIIkiGCAbfCIbIA6FQiiJIg4gHHwgEnwiHCAYhUIwiSIYIBcgFoVCAYkiFiAZfCABfCIXICKFQiCJIhkgFSAffCIVfCIfIBaFQiiJIhYgF3wgBnwiFyAZhUIwiSIZIB98Ih8gFoVCAYkiFiAVIA+FQgGJIg8gGnwgCHwiFSAdhUIgiSIaICN8Ih0gD4VCKIkiDyAVfCACfCIVfCANfCIihUIgiSIjfCIkIBaFQiiJIhYgInwgCXwiIiAjhUIwiSIjICR8IiQgFoVCAYkiFiAYIBt8IhggFSAahUIwiSIVICEgFIVCAYkiFCAXfCASfCIXhUIgiSIafCIbIBSFQiiJIhQgF3wgCHwiF3wgB3wiISAVIB18IhUgGSAYIA6FQgGJIg4gHnwgBnwiGIVCIIkiGXwiHSAOhUIoiSIOIBh8IAt8IhggGYVCMIkiGYVCIIkiHiAfICAgFSAPhUIBiSIPIBx8IAp8IhWFQiCJIhx8Ih8gD4VCKIkiDyAVfCAEfCIVIByFQjCJIhwgH3wiH3wiICAWhUIoiSIWICF8IAN8IiEgHoVCMIkiHiAgfCIgIBggFyAahUIwiSIXIBt8IhogFIVCAYkiFHwgBXwiGCAchUIgiSIbICR8IhwgFIVCKIkiFCAYfCABfCIYIBuFQjCJIhsgHyAPhUIBiSIPICJ8IAx8Ih8gF4VCIIkiFyAZIB18Ihl8Ih0gD4VCKIkiDyAffCATfCIfIBeFQjCJIhcgHXwiHSAPhUIBiSIPIBkgDoVCAYkiDiAVfCAQfCIVICOFQiCJIhkgGnwiGiAOhUIoiSIOIBV8IAJ8IhV8IBN8IiKFQiCJIiN8IiQgD4VCKIkiDyAifCASfCIiICOFQjCJIiMgJHwiJCAPhUIBiSIPIBsgHHwiGyAVIBmFQjCJIhUgICAWhUIBiSIWIB98IAt8IhmFQiCJIhx8Ih8gFoVCKIkiFiAZfCACfCIZfCAJfCIgIBUgGnwiFSAXIBsgFIVCAYkiFCAhfCAFfCIahUIgiSIXfCIbIBSFQiiJIhQgGnwgA3wiGiAXhUIwiSIXhUIgiSIhIB0gHiAVIA6FQgGJIg4gGHwgEHwiFYVCIIkiGHwiHSAOhUIoiSIOIBV8IAF8IhUgGIVCMIkiGCAdfCIdfCIeIA+FQiiJIg8gIHwgDXwiICAhhUIwiSIhIB58Ih4gGSAchUIwiSIZIB98IhwgFoVCAYkiFiAafCAIfCIaIBiFQiCJIhggJHwiHyAWhUIoiSIWIBp8IAp8IhogGIVCMIkiGCAdIA6FQgGJIg4gInwgBHwiHSAZhUIgiSIZIBcgG3wiF3wiGyAOhUIoiSIOIB18IAd8Ih0gGYVCMIkiGSAbfCIbIA6FQgGJIg4gFSAXIBSFQgGJIhR8IAx8IhUgI4VCIIkiFyAcfCIcIBSFQiiJIhQgFXwgBnwiFXwgEnwiIoVCIIkiI3wiJCAOhUIoiSIOICJ8IBN8IiIgGyAhIBUgF4VCMIkiFSAcfCIXIBSFQgGJIhQgGnwgBnwiGoVCIIkiHHwiGyAUhUIoiSIUIBp8IBB8IhogHIVCMIkiHCAbfCIbIBSFQgGJIhR8IA18IiEgGCAffCIYIBUgHiAPhUIBiSIPIB18IAJ8Ih2FQiCJIhV8Ih4gD4VCKIkiDyAdfCABfCIdIBWFQjCJIhWFQiCJIh8gFyAZIBggFoVCAYkiFiAgfCADfCIYhUIgiSIZfCIXIBaFQiiJIhYgGHwgBHwiGCAZhUIwiSIZIBd8Ihd8IiAgFIVCKIkiFCAhfCAIfCIhIB+FQjCJIh8gIHwiICAiICOFQjCJIiIgJHwiIyAOhUIBiSIOIB18IAd8Ih0gGYVCIIkiGSAbfCIbIA6FQiiJIg4gHXwgDHwiHSAZhUIwiSIZIBcgFoVCAYkiFiAafCALfCIXICKFQiCJIhogFSAefCIVfCIeIBaFQiiJIhYgF3wgCXwiFyAahUIwiSIaIB58Ih4gFoVCAYkiFiAVIA+FQgGJIg8gGHwgBXwiFSAchUIgiSIYICN8IhwgD4VCKIkiDyAVfCAKfCIVfCACfCIChUIgiSIifCIjIBaFQiiJIhYgAnwgC3wiAiAihUIwiSILICN8IiIgFoVCAYkiFiAZIBt8IhkgFSAYhUIwiSIVICAgFIVCAYkiFCAXfCANfCINhUIgiSIXfCIYIBSFQiiJIhQgDXwgBXwiBXwgEHwiECAVIBx8Ig0gGiAZIA6FQgGJIg4gIXwgDHwiDIVCIIkiFXwiGSAOhUIoiSIOIAx8IBJ8IhIgFYVCMIkiDIVCIIkiFSAeIB8gDSAPhUIBiSINIB18IAl8IgmFQiCJIg98IhogDYVCKIkiDSAJfCAIfCIJIA+FQjCJIgggGnwiD3wiGiAWhUIoiSIWIBB8IAd8IhAgEYUgDCAZfCIHIA6FQgGJIgwgCXwgCnwiCiALhUIgiSILIAUgF4VCMIkiBSAYfCIJfCIOIAyFQiiJIgwgCnwgE3wiEyALhUIwiSIKIA58IguFNwOAiQFBACADIAYgDyANhUIBiSINIAJ8fCICIAWFQiCJIgUgB3wiBiANhUIoiSIHIAJ8fCICQQApA4iJAYUgBCABIBIgCSAUhUIBiSIDfHwiASAIhUIgiSISICJ8IgkgA4VCKIkiAyABfHwiASAShUIwiSIEIAl8IhKFNwOIiQFBACATQQApA5CJAYUgECAVhUIwiSIQIBp8IhOFNwOQiQFBACABQQApA5iJAYUgAiAFhUIwiSICIAZ8IgGFNwOYiQFBACASIAOFQgGJQQApA6CJAYUgAoU3A6CJAUEAIBMgFoVCAYlBACkDqIkBhSAKhTcDqIkBQQAgASAHhUIBiUEAKQOwiQGFIASFNwOwiQFBACALIAyFQgGJQQApA7iJAYUgEIU3A7iJAQvdAgUBfwF+AX8BfgJ/IwBBwABrIgAkAAJAQQApA9CJAUIAUg0AQQBBACkDwIkBIgFBACgC4IoBIgKsfCIDNwPAiQFBAEEAKQPIiQEgAyABVK18NwPIiQECQEEALQDoigFFDQBBAEJ/NwPYiQELQQBCfzcD0IkBAkAgAkH/AEoNAEEAIQQDQCACIARqQeCJAWpBADoAACAEQQFqIgRBgAFBACgC4IoBIgJrSA0ACwtB4IkBEAIgAEEAKQOAiQE3AwAgAEEAKQOIiQE3AwggAEEAKQOQiQE3AxAgAEEAKQOYiQE3AxggAEEAKQOgiQE3AyAgAEEAKQOoiQE3AyggAEEAKQOwiQE3AzAgAEEAKQO4iQE3AzhBACgC5IoBIgVBAUgNAEEAIQRBACECA0AgBEGACWogACAEai0AADoAACAEQQFqIQQgBSACQQFqIgJB/wFxSg0ACwsgAEHAAGokAAv9AwMBfwF+AX8jAEGAAWsiAiQAQQBBgQI7AfKKAUEAIAE6APGKAUEAIAA6APCKAUGQfiEAA0AgAEGAiwFqQgA3AAAgAEH4igFqQgA3AAAgAEHwigFqQgA3AAAgAEEYaiIADQALQQAhAEEAQQApA/CKASIDQoiS853/zPmE6gCFNwOAiQFBAEEAKQP4igFCu86qptjQ67O7f4U3A4iJAUEAQQApA4CLAUKr8NP0r+68tzyFNwOQiQFBAEEAKQOIiwFC8e30+KWn/aelf4U3A5iJAUEAQQApA5CLAULRhZrv+s+Uh9EAhTcDoIkBQQBBACkDmIsBQp/Y+dnCkdqCm3+FNwOoiQFBAEEAKQOgiwFC6/qG2r+19sEfhTcDsIkBQQBBACkDqIsBQvnC+JuRo7Pw2wCFNwO4iQFBACADp0H/AXE2AuSKAQJAIAFBAUgNACACQgA3A3ggAkIANwNwIAJCADcDaCACQgA3A2AgAkIANwNYIAJCADcDUCACQgA3A0ggAkIANwNAIAJCADcDOCACQgA3AzAgAkIANwMoIAJCADcDICACQgA3AxggAkIANwMQIAJCADcDCCACQgA3AwBBACEEA0AgAiAAaiAAQYAJai0AADoAACAAQQFqIQAgBEEBaiIEQf8BcSABSA0ACyACQYABEAELIAJBgAFqJAALEgAgAEEDdkH/P3EgAEEQdhAECwkAQYAJIAAQAQsGAEGAiQELGwAgAUEDdkH/P3EgAUEQdhAEQYAJIAAQARADCwsLAQBBgAgLBPAAAAA=",Wl="c6f286e6",Xl={name:zl,data:Vl,hash:Wl},aA=new re;function Ra(A){return!Number.isInteger(A)||A<8||A>512||A%8!==0?new Error("Invalid variant! Valid values: 8, 16, ..., 512"):null}function Zl(A,r){return A|r<<16}function ao(A=512,r=null){if(Ra(A))return Promise.reject(Ra(A));let e=null,t=A;if(r!==null){if(e=At(r),e.length>64)return Promise.reject(new Error("Max key length is 64 bytes"));t=Zl(A,e.length)}let i=A/8;return Ka(Xl,i).then(s=>{t>512&&s.writeMemory(e),s.init(t);let n={init:t>512?()=>(s.writeMemory(e),s.init(t),n):()=>(s.init(t),n),update:o=>(s.update(o),n),digest:o=>s.digest(o),save:()=>s.save(),load:o=>(s.load(o),n),blockSize:128,digestSize:i};return n})}function jl(A,r,e){let t=[`m=${r.memorySize}`,`t=${r.iterations}`,`p=${r.parallelism}`].join(",");return`$argon2${r.hashType}$v=19$${t}$${Ua(A,!1)}$${Ua(e,!1)}`}var Ga=new DataView(new ArrayBuffer(4));function dt(A){return Ga.setInt32(0,A,!0),new Uint8Array(Ga.buffer)}function so(A,r,e){return Lt(this,void 0,void 0,function*(){if(e<=64){let a=yield ao(e*8);return a.update(dt(e)),a.update(r),a.digest("binary")}let t=Math.ceil(e/32)-2,i=new Uint8Array(e);A.init(),A.update(dt(e)),A.update(r);let s=A.digest("binary");i.set(s.subarray(0,32),0);for(let a=1;a<t;a++)A.init(),A.update(s),s=A.digest("binary"),i.set(s.subarray(0,32),a*32);let n=e-32*t,o;return n===64?(o=A,o.init()):o=yield ao(n*8),o.update(s),s=o.digest("binary"),i.set(s.subarray(0,n),t*32),i})}function _l(A){switch(A){case"d":return 0;case"i":return 1;default:return 2}}function ec(A){return Lt(this,void 0,void 0,function*(){var r;let{parallelism:e,iterations:t,hashLength:i}=A,s=At(A.password),n=At(A.salt),o=19,a=_l(A.hashType),{memorySize:l}=A,c=At((r=A.secret)!==null&&r!==void 0?r:""),[d,h]=yield Promise.all([Ka($l,1024),ao(512)]);d.setMemorySize(l*1024+1024);let g=new Uint8Array(24),u=new DataView(g.buffer);u.setInt32(0,e,!0),u.setInt32(4,i,!0),u.setInt32(8,l,!0),u.setInt32(12,t,!0),u.setInt32(16,o,!0),u.setInt32(20,a,!0),d.writeMemory(g,l*1024),h.init(),h.update(g),h.update(dt(s.length)),h.update(s),h.update(dt(n.length)),h.update(n),h.update(dt(c.length)),h.update(c),h.update(dt(0));let f=Math.floor(l/(e*4))*4,I=new Uint8Array(72),m=h.digest("binary");I.set(m);for(let w=0;w<e;w++){I.set(dt(0),64),I.set(dt(w),68);let y=w*f,E=yield so(h,I,1024);d.writeMemory(E,y*1024),y+=1,I.set(dt(1),64),E=yield so(h,I,1024),d.writeMemory(E,y*1024)}let C=new Uint8Array(1024);qa(C,d.calculate(new Uint8Array([]),l));let B=yield so(h,C,i);if(A.outputType==="hex"){let w=new Uint8Array(i*2);return oo(w,B,i)}return A.outputType==="encoded"?jl(n,A,B):B})}var tc=A=>{var r;if(!A||typeof A!="object")throw new Error("Invalid options parameter. It requires an object.");if(!A.password)throw new Error("Password must be specified");if(A.password=At(A.password),A.password.length<1)throw new Error("Password must be specified");if(!A.salt)throw new Error("Salt must be specified");if(A.salt=At(A.salt),A.salt.length<8)throw new Error("Salt should be at least 8 bytes long");if(A.secret=At((r=A.secret)!==null&&r!==void 0?r:""),!Number.isInteger(A.iterations)||A.iterations<1)throw new Error("Iterations should be a positive number");if(!Number.isInteger(A.parallelism)||A.parallelism<1)throw new Error("Parallelism should be a positive number");if(!Number.isInteger(A.hashLength)||A.hashLength<4)throw new Error("Hash length should be at least 4 bytes.");if(!Number.isInteger(A.memorySize))throw new Error("Memory size should be specified.");if(A.memorySize<8*A.parallelism)throw new Error("Memory size should be at least 8 * parallelism.");if(A.outputType===void 0&&(A.outputType="hex"),!["hex","binary","encoded"].includes(A.outputType))throw new Error(`Insupported output type ${A.outputType}. Valid values: ['hex', 'binary', 'encoded']`)};function ro(A){return Lt(this,void 0,void 0,function*(){return tc(A),ec(Object.assign(Object.assign({},A),{hashType:"id"}))})}var rA=new re;var lA=new re;var cA=new re;var dA=new re;var AA=new Uint8Array(8);var hA=new re;var pA=new re;var gA=new re;var uA=new re;var fA=new re;var IA=new re;var mA=new re;var CA=new re;var BA=new re;var yA=new re;var wA=new re;var QA=new Uint8Array(8);var EA=new re;var vA=new Uint8Array(8);var FA=new re;var kA=new Uint8Array(8);var SA=new re;var bA=new re;var DA=new re;Be();var Oe=new Uint8Array([83,84,65,83,72,69,78,67]),Ao=2,ho=1,po=2,Ya=3,ic=32,ri=12,lo=32,sc=6e5,nc=47104,oc=3,ac=1,ge=A=>A;function Ne(A){if(A.length<Oe.length+1)return!1;for(let r=0;r<Oe.length;r++)if(A[r]!==Oe[r])return!1;return!0}function Ja(A,r,e){A[r]=e>>>24&255,A[r+1]=e>>>16&255,A[r+2]=e>>>8&255,A[r+3]=e&255}function Oa(A,r){return A[r]*16777216+(A[r+1]<<16)+(A[r+2]<<8)+A[r+3]}async function co(A,r,e){if(e.id===po)return await ro({password:A,salt:r,parallelism:e.c,iterations:e.b,memorySize:e.a,hashLength:lo,outputType:"binary"});if(e.id===ho){let t=await crypto.subtle.importKey("raw",ge(new TextEncoder().encode(A)),"PBKDF2",!1,["deriveBits"]),i=await crypto.subtle.deriveBits({name:"PBKDF2",salt:ge(r),iterations:e.a,hash:"SHA-256"},t,lo*8);return new Uint8Array(i)}throw new Error(`Unsupported KDF id (${e.id}).`)}async function xs(A){return crypto.subtle.importKey("raw",ge(A),{name:"AES-GCM"},!1,["encrypt","decrypt"])}var go={argon2id:{label:"Argon2id",strong:!0},pbkdf2:{label:"PBKDF2 (fallback)",strong:!1}};async function _e(A,r){let e=crypto.getRandomValues(new Uint8Array(ic)),t=crypto.getRandomValues(new Uint8Array(ri)),i,s,n;try{i={id:po,a:nc,b:oc,c:ac},n=await co(r,e,i),s="argon2id"}catch(d){console.warn("[Stashpad] Argon2id unavailable \u2014 encrypting with PBKDF2 fallback.",d),i={id:ho,a:sc,b:0,c:0},n=await co(r,e,i),s="pbkdf2"}let o=$a(i,e,t),a=await xs(n),l=new Uint8Array(await crypto.subtle.encrypt({name:"AES-GCM",iv:ge(t),additionalData:ge(o)},a,ge(A))),c=new Uint8Array(o.length+l.length);return c.set(o,0),c.set(l,o.length),{data:c,kdf:s}}var Wi=null;async function Xi(){if(Wi!==null)return Wi;try{await ro({password:"x",salt:new Uint8Array(16),parallelism:1,iterations:1,memorySize:8,hashLength:16,outputType:"binary"}),Wi=!0}catch{Wi=!1}return Wi}function $a(A,r,e){let t=new Uint8Array(Oe.length+1+1+4+4+1+1+r.length+e.length),i=0;return t.set(Oe,i),i+=Oe.length,t[i++]=Ao,t[i++]=A.id,Ja(t,i,A.a),i+=4,Ja(t,i,A.b),i+=4,t[i++]=A.c,t[i++]=r.length,t.set(r,i),i+=r.length,t.set(e,i),i+=e.length,t}async function je(A,r){if(!Ne(A))throw new Error("Not an encrypted Stashpad file.");let e=A[Oe.length];if(e===1)return rc(A,r);if(e!==Ao)throw new Error(`Unsupported encrypted .stash version (${e}).`);let t=Oe.length+1,i={id:A[t++],a:0,b:0,c:0};if(i.a=Oa(A,t),t+=4,i.b=Oa(A,t),t+=4,i.c=A[t++],i.id===po){if(i.a>2097152||i.b>64||i.c<1||i.c>8)throw new Error("Unsupported KDF parameters (file may be corrupted or malicious).")}else if(i.id===ho&&i.a>1e7)throw new Error("Unsupported KDF parameters (file may be corrupted or malicious).");let s=A[t++];if(s<8||s>64)throw new Error("Unsupported KDF parameters (file may be corrupted or malicious).");let n=A.slice(t,t+s);t+=s;let o=A.slice(t,t+ri);t+=ri;let a=A.slice(0,t),l=A.slice(t),c=await co(r,n,i),d=await xs(c),h=await crypto.subtle.decrypt({name:"AES-GCM",iv:ge(o),additionalData:ge(a)},d,ge(l));return new Uint8Array(h)}async function rc(A,r){let e=Oe.length+1,t=A.slice(e,e+16);e+=16;let i=A.slice(e,e+12);e+=12;let s=A.slice(e),n=await crypto.subtle.importKey("raw",ge(new TextEncoder().encode(r)),"PBKDF2",!1,["deriveKey"]),o=await crypto.subtle.deriveKey({name:"PBKDF2",salt:ge(t),iterations:21e4,hash:"SHA-256"},n,{name:"AES-GCM",length:256},!1,["decrypt"]),a=await crypto.subtle.decrypt({name:"AES-GCM",iv:ge(i)},o,ge(s));return new Uint8Array(a)}async function wt(A,r){if(r.length!==lo)throw new Error("Encryption key must be 32 bytes.");let e=crypto.getRandomValues(new Uint8Array(ri)),i=$a({id:Ya,a:0,b:0,c:0},new Uint8Array(0),e),s=await xs(r),n=new Uint8Array(await crypto.subtle.encrypt({name:"AES-GCM",iv:ge(e),additionalData:ge(i)},s,ge(A))),o=new Uint8Array(i.length+n.length);return o.set(i,0),o.set(n,i.length),o}async function ve(A,r){if(!Ne(A))throw new Error("Not an encrypted Stashpad file.");let e=A[Oe.length];if(e!==Ao)throw new Error(`Unsupported encrypted version (${e}).`);let t=Oe.length+1;if(A[t++]!==Ya)throw new Error("Not a raw-key (.stashenc) envelope.");t+=9;let s=A[t++];t+=s;let n=A.slice(t,t+ri);t+=ri;let o=A.slice(0,t),a=A.slice(t),l=await xs(r),c=await crypto.subtle.decrypt({name:"AES-GCM",iv:ge(n),additionalData:ge(o)},l,ge(a));return new Uint8Array(c)}async function li(A,r,e={}){if(!Ne(r))return r;if(e.secretId){let i=A.secretStorage,s=null;try{s=i?.getSecret(e.secretId)??null}catch{s=null}if(s)try{return await je(r,s)}catch{}}let t;for(;;){let i=await new Promise(s=>{new Pi(A,t,!!e.allowLater,s).open()});if(i.kind==="cancel")return null;if(i.kind==="later")return e.onLater?.(),null;try{return await je(r,i.value)}catch{t="Wrong password or corrupted file. Try again."}}}qe();var Ns={name:"ECDH",namedCurve:"P-256"},lc="stashpad-vault-dek-wrap",cc=new TextEncoder;function uo(A){let r="";for(let e=0;e<A.length;e++)r+=String.fromCharCode(A[e]);return btoa(r)}function fo(A){let r=atob(A),e=new Uint8Array(r.length);for(let t=0;t<r.length;t++)e[t]=r.charCodeAt(t);return e}async function za(){let A=await crypto.subtle.generateKey(Ns,!0,["deriveBits"]);return{pubKeySpki:new Uint8Array(await crypto.subtle.exportKey("spki",A.publicKey)),privKeyPkcs8:new Uint8Array(await crypto.subtle.exportKey("pkcs8",A.privateKey))}}function Va(A){return crypto.subtle.importKey("spki",A,Ns,!1,[])}function dc(A){return crypto.subtle.importKey("pkcs8",A,Ns,!1,["deriveBits"])}async function Wa(A,r){let e=await crypto.subtle.deriveBits({name:"ECDH",public:r},A,256),t=await crypto.subtle.importKey("raw",e,"HKDF",!1,["deriveKey"]);return crypto.subtle.deriveKey({name:"HKDF",hash:"SHA-256",salt:new Uint8Array(0),info:cc.encode(lc)},t,{name:"AES-GCM",length:256},!1,["encrypt","decrypt"])}async function Ms(A,r,e){let t=await crypto.subtle.generateKey(Ns,!0,["deriveBits"]),i=await Wa(t.privateKey,await Va(r)),s=crypto.getRandomValues(new Uint8Array(12)),n=new Uint8Array(await crypto.subtle.encrypt({name:"AES-GCM",iv:s},i,A)),o=new Uint8Array(await crypto.subtle.exportKey("spki",t.publicKey));return{recipientId:e,ephPub:uo(o),iv:uo(s),ct:uo(n)}}async function Xa(A,r){let e=await Wa(await dc(r),await Va(fo(A.ephPub)));return new Uint8Array(await crypto.subtle.decrypt({name:"AES-GCM",iv:fo(A.iv)},e,fo(A.ct)))}var Za=".stashpad/keys.json",Ac=".stashpad",Zi="_keys";function Io(A){return{v:2,keyId:A,identities:[],slots:[],joinRequests:[]}}var Ts=class{constructor(r){this.app=r}get a(){return this.app.vault.adapter}validate(r){let e=r;return!!e&&e.v===2&&typeof e.keyId=="string"&&Array.isArray(e.identities)&&Array.isArray(e.slots)&&Array.isArray(e.joinRequests)}async readValid(r){try{if(!await this.a.exists(r))return null;let e=JSON.parse(await this.a.read(r));return this.validate(e)?e:null}catch{return null}}async load(){let r=await this.readValid(Za);if(r)return r;try{let t=((await this.a.list(Zi)).files||[]).filter(i=>/\/keys-\d+\.json$/.test(i)).sort();for(let i of t.reverse()){let s=await this.readValid(i);if(s)return s}}catch{}return null}async exists(){return await this.load()!==null}async ensureDir(r){try{await this.a.exists(r)||await this.a.mkdir(r)}catch{}}async save(r){let e=JSON.stringify(r,null,2);await this.ensureDir(Ac),await this.a.write(Za,e),await this.ensureDir(Zi);for(let t=4;t>=1;t--){let i=`${Zi}/keys-${t}.json`,s=`${Zi}/keys-${t+1}.json`;try{await this.a.exists(i)&&await this.a.write(s,await this.a.read(i))}catch{}}try{await this.a.write(`${Zi}/keys-1.json`,e)}catch{}}};var qt="stashpad-vault-encryption";function ji(){return{wrappedKey:null,kdf:null,identityId:null,identityLabel:null,identityPub:null,identityPrivWrapped:null,identityPrivKdf:null}}function Kt(A){let r="";for(let e=0;e<A.length;e++)r+=String.fromCharCode(A[e]);return btoa(r)}function et(A){let r=atob(A),e=new Uint8Array(r.length);for(let t=0;t<r.length;t++)e[t]=r.charCodeAt(t);return e}var ja=32,Ps=class{constructor(r,e,t,i=()=>0){this.app=r;this.load=e;this.save=t;this.idleMinutes=i;this.sessionKey=null;this.folderSessionKeys=new Map;this.idleTimer=null;this.kf=null;this.keyfiles=new Ts(r)}argonProbe(){return Xi()}async init(){await this.refresh()}async refresh(){this.kf=await this.keyfiles.load()}secretStore(){return this.app.secretStorage??null}keychainAvailable(){return!!this.secretStore()}appTag(){return(this.app.appId||"default").toLowerCase().replace(/[^a-z0-9]/g,"").slice(0,20)||"default"}keychainId(){return`${qt}-${this.appTag()}`}legacyAppIdKeychainId(){let r=this.app.appId||"default";return`${qt}-${r}`}isRemembered(){try{let r=this.secretStore();return!!(r?.getSecret(this.keychainId())||r?.getSecret(this.legacyAppIdKeychainId()))}catch{return!1}}rememberedPassword(){let r=this.secretStore();if(!r)return null;try{let e=r.getSecret(this.keychainId());if(e)return e}catch{}try{let e=r.getSecret(this.legacyAppIdKeychainId());if(e)return e}catch{}try{return r.getSecret(qt)||null}catch{return null}}async verifyWithKeychain(){let r=this.rememberedPassword();if(!r)return!1;try{return await this.verifyPassword(r)}catch{return!1}}async remember(r){try{await this.secretStore()?.setSecret(this.keychainId(),r)}catch(e){console.warn("[Stashpad] couldn't save password to keychain",e)}}async forgetKeychain(){let r=this.secretStore();if(r)try{r.removeSecret?await r.removeSecret(this.keychainId()):await r.setSecret(this.keychainId(),"")}catch(e){console.warn("[Stashpad] couldn't clear keychain",e)}}async tryAutoUnlock(){if(!this.isConfigured()||this.isUnlocked())return this.isUnlocked();if(this.accessState()!=="member"&&!this.hasSharedPassword())return!1;let r=this.secretStore(),e=null;try{e=r?.getSecret(this.keychainId())??null}catch{e=null}if(e)return this.unlock(e);let t=null;try{t=r?.getSecret(qt)??null}catch{t=null}if(!t)return!1;let i=await this.unlock(t);if(i){await this.remember(t);try{r?.removeSecret?await r.removeSecret(qt):await r?.setSecret(qt,"")}catch{}}return i}isConfigured(){return!!this.kf||!!this.load().wrappedKey}isUnlocked(){return this.sessionKey!==null}kdf(){return this.load().identityPrivKdf??this.load().kdf}hasIdentity(){return!!this.load().identityPrivWrapped&&!!this.load().identityId}mySlot(){let r=this.load().identityId;return r?this.kf?.slots.find(e=>e.recipientId===r)??null:null}accessState(){let r=this.load();return!this.kf&&r.wrappedKey?"member":this.kf?this.hasIdentity()&&this.mySlot()?"member":r.identityId&&this.kf.joinRequests.some(e=>e.id===r.identityId)?"pending":"outsider":"none"}amIMember(){return this.accessState()==="member"}async setup(r,e=!1,t){if(await this.refresh(),this.isConfigured())throw new Error("Encryption is already set up in this vault.");if(!r)throw new Error("Password required.");let i=crypto.getRandomValues(new Uint8Array(ja)),s=await this.mintIdentity(r,t),n=Io(Z(8));n.identities.push(this.identityRecord(s)),n.slots.push(await Ms(i,et(s.pub),s.id)),await this.keyfiles.save(n),this.kf=n,this.sessionKey=i,e&&await this.remember(r),this.armIdle()}async unlock(r,e=!1){await this.refresh();let t=this.load();if(!this.kf&&t.wrappedKey){let s;try{s=await je(et(t.wrappedKey),r)}catch{return!1}let n=await this.mintIdentity(r,t.identityLabel??void 0),o=Io(Z(8));return o.identities.push(this.identityRecord(n)),o.slots.push(await Ms(s,et(n.pub),n.id)),await this.keyfiles.save(o),this.kf=o,this.sessionKey=s,e&&await this.remember(r),this.armIdle(),!0}let i=this.hasIdentity()?this.mySlot():null;if(i)try{let s=await je(et(t.identityPrivWrapped),r);try{let n=await Xa(i,s);return s.fill(0),this.sessionKey=n,e&&await this.remember(r),this.armIdle(),!0}catch{s.fill(0)}}catch{}for(let s of(this.kf?.passwordSlots??[]).filter(n=>!n.label.startsWith("[deprecated]")))try{let n=await je(et(s.wrapped),r);return this.sessionKey=n,e&&await this.remember(r),this.armIdle(),!0}catch{}return!1}hasSharedPassword(){return(this.kf?.passwordSlots??[]).some(r=>!r.label.startsWith("[deprecated]"))}async setSharedPassword(r,e=!1){if(!this.sessionKey)throw new Error("Unlock encryption first.");if(!r)throw new Error("Password required.");if(await this.refresh(),!this.kf)throw new Error("Encryption is not set up.");let t=(this.kf.passwordSlots??[]).filter(o=>!o.label.startsWith("[deprecated]")),i=this.rememberedPassword();if(i&&t[0])try{await this.secretStore()?.setSecret(`${this.keychainId()}-d-${t[0].id}`,i)}catch{}let s=await _e(this.sessionKey,r),n=(this.kf.passwordSlots??[]).map(o=>o.label.startsWith("[deprecated]")?o:{...o,label:`[deprecated] ${o.label}`});this.kf.passwordSlots=[{id:Z(8),label:"Shared password",wrapped:Kt(s.data),kdf:s.kdf,createdAt:new Date().toISOString()},...n],await this.keyfiles.save(this.kf),(e||this.isRemembered())&&await this.remember(r)}async removeSharedPassword(){await this.refresh(),!(!this.kf||!this.kf.passwordSlots?.length)&&(this.kf.passwordSlots=[],await this.keyfiles.save(this.kf))}owningFolder(r){let e=this.kf?.folderKeys;if(!e)return null;let t=this.cleanFolder(r);for(;t;){if(e[t])return t;let i=t.lastIndexOf("/");if(i<0)break;t=t.slice(0,i)}return null}hasOwnFolderKey(r){return!!this.kf?.folderKeys?.[this.cleanFolder(r)]}folderKeyEntry(r){let e=this.owningFolder(r);return e?this.kf.folderKeys[e]??null:null}hasFolderKey(r){return this.owningFolder(r)!==null}folderPathByKeyId(r){for(let[e,t]of Object.entries(this.kf?.folderKeys??{}))if(t.keyId===r)return e;return null}folderActiveSlots(r){return r.passwordSlots.filter(e=>!e.label.startsWith("[deprecated]"))}isFolderUnlocked(r){let e=this.owningFolder(r);return e?this.folderSessionKeys.has(e):this.isUnlocked()}getFolderKey(r){let e=this.owningFolder(r);if(!e)return this.getSessionKey();let t=this.folderSessionKeys.get(e);return t?(this.armIdle(),t.slice()):null}async setupFolderKey(r,e,t,i=!1){if(!e)throw new Error("Password required.");if(await this.refresh(),!this.kf)throw new Error("Set up vault encryption first.");let s=this.cleanFolder(r);if(this.kf.folderKeys?.[s])throw new Error("This folder already has its own key.");let n=this.owningFolder(s);if(n&&n!==s)throw new Error(`A parent folder (\u201C${n.split("/").pop()}\u201D) already has its own password; this folder inherits it.`);let o=crypto.getRandomValues(new Uint8Array(ja)),a=await _e(o,e),l=Z(8),c={keyId:l,folderPath:s,label:t,kcId:this.folderKcId(t,l),passwordSlots:[{id:Z(8),label:"Folder password",wrapped:Kt(a.data),kdf:a.kdf,createdAt:new Date().toISOString()}],createdAt:new Date().toISOString()};this.kf.folderKeys={...this.kf.folderKeys??{},[s]:c},await this.keyfiles.save(this.kf),this.folderSessionKeys.set(s,o),i&&await this.rememberFolder(this.folderKcIdFor(c),e),this.armIdle()}async unlockFolder(r,e,t=!1){await this.refresh();let i=this.cleanFolder(r),s=this.folderKeyEntry(i);if(!s)return!1;for(let n of this.folderActiveSlots(s))try{let o=await je(et(n.wrapped),e);this.folderSessionKeys.set(s.folderPath,o);let a=this.folderKcIdFor(s);return(t||this.isFolderRemembered(a))&&await this.rememberFolder(a,e),this.armIdle(),!0}catch{}return!1}async tryAutoUnlockFolder(r){let e=this.folderKeyEntry(r);if(!e||this.isFolderUnlocked(r))return this.isFolderUnlocked(r);let t=this.rememberedFolderPassword(this.folderKcIdFor(e));return t?this.unlockFolder(r,t):!1}async changeFolderPassword(r,e,t=!1){if(!e)throw new Error("Password required.");let i=this.cleanFolder(r);await this.refresh();let s=this.folderKeyEntry(i);if(!s||!this.kf)throw new Error("This folder has no key.");let n=s.folderPath,o=this.folderSessionKeys.get(n);if(!o)throw new Error("Unlock this folder first.");let a=s.passwordSlots.filter(u=>!u.label.startsWith("[deprecated]")),l=this.folderKcIdFor(s),c=this.rememberedFolderPassword(l);if(c&&a[0])try{await this.secretStore()?.setSecret(`${l}-d-${a[0].id}`,c)}catch{}let d=await _e(o,e),h=s.passwordSlots.map(u=>u.label.startsWith("[deprecated]")?u:{...u,label:`[deprecated] ${u.label}`}),g={...s,kcId:s.kcId??this.folderKcId(s.label,s.keyId),passwordSlots:[{id:Z(8),label:"Folder password",wrapped:Kt(d.data),kdf:d.kdf,createdAt:new Date().toISOString()},...h]};this.kf.folderKeys={...this.kf.folderKeys??{},[n]:g},await this.keyfiles.save(this.kf),(t||this.isFolderRemembered(l))&&await this.rememberFolder(this.folderKcIdFor(g),e)}async commitFolderRotation(r,e,t,i,s,n=!1){if(!e)throw new Error("Password required.");if(await this.refresh(),!this.kf)throw new Error("Encryption is not set up.");let o=this.cleanFolder(r),a=this.folderKeyEntry(o),l=a?.folderPath??o,c=a?.keyId??Z(8),d=a?this.folderKcIdFor(a):null,h=(a?.passwordSlots??[]).filter(C=>!C.label.startsWith("[deprecated]")),g=d?this.rememberedFolderPassword(d):null;if(g&&h[0])try{await this.secretStore()?.setSecret(`${d}-d-${h[0].id}`,g)}catch{}let u=await _e(t,e),p=(a?.passwordSlots??[]).map(C=>C.label.startsWith("[deprecated]")?C:{...C,label:`[deprecated] ${C.label}`}),f=s??a?.label??`rotated - ${o.split("/").pop()||o}`,I={keyId:c,folderPath:l,label:f,kcId:this.folderKcId(f,c),rotId:i,passwordSlots:[{id:Z(8),label:"Folder password",wrapped:Kt(u.data),kdf:u.kdf,createdAt:new Date().toISOString()},...p],createdAt:a?.createdAt??new Date().toISOString()};this.kf.folderKeys={...this.kf.folderKeys??{},[l]:I},await this.keyfiles.save(this.kf);let m=this.folderSessionKeys.get(l);m&&m!==t&&m.fill(0),this.folderSessionKeys.set(l,t.slice()),(n||d&&this.isFolderRemembered(d))&&await this.rememberFolder(this.folderKcIdFor(I),e)}folderKcId(r,e){return(`sp-${(r||"").toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")}`.slice(0,44)+`-${e.slice(0,6)}`).replace(/-+/g,"-")}folderKcIdFor(r){return r.kcId||`${qt}-f-${r.keyId}`}isFolderRemembered(r){try{return!!this.secretStore()?.getSecret(r)}catch{return!1}}rememberedFolderPassword(r){try{return this.secretStore()?.getSecret(r)||null}catch{return null}}async rememberFolder(r,e){try{await this.secretStore()?.setSecret(r,e)}catch(t){console.warn("[Stashpad] couldn't save folder password to keychain",t)}}async forgetFolderKeychain(r){let e=this.folderKeyEntry(r);if(!e)return;let t=this.secretStore();if(!t)return;let i=this.folderKcIdFor(e);try{t.removeSecret?await t.removeSecret(i):await t.setSecret(i,"")}catch(s){console.warn("[Stashpad] couldn't clear folder keychain",s)}}async verifyPassword(r){let e=this.load();try{if(e.identityPrivWrapped)return(await je(et(e.identityPrivWrapped),r)).fill(0),!0;if(e.wrappedKey)return(await je(et(e.wrappedKey),r)).fill(0),!0}catch{}return!1}async changePassword(r,e,t=!1){let i=this.load();if(!e)throw new Error("New password required.");if(!i.identityPrivWrapped&&!await this.unlock(r,!1))return!1;let s=this.load(),n;try{n=await je(et(s.identityPrivWrapped),r)}catch{return!1}let o=await _e(n,e);return n.fill(0),await this.save({...s,identityPrivWrapped:Kt(o.data),identityPrivKdf:o.kdf}),(t||this.isRemembered())&&await this.remember(e),this.armIdle(),!0}async requestAccess(r,e,t=!1){if(await this.refresh(),!this.kf)throw new Error("This vault has no encryption set up yet.");if(this.amIMember())return;if(!e)throw new Error("Password required.");let i=this.hasIdentity()?{id:this.load().identityId,label:this.load().identityLabel??r,pub:this.load().identityPub}:await this.mintIdentity(e,r);r&&i.label!==r&&(await this.save({...this.load(),identityLabel:r}),i.label=r);let s={id:i.id,label:i.label,pubKey:i.pub,requestedAt:new Date().toISOString()};this.kf.joinRequests=[...this.kf.joinRequests.filter(n=>n.id!==i.id),s],await this.keyfiles.save(this.kf),(t||this.isRemembered())&&await this.remember(e)}pendingJoinRequests(){return this.kf?.joinRequests??[]}members(){return this.kf?.identities??[]}myIdentityId(){return this.load().identityId}async approveJoinRequest(r,e){if(!this.sessionKey)throw new Error("Unlock encryption first.");if(await this.refresh(),!this.kf)return!1;let t=this.kf.joinRequests.find(i=>i.id===r);return t?(this.kf.slots=[...this.kf.slots.filter(i=>i.recipientId!==t.id),await Ms(this.sessionKey,et(t.pubKey),t.id)],this.kf.identities=[...this.kf.identities.filter(i=>i.id!==t.id),{id:t.id,label:e??t.label,pubKey:t.pubKey,addedAt:new Date().toISOString()}],this.kf.joinRequests=this.kf.joinRequests.filter(i=>i.id!==r),await this.keyfiles.save(this.kf),!0):!1}async removeMember(r){await this.refresh(),this.kf&&(this.kf.slots=this.kf.slots.filter(e=>e.recipientId!==r),this.kf.identities=this.kf.identities.filter(e=>e.id!==r),this.kf.joinRequests=this.kf.joinRequests.filter(e=>e.id!==r),await this.keyfiles.save(this.kf))}async denyJoinRequest(r){await this.refresh(),this.kf&&(this.kf.joinRequests=this.kf.joinRequests.filter(e=>e.id!==r),await this.keyfiles.save(this.kf))}lock(){this.sessionKey&&this.sessionKey.fill(0),this.sessionKey=null;for(let r of this.folderSessionKeys.values())r.fill(0);this.folderSessionKeys.clear(),this.clearIdle()}lockFolder(r){let e=this.owningFolder(r)??this.cleanFolder(r),t=this.folderSessionKeys.get(e);t&&(t.fill(0),this.folderSessionKeys.delete(e))}cleanFolder(r){return(r||"").replace(/\/+$/,"")}async clear(){this.lock(),await this.forgetKeychain();try{let r=this.app.vault.adapter;for(let e of[".stashpad/keys.json"])try{await r.exists(e)&&await r.remove(e)}catch{}try{let e=await r.list("_keys");for(let t of e.files||[])if(/\/keys-\d+\.json$/.test(t))try{await r.remove(t)}catch{}}catch{}}catch{}this.kf=null,await this.save(ji())}getSessionKey(){return this.sessionKey&&this.armIdle(),this.sessionKey?this.sessionKey.slice():null}async mintIdentity(r,e){let t=await za(),i=await _e(t.privKeyPkcs8,r);t.privKeyPkcs8.fill(0);let s=this.load(),n=s.identityId??Z(8),o=e??s.identityLabel??"This device",a=Kt(t.pubKeySpki);return await this.save({...s,identityId:n,identityLabel:o,identityPub:a,identityPrivWrapped:Kt(i.data),identityPrivKdf:i.kdf}),{id:n,label:o,pub:a}}identityRecord(r){return{id:r.id,label:r.label,pubKey:r.pub,addedAt:new Date().toISOString()}}armIdle(){this.clearIdle();let r=this.idleMinutes();r>0&&(this.idleTimer=window.setTimeout(()=>this.lock(),r*6e4))}clearIdle(){this.idleTimer!=null&&(window.clearTimeout(this.idleTimer),this.idleTimer=null)}dispose(){this.lock()}};var _i=null,Hs=new Set;function Us(A){if(_i!==A){_i=A;for(let r of Hs)r()}}function ht(){return _i}function _a(A){if(_i===A){_i=null;for(let r of Hs)r()}}function er(A){return Hs.add(A),()=>Hs.delete(A)}function hc(){return M.Platform.isMacOS?"Finder":M.Platform.isWin?"File Explorer":"file manager"}var ci=[{id:"move",label:"Move (picker)",desc:"Open a fuzzy picker to choose the new parent.",defaultPrimary:"M"},{id:"pickMove",label:"Move (in-list)",desc:"Highlight a note in the list with arrows; Enter sets it as parent.",defaultPrimary:"O"},{id:"merge",label:"Merge",desc:"Concatenate selected notes into the oldest one.",defaultPrimary:"&"},{id:"copy",label:"Copy",desc:"Copy selected note bodies to clipboard.",defaultPrimary:"C"},{id:"copyTree",label:"Copy tree",desc:"Copy the focused note + all descendants, indented.",defaultPrimary:"Y"},{id:"openEditor",label:"Open in editor",desc:"Open the cursor row (or focused note) in a regular Obsidian markdown tab.",defaultPrimary:"E"},{id:"openTab",label:"Open in new Stashpad tab",desc:"Open the cursor row (or focused note) in a new Stashpad tab focused on it.",defaultPrimary:"T"},{id:"split",label:"Split note",desc:"Split the cursor row (or focused note) into two notes at a chosen line.",defaultPrimary:"S"},{id:"copyOutline",label:"Copy as outline",desc:"Copy selection (or cursor row) as a nested ![[embed]] outline.",defaultPrimary:"L"},{id:"toggleSplit",label:"Toggle split-on-newlines",desc:"Default: Mod+/",defaultPrimary:"Mod+/"},{id:"pickDestination",label:"Pick destination",desc:"Default: Mod+D",defaultPrimary:"Mod+D"},{id:"search",label:"Search notes",desc:"Default: Mod+F",defaultPrimary:"Mod+F"},{id:"searchInParent",label:"Search in current parent",desc:"Default: Mod+Alt+F (Mod+Shift+F is taken by Obsidian's global search).",defaultPrimary:"Mod+Alt+F"},{id:"delete",label:"Delete selection",desc:"Default: Mod+Backspace",defaultPrimary:"Mod+Backspace"},{id:"undo",label:"Undo",desc:"Default: Mod+Z (Stashpad-only \u2014 won't fire while typing in the composer).",defaultPrimary:"Mod+Z"},{id:"redo",label:"Redo",desc:"Default: Mod+Shift+Z",defaultPrimary:"Mod+Shift+Z"},{id:"toggleComplete",label:"Toggle complete (strikethrough)",desc:"Default: Mod+Enter or X \u2014 marks selected/focused notes as complete (both chords active).",defaultPrimary:"Mod+Enter",defaultSecondary:"X",defaultUseBoth:!0},{id:"moveUp",label:"Move note up",desc:"Default: Mod+ArrowUp",defaultPrimary:"Mod+ArrowUp"},{id:"moveDown",label:"Move note down",desc:"Default: Mod+ArrowDown",defaultPrimary:"Mod+ArrowDown"},{id:"moveToTop",label:"Move note to top",desc:"Default: Mod+Shift+ArrowUp",defaultPrimary:"Mod+Shift+ArrowUp"},{id:"moveToBottom",label:"Move note to bottom",desc:"Default: Mod+Shift+ArrowDown",defaultPrimary:"Mod+Shift+ArrowDown"},{id:"outdent",label:"Outdent (move to grandparent)",desc:"Default: Mod+[ \u2014 re-parents the selection one level up.",defaultPrimary:"Mod+["},{id:"setColor",label:"Set note color",desc:"Default: Shift+: or ; \u2014 open the color picker for the selection (both chords active).",defaultPrimary:"Shift+:",defaultSecondary:";",defaultUseBoth:!0},{id:"clone",label:"Clone (duplicate / copy) selection",desc:"Default: Mod+Shift+D \u2014 clone selected notes (with their subtrees) as siblings.",defaultPrimary:"Mod+Shift+D"},{id:"forkNote",label:"Fork into a separate note (under a chosen parent)",desc:'Duplicate the cursor row (with its subtree) as a separate note and pick which parent it nests under. Distinct from "Fork as a version" (a draft within a sheet group). No default chord.',defaultPrimary:""},{id:"insertTemplate",label:"Insert template (clone an existing note)",desc:"Pick any note in this Stashpad; clone it (with subtree + attachments) into the current view, retimestamped.",defaultPrimary:""},{id:"toggleExpand",label:"Show more / show less (expand toggle)",desc:"Default: Shift+? \u2014 toggle the clamp on the cursor row (or every selected row).",defaultPrimary:"Shift+?"},{id:"expandAll",label:"Expand all (show every note's full body)",desc:"Un-clamp every note in the current list at once.",defaultPrimary:""},{id:"collapseAll",label:"Collapse all (clamp every note's body)",desc:"Re-clamp every note in the current list at once.",defaultPrimary:""},{id:"exportStash",label:"Export selection to .stash",desc:"Export the selected subtree(s) as a .stash bundle (notes + attachments).",defaultPrimary:""},{id:"importStash",label:"Import .stash file",desc:"Open the .stash bundle picker and import its notes into this Stashpad.",defaultPrimary:""},{id:"pickFolder",label:"Open / switch / create Stashpad folder",desc:"Default: Mod+S \u2014 opens the unified folder picker (reveal, switch, create, convert).",defaultPrimary:"Mod+S"},{id:"cloneStashpadTab",label:"Clone (duplicate / copy) this Stashpad tab",desc:'Open a second tab on the same folder + focus, mirroring the "copy" button in the focused-header actions.',defaultPrimary:""},{id:"selectAll",label:"Select all notes in view",desc:"Default: Mod+A \u2014 adds every visible row to the selection.",defaultPrimary:"Mod+A"},{id:"copyCodeBlock",label:"Copy code from codeblock",desc:"Default: { \u2014 copy the contents of the cursor row's first codeblock (or pick one when multiple exist).",defaultPrimary:"{"},{id:"swapWithParent",label:"Swap with parent (ouroboros)",desc:"Promote the cursor row above its current parent; the parent slides under it (carrying its other children). No default \u2014 bind in this tab.",defaultPrimary:""},{id:"togglePin",label:"Pin / unpin selected note",desc:"Default: P \u2014 toggle the sidebar pin state of the cursor row (or focused note).",defaultPrimary:"P"},{id:"listPin",label:"Pin / unpin to top of list",desc:"Float the cursor row (or selection) to the TOP of its list \u2014 distinct from the sidebar pin. No default chord.",defaultPrimary:""},{id:"toggleTask",label:"Toggle task (todo)",desc:"Default: G \u2014 mark the selection (or cursor row) as a task / todo, or clear it. Tasks appear in the Tasks panel.",defaultPrimary:"G"},{id:"setDue",label:"Set due date\u2026",desc:"Default: D \u2014 open a date+time picker to set (or clear) the due date on the selection. Setting a due date also marks the note as a task.",defaultPrimary:"D"},{id:"jumpToTop",label:"Jump to top of list",desc:"Default: Home \u2014 move the cursor to the first note in the current list.",defaultPrimary:"Home"},{id:"jumpToBottom",label:"Jump to bottom of list",desc:"Default: End \u2014 move the cursor to the last note in the current list.",defaultPrimary:"End"},{id:"commandPalette",label:"Command palette (Stashpad only)",desc:"Default: Mod+K \u2014 open a command palette listing only Stashpad's commands, with Sift search.",defaultPrimary:"Mod+K"},{id:"lockSelection",label:"Encrypt (lock) selection",desc:"Encrypt the selected note(s) + their children into a locked .stashenc bundle in place (prompts to unlock first if needed). No default chord.",defaultPrimary:""},{id:"unlockAll",label:"Decrypt (unlock) locked notes in view",desc:"Decrypt every locked stash shown in the current view back into place, skipping any that can't be read. No default chord.",defaultPrimary:""},{id:"moveToArchive",label:"Move selection to archive (encrypt)",desc:"Move the selected note(s) to the default archive folder, encrypted on arrival. Undoable. No default chord.",defaultPrimary:""},{id:"encryptDelete",label:"Encrypt & delete selection",desc:"Send the selected note(s) to the encrypted trash (recoverable with your password, Ctrl/Cmd+Z undoable). No default chord.",defaultPrimary:""},{id:"copyNotes",label:"Copy notes (note clipboard)",desc:"Copy the selected note(s) as NOTES: paste in the list to duplicate them (new ids), or anywhere else to paste their text. Skipped when text is highlighted (normal copy wins).",defaultPrimary:"Mod+C"},{id:"cutNotes",label:"Cut notes",desc:"Cut the selected note(s): paste in the list to MOVE them, or in the composer to extract their text and delete the originals (undoable).",defaultPrimary:"Mod+X"},{id:"pasteNotes",label:"Paste notes",desc:"Paste previously copied/cut notes at the cursor row (after it, same parent). Does nothing if the note clipboard is empty.",defaultPrimary:"Mod+V"}];function Bo(){let A={};for(let r of ci)A[r.id]={primary:r.defaultPrimary,secondary:r.defaultSecondary??"",preferRight:!1,useBoth:!!r.defaultUseBoth};return A}var pc=!1,gc=!1,Qt={folder:"Stashpad",importDropFolder:"",exportFolder:"_exports",autoImport:!1,inheritObsidianExclusions:!0,folderPanelPinnedFraction:.5,folderPanelPinned:[],folderPanelDownranked:[],folderPanelHidden:[],folderPanelPinnedGrouping:"pin-order",enablePerfProfiling:!1,debugTrace:!1,writeRecoveryLinks:!0,useTemplatesFormat:!1,prefixTimestampsOnCopy:!0,splitOnLines:!1,splitMode:"lines",confirmCrossParentDrag:!0,confirmBulkDelete:!0,confirmAttachmentDelete:!0,autofocusComposerAfterSend:!0,focusComposerOnOpen:!1,searchOpensInContext:!0,popoutDuplicates:!0,encryption:ji(),encryptTrash:!1,encryptTrashFilenames:!1,encryptionIdleLockMinutes:0,hideLockedTitles:!1,migratedToggleTaskG:!1,dueQuickAdjusts:["5m","15m","30m","1h","1d","1w"],archiveFolders:[],folderEncPrefs:{},folderIcons:{},folderSwitcherIncludePinned:!1,importExcludePrefixes:"_",lockedSubtrees:[],searchOpensInNewTab:!0,pinnedNotes:[],hideMobileToolbarInStashpad:!0,slugStopWords:[],searchIncludedFolders:[],searchExcludedFolders:[],shortcuts:{move:"M",pickMove:"O",merge:"&",copy:"C",copyTree:"Y",openEditor:"E",openTab:"T",split:"S",copyOutline:"L"},mod:{toggleSplit:"Mod+/",pickDestination:"Mod+D",search:"Mod+F",delete:"Mod+Backspace",undo:"Mod+Z",redo:"Mod+Shift+Z",toggleComplete:"Mod+Enter",moveUp:"Mod+ArrowUp",moveDown:"Mod+ArrowDown",moveToTop:"Mod+Shift+ArrowUp",moveToBottom:"Mod+Shift+ArrowDown",outdent:"Mod+[",setColor:"Shift+:"},customPalette:[],colorAliases:{},noteTemplates:{},authorName:"",authorId:"",authorRole:"",authorDepartment:"",showAuthor:!0,showContributors:!0,showLastEdit:!0,viewModes:{},includeAttachmentsInEverything:{},hideChildlessNotes:{},hideCompletedNotes:{},attachmentsOnlyNotes:{},mutedNotificationCategories:[],notificationHistoryLimit:5e3,notifiedDueKeys:[],autoNavOnMoveIn:!1,autoNavOnMoveOut:!1,autoExpandCursorRow:!1,expandBodiesByDefault:!1,autoOpenDetailPanel:!1,doubleClickToFocus:!0,enableSheetVersions:!1,dateDisplayFormat:"locale",dateDisplayTimezone:"",jdIndexScope:"vault",jdIndexScopeFolder:"",jdIndexStashpadFolder:"",jdIndexFile:"Index",jdIndexIncludeStashpadFolders:!1,jdIndexSort:"natural",jdIndexHasBuilt:!1,okfEnabled:!1,okfTemplatePath:"",drafts:{},lastSubmitted:{},bindings:Bo()},tr=JSON.parse(JSON.stringify(Qt)),Co=new Set;function X(){return tr}function yo(A){tr=A;for(let r of Co)r()}function ir(A){return Co.add(A),()=>Co.delete(A)}function Ls(A){try{let r=A.internalPlugins?.plugins?.templates;if(!r?.enabled)return null;let e=r.instance?.options??{};return{dateFormat:e.dateFormat||"YYYY-MM-DD",timeFormat:e.timeFormat||"HH:mm"}}catch{return null}}var Rs=[{id:"foldersStorage",label:"\u{1F4C1} Folders & Storage"},{id:"importExport",label:"\u{1F504} Import & Export"},{id:"datesTime",label:"\u{1F552} Dates & Time"},{id:"behaviors",label:"\u{1F39B}\uFE0F Behavior"},{id:"notifications",label:"\u{1F514} Notifications"},{id:"encryption",label:"\u{1F512} Encryption"},{id:"authorship",label:"\u2712\uFE0F Authorship"},{id:"templates",label:"\u{1F4C4} Templates"},{id:"organizationSystems",label:"\u{1F5C2}\uFE0F Organization Systems"},{id:"maintenance",label:"\u{1F6E0}\uFE0F Maintenance"},{id:"diagnostics",label:"\u{1FA7A} Diagnostics"},{id:"hotkeys",label:"\u2328\uFE0F Hotkeys"}].sort((A,r)=>{let e=t=>t.replace(/^[^\p{L}\p{N}]+/u,"");return e(A.label).localeCompare(e(r.label))}),mo=null;function uc(A,r){return mo||(mo=class extends M.SettingPage{constructor(e,t){super(),this.title=e,this._renderFn=t}display(){this.containerEl.empty(),this._renderFn(this.containerEl)}}),new mo(A,r)}var Gs=class extends M.PluginSettingTab{constructor(e,t){super(e,t);this.plugin=t;this.pfeSelected=null;this.iconPickFolder=null}display(){let{containerEl:e}=this;e.empty();for(let t of Rs){new M.Setting(e).setName(t.label).setHeading();let i=this.itemsForTab(t.id);if(i)for(let s of i){let n=new M.Setting(e);typeof s.render=="function"?s.render(n):(s.name&&n.setName(s.name),s.desc&&n.setDesc(s.desc))}else this.renderTabContent(e,t.id)}}getSettingDefinitions(){return Rs.map(e=>{let t=this.itemsForTab(e.id);return t?{type:"page",name:e.label,items:t}:{type:"page",name:e.label,page:()=>uc(e.label,i=>this.renderTabContent(i,e.id))}})}itemsForTab(e){switch(e){case"hotkeys":return this.hotkeyItems();case"diagnostics":return this.diagnosticsItems();case"notifications":return this.notificationsItems();case"foldersStorage":return this.buildGeneralCategories().foldersStorage;case"importExport":return this.buildGeneralCategories().importExport;case"datesTime":return this.buildGeneralCategories().datesTime;case"maintenance":return this.buildGeneralCategories().maintenance;case"behaviors":{let t=this.buildGeneralCategories();return[this.headingDef("\u{1F4CB} List & Display"),...t.listDisplay,this.headingDef("\u2195\uFE0F Moving Notes"),...t.movingNotes,this.headingDef("\u270D\uFE0F Composer & Copying"),...t.composerCopy,this.headingDef("\u{1F5D1}\uFE0F Deleting"),...t.deleting,this.headingDef("\u{1FA9F} Windows & Tabs"),...t.windowsTabs,this.headingDef("\u2699\uFE0F Misc"),...t.misc]}case"organizationSystems":return[this.headingDef("\u{1F522} JD Index (Johnny Decimal)"),...this.jdIndexItems(),this.headingDef("\u{1F4DA} Open Knowledge Format (OKF)"),...this.okfItems()];case"encryption":return this.encryptionItems();case"authorship":return this.authorshipItems();case"templates":return this.templatesItems();default:return null}}renderTabContent(e,t){switch(t){case"authorship":this.renderAuthorshipSection(e);break;case"templates":this.renderTemplatesTab(e);break}}diagnosticsItems(){return[this.renderDef("Performance profiling","Record timing for list rendering, body reads, and file writes. Turn on, use Stashpad normally (especially the slow operations), then run \u201CDump performance profile\u201D from the command palette and share the result. Off = zero overhead.",e=>e.addToggle(t=>t.setValue(this.plugin.settings.enablePerfProfiling).onChange(async i=>{this.plugin.settings.enablePerfProfiling=i,await this.plugin.saveSettings()})),["perf","profiling","timing","slow"]),this.renderDef("Debug trace","Record low-level diagnostic lines (e.g. tap coordinates vs the row they resolve to) to an in-memory buffer while you reproduce a bug, then copy them below to share. Local only \u2014 no network, no file writes; zero overhead when off.",e=>e.addToggle(t=>t.setValue(this.plugin.settings.debugTrace).onChange(async i=>{this.plugin.settings.debugTrace=i,await this.plugin.saveSettings()})),["debug","trace","diagnostics","tap","log"]),this.renderDef("Copy / clear debug trace","Copy the captured debug lines to the clipboard (paste them back to share), or clear the buffer to start a fresh capture.",e=>{e.addButton(t=>t.setButtonText("Copy").onClick(async()=>{let i=this.plugin.getDebugTrace();if(!i){new M.Notice("Debug trace is empty \u2014 enable it and reproduce the issue first.");return}try{await navigator.clipboard.writeText(i),new M.Notice("Debug trace copied.")}catch{new M.Notice("Couldn't access clipboard.")}})),e.addButton(t=>t.setButtonText("Clear").onClick(()=>{this.plugin.clearDebugTrace(),new M.Notice("Debug trace cleared.")}))},["debug","trace","copy","clear"]),this.renderDef("Open log file","Append-only history of creates, deletes, parent changes, renames. Stored alongside the plugin's other private files.",e=>e.addButton(t=>t.setButtonText("Open log").onClick(async()=>{let i=this.app.vault.adapter,s=this.plugin.pluginPrivatePath("log.jsonl");if(!await i.exists(s)){new M.Notice("No log yet \u2014 make some changes first.");return}let n=await i.read(s);new _t(this.app,n,s).open()})),["log","history","diagnostics"])]}notificationsItems(){let e=new Set(this.plugin.settings.mutedNotificationCategories??[]),t=Object.keys(jn);return[this.renderDef("Notification history limit","Maximum number of notifications kept in the persistent history. Set to 0 for unlimited (the file size grows with usage; expect a few hundred KB per ~5000 entries). Default: 5000.",i=>i.addText(s=>s.setValue(String(this.plugin.settings.notificationHistoryLimit??5e3)).setPlaceholder("5000").onChange(async n=>{let o=parseInt(n,10);Number.isFinite(o)&&(this.plugin.settings.notificationHistoryLimit=o,this.plugin.notifications.setHistoryLimit(o),await this.plugin.saveSettings())})),["notification","history","limit"]),{type:"group",heading:"Mute notification categories",items:t.map(i=>{let s=jn[i];return this.renderDef(s.label,s.desc,n=>n.addToggle(o=>o.setValue(!e.has(i)).onChange(async a=>{let l=!a;l?e.add(i):e.delete(i),this.plugin.settings.mutedNotificationCategories=Array.from(e),this.plugin.notifications.setMuted(i,l),await this.plugin.saveSettings()})),["notification","mute","toast","category"])})},this.renderDef("Notification history","Browse the last 200 toasts. Filter by category. Live-updates as new notifications arrive. Muted categories still appear here so you can review what was suppressed.",i=>i.addButton(s=>s.setButtonText("View notification history").onClick(()=>{new Ri(this.app,this.plugin.notifications,async n=>{let o=this.app.vault.adapter,a=this.plugin.pluginPrivatePath("log.jsonl");if(!await o.exists(a)){new M.Notice("No log yet \u2014 make some changes first.");return}let l=await o.read(a);new _t(this.app,l,a).open()},this.plugin.settings.authorId||null,n=>this.plugin.lookupNoteAuthorIds(n)).open()})),["notification","history","panel"])]}renderTemplatesTab(e){this.renderColorAliasesSection(e),this.renderNoteTemplatesSection(e)}renderDef(e,t,i,s){return{name:e,desc:t,aliases:s,render:n=>{n.setName(e).setDesc(t);try{i(n)}catch(o){console.error(`[Stashpad] settings control "${e}" failed to render:`,o)}}}}sectionDef(e,t,i,s){return{name:e,desc:t,aliases:s,render:n=>{let o=n.settingEl;o.empty(),o.removeClass("setting-item"),o.addClass("stashpad-settings-section");try{i(o)}catch(a){console.error(`[Stashpad] settings section "${e}" failed to render:`,a)}}}}headingDef(e,t){return this.sectionDef(e,t??"",i=>{let s=new M.Setting(i).setName(e).setHeading();t&&s.setDesc(t)},[e.toLowerCase()])}folderKeyLabel(e){let t=new Date,i=a=>String(a).padStart(2,"0"),s=`${t.getFullYear()}${i(t.getMonth()+1)}${i(t.getDate())}-${i(t.getHours())}${i(t.getMinutes())}`,n=(this.plugin.settings.authorName||"").trim(),o=n?n.split(/\s+/).map(a=>a[0]).join(""):(this.plugin.settings.authorId||"anon").slice(0,4);return`${s} - ${e.split("/").pop()||e} - ${o}`}promptSetFolderPassword(e){new de(this.app,{mode:"setup",offerKeychain:!0,title:`Set password for \u201C${e.split("/").pop()}\u201D`,intro:"A separate password just for this folder. Share it with collaborators out-of-band (a password manager, Signal, in person). There is NO recovery if it's lost.",onSubmit:async({next:t,remember:i})=>{if(!t)return"Enter a password.";try{await this.plugin.encryption.setupFolderKey(e,t,this.folderKeyLabel(e),i)}catch(s){return s.message}return new M.Notice("Folder password set \u2014 share it securely."),this.update?.(),null}}).open()}promptChangeFolderPassword(e){new de(this.app,{mode:"setup",offerKeychain:!0,title:`Change password for \u201C${e.split("/").pop()}\u201D`,intro:"Re-wraps this folder's key under a new password. The OLD password stops working on THIS device right away (un-synced copies elsewhere keep working until they sync). To truly cut off someone who left, you'll want \u201CRotate key\u201D (re-encrypts everything) \u2014 coming soon.",onSubmit:async({next:t,remember:i})=>{if(!t)return"Enter a password.";try{await this.plugin.encryption.changeFolderPassword(e,t,i)}catch(s){return s.message}return new M.Notice("Folder password changed."),this.update?.(),null}}).open()}promptRotateFolderKey(e){let t=e.split("/").pop()||e;new ie(this.app,`Rotate key for \u201C${t}\u201D?`,`This RE-ENCRYPTS every locked note in \u201C${t}\u201D under a brand-new key, then retires the old password so it can no longer decrypt them \u2014 even copies a collaborator synced earlier. Use this when someone should LOSE access (e.g. they left the org). Large folders can take a while. Everyone who should keep access will need the new password.`,"Rotate key",i=>{i&&new de(this.app,{mode:"setup",offerKeychain:!0,title:`New password for \u201C${t}\u201D`,intro:"The new password to use after rotating. Share it only with people who should keep access.",onSubmit:async({next:s,remember:n})=>s?await this.plugin.rotateFolderKey(e,s,n)<0?"Rotation failed \u2014 see the notice.":(this.update?.(),null):"Enter a password."}).open()}).open()}promptUnlockFolder(e){new de(this.app,{mode:"unlock",offerKeychain:!0,title:`Unlock \u201C${e.split("/").pop()}\u201D`,intro:"Enter this folder's password.",onSubmit:async({current:t,remember:i})=>await this.plugin.encryption.unlockFolder(e,t,i)?(new M.Notice("Folder unlocked."),this.update?.(),null):"Wrong password. Try again."}).open()}encryptionOrOnboard(){return this.plugin.encryption?.isConfigured?.()?!0:(new M.Notice("That setting needs vault encryption, which isn't set up yet \u2014 let's set it up first.",6e3),new de(this.app,{mode:"setup",offerKeychain:!0,title:"Set up encryption",intro:"One password protects what you encrypt in this vault. It's stored only on this device \u2014 there is NO recovery if you lose it.",onSubmit:async({next:e,remember:t})=>{if(!e)return"Enter a password.";try{await this.plugin.encryption.setup(e,t,this.plugin.settings.authorName||"This device")}catch(i){return i.message}return new M.Notice("Encryption set up \u2014 now switch on the setting you wanted."),this.update?.(),null}}).open(),!1)}renderPerFolderEncryption(e){if(!this.plugin.encryption.isConfigured()){e.createEl("p",{cls:"setting-item-description"}).setText("Set up vault encryption above first. Per-folder passwords layer on top: a folder with its own password uses a separate key; folders without one use the vault password.");return}e.createEl("p",{cls:"setting-item-description stashpad-enc-warning"}).setText("\u26A0\uFE0F Encryption has no recovery \u2014 if you lose a password, anything encrypted under it (notes, archived items, and encrypted-trash items) is gone for good. Locking / archiving / secure-deleting permanently removes the plaintext; the encrypted copy is the only one left. A plaintext archive only de-indexes notes from search \u2014 it does NOT encrypt them.");let i=this.plugin.discoverStashpadFolders();if(i.length===0){e.createEl("p",{cls:"setting-item-description"}).setText("No Stashpad folders found yet.");return}(!this.pfeSelected||!i.includes(this.pfeSelected))&&(this.pfeSelected=i[0]),new M.Setting(e).setName("Folder").setDesc("Pick a folder to configure its password, archive, and trash options. Everything below applies to the selected folder only.").addDropdown(n=>{for(let o of i)n.addOption(o,o);n.setValue(this.pfeSelected),n.onChange(o=>{this.pfeSelected=o,this.update?.()})});let s=e.createDiv({cls:"stashpad-folderenc-panel"});this.renderFolderEncPanel(s,this.pfeSelected)}renderFolderEncPanel(e,t){let i=this.plugin.encryption,s=i.hasOwnFolderKey(t),n=i.folderKeyEntry(t)?.folderPath??null,o=!!n&&!s,a=i.isFolderUnlocked(t),l=(this.plugin.settings.folderEncPrefs??{})[t]??{},c=s?a?"Has its own password \xB7 unlocked":"Has its own password \xB7 locked":o?`Inherits \u201C${n.split("/").pop()||n}\u201D's password \xB7 ${a?"unlocked":"locked"}`:"Uses the vault password",d=new M.Setting(e).setName("Password").setDesc(c);o?d.addButton(p=>p.setButtonText("Manage on parent folder").onClick(()=>{this.pfeSelected=n,this.update?.()})):s?a?(d.addButton(p=>p.setButtonText("Lock (forget password)").onClick(()=>{this.plugin.encryption.lockFolder(t),new M.Notice("Folder locked \u2014 you'll re-enter its password next time."),this.update?.()})),d.addButton(p=>p.setButtonText("Change password\u2026").onClick(()=>this.promptChangeFolderPassword(t))),d.addButton(p=>{p.setButtonText("Rotate key\u2026").onClick(()=>this.promptRotateFolderKey(t)),p.buttonEl.addClass("mod-warning")})):d.addButton(p=>p.setButtonText("Unlock\u2026").setCta().onClick(()=>this.promptUnlockFolder(t))):d.addButton(p=>p.setButtonText("Set folder password\u2026").setCta().onClick(()=>this.promptSetFolderPassword(t)));let h=async p=>{this.plugin.settings.folderEncPrefs={...this.plugin.settings.folderEncPrefs??{},[t]:{...(this.plugin.settings.folderEncPrefs??{})[t]??{},...p}},await this.plugin.saveSettings()};new M.Setting(e).setName("Archive").setDesc("De-indexes this folder from cross-folder search; notes moved in get auto-encrypted (when \u201CEncrypt archived notes\u201D is on below).").addToggle(p=>p.setValue(!!l.archive).onChange(async f=>{await h({archive:f,...f&&l.archiveEncryptContent===void 0?{archiveEncryptContent:!0}:{}});let I=new Set((this.plugin.settings.archiveFolders??[]).map(m=>m.replace(/\/+$/,"")));f?I.add(t):I.delete(t),this.plugin.settings.archiveFolders=[...I],!f&&(this.plugin.settings.defaultArchiveFolder??"")===t&&(this.plugin.settings.defaultArchiveFolder=void 0),await this.plugin.saveSettings(),this.plugin.refreshFolderPanels(),this.update?.()})),new M.Setting(e).setName("Trash handling").setDesc("Where notes deleted from this folder go.").addDropdown(p=>p.addOption("","Use global default").addOption("stashpad","Stashpad encrypted trash").addOption("obsidian","Obsidian native trash").setValue(l.trashHandling??"").onChange(f=>h({trashHandling:f||void 0})));let g=(p,f,I,m)=>{new M.Setting(e).setName(p).addToggle(C=>C.setValue(!!l[f]).onChange(async B=>{let w={};w[f]=B,B||(w[I]=!1),await h(w),m&&await m(B),this.update?.()})),new M.Setting(e).setName(`\u21B3 ${p} \u2014 hide filenames`).setClass("stashpad-subsetting").addToggle(C=>{C.setValue(!!l[I]),C.setDisabled(!l[f]),C.onChange(B=>{let w={};w[I]=B,h(w)})})};g("Encrypt this folder's notes","encryptContent","encryptFilenames",async p=>{p?await this.plugin.lockFolder(t):await this.plugin.unlockFolder(t);let f=(this.plugin.settings.lockedSubtrees??[]).some(I=>(I.folder||"").replace(/\/+$/,"")===t);await h({encryptContent:f,...f?{}:{encryptFilenames:!1}})}),g("Encrypt archived notes","archiveEncryptContent","archiveEncryptFilenames"),g("Encrypt trashed notes","trashEncryptContent","trashEncryptFilenames");let u=this.plugin.discoverStashpadFolders().filter(p=>p!==t&&p.startsWith(t+"/")).sort();if(u.length){new M.Setting(e).setName("Subfolders").setDesc(`${u.length} subfolder${u.length===1?"":"s"} \u2014 they use \u201C${t.split("/").pop()}\u201D's password. Encrypt each individually.`).setHeading();for(let p of u){let f=(this.plugin.settings.folderEncPrefs??{})[p]??{},I=async B=>{this.plugin.settings.folderEncPrefs={...this.plugin.settings.folderEncPrefs??{},[p]:{...(this.plugin.settings.folderEncPrefs??{})[p]??{},...B}},await this.plugin.saveSettings()},m=(this.plugin.settings.lockedSubtrees??[]).some(B=>(B.folder||"").replace(/\/+$/,"")===p),C=p.slice(t.length+1);new M.Setting(e).setName(C).addToggle(B=>B.setValue(m).onChange(async w=>{w?await this.plugin.lockFolder(p):await this.plugin.unlockFolder(p);let y=(this.plugin.settings.lockedSubtrees??[]).some(E=>(E.folder||"").replace(/\/+$/,"")===p);await I({encryptContent:y,...y?{}:{encryptFilenames:!1}}),this.update?.()})),new M.Setting(e).setName(`\u21B3 ${C} \u2014 hide filenames`).setClass("stashpad-subsetting").addToggle(B=>{B.setValue(!!f.encryptFilenames),B.setDisabled(!m),B.onChange(w=>void I({encryptFilenames:w}))})}}}buildGeneralCategories(){let e=async()=>this.plugin.saveSettings(),t=(s,n,o,a,l)=>this.renderDef(s,n,c=>c.addToggle(d=>d.setValue(o()).onChange(async h=>{a(h),await e()})),l),i={foldersStorage:[],importExport:[],datesTime:[],listDisplay:[],movingNotes:[],deleting:[],composerCopy:[],windowsTabs:[],maintenance:[],misc:[]};i.foldersStorage.push(this.renderDef("Stashpad notes folder","Vault-relative folder where Stashpad stores its notes and attachments. Created on demand.",s=>{s.addText(n=>{new oi(this.app,n.inputEl),n.setValue(this.plugin.settings.folder).setPlaceholder("Stashpad").onChange(async o=>{let a=(o||"").trim().replace(/^\/+|\/+$/g,"")||Qt.folder,l=a.split("/").filter(Boolean).pop()??"";if(new Set([this.plugin.settings.importDropFolder,this.plugin.settings.exportFolder,"_attachments","_processed"].map(d=>(d??"").trim().replace(/^\/+|\/+$/g,"")).filter(Boolean)).has(l)){new M.Notice(`"${a}" uses a reserved Stashpad subfolder name. Pick something else.`);return}this.plugin.settings.folder=a,await e()})})},["folder","path","location","notes"])),i.importExport.push(t("Auto-import dropped files","When on, any file you drop directly into a Stashpad folder is imported automatically: markdown becomes a note (the original is archived to .archive); other files move to _attachments with a note that links to them. Large drops ask for confirmation first.",()=>this.plugin.settings.autoImport,s=>{this.plugin.settings.autoImport=s},["import","drop","auto"])),i.foldersStorage.push(t("Inherit Obsidian's excluded files","Also hide files matching Obsidian's \u201CExcluded files\u201D list (Settings \u2192 Files & Links) from Stashpad's link autocomplete and file surfaces \u2014 so you manage exclusions in one place. Plugin-internal formats like .edtz are always excluded regardless.",()=>this.plugin.settings.inheritObsidianExclusions,s=>{this.plugin.settings.inheritObsidianExclusions=s},["excluded","ignore","files"])),i.foldersStorage.push(t("Include pinned notes in the folder switcher","When on, the folder switcher / creator (the folder button and the \u201COpen or switch Stashpad folder\u201D command) also lists your pinned notes, so you can jump straight to one. Off keeps the picker focused on folders.",()=>this.plugin.settings.folderSwitcherIncludePinned,s=>{this.plugin.settings.folderSwitcherIncludePinned=s},["pinned","switcher","folder","picker","jump"])),i.foldersStorage.push(this.renderDef("Folder tab icon","Give a folder its own Lucide icon (e.g. rocket, star, book-open) shown on its tab, the folder switcher, and its folder-panel row. Pick a folder, then start typing in the icon box \u2014 matching icons appear with previews; pick one. Blank = the default icon. Set per folder.",s=>{let n=this.plugin.discoverStashpadFolders();if(n.length===0){s.setDesc("No Stashpad folders found yet.");return}(!this.iconPickFolder||!n.includes(this.iconPickFolder))&&(this.iconPickFolder=n[0]);let o=null,a=s.controlEl.createSpan({cls:"stashpad-folder-icon-preview"}),l=c=>{a.empty();let d=c.trim();d&&(0,M.setIcon)(a,d)};s.addDropdown(c=>{for(let d of n)c.addOption(d,d.split("/").pop()||d);c.setValue(this.iconPickFolder),c.onChange(d=>{this.iconPickFolder=d;let h=this.plugin.getFolderIcon(d)??"";o?.setValue(h),l(h)})}),s.addText(c=>{o=c,c.setPlaceholder("type to search\u2026 (e.g. rocket)"),new ks(this.app,c.inputEl),c.setValue(this.plugin.getFolderIcon(this.iconPickFolder)??""),c.onChange(async d=>{l(d),await this.plugin.setFolderIcon(this.iconPickFolder,d.trim())})}),l(this.plugin.getFolderIcon(this.iconPickFolder)??"")},["icon","folder","tab","lucide","emoji","switcher"])),i.importExport.push(this.renderDef("Dedicated import subfolder (optional)","Optional. A subfolder (relative to each Stashpad folder) where dropped .stash files auto-import. Leave blank to just drop files into the Stashpad folder itself (recommended). Suggested name: _imports.",s=>s.addText(n=>n.setValue(this.plugin.settings.importDropFolder).setPlaceholder("_imports (leave blank to use the folder root)").onChange(async o=>{this.plugin.settings.importDropFolder=(o||"").trim().replace(/^\/+|\/+$/g,""),await e()})),["import","subfolder"])),i.importExport.push(this.renderDef("Stash export subfolder","Subfolder name (relative to each Stashpad folder) where exports land. Must differ from the import subfolder above.",s=>s.addText(n=>n.setValue(this.plugin.settings.exportFolder).setPlaceholder("_exports").onChange(async o=>{this.plugin.settings.exportFolder=(o||"").trim().replace(/^\/+|\/+$/g,"")||Qt.exportFolder,await e()})),["export","stash","subfolder"])),i.importExport.push(this.renderDef("Exclude subfolders by prefix","Comma-separated name prefixes (default \u201C_\u201D). A subfolder whose name starts with any of these \u2014 at any depth \u2014 is NOT surfaced as a Stashpad folder or imported (it stays local). To encrypt such a folder, right-click it in Obsidian's file explorer \u2192 \u201C\u{1F512} Encrypt with Stashpad\u201D.",s=>s.addText(n=>n.setValue(this.plugin.settings.importExcludePrefixes??"_").setPlaceholder("_").onChange(async o=>{this.plugin.settings.importExcludePrefixes=o,await e()})),["import","exclude","prefix","subfolder","underscore","ignore"])),i.maintenance.push(this.renderDef("Rebootstrap existing Stashpad folders","Walk every folder that has a home note: ensure infrastructure (_imports, _exports, drafts file), backfill the redundant parentLink + children frontmatter fields, rename any note whose filename slug no longer matches its body's first line, AND migrate legacy attachment filenames to the new name-first format (`photo-<id>.png`). Safe to run anytime; skip-if-equal means already-synced notes are no-op writes.",s=>s.addButton(n=>n.setButtonText("Rebootstrap now").onClick(async()=>{n.setDisabled(!0).setButtonText("Working\u2026");try{await this.plugin.runRebootstrapWithUI()}catch{}finally{n.setDisabled(!1).setButtonText("Rebootstrap now")}})),["rebootstrap","rebuild","repair","backfill","slug"])),i.maintenance.push(this.renderDef("Write recovery navigation links","Maintain the redundant parentLink/children frontmatter so you can walk the hierarchy from raw Markdown if the index ever breaks. On a slow / network drive this is a big per-move cost (several round-trips each); turn it off there for snappier moves \u2014 Rebootstrap rebuilds the fields on demand, and your notes' canonical structure (id/parent) is unaffected either way.",s=>s.addToggle(n=>n.setValue(this.plugin.settings.writeRecoveryLinks).onChange(async o=>{this.plugin.settings.writeRecoveryLinks=o,await e()})),["recovery","parentlink","children","frontmatter"]));{let s=null,n=()=>{s&&s.setText(`Sample: ${ct(Date.now(),this.plugin.settings)}`)};i.datesTime.push(this.renderDef("Date display format","How created / edited timestamps and due dates are shown \u2014 in the notes list, the Tasks panel, and the detail panel. Overridden by the Templates-plugin formats when the option below is on.",o=>{o.addDropdown(a=>{a.addOption("locale","Locale, short (Mar 5, 9:00 AM)"),a.addOption("long","Locale, long (Thursday, March 5\u2026)"),a.addOption("iso","ISO (2026-03-05 09:00)"),a.addOption("us","US (3/5/2026, 9:00 AM)"),a.addOption("eu","EU (5/3/2026, 09:00)"),a.setValue(this.plugin.settings.dateDisplayFormat??"locale"),a.onChange(async l=>{this.plugin.settings.dateDisplayFormat=l,await e(),n()})})},["date","format","display"])),i.datesTime.push(this.renderDef("Use Templates plugin date/time formats","Use the date/time formats configured in the core Templates plugin instead of the Date display format above. Off = the Date display format above is used everywhere.",o=>{o.addToggle(l=>l.setValue(this.plugin.settings.useTemplatesFormat).onChange(async c=>{this.plugin.settings.useTemplatesFormat=c,await e(),n()}));let a=Ls(this.app);o.descEl.createDiv({cls:"stashpad-settings-note"}).setText(a?`Templates plugin: date = "${a.dateFormat}", time = "${a.timeFormat}"`:"Templates plugin not enabled.")},["templates","date","time","format"])),i.datesTime.push(this.renderDef("Display timezone","IANA timezone name (e.g. America/New_York, Europe/London, Asia/Kolkata). Leave blank to use your system timezone.",o=>{o.addText(a=>{a.setPlaceholder("(system timezone)"),a.setValue(this.plugin.settings.dateDisplayTimezone??""),a.onChange(async l=>{this.plugin.settings.dateDisplayTimezone=(l||"").trim(),await e(),n()})})},["timezone","tz","date","iana"])),i.datesTime.push(this.renderDef("Quick due-date adjustments","Comma-separated relative amounts shown as quick +/- buttons in the due-date and snooze pickers (e.g. 5m, 15m, 1h, 1d, 1w). Units: m=minutes, h=hours, d=days, w=weeks. A +/- flip in the picker toggles add vs subtract. Leave blank to hide the row.",o=>{o.addText(a=>{a.setPlaceholder("5m, 15m, 1h, 1d, 1w"),a.setValue((this.plugin.settings.dueQuickAdjusts??[]).join(", ")),a.onChange(async l=>{let c=l.split(",").map(d=>d.trim()).filter(d=>/^\d+\s*[mhdw]$/i.test(d)).map(d=>d.replace(/\s+/g,"").toLowerCase());this.plugin.settings.dueQuickAdjusts=c,await e()})})},["quick","adjust","snooze","due","preset","increment","decrement"])),i.datesTime.push({name:"Date sample",searchable:!1,render:o=>{let a=o.settingEl;a.empty(),a.removeClass("setting-item"),a.addClass("stashpad-settings-section"),s=a.createDiv({cls:"setting-item-description stashpad-settings-note stashpad-date-sample"}),n()}})}return i.movingNotes.push(t("Navigate into parent after moving a note IN","When you move a note onto another note via the in-list move picker (drag-onto-sibling), automatically drill into the new parent so you can see the moved note in its new home. Off = stay focused where you were.",()=>this.plugin.settings.autoNavOnMoveIn,s=>{this.plugin.settings.autoNavOnMoveIn=s},["navigate","move","in"])),i.movingNotes.push(t("Navigate to destination after moving a note OUT","When you outdent a note, move it via the cross-parent picker, or send it to Home, automatically drill into the destination parent. Off = stay focused where you were.",()=>this.plugin.settings.autoNavOnMoveOut,s=>{this.plugin.settings.autoNavOnMoveOut=s},["navigate","move","out"])),i.listDisplay.push(t("Double-click a note to open it","Double-click (or double-tap on mobile) a note in the list to focus/open it \u2014 the same as pressing \u2192 or clicking the enter arrow. Single click still just selects. On by default.",()=>this.plugin.settings.doubleClickToFocus,s=>{this.plugin.settings.doubleClickToFocus=s},["double","click","open","focus"])),i.misc.push(t("Sheet versions (alternate drafts)",`Treat notes that share a 'sheet-group' frontmatter id as alternate versions of one item: only the active version shows as a row, and its siblings collapse into a tab bar at the bottom of that row. Use "Fork as version" on a note to start. Off by default \u2014 when off, no note is ever hidden by this feature and the commands do nothing.`,()=>this.plugin.settings.enableSheetVersions,s=>{this.plugin.settings.enableSheetVersions=s},["sheet","version","draft","alternate","fork"])),i.listDisplay.push(t("Auto-open the detail panel","Open the right-sidebar Stashpad detail panel automatically whenever a Stashpad view becomes active. The panel shows the cursored note's body, metadata, and children. Off = open manually via ribbon or command palette.",()=>this.plugin.settings.autoOpenDetailPanel,s=>{this.plugin.settings.autoOpenDetailPanel=s},["detail","panel","sidebar"])),i.listDisplay.push(t("Expand the cursor row's body automatically","As you arrow-key through the list, the row under the cursor temporarily un-clamps to show its full body. Moving away re-collapses it. Doesn't affect the persistent 'Show more' state \u2014 this is a transient view-only effect.",()=>this.plugin.settings.autoExpandCursorRow,s=>{this.plugin.settings.autoExpandCursorRow=s},["expand","cursor","body"])),i.listDisplay.push(t("Expand note bodies by default","Show every note's full body by default instead of clamping long notes. The per-note 'Show more / show less' toggle and the Expand-all / Collapse-all commands then work in reverse \u2014 they let you collapse individual notes back down. Off = bodies clamp by default (expand is opt-in).",()=>this.plugin.settings.expandBodiesByDefault,s=>{this.plugin.settings.expandBodiesByDefault=s},["expand","collapse","default","body","clamp"])),i.movingNotes.push(t("Confirm cross-parent drag-and-drop","When dragging notes onto a note that has a different parent, ask before re-parenting (turn off to allow direct moves).",()=>this.plugin.settings.confirmCrossParentDrag,s=>{this.plugin.settings.confirmCrossParentDrag=s},["confirm","drag","drop","reparent"])),i.deleting.push(t("Confirm bulk deletes","Warn before deletes that affect more than one note \u2014 multi-selection delete OR deleting a note that has descendants. A single childless note with no attachments never prompts. Off = those deletes apply immediately (undo still recovers everything).",()=>this.plugin.settings.confirmBulkDelete,s=>{this.plugin.settings.confirmBulkDelete=s},["confirm","delete","bulk"])),i.deleting.push(t("Offer to delete attachments with note",`When a note references attachments, the delete modal includes an "Also delete attachments" checkbox so orphaned files don't pile up in your vault. Attachments are detected from both ![[\u2026]] embeds in the body and the frontmatter attachments: list. Off = attachments are always preserved on delete (no checkbox shown), and a single childless note with attachments deletes silently.`,()=>this.plugin.settings.confirmAttachmentDelete,s=>{this.plugin.settings.confirmAttachmentDelete=s},["delete","attachment","orphan"])),i.foldersStorage.push(this.sectionDef("Cross-Stashpad Search Scope","Toggle each Stashpad's pill to choose whether its notes contribute to cross-folder search. Excluded folders are still valid move destinations. Also: create a new Stashpad.",s=>{let n=this.plugin.discoverStashpadFolders();if(new M.Setting(s).setName("Cross-Stashpad Search Scope").setDesc("Toggle each Stashpad's pill to choose whether its notes contribute to cross-folder search. Excluded folders are still valid move destinations \u2014 their notes just don't appear in search results from elsewhere."),n.length===0)s.createEl("p",{cls:"setting-item-description"}).setText('No Stashpads found in this vault yet. A Stashpad is just a folder that contains a Stashpad-shaped note (frontmatter has both `id` and `parent`). Easiest way: open Stashpad (ribbon icon or command "Reveal or open Stashpad") \u2014 it auto-creates the default folder on first use. Or create one below.');else{let a=s.createDiv({cls:"stashpad-folder-list"});for(let l of n)this.renderFolderScopeRow(a,l)}let o=null;new M.Setting(s).setName("Create a new Stashpad").setDesc("Type a vault-relative folder path. The folder is created (with intermediates) and seeded with a Home note so Stashpad recognizes it.").addText(a=>{a.setPlaceholder("my-stashpad"),o=a.inputEl}).addButton(a=>a.setButtonText("Create").setCta().onClick(async()=>{let l=(o?.value??"").trim().replace(/^\/+|\/+$/g,"");if(!l){new M.Notice("Enter a folder name first.");return}try{await this.plugin.createNewStashpad(l),new M.Notice(`Created Stashpad "${l}".`),o&&(o.value=""),await this.plugin.waitForStashpadFolder(l,2e3),this.update?.()}catch(c){new M.Notice(`Couldn't create: ${c.message}`)}}))},["search","scope","exclude","include","create","new","stashpad","folder"])),i.foldersStorage.push(this.sectionDef("Folder Panel Placement","Pin, downrank, or hide folders in the Stashpad folder panel. Restore hidden folders here or from the panel's \u201CHidden\u201D section.",s=>{new M.Setting(s).setName("Folder Panel Placement").setDesc("Folders you've pinned, downranked, or hidden in the Stashpad folder panel. Pin/downrank from a folder's right-click menu in the panel; restore here or from the panel's \u201CHidden\u201D section."),this.renderFolderPlacementList(s)},["folder","panel","pin","pinned","downrank","hide","hidden","restore","placement"])),i.foldersStorage.push(this.headingDef("\u{1F3F7}\uFE0F Note Titles")),i.foldersStorage.push(this.renderDef("Slug stop-words","Words removed from auto-generated note titles (filenames). One per line.",s=>{let n=null,o=(this.plugin.settings.slugStopWords?.length?this.plugin.settings.slugStopWords:Ze).join(` -`);s.addTextArea(a=>{a.setValue(o),n=a.inputEl,n.rows=6,n.setCssStyles({fontFamily:"var(--font-monospace)"}),a.onChange(async l=>{this.plugin.settings.slugStopWords=(l||"").split(/\r?\n/).map(c=>c.trim().toLowerCase()).filter(Boolean),await e()})}).addExtraButton(a=>a.setIcon("rotate-ccw").setTooltip("Reset to defaults").onClick(async()=>{this.plugin.settings.slugStopWords=[...Ze],n&&(n.value=Ze.join(` -`)),await e()}))},["slug","stopwords","filename","title","note titles"])),i.composerCopy.push(t("Autofocus composer after sending","After Enter-submitting a note, return focus to the composer so you can keep typing. Off keeps focus in the list \u2014 useful if you want arrow keys to work without an extra click.",()=>this.plugin.settings.autofocusComposerAfterSend,s=>{this.plugin.settings.autofocusComposerAfterSend=s},["composer","focus","send"])),i.composerCopy.push(t("Focus composer when opening a note","Focus the composer when you open or switch into a Stashpad view. OFF (default) focuses the LIST instead, so arrow-key navigation works right away instead of the composer grabbing focus every time. (Separate from 'after sending' above.)",()=>this.plugin.settings.focusComposerOnOpen,s=>{this.plugin.settings.focusComposerOnOpen=s},["composer","focus","open","navigate","list"])),i.windowsTabs.push(t("Search opens the note in its list (in context)","When you pick a search result, open the LIST that contains the note (focus its parent) and scroll to the note \u2014 so you see it in context instead of landing on the focused-note header. On by default.",()=>this.plugin.settings.searchOpensInContext,s=>{this.plugin.settings.searchOpensInContext=s},["search","context","list","scroll","parent"])),i.windowsTabs.push(t("Open in new window \u2014 duplicate tab","ON: the new-window button (in the time-filter row) duplicates the current Stashpad tab \u2014 original stays open in the main window. OFF: the leaf is MOVED to the new window, closing the original tab.",()=>this.plugin.settings.popoutDuplicates,s=>{this.plugin.settings.popoutDuplicates=s},["popout","window","duplicate"])),i.windowsTabs.push(t("Search results open in a new tab","When you pick a result in the Search modal, open it in a new Stashpad tab instead of navigating the current tab. Applies to same-folder and cross-Stashpad results alike. On by default.",()=>this.plugin.settings.searchOpensInNewTab,s=>{this.plugin.settings.searchOpensInNewTab=s},["search","new tab","results","open"])),i.composerCopy.push(t("Prefix timestamps when copying","Include each note's timestamp before its body when copying with C or Y.",()=>this.plugin.settings.prefixTimestampsOnCopy,s=>{this.plugin.settings.prefixTimestampsOnCopy=s},["copy","timestamp","prefix"])),i}encryptionItems(){let e=this.plugin.encryption,t=[];return t.push(this.sectionDef("Vault Encryption","Set one password to encrypt content in this vault. Stored only on this device \u2014 there is no recovery if you lose it.",i=>{i.addClass("stashpad-encryption-section");let s=i.createDiv({cls:"stashpad-beta-row"});s.createEl("span",{cls:"stashpad-beta-badge",text:"BETA"}),s.createEl("span",{cls:"stashpad-beta-note",text:"Encryption is in beta \u2014 keep your own backups of anything important."}),i.createEl("div",{cls:"stashpad-ai-disclaimer"}).setText("\u26A0\uFE0F AI-built, NOT human-audited. This encryption was written by an AI assistant \u2014 not designed, reviewed, or security-audited by a human, and not tested by any security professional. It may carry real security, privacy, and DATA-LOSS risks. Treat it as a best-effort nice-to-have that might buy a little time against a casual snoop \u2014 nothing is guaranteed. Do NOT rely on it for anything sensitive, and always keep your own unencrypted backups of anything important."),i.createEl("p",{cls:"setting-item-description"}).setText("\u26A0\uFE0F Encryption protects what you lock in this vault. Each device unlocks with its own password (which never leaves the device); the vault key is shared with collaborators by approving their device \u2014 no shared password. If everyone with access loses their password, anything encrypted is gone for good. While encrypting, avoid a sync/cloud service writing the vault mid-operation \u2014 it can corrupt files.");let n=()=>e.argonProbe(),o=()=>this.plugin.settings.authorName?.trim()||"This device",a=e.accessState();e.refresh().then(()=>{e.accessState()!==a&&this.update?.()});let l=a;if(l==="none"){new M.Setting(i).setName("Encryption").setDesc("Not set up yet in this vault.").addButton(h=>h.setButtonText("Set up password\u2026").setCta().onClick(()=>{new de(this.app,{mode:"setup",offerKeychain:!0,kdfProbe:n,onSubmit:async({next:g,remember:u})=>{if(!g)return"Enter a password.";try{await e.setup(g,u,o())}catch(p){return p.message}return new M.Notice("Encryption set up \u2014 unlocked for this session."),this.update?.(),null}}).open()}));return}if(l==="outsider"&&!e.isUnlocked()){e.hasSharedPassword()&&new M.Setting(i).setName("This vault is encrypted").setDesc("Enter the shared password (ask whoever set it up \u2014 they'll send it via a password manager or secure message).").addButton(h=>h.setButtonText("Unlock with shared password\u2026").setCta().onClick(()=>{new de(this.app,{mode:"unlock",offerKeychain:!0,onSubmit:async({current:g,remember:u})=>await e.unlock(g,u)?(new M.Notice("Encryption unlocked."),this.update?.(),null):"Wrong password (or the keyfile hasn't synced here yet)."}).open()})),new M.Setting(i).setName(e.hasSharedPassword()?"Or request device approval":"This vault is encrypted by a collaborator").setDesc("Request access \u2014 pick a password for THIS device, then ask an existing member to approve it. Once approved (and the keyfile syncs to you), you'll unlock with that password. No shared secret.").addButton(h=>h.setButtonText("Request access\u2026").onClick(()=>{new de(this.app,{mode:"setup",offerKeychain:!0,kdfProbe:n,onSubmit:async({next:g,remember:u})=>{if(!g)return"Choose a password for this device.";try{await e.requestAccess(o(),g,u)}catch(p){return p.message}return new M.Notice("Access requested. An existing member can now approve this device."),this.update?.(),null}}).open()}));return}if(l==="pending"){new M.Setting(i).setName("Access requested \u2014 waiting for approval").setDesc("An existing member needs to approve this device. After they do and the keyfile syncs here, reopen this page to unlock with the password you chose.").addButton(h=>h.setButtonText("Try unlock now").setCta().onClick(()=>{new de(this.app,{mode:"unlock",offerKeychain:!0,onSubmit:async({current:g,remember:u})=>await e.unlock(g,u)?(new M.Notice("Encryption unlocked."),this.update?.(),null):"Not approved yet (or wrong password). Ask a member to approve this device, then try again."}).open()})).addButton(h=>h.setButtonText("Cancel request").onClick(async()=>{let g=e.myIdentityId();g&&await e.denyJoinRequest(g),new M.Notice("Access request cancelled."),this.update?.()}));return}let c=e.kdf()==="argon2id"?"Argon2id":e.kdf()==="pbkdf2"?"PBKDF2 (fallback)":"",d=e.isRemembered()?" \xB7 remembered on this device":"";if(new M.Setting(i).setName("Status").setDesc(`${e.isUnlocked()?"Set up \xB7 unlocked this session":"Set up \xB7 locked"}${c?` \xB7 ${c}`:""}${d}`),e.isUnlocked()?new M.Setting(i).setName("Lock now").setDesc("Forget the password from memory until you re-enter it.").addButton(h=>h.setButtonText("Lock now").onClick(()=>{e.lock(),new M.Notice("Encryption locked."),this.update?.()})):new M.Setting(i).setName("Unlock").setDesc("Enter your password to use encryption this session.").addButton(h=>h.setButtonText("Unlock\u2026").setCta().onClick(()=>{new de(this.app,{mode:"unlock",offerKeychain:!0,onSubmit:async({current:g,remember:u})=>await e.unlock(g,u)?(new M.Notice("Encryption unlocked."),this.update?.(),null):"Wrong password. Try again."}).open()})),gc&&e.keychainAvailable()&&e.isRemembered()&&new M.Setting(i).setName("Forget password on this device").setDesc("Drops ONLY the copy saved in this device's keychain \u2014 your encryption stays set up and nothing is decrypted or deleted. You'll just re-type the password next session. (To turn encryption off entirely, use \u201CRemove encryption\u201D below.)").addButton(h=>h.setButtonText("Forget on this device").onClick(async()=>{await e.forgetKeychain(),new M.Notice("Removed from this device's keychain \u2014 encryption still set up."),this.update?.()})),e.amIMember()&&new M.Setting(i).setName("Change this device's password").setDesc("Re-wraps THIS device's key under a new password \u2014 doesn't re-encrypt files or affect other people.").addButton(h=>h.setButtonText("Change\u2026").onClick(()=>{new de(this.app,{mode:"change",offerKeychain:!0,kdfProbe:n,onSubmit:async({current:g,next:u,remember:p})=>await e.changePassword(g,u,p)?(new M.Notice("Password changed."),this.update?.(),null):"Wrong current password. Try again."}).open()})),e.amIMember()&&new M.Setting(i).setName("Remove encryption").setDesc("Erases the key for this vault so you can start fresh. If anything is still encrypted, you'll be offered to decrypt it (needs your password) or \u2014 if you've lost the password \u2014 permanently delete it. Then just type the confirmation phrase; no password needed.").addButton(h=>{let g=async()=>{let u=await this.plugin.encryptionStateStrict();if(u.live){let f=()=>{new ie(this.app,"Delete all locked content forever?","This PERMANENTLY destroys every locked note and folder \u2014 there is NO decrypted copy and NO recovery. Only do this if you've lost the password and want to start fresh.","Delete locked content forever",async m=>{if(!m)return;let C=await this.plugin.purgeAllLockedContent();new M.Notice(`Deleted ${C} locked item${C===1?"":"s"}.`),g()}).open()},I=async()=>{if(await this.plugin.unlockAllInVault(),(await this.plugin.encryptionStateStrict()).live){new M.Notice("Some notes are still locked (decryption was cancelled or failed) \u2014 removal cancelled.",1e4);return}g()};new ie(this.app,"Encrypted notes are still locked",`Some notes or folders are still encrypted (locked) and have no plaintext copy. Before removing encryption: +`}function Ts(A,r){let e=document.createDocumentFragment();e.createEl("div",{text:"Home note updated"}).setCssStyles({fontWeight:"600",marginBottom:"6px"});let i=e.createEl("ul");i.setCssStyles({margin:"0 0 8px 0",paddingLeft:"18px"}),i.createEl("li",{text:`${r.indexed.length} indexed`}),i.createEl("li",{text:`${r.nonIndex.length+r.skippedStashpadNotes.length} excluded`});let s=e.createEl("button",{text:"Open",cls:"mod-cta"});s.setCssStyles({padding:"2px 10px",fontSize:"var(--font-ui-smaller)"});let n;return s.onclick=async o=>{if(o.stopPropagation(),r.previewPath){let a=A.vault.getAbstractFileByPath(r.previewPath);a instanceof Ie.TFile&&await A.workspace.getLeaf("tab").openFile(a)}n?.hide()},n=new Ie.Notice(e,0),n}var Ie,Kr,Wi,Xi=$e(()=>{"use strict";Ie=require("obsidian");Le();ri();le();Kr=/^(\d+-\d+|\d+|[A-Za-z0-9]+(?:\.[A-Za-z0-9]+)+)\s+(.+)$/;Wi=class extends Ie.Modal{constructor(e,t,i,s,n){super(e);this.plugin=t;this.settings=i;this.indexedCount=s;this.onBuild=n;this.LARGE_BUILD=50;this.previewRan=!1;this.previewPath=null}onOpen(){let{contentEl:e,titleEl:t}=this,i=!this.settings.jdIndexHasBuilt,s=this.indexedCount>this.LARGE_BUILD;t.setText(i?"Build JD index \u2014 first time?":"Build JD index");let n=this.settings.jdIndexStashpadFolder,o=e.createEl("p"),a=`${this.indexedCount} note${this.indexedCount===1?"":"s"}`;i?(o.appendText(`Stashpad is about to create ${a} (plus synthetic parents as needed) inside "`),o.createEl("code",{text:n}),o.appendText('". Existing notes with the same '),o.createEl("code",{text:"jdPrefix"}),o.appendText(" are updated, never deleted \u2014 but if the prefix detection picks up notes you didn't mean to index, you'll end up with a lot of unwanted notes."),e.createEl("p").setText("Running Preview first writes a single Markdown file showing exactly what would be built (and what wouldn't), so you can sanity-check before committing.")):s?(o.appendText(`You're about to create ${this.indexedCount} notes in "`),o.createEl("code",{text:n}),o.appendText(`". That's a big batch \u2014 if anything looks off, Preview the single-file output first.`)):(o.appendText('Build the JD index into "'),o.createEl("code",{text:n}),o.appendText(`"? Stashpad will create / update ${a}. Existing notes with matching `),o.createEl("code",{text:"jdPrefix"}),o.appendText(" are updated in place.")),this.previewRan&&this.previewPath&&e.createEl("p",{cls:"setting-item-description"}).setText(`\u2713 Preview written to home note (${this.previewPath}). Open it before building if you haven't.`);let l=new Ie.Setting(e);(i||s)&&l.addButton(c=>{c.setButtonText(this.previewRan?"Re-run preview":"Run preview first"),c.setCta(),c.onClick(async()=>{try{let d=await Ms(this.app,this.plugin,this.settings);if(d.error==="no-dest"){new Ie.Notice("Set a Designated Stashpad folder for Index first.",5e3),this.close();return}if(d.error==="no-home"){new Ie.Notice(`"${this.settings.jdIndexStashpadFolder}" has no Stashpad home note. Open the folder in Stashpad first.`,7e3),this.close();return}this.previewRan=!0,this.previewPath=d.previewPath,Ts(this.app,d),this.contentEl.empty(),this.titleEl.empty(),this.onOpen()}catch(d){new Ie.Notice(`Preview failed: ${d?.message??d}`,8e3)}})}),l.addButton(c=>{c.setButtonText(i?"Build anyway":"Build"),!i&&!s&&c.setCta(),c.onClick(async()=>{this.close(),await this.onBuild()})}),l.addButton(c=>{c.setButtonText("Cancel"),c.onClick(()=>this.close())})}onClose(){this.contentEl.empty()}}});function id(A,r){return td(A,r||{},0,0)}function sd(A,r){return Xc(A,{i:2},r&&r.out,r&&r.dictionary)}function gn(A,r){if(r){for(var e=new Ae(A.length),t=0;t<A.length;++t)e[t]=A.charCodeAt(t);return e}if(Pa)return Pa.encode(A);for(var i=A.length,s=new Ae(A.length+(A.length>>1)),n=0,o=function(c){s[n++]=c},t=0;t<i;++t){if(n+5>s.length){var a=new Ae(n+8+(i-t<<1));a.set(s),s=a}var l=A.charCodeAt(t);l<128||r?o(l):l<2048?(o(192|l>>6),o(128|l&63)):l>55295&&l<57344?(l=65536+(l&1047552)|A.charCodeAt(++t)&1023,o(240|l>>18),o(128|l>>12&63),o(128|l>>6&63),o(128|l&63)):(o(224|l>>12),o(128|l>>6&63),o(128|l&63))}return ds(s,0,n)}function tr(A,r){if(r){for(var e="",t=0;t<A.length;t+=16384)e+=String.fromCharCode.apply(null,A.subarray(t,t+16384));return e}else{if(Zo)return Zo.decode(A);var i=od(A),s=i.s,e=i.r;return e.length&&Ee(8),s}}function $a(A,r){r||(r={});var e={},t=[];Ja(A,"",e,r);var i=0,s=0;for(var n in e){var o=e[n],a=o[0],l=o[1],c=l.level==0?0:8,d=gn(n),h=d.length,g=l.comment,f=g&&gn(g),p=f&&f.length,u=jo(l.extra);h>65535&&Ee(11);var I=c?id(a,l):a,m=I.length,C=ed();C.p(a),t.push(Ka(l,{size:a.length,crc:C.d(),c:I,f:d,m:f,u:h!=n.length||f&&g.length!=p,o:i,compression:c})),i+=30+h+u+m,s+=76+2*(h+u)+(p||0)+m}for(var B=new Ae(s+22),w=i,y=s-i,v=0;v<t.length;++v){var d=t[v];Ha(B,d.o,d,d.f,d.u,d.c.length);var E=30+d.f.length+jo(d.extra);B.set(d.c,d.o+E),Ha(B,i,d,d.f,d.u,d.c.length,d.o,d.m),i+=16+E+(d.m?d.m.length:0)}return cd(B,i,t.length,y,w),B}function Ya(A,r){for(var e={},t=A.length-22;Ue(A,t)!=101010256;--t)(!t||A.length-t>65558)&&Ee(13);var i=st(A,t+8);if(!i)return{};var s=Ue(A,t+16),n=Ue(A,t-20)==117853008;if(n){var o=Ue(A,t-12);n=Ue(A,o)==101075792,n&&(i=Ue(A,o+32),s=Ue(A,o+48))}for(var a=r&&r.filter,l=0;l<i;++l){var c=ad(A,s,n),d=c[0],h=c[1],g=c[2],f=c[3],p=c[4],u=c[5],I=rd(A,u);s=p,(!a||a({name:f,size:h,originalSize:g,compression:d}))&&(d?d==8?e[f]=sd(A.subarray(I,I+h),{out:new Ae(g)}):Ee(14,"unknown compression type "+d):e[f]=ds(A,I,I+h))}return e}var Ae,Te,_o,un,fn,zo,Ra,Ua,Ga,Vo,La,Jc,Na,Wo,ut,j,nt,St,j,j,j,j,cs,j,$c,Yc,zc,Vc,Ko,Je,Jo,er,ds,Wc,Ee,Xc,ft,as,$o,Xo,Ma,ls,Oa,Ta,Zc,qa,jc,_c,ed,td,Ka,st,Ue,Yo,me,Ja,Pa,Zo,nd,od,rd,ad,ld,jo,Ha,cd,za=$e(()=>{Ae=Uint8Array,Te=Uint16Array,_o=Int32Array,un=new Ae([0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0,0]),fn=new Ae([0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,0,0]),zo=new Ae([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]),Ra=function(A,r){for(var e=new Te(31),t=0;t<31;++t)e[t]=r+=1<<A[t-1];for(var i=new _o(e[30]),t=1;t<30;++t)for(var s=e[t];s<e[t+1];++s)i[s]=s-e[t]<<5|t;return{b:e,r:i}},Ua=Ra(un,2),Ga=Ua.b,Vo=Ua.r;Ga[28]=258,Vo[258]=28;La=Ra(fn,0),Jc=La.b,Na=La.r,Wo=new Te(32768);for(j=0;j<32768;++j)ut=(j&43690)>>1|(j&21845)<<1,ut=(ut&52428)>>2|(ut&13107)<<2,ut=(ut&61680)>>4|(ut&3855)<<4,Wo[j]=((ut&65280)>>8|(ut&255)<<8)>>1;nt=function(A,r,e){for(var t=A.length,i=0,s=new Te(r);i<t;++i)A[i]&&++s[A[i]-1];var n=new Te(r);for(i=1;i<r;++i)n[i]=n[i-1]+s[i-1]<<1;var o;if(e){o=new Te(1<<r);var a=15-r;for(i=0;i<t;++i)if(A[i])for(var l=i<<4|A[i],c=r-A[i],d=n[A[i]-1]++<<c,h=d|(1<<c)-1;d<=h;++d)o[Wo[d]>>a]=l}else for(o=new Te(t),i=0;i<t;++i)A[i]&&(o[i]=Wo[n[A[i]-1]++]>>15-A[i]);return o},St=new Ae(288);for(j=0;j<144;++j)St[j]=8;for(j=144;j<256;++j)St[j]=9;for(j=256;j<280;++j)St[j]=7;for(j=280;j<288;++j)St[j]=8;cs=new Ae(32);for(j=0;j<32;++j)cs[j]=5;$c=nt(St,9,0),Yc=nt(St,9,1),zc=nt(cs,5,0),Vc=nt(cs,5,1),Ko=function(A){for(var r=A[0],e=1;e<A.length;++e)A[e]>r&&(r=A[e]);return r},Je=function(A,r,e){var t=r/8|0;return(A[t]|A[t+1]<<8)>>(r&7)&e},Jo=function(A,r){var e=r/8|0;return(A[e]|A[e+1]<<8|A[e+2]<<16)>>(r&7)},er=function(A){return(A+7)/8|0},ds=function(A,r,e){return(r==null||r<0)&&(r=0),(e==null||e>A.length)&&(e=A.length),new Ae(A.subarray(r,e))},Wc=["unexpected EOF","invalid block type","invalid length/literal","invalid distance","stream finished","no stream handler",,"no callback","invalid UTF-8 data","extra field too long","date not in range 1980-2099","filename too long","stream finishing","invalid zip data"],Ee=function(A,r,e){var t=new Error(r||Wc[A]);if(t.code=A,Error.captureStackTrace&&Error.captureStackTrace(t,Ee),!e)throw t;return t},Xc=function(A,r,e,t){var i=A.length,s=t?t.length:0;if(!i||r.f&&!r.l)return e||new Ae(0);var n=!e,o=n||r.i!=2,a=r.i;n&&(e=new Ae(i*3));var l=function(pe){var He=e.length;if(pe>He){var ne=new Ae(Math.max(He*2,pe));ne.set(e),e=ne}},c=r.f||0,d=r.p||0,h=r.b||0,g=r.l,f=r.d,p=r.m,u=r.n,I=i*8;do{if(!g){c=Je(A,d,1);var m=Je(A,d+1,3);if(d+=3,m)if(m==1)g=Yc,f=Vc,p=9,u=5;else if(m==2){var y=Je(A,d,31)+257,v=Je(A,d+10,15)+4,E=y+Je(A,d+5,31)+1;d+=14;for(var Q=new Ae(E),S=new Ae(19),x=0;x<v;++x)S[zo[x]]=Je(A,d+x*3,7);d+=v*3;for(var H=Ko(S),G=(1<<H)-1,N=nt(S,H,1),x=0;x<E;){var R=N[Je(A,d,G)];d+=R&15;var C=R>>4;if(C<16)Q[x++]=C;else{var U=0,D=0;for(C==16?(D=3+Je(A,d,3),d+=2,U=Q[x-1]):C==17?(D=3+Je(A,d,7),d+=3):C==18&&(D=11+Je(A,d,127),d+=7);D--;)Q[x++]=U}}var b=Q.subarray(0,y),P=Q.subarray(y);p=Ko(b),u=Ko(P),g=nt(b,p,1),f=nt(P,u,1)}else Ee(1);else{var C=er(d)+4,B=A[C-4]|A[C-3]<<8,w=C+B;if(w>i){a&&Ee(0);break}o&&l(h+B),e.set(A.subarray(C,w),h),r.b=h+=B,r.p=d=w*8,r.f=c;continue}if(d>I){a&&Ee(0);break}}o&&l(h+131072);for(var O=(1<<p)-1,L=(1<<u)-1,q=d;;q=d){var U=g[Jo(A,d)&O],V=U>>4;if(d+=U&15,d>I){a&&Ee(0);break}if(U||Ee(2),V<256)e[h++]=V;else if(V==256){q=d,g=null;break}else{var Y=V-254;if(V>264){var x=V-257,$=un[x];Y=Je(A,d,(1<<$)-1)+Ga[x],d+=$}var _=f[Jo(A,d)&L],ce=_>>4;_||Ee(3),d+=_&15;var P=Jc[ce];if(ce>3){var $=fn[ce];P+=Jo(A,d)&(1<<$)-1,d+=$}if(d>I){a&&Ee(0);break}o&&l(h+131072);var re=h+Y;if(h<P){var se=s-P,te=Math.min(P,re);for(se+h<0&&Ee(3);h<te;++h)e[h]=t[se+h]}for(;h<re;++h)e[h]=e[h-P]}}r.l=g,r.p=q,r.b=h,r.f=c,g&&(c=1,r.m=p,r.d=f,r.n=u)}while(!c);return h!=e.length&&n?ds(e,0,h):e.subarray(0,h)},ft=function(A,r,e){e<<=r&7;var t=r/8|0;A[t]|=e,A[t+1]|=e>>8},as=function(A,r,e){e<<=r&7;var t=r/8|0;A[t]|=e,A[t+1]|=e>>8,A[t+2]|=e>>16},$o=function(A,r){for(var e=[],t=0;t<A.length;++t)A[t]&&e.push({s:t,f:A[t]});var i=e.length,s=e.slice();if(!i)return{t:qa,l:0};if(i==1){var n=new Ae(e[0].s+1);return n[e[0].s]=1,{t:n,l:1}}e.sort(function(w,y){return w.f-y.f}),e.push({s:-1,f:25001});var o=e[0],a=e[1],l=0,c=1,d=2;for(e[0]={s:-1,f:o.f+a.f,l:o,r:a};c!=i-1;)o=e[e[l].f<e[d].f?l++:d++],a=e[l!=c&&e[l].f<e[d].f?l++:d++],e[c++]={s:-1,f:o.f+a.f,l:o,r:a};for(var h=s[0].s,t=1;t<i;++t)s[t].s>h&&(h=s[t].s);var g=new Te(h+1),f=Xo(e[c-1],g,0);if(f>r){var t=0,p=0,u=f-r,I=1<<u;for(s.sort(function(y,v){return g[v.s]-g[y.s]||y.f-v.f});t<i;++t){var m=s[t].s;if(g[m]>r)p+=I-(1<<f-g[m]),g[m]=r;else break}for(p>>=u;p>0;){var C=s[t].s;g[C]<r?p-=1<<r-g[C]++-1:++t}for(;t>=0&&p;--t){var B=s[t].s;g[B]==r&&(--g[B],++p)}f=r}return{t:new Ae(g),l:f}},Xo=function(A,r,e){return A.s==-1?Math.max(Xo(A.l,r,e+1),Xo(A.r,r,e+1)):r[A.s]=e},Ma=function(A){for(var r=A.length;r&&!A[--r];);for(var e=new Te(++r),t=0,i=A[0],s=1,n=function(a){e[t++]=a},o=1;o<=r;++o)if(A[o]==i&&o!=r)++s;else{if(!i&&s>2){for(;s>138;s-=138)n(32754);s>2&&(n(s>10?s-11<<5|28690:s-3<<5|12305),s=0)}else if(s>3){for(n(i),--s;s>6;s-=6)n(8304);s>2&&(n(s-3<<5|8208),s=0)}for(;s--;)n(i);s=1,i=A[o]}return{c:e.subarray(0,t),n:r}},ls=function(A,r){for(var e=0,t=0;t<r.length;++t)e+=A[t]*r[t];return e},Oa=function(A,r,e){var t=e.length,i=er(r+2);A[i]=t&255,A[i+1]=t>>8,A[i+2]=A[i]^255,A[i+3]=A[i+1]^255;for(var s=0;s<t;++s)A[i+s+4]=e[s];return(i+4+t)*8},Ta=function(A,r,e,t,i,s,n,o,a,l,c){ft(r,c++,e),++i[256];for(var d=$o(i,15),h=d.t,g=d.l,f=$o(s,15),p=f.t,u=f.l,I=Ma(h),m=I.c,C=I.n,B=Ma(p),w=B.c,y=B.n,v=new Te(19),E=0;E<m.length;++E)++v[m[E]&31];for(var E=0;E<w.length;++E)++v[w[E]&31];for(var Q=$o(v,7),S=Q.t,x=Q.l,H=19;H>4&&!S[zo[H-1]];--H);var G=l+5<<3,N=ls(i,St)+ls(s,cs)+n,R=ls(i,h)+ls(s,p)+n+14+3*H+ls(v,S)+2*v[16]+3*v[17]+7*v[18];if(a>=0&&G<=N&&G<=R)return Oa(r,c,A.subarray(a,a+l));var U,D,b,P;if(ft(r,c,1+(R<N)),c+=2,R<N){U=nt(h,g,0),D=h,b=nt(p,u,0),P=p;var O=nt(S,x,0);ft(r,c,C-257),ft(r,c+5,y-1),ft(r,c+10,H-4),c+=14;for(var E=0;E<H;++E)ft(r,c+3*E,S[zo[E]]);c+=3*H;for(var L=[m,w],q=0;q<2;++q)for(var V=L[q],E=0;E<V.length;++E){var Y=V[E]&31;ft(r,c,O[Y]),c+=S[Y],Y>15&&(ft(r,c,V[E]>>5&127),c+=V[E]>>12)}}else U=$c,D=St,b=zc,P=cs;for(var E=0;E<o;++E){var $=t[E];if($>255){var Y=$>>18&31;as(r,c,U[Y+257]),c+=D[Y+257],Y>7&&(ft(r,c,$>>23&31),c+=un[Y]);var _=$&31;as(r,c,b[_]),c+=P[_],_>3&&(as(r,c,$>>5&8191),c+=fn[_])}else as(r,c,U[$]),c+=D[$]}return as(r,c,U[256]),c+D[256]},Zc=new _o([65540,131080,131088,131104,262176,1048704,1048832,2114560,2117632]),qa=new Ae(0),jc=function(A,r,e,t,i,s){var n=s.z||A.length,o=new Ae(t+n+5*(1+Math.ceil(n/7e3))+i),a=o.subarray(t,o.length-i),l=s.l,c=(s.r||0)&7;if(r){c&&(a[0]=s.r>>3);for(var d=Zc[r-1],h=d>>13,g=d&8191,f=(1<<e)-1,p=s.p||new Te(32768),u=s.h||new Te(f+1),I=Math.ceil(e/3),m=2*I,C=function(ve){return(A[ve]^A[ve+1]<<I^A[ve+2]<<m)&f},B=new _o(25e3),w=new Te(288),y=new Te(32),v=0,E=0,Q=s.i||0,S=0,x=s.w||0,H=0;Q+2<n;++Q){var G=C(Q),N=Q&32767,R=u[G];if(p[N]=R,u[G]=N,x<=Q){var U=n-Q;if((v>7e3||S>24576)&&(U>423||!l)){c=Ta(A,a,0,B,w,y,E,S,H,Q-H,c),S=v=E=0,H=Q;for(var D=0;D<286;++D)w[D]=0;for(var D=0;D<30;++D)y[D]=0}var b=2,P=0,O=g,L=N-R&32767;if(U>2&&G==C(Q-L))for(var q=Math.min(h,U)-1,V=Math.min(32767,Q),Y=Math.min(258,U);L<=V&&--O&&N!=R;){if(A[Q+b]==A[Q+b-L]){for(var $=0;$<Y&&A[Q+$]==A[Q+$-L];++$);if($>b){if(b=$,P=L,$>q)break;for(var _=Math.min(L,$-2),ce=0,D=0;D<_;++D){var re=Q-L+D&32767,se=p[re],te=re-se&32767;te>ce&&(ce=te,R=re)}}}N=R,R=p[N],L+=N-R&32767}if(P){B[S++]=268435456|Vo[b]<<18|Na[P];var pe=Vo[b]&31,He=Na[P]&31;E+=un[pe]+fn[He],++w[257+pe],++y[He],x=Q+b,++v}else B[S++]=A[Q],++w[A[Q]]}}for(Q=Math.max(Q,x);Q<n;++Q)B[S++]=A[Q],++w[A[Q]];c=Ta(A,a,l,B,w,y,E,S,H,Q-H,c),l||(s.r=c&7|a[c/8|0]<<3,c-=7,s.h=u,s.p=p,s.i=Q,s.w=x)}else{for(var Q=s.w||0;Q<n+l;Q+=65535){var ne=Q+65535;ne>=n&&(a[c/8|0]=l,ne=n),c=Oa(a,c+1,A.subarray(Q,ne))}s.i=n}return ds(o,0,t+er(c)+i)},_c=function(){for(var A=new Int32Array(256),r=0;r<256;++r){for(var e=r,t=9;--t;)e=(e&1&&-306674912)^e>>>1;A[r]=e}return A}(),ed=function(){var A=-1;return{p:function(r){for(var e=A,t=0;t<r.length;++t)e=_c[e&255^r[t]]^e>>>8;A=e},d:function(){return~A}}},td=function(A,r,e,t,i){if(!i&&(i={l:1},r.dictionary)){var s=r.dictionary.subarray(-32768),n=new Ae(s.length+A.length);n.set(s),n.set(A,s.length),A=n,i.w=s.length}return jc(A,r.level==null?6:r.level,r.mem==null?i.l?Math.ceil(Math.max(8,Math.min(13,Math.log(A.length)))*1.5):20:12+r.mem,e,t,i)},Ka=function(A,r){var e={};for(var t in A)e[t]=A[t];for(var t in r)e[t]=r[t];return e},st=function(A,r){return A[r]|A[r+1]<<8},Ue=function(A,r){return(A[r]|A[r+1]<<8|A[r+2]<<16|A[r+3]<<24)>>>0},Yo=function(A,r){return Ue(A,r)+Ue(A,r+4)*4294967296},me=function(A,r,e){for(;e;++r)A[r]=e,e>>>=8};Ja=function(A,r,e,t){for(var i in A){var s=A[i],n=r+i,o=t;Array.isArray(s)&&(o=Ka(t,s[1]),s=s[0]),ArrayBuffer.isView(s)?e[n]=[s,o]:(e[n+="/"]=[new Ae(0),o],Ja(s,n,e,t))}},Pa=typeof TextEncoder<"u"&&new TextEncoder,Zo=typeof TextDecoder<"u"&&new TextDecoder,nd=0;try{Zo.decode(qa,{stream:!0}),nd=1}catch{}od=function(A){for(var r="",e=0;;){var t=A[e++],i=(t>127)+(t>223)+(t>239);if(e+i>A.length)return{s:r,r:ds(A,e-1)};i?i==3?(t=((t&15)<<18|(A[e++]&63)<<12|(A[e++]&63)<<6|A[e++]&63)-65536,r+=String.fromCharCode(55296|t>>10,56320|t&1023)):i&1?r+=String.fromCharCode((t&31)<<6|A[e++]&63):r+=String.fromCharCode((t&15)<<12|(A[e++]&63)<<6|A[e++]&63):r+=String.fromCharCode(t)}};rd=function(A,r){return r+30+st(A,r+26)+st(A,r+28)},ad=function(A,r,e){var t=st(A,r+28),i=st(A,r+30),s=tr(A.subarray(r+46,r+46+t),!(st(A,r+8)&2048)),n=r+46+t,o=ld(A,n,i,e,Ue(A,r+20),Ue(A,r+24),Ue(A,r+42)),a=o[0],l=o[1],c=o[2];return[st(A,r+10),a,l,s,n+i+st(A,r+32),c]},ld=function(A,r,e,t,i,s,n){var o=i==4294967295,a=s==4294967295,l=n==4294967295,c=r+e,d=o+a+l;if(t&&d){for(;r+4<c;r+=4+st(A,r+2))if(st(A,r)==1)return[o?Yo(A,r+4+8*a):i,a?Yo(A,r+4):s,l?Yo(A,r+4+8*(a+o)):n,1];t<2&&Ee(13)}return[i,s,n,0]},jo=function(A){var r=0;if(A)for(var e in A){var t=A[e].length;t>65535&&Ee(9),r+=t+4}return r},Ha=function(A,r,e,t,i,s,n,o){var a=t.length,l=e.extra,c=o&&o.length,d=jo(l);me(A,r,n!=null?33639248:67324752),r+=4,n!=null&&(A[r++]=20,A[r++]=e.os),A[r]=20,r+=2,A[r++]=e.flag<<1|(s<0&&8),A[r++]=i&&8,A[r++]=e.compression&255,A[r++]=e.compression>>8;var h=new Date(e.mtime==null?Date.now():e.mtime),g=h.getFullYear()-1980;if((g<0||g>119)&&Ee(10),me(A,r,g<<25|h.getMonth()+1<<21|h.getDate()<<16|h.getHours()<<11|h.getMinutes()<<5|h.getSeconds()>>1),r+=4,s!=-1&&(me(A,r,e.crc),me(A,r+4,s<0?-s-2:s),me(A,r+8,e.size)),me(A,r+12,a),me(A,r+14,d),r+=16,n!=null&&(me(A,r,c),me(A,r+6,e.attrs),me(A,r+10,n),r+=14),A.set(t,r),r+=a,d)for(var f in l){var p=l[f],u=p.length;me(A,r,+f),me(A,r+2,u),A.set(p,r+4),r+=4+u}return c&&(A.set(o,r),r+=c),r},cd=function(A,r,e,t,i){me(A,r,101010256),me(A,r+8,e),me(A,r+10,e),me(A,r+12,t),me(A,r+16,i)}});var Xa={};ki(Xa,{bytesToStr:()=>In,unzipFiles:()=>yi,zipFiles:()=>Bi});function Ad(A){return typeof A=="string"?gn(A):A instanceof Uint8Array?A:new Uint8Array(A)}function Bi(A,r=6){let e={};for(let t of A)e[t.name]=[Ad(t.data),{level:r}];return Promise.resolve($a(e,{level:r}))}function In(A){return tr(A)}function yi(A){let r=A instanceof Uint8Array?A:new Uint8Array(A),e=0,t=0,i=s=>{if(t+=1,t>Wa)throw new Error(`Refusing to unzip: too many entries (>${Wa}) \u2014 possible zip bomb.`);if(s.originalSize>dd)throw new Error(`Refusing to unzip: entry "${s.name}" is ${Math.round(s.originalSize/1048576)} MiB \u2014 possible zip bomb.`);if(e+=s.originalSize,e>Va)throw new Error(`Refusing to unzip: total size >${Math.round(Va/1048576)} MiB \u2014 possible zip bomb.`);return!0};return Promise.resolve(Ya(r,{filter:i}))}var Va,dd,Wa,mn=$e(()=>{"use strict";za();Va=512*1024*1024,dd=256*1024*1024,Wa=2e4});var Td={};ki(Td,{default:()=>Yn});module.exports=Gl(Td);var M=require("obsidian");var Si="sheet-group",lt="sheet-order",Xt="sheet-final",Ir="sheet-label",bi="sheet-origin",Di="forked-from",Ye="fork-siblings",Xn="forked-at",Zn=[Si,lt,Xt,Ir,bi,Di,Ye,Xn];function ze(A){if(!A)return null;let r=A[Si];return typeof r=="string"&&r.trim()?r:null}function Wn(A){let r=A?.[lt];return typeof r=="number"?r:null}function xi(A){return ze(A)!==null&&Wn(A)!==null}function Ni(A){return A?.[Xt]===!0}function mr(A){return A?.[bi]===!0}function Ll(A){let r=A?.[Di];return typeof r=="string"&&r.trim()?r:null}function Cs(A){return typeof A!="string"?null:A.replace(/^\[\[/,"").replace(/\]\]$/,"").split("|")[0].split("#")[0].trim()||null}function Cr(A){return Cs(Ll(A))}function Ol(A){let r=A?.[Ir];return typeof r=="string"&&r.trim()?r:null}function Br(){let A=Date.now().toString(36),r=Math.floor(Math.random()*1e6).toString(36);return`s-${A}-${r}`}function Be(A,r){return r.file?A.metadataCache.getFileCache(r.file)?.frontmatter:null}function jn(A,r){return r.slice().sort((e,t)=>{let i=Wn(Be(A,e)),s=Wn(Be(A,t));if(i!=null&&s!=null&&i!==s)return i-s;if(i!=null&&s==null)return-1;if(i==null&&s!=null)return 1;let n=e.created??"",o=t.created??"";return n!==o?n<o?-1:1:e.id<t.id?-1:e.id>t.id?1:0})}function yr(A,r){if(r.length===0)return null;let e=jn(A,r);return e.find(t=>Ni(Be(A,t)))??e[0]}function wr(A,r,e){let t=Ol(Be(A,r));return t||e}le();var ke=require("obsidian");var oe=require("obsidian");le();function Es(A){let r=(A.dateDisplayTimezone||"").trim();return r?{timeZone:r}:{}}function dt(A,r){try{return new Intl.DateTimeFormat(void 0,r).format(new Date(A))}catch{let{timeZone:e,...t}=r;return new Intl.DateTimeFormat(void 0,t).format(new Date(A))}}function Xe(A,r){let e=Es(r);switch(r.dateDisplayFormat??"locale"){case"iso":return Sr(A,r,!0);case"us":return dt(A,{...e,month:"numeric",day:"numeric",year:"numeric",hour:"numeric",minute:"2-digit"});case"eu":return dt(A,{...e,day:"numeric",month:"numeric",year:"numeric",hour:"2-digit",minute:"2-digit",hourCycle:"h23"});case"long":return dt(A,{...e,dateStyle:"full",timeStyle:"short"});case"locale":default:return dt(A,{...e,dateStyle:"medium",timeStyle:"short"})}}function Bt(A,r){let e=Es(r);switch(r.dateDisplayFormat??"locale"){case"iso":return Sr(A,r,!1);case"us":return dt(A,{...e,month:"numeric",day:"numeric",year:"numeric"});case"eu":return dt(A,{...e,day:"numeric",month:"numeric",year:"numeric"});case"long":return dt(A,{...e,dateStyle:"full"});case"locale":default:{let t=new Date,i=new Date(A).getFullYear()===t.getFullYear();return dt(A,i?{...e,month:"short",day:"numeric"}:{...e,month:"short",day:"numeric",year:"numeric"})}}}function ei(A,r){let e=Es(r),t=r.dateDisplayFormat==="iso"||r.dateDisplayFormat==="eu";return dt(A,{...e,hour:t?"2-digit":"numeric",minute:"2-digit",...t?{hourCycle:"h23"}:{}})}function Sr(A,r,e){let t=Es(r),i=new Intl.DateTimeFormat("en-CA",{...t,year:"numeric",month:"2-digit",day:"2-digit",...e?{hour:"2-digit",minute:"2-digit",hourCycle:"h23"}:{}}).formatToParts(new Date(A)),s=o=>i.find(a=>a.type===o)?.value??"",n=`${s("year")}-${s("month")}-${s("day")}`;return e?`${n} ${s("hour")}:${s("minute")}`:n}ye();le();function ao(A,r){let e=A.basename;return r&&e.endsWith(`-${r}`)?e=e.slice(0,-(r.length+1)):r||(e=e.replace(/-[abcdefghijkmnpqrstuvwxyz23456789]{6}$/,"")),e.replace(/-/g," ").trim()||A.basename}function Ss(A,r){let e=new Set(r.discoverStashpadFolders()),t=[];for(let i of A.vault.getMarkdownFiles()){let s=i.parent?.path?.replace(/\/+$/,"")??"";if(!e.has(s))continue;let n=A.metadataCache.getFileCache(i)?.frontmatter??{},o=typeof n.id=="string"?n.id:null;if(!o||o===F)continue;let a=n.completed===!0,l=_t(n,"task")||n.task===!0||n.completed!==void 0,c=typeof n.due=="string"||typeof n.due=="number"?String(n.due):null,d=null;if(typeof n.due=="number")Number.isFinite(n.due)&&n.due>=1e11&&(d=n.due);else if(c){let h=Date.parse(c);Number.isNaN(h)||(d=h)}!l&&!a&&d==null&&!c||t.push({file:i,folder:s,id:o,title:ao(i,o),task:l,completed:a,due:d,dueRaw:c,color:typeof n.color=="string"?n.color:null,assignedTo:Ut(n),assignedBy:ct(n.assignedBy),author:ct(n.author)})}return t}var Or=require("obsidian");var Ze=require("obsidian");ye();var Vl=0,lo=[{key:"overdue",label:"Overdue",icon:"alert-circle"},{key:"today",label:"Due today",icon:"calendar-clock"},{key:"upcoming",label:"Upcoming",icon:"calendar"},{key:"nodate",label:"No date",icon:"inbox"},{key:"completed",label:"Completed",icon:"check-circle-2"}];function bs(){return{assign:"all",person:"",folder:"all",status:"all"}}function zi(A,r,e,t,i){let s=()=>zi(A,r,e,t,i);A.empty(),A.addClass("stashpad-task-triage");let n=Ss(r,e),o=(e.settings.authorId??"").trim(),a=b=>!!o&&b.assignedTo.some(P=>P.id===o),l=b=>{switch(t.assign){case"mine":return a(b);case"others":return b.assignedTo.length>0&&!a(b);case"byme":return!!o&&b.assignedBy?.id===o;case"unassigned":return b.assignedTo.length===0;default:return!0}},c=b=>!t.person||b.assignedTo.some(P=>P.id===t.person)||b.author?.id===t.person||b.assignedBy?.id===t.person,d=new Map;for(let b of n){for(let P of b.assignedTo)d.set(P.id,P.name);b.assignedBy&&d.set(b.assignedBy.id,b.assignedBy.name),b.author&&d.set(b.author.id,b.author.name)}let h=[...d.entries()].map(([b,P])=>({id:b,name:P})).sort((b,P)=>b.name.localeCompare(P.name)),g=[...new Set(n.map(b=>b.folder))].sort((b,P)=>b.localeCompare(P));t.folder!=="all"&&!g.includes(t.folder)&&(t.folder="all");let f=b=>t.folder==="all"||b.folder===t.folder,p=A.createDiv({cls:"stashpad-triage-filters"}),u=p.createEl("button",{cls:"stashpad-triage-reset",attr:{"aria-label":"Reset filters"}});(0,Ze.setIcon)(u,"rotate-ccw"),u.onclick=()=>{t.assign="all",t.person="",t.folder="all",s()};let I=p.createEl("select",{cls:"stashpad-triage-select"}),m={all:"Everyone",mine:"Assigned to me",others:"Assigned to others",byme:"Assigned by me",unassigned:"Unassigned"};for(let b of["all","mine","others","byme","unassigned"]){let P=I.createEl("option",{text:m[b],value:b});t.assign===b&&(P.selected=!0)}I.onchange=()=>{t.assign=I.value,s()};let C=p.createEl("select",{cls:"stashpad-triage-select"}),B=C.createEl("option",{text:"All folders",value:"all"});t.folder==="all"&&(B.selected=!0);for(let b of g){let P=C.createEl("option",{text:b.split("/").pop()||b,value:b});t.folder===b&&(P.selected=!0)}if(C.onchange=()=>{t.folder=C.value,s()},h.length>0){let b=new Map;for(let $ of h)b.has($.name)||b.set($.name,$.id);let P=`stashpad-task-authors-${++Vl}`,O=p.createDiv({cls:"stashpad-triage-authorwrap"}),L=O.createEl("input",{cls:"stashpad-triage-authorsearch",attr:{type:"text",list:P,placeholder:"Any person\u2026",spellcheck:"false"}}),q=O.createEl("button",{cls:"stashpad-triage-authorclear",attr:{"aria-label":"Clear person filter"}});(0,Ze.setIcon)(q,"x"),q.onclick=()=>{L.value="",t.person?(t.person="",s()):L.focus()};let V=O.createEl("datalist");V.id=P;for(let $ of h)V.createEl("option",{value:$.name});t.person&&(L.value=d.get(t.person)??"");let Y=()=>{let $=L.value.trim();if($===""){t.person&&(t.person="",s());return}let _=b.get($);_&&t.person!==_&&(t.person=_,s())};L.onchange=Y,L.addEventListener("keydown",$=>{$.key==="Enter"&&Y()})}let w=Date.now(),y=new Date;y.setHours(0,0,0,0);let v=y.getTime(),E=v+864e5,Q=n.filter(b=>l(b)&&c(b)&&f(b)),S={overdue:[],today:[],upcoming:[],nodate:[],completed:[]};for(let b of Q){if(b.completed){S.completed.push(b);continue}if(b.due==null){S.nodate.push(b);continue}b.due<v?S.overdue.push(b):b.due<E?S.today.push(b):S.upcoming.push(b)}let x=(b,P)=>b.due==null&&P.due==null?b.title.localeCompare(P.title):b.due==null?1:P.due==null?-1:b.due-P.due,H=A.createDiv({cls:"stashpad-triage-chips"}),G=(b,P,O)=>{let L=H.createEl("button",{cls:"stashpad-triage-chip"});t.status===b&&L.addClass("is-active"),L.createSpan({text:P}),L.createSpan({cls:"stashpad-triage-chip-count",text:String(O)}),L.onclick=()=>{t.status=b,s()}};G("all","All",Q.length);for(let b of lo)G(b.key,b.label,S[b.key].length);if(Q.length===0){A.createDiv({cls:"stashpad-tasks-empty"}).setText(n.length===0?"No tasks yet \u2014 press G on a note to make it a task, or D to give it a due date.":"No tasks match the current filters.");return}let N=async b=>{try{await r.fileManager.processFrontMatter(b.file,P=>{P.completed=P.completed!==!0})}catch(P){new Ze.Notice(`Couldn't update task: ${P.message}`);return}s()},R=b=>{let P=b.dueRaw??(b.due!=null?new Date(b.due).toISOString():null);new Oe(r,P,O=>{r.fileManager.processFrontMatter(b.file,L=>{O.iso===null?delete L.due:(L.due=O.iso,L.task=!0)}).then(s).catch(L=>new Ze.Notice(`Couldn't snooze: ${L.message}`))},{title:"Snooze \u2014 reschedule",hideAssignees:!0,quickAdjusts:e.settings.dueQuickAdjusts}).open()},U=t.status==="all"?lo:lo.filter(b=>b.key===t.status),D=!1;for(let b of U){let P=S[b.key];if(P.length===0)continue;if(D=!0,P.sort(b.key==="completed"?(L,q)=>x(q,L):x),t.status==="all"){let L=A.createDiv({cls:`stashpad-review-section is-${b.key}`});(0,Ze.setIcon)(L.createSpan({cls:"stashpad-review-section-icon"}),b.icon),L.createSpan({cls:"stashpad-review-section-name",text:b.label}),L.createSpan({cls:"stashpad-review-section-count",text:String(P.length)})}let O=A.createDiv({cls:"stashpad-review-list"});for(let L of P)Wl(O,L,w,e,{toggleCompleted:N,snooze:R,onOpen:i.onOpen})}D||A.createDiv({cls:"stashpad-tasks-empty"}).setText("Nothing in this view.")}function Wl(A,r,e,t,i){let s=A.createDiv({cls:"stashpad-review-row"});r.completed&&s.addClass("is-completed");let n=s.createSpan({cls:"stashpad-review-check"});(0,Ze.setIcon)(n,r.completed?"check-square":"square"),r.color&&(n.style.color=r.color),n.title=r.completed?"Mark not done":"Mark done",n.onclick=()=>i.toggleCompleted(r);let o=s.createDiv({cls:"stashpad-review-main"}),a=o.createDiv({cls:"stashpad-review-title",text:r.title});a.onclick=()=>i.onOpen(r.folder,r.id);let l=o.createDiv({cls:"stashpad-review-meta"});if(l.createSpan({cls:"stashpad-review-folder",text:r.folder.split("/").pop()||r.folder}),r.author&&l.createSpan({cls:"stashpad-review-author",text:`by ${r.author.name}`}),r.due!=null){let g=r.due<e&&!r.completed,f=new Date(e);f.setHours(0,0,0,0);let p=f.getTime(),u=r.due>=p&&r.due<p+864e5,I=l.createSpan({cls:"stashpad-review-due",text:u?ei(r.due,t.settings):Bt(r.due,t.settings)});g&&I.addClass("is-overdue")}else r.dueRaw?l.createSpan({cls:"stashpad-review-due",text:r.dueRaw}):l.createSpan({cls:"stashpad-review-due is-none",text:"no date"});for(let g of r.assignedTo)l.createSpan({cls:"stashpad-review-assignee",text:g.name});let c=s.createDiv({cls:"stashpad-review-actions"}),d=c.createEl("button",{cls:"stashpad-review-btn"});(0,Ze.setIcon)(d,"alarm-clock"),d.title="Snooze \u2014 reschedule",d.onclick=()=>i.snooze(r);let h=c.createEl("button",{cls:"stashpad-review-btn"});(0,Ze.setIcon)(h,"arrow-right"),h.title="Open note",h.onclick=()=>i.onOpen(r.folder,r.id)}var oi=class extends Or.Modal{constructor(e,t){super(e);this.plugin=t;this.state=bs()}onOpen(){this.modalEl.addClass("stashpad-task-review-modal"),this.titleEl.setText("Daily task review"),zi(this.contentEl,this.app,this.plugin,this.state,{onOpen:(e,t)=>{this.plugin.revealNoteByRef(e,t),this.close()}})}onClose(){this.contentEl.empty()}};function yt(A,r,e){let t=A.createSpan({cls:"stashpad-count-badge"});e&&t.addClass("is-expanded"),t.setText(r>99?"99+":String(r))}var Vi={pinned:{label:"Pinned",icon:"pin"},shared:{label:"Shared",icon:"users"},tasks:{label:"Tasks",icon:"check-circle-2"}},Ds=class extends oe.ItemView{constructor(e,t){super(e);this.plugin=t;this.activePanel="pinned";this.taskFilter="all";this.taskAssignFilter="all";this.taskFolderFilter="all";this.expanded=new Set;this.renderTimer=null;this.sharedAuthorFilter="all";this.sharedContribOnly=!1}setActivePanel(e){this.activePanel=e,this.containerEl.isConnected&&this.render()}getViewType(){return It}getDisplayText(){return"Stashpad panels"}getIcon(){return"panel-left"}async onOpen(){this.render(),this.registerEvent(this.app.metadataCache.on("changed",()=>this.scheduleRender())),this.registerEvent(this.app.vault.on("rename",()=>this.scheduleRender())),this.registerEvent(this.app.vault.on("delete",()=>this.scheduleRender())),this.registerEvent(this.app.workspace.on("active-leaf-change",e=>{e&&e.view.getViewType()===z&&this.scheduleRender()}))}scheduleRender(){this.renderTimer==null&&(this.renderTimer=window.setTimeout(()=>{this.renderTimer=null,this.containerEl.isConnected&&this.render()},80))}render(){let e=this.contentEl;e.empty(),e.addClass("stashpad-panels-root");let t=e.createDiv({cls:"stashpad-panels-globals"}),i=t.createEl("button",{cls:"stashpad-panels-global-btn"});(0,oe.setIcon)(i.createSpan({cls:"stashpad-panels-global-btn-icon"}),"search"),i.createSpan({cls:"stashpad-panels-global-btn-text",text:"Search"}),i.onclick=()=>this.openSearchFromPanel();let s=t.createEl("button",{cls:"stashpad-panels-global-btn"});(0,oe.setIcon)(s.createSpan({cls:"stashpad-panels-global-btn-icon"}),"folder-tree"),s.createSpan({cls:"stashpad-panels-global-btn-text",text:"Folder Switcher"}),s.onclick=()=>this.plugin.openFolderPicker();let n=t.createDiv({cls:"stashpad-panels-globals-row"}),o=n.createEl("button",{cls:"stashpad-panels-global-btn"});(0,oe.setIcon)(o.createSpan({cls:"stashpad-panels-global-btn-icon"}),"scroll-text"),o.createSpan({cls:"stashpad-panels-global-btn-text",text:"Log"}),o.onclick=()=>this.openLogFromPanel();let a=n.createEl("button",{cls:"stashpad-panels-global-btn"});(0,oe.setIcon)(a.createSpan({cls:"stashpad-panels-global-btn-icon"}),"bell"),a.createSpan({cls:"stashpad-panels-global-btn-text",text:"Notifications"}),a.onclick=()=>this.openNotificationsFromPanel();let l=e.createDiv({cls:"stashpad-panels-bar"});for(let d of Object.keys(Vi)){let h=Vi[d],g=l.createEl("button",{cls:"stashpad-panels-bar-btn"});(0,oe.setIcon)(g.createSpan({cls:"stashpad-panels-bar-btn-icon"}),h.icon),g.createSpan({cls:"stashpad-panels-bar-btn-text",text:h.label}),this.activePanel===d&&g.addClass("is-active"),g.onclick=()=>{this.activePanel!==d&&(this.activePanel=d,this.render())}}let c=e.createDiv({cls:"stashpad-panels-body"});this.activePanel==="pinned"?this.renderPinnedPanel(c):this.activePanel==="shared"?this.renderSharedPanel(c):this.activePanel==="tasks"&&this.renderTasksPanel(c)}openPinnedOptionsMenu(e){let t=this.plugin.settings.folderPanelPinnedGrouping??"pin-order",i=new oe.Menu;i.addItem(s=>s.setTitle("Sort by pin order").setChecked(t==="pin-order").onClick(()=>void this.setPinnedGrouping("pin-order"))),i.addItem(s=>s.setTitle("Group by folder").setChecked(t==="folder").onClick(()=>void this.setPinnedGrouping("folder"))),i.showAtMouseEvent(e)}async setPinnedGrouping(e){(this.plugin.settings.folderPanelPinnedGrouping??"pin-order")!==e&&(this.plugin.settings.folderPanelPinnedGrouping=e,await this.plugin.saveSettings(),this.render())}renderPinnedPanel(e){let t=e.createDiv({cls:"stashpad-panel-pinned"});(this.plugin.settings.folderPanelPinnedGrouping??"pin-order")!=="folder"&&t.addClass("is-flat");let i=t.createDiv({cls:"stashpad-pinned-headrow"}),s=i.createEl("button",{cls:"stashpad-pinned-row stashpad-pinned-home"}),n=s.createSpan({cls:"stashpad-pinned-icon"});(0,oe.setIcon)(n,"home"),s.createSpan({cls:"stashpad-pinned-label",text:"Home"}),s.onclick=()=>this.openHomeFromPanel();let o=i.createEl("button",{cls:"stashpad-folderpanel-iconbtn stashpad-pinned-opts"});(0,oe.setIcon)(o,"list"),o.setAttr("aria-label","Pinned view options"),o.onclick=g=>{g.stopPropagation(),this.openPinnedOptionsMenu(g)};let a=this.plugin.listPinnedNotes();if(a.length===0){t.createDiv({cls:"stashpad-pinned-empty"}).setText("No pinned notes yet \u2014 right-click a note and choose \u201CPin to sidebar.\u201D");return}if((this.plugin.settings.folderPanelPinnedGrouping??"pin-order")!=="folder"){a.forEach((g,f)=>this.renderPinnedRow(t,g,f));return}let c=new Map;a.forEach((g,f)=>{let p=c.get(g.folder);p||(p=[],c.set(g.folder,p)),p.push({pin:g,idx:f})});let d=this.plugin.lastActiveStashpadLeaf?.view?.noteFolder,h=Array.from(c.keys());d&&c.has(d)&&(h.splice(h.indexOf(d),1),h.unshift(d));for(let g of h){let f=g.split("/").pop()||g,p=t.createDiv({cls:"stashpad-pinned-group-header"});g===d&&p.addClass("is-active-folder"),p.createSpan({cls:"stashpad-pinned-group-name",text:f});let u=c.get(g)??[];for(let{pin:I,idx:m}of u)this.renderPinnedRow(t,I,m)}}renderPinnedRow(e,t,i){let s=this.findFileFor(t);if(!s)return;let n=this.app.metadataCache.getFileCache(s)?.frontmatter??{},o=this.titleFromFile(s),a=typeof n.color=="string"?n.color:null,l=n.completed===!0,c=this.childrenOf(t.folder,t.id).length,d=c>0,h=this.expanded.has(`${t.folder}|${t.id}`),g=e.createDiv({cls:"stashpad-pinned-row"});a&&g.addClass("has-color"),l&&g.addClass("is-completed"),g.draggable=!0,g.dataset.pinIdx=String(i),g.addEventListener("dragstart",m=>{m.dataTransfer?.setData("text/plain",String(i)),m.dataTransfer&&(m.dataTransfer.effectAllowed="move"),g.addClass("is-dragging")}),g.addEventListener("dragend",()=>g.removeClass("is-dragging")),g.addEventListener("dragover",m=>{m.preventDefault(),m.dataTransfer&&(m.dataTransfer.dropEffect="move");let C=g.getBoundingClientRect(),B=m.clientY-C.top<C.height/2;g.toggleClass("drop-before",B),g.toggleClass("drop-after",!B)}),g.addEventListener("dragleave",()=>{g.removeClass("drop-before"),g.removeClass("drop-after")}),g.addEventListener("drop",m=>{m.preventDefault(),g.removeClass("drop-before"),g.removeClass("drop-after");let C=parseInt(m.dataTransfer?.getData("text/plain")??"",10);if(!Number.isFinite(C)||C===i)return;let B=g.getBoundingClientRect(),w=m.clientY-B.top<B.height/2;this.reorderPin(C,w?i:i+1)});let f=g.createSpan({cls:"stashpad-pinned-toggle"});d&&(yt(f,c,h),f.onclick=m=>{m.stopPropagation();let C=`${t.folder}|${t.id}`;this.expanded.has(C)?this.expanded.delete(C):this.expanded.add(C),this.render()});let p=g.createSpan({cls:"stashpad-pinned-icon"});(0,oe.setIcon)(p,d?"folder-tree":"file-text"),a&&(p.style.color=a);let u=g.createSpan({cls:"stashpad-pinned-label",text:o});u.onclick=()=>this.openPinFromPanel(t);let I=t.folder.split("/").pop()||t.folder;if(g.createSpan({cls:"stashpad-pinned-folder",text:I}),g.oncontextmenu=m=>{m.preventDefault();let C=new oe.Menu;C.addItem(B=>B.setTitle("Unpin from sidebar").setIcon("pin-off").onClick(()=>{this.plugin.unpinNote(t)})),C.showAtMouseEvent(m)},d&&h){let m=e.createDiv({cls:"stashpad-pinned-children"});this.renderPinnedSubtree(m,t.folder,t.id,1)}}async reorderPin(e,t){let i=this.plugin.listPinnedNotes();if(e<0||e>=i.length)return;let s=i[e],n=i.filter((d,h)=>h!==e),o=Math.max(0,Math.min(t>e?t-1:t,n.length)),a=n[o-1],l=n[o],c;!a&&!l?c=Date.now():a?l?c=(a.pinnedAt+l.pinnedAt)/2:c=a.pinnedAt+1e3:c=l.pinnedAt-1e3;try{await this.app.fileManager.processFrontMatter(s.file,d=>{d.pinnedAt=c})}catch(d){console.warn("[Stashpad] pin reorder failed",d)}this.render()}renderPinnedSubtree(e,t,i,s){let n=this.childrenOf(t,i);for(let o of n){let a=this.app.metadataCache.getFileCache(o)?.frontmatter??{},l=typeof a.id=="string"?a.id:null;if(!l)continue;let c=typeof a.color=="string"?a.color:null,d=a.completed===!0,h=this.childrenOf(t,l).length,g=h>0,f=this.expanded.has(`${t}|${l}`),p=e.createDiv({cls:"stashpad-pinned-subrow"});d&&p.addClass("is-completed"),p.style.paddingLeft=`${s*16}px`;let u=p.createSpan({cls:"stashpad-pinned-toggle"});g&&(yt(u,h,f),u.onclick=C=>{C.stopPropagation();let B=`${t}|${l}`;this.expanded.has(B)?this.expanded.delete(B):this.expanded.add(B),this.render()});let I=p.createSpan({cls:"stashpad-pinned-icon"});(0,oe.setIcon)(I,"file-text"),c&&(I.style.color=c);let m=p.createSpan({cls:"stashpad-pinned-label",text:this.titleFromFile(o)});m.onclick=()=>this.openPinFromPanel({folder:t,id:l}),g&&f&&this.renderPinnedSubtree(e,t,l,s+1)}}findFileFor(e){for(let t of this.app.vault.getMarkdownFiles()){if((t.parent?.path?.replace(/\/+$/,"")??"")!==e.folder)continue;if(this.app.metadataCache.getFileCache(t)?.frontmatter?.id===e.id)return t}return null}childrenOf(e,t){let i=[];for(let s of this.app.vault.getMarkdownFiles()){if((s.parent?.path?.replace(/\/+$/,"")??"")!==e)continue;let o=this.app.metadataCache.getFileCache(s)?.frontmatter;if(!o||typeof o.id!="string")continue;let a=o.parent;if(a===t||t===F&&(a==null||a===F)){if(o.id===F)continue;i.push(s)}}return i.sort((s,n)=>{let o=this.app.metadataCache.getFileCache(s)?.frontmatter,a=this.app.metadataCache.getFileCache(n)?.frontmatter,l=o?.created??"",c=a?.created??"";return l.localeCompare(c)}),i}titleFromFile(e){let t=this.app.metadataCache.getFileCache(e)?.frontmatter?.id;return ao(e,typeof t=="string"?t:null)}async openSearchFromPanel(){let e=await this.resolveTargetStashpad();e&&typeof e.openSearchModal=="function"&&e.openSearchModal()}async openLogFromPanel(){let e=this.app.vault.adapter,t=this.plugin.pluginPrivatePath("log.jsonl");if(!await e.exists(t)){new oe.Notice("No log yet \u2014 make some changes first.");return}let i=await e.read(t),{LogModal:s}=await Promise.resolve().then(()=>(ye(),Yi));new s(this.app,i,t).open()}openNotificationsFromPanel(){this.app.commands?.executeCommandById?.("stashpad:stashpad-open-notification-history")}async openHomeFromPanel(){let e=await this.resolveTargetStashpad();e&&typeof e.navigateTo=="function"&&e.navigateTo(F)}async openPinFromPanel(e){await this.plugin.revealNoteByRef(e.folder,e.id)}async resolveTargetStashpad(){let e=this.plugin.lastActiveStashpadLeaf;if(e&&e.view.getViewType()===z)return this.app.workspace.revealLeaf(e),e.view;let t=this.findActiveStashpad();if(t)return t;let i=this.app.workspace.getLeavesOfType(z);return i.length>0?(this.app.workspace.revealLeaf(i[0]),i[0].view):(await this.plugin.activateView({reveal:!0}),this.findActiveStashpad())}findActiveStashpad(){let e=this.app.workspace.activeLeaf;return e&&e.view.getViewType()===z?e.view:null}renderSharedPanel(e){let t=e.createDiv({cls:"stashpad-panel-shared"}),i=(this.plugin.settings.authorId??"").trim();if(!i){t.createDiv({cls:"stashpad-shared-empty"}).setText("Set an author name in Stashpad settings to populate Shared.");return}let s=this.collectSharedNotes(i),n=new Map;for(let d of s){let h=d.authorId;h&&!n.has(h)&&n.set(h,d.authorDisplay||h)}let o=t.createDiv({cls:"stashpad-shared-filters"}),a=(d,h,g)=>{let f=o.createEl("button",{cls:"stashpad-shared-chip",text:d});return h&&f.addClass("is-active"),f.onclick=g,f};if(a("All",this.sharedAuthorFilter==="all",()=>{this.sharedAuthorFilter="all",this.render()}),a("Mine",this.sharedAuthorFilter==="mine",()=>{this.sharedAuthorFilter="mine",this.render()}),a("Others",this.sharedAuthorFilter==="others",()=>{this.sharedAuthorFilter="others",this.render()}),n.size>1){let d=o.createEl("select",{cls:"stashpad-shared-author-select"}),h=d.createEl("option",{text:"Any author"});h.value="__any__";for(let[f,p]of n){let u=d.createEl("option",{text:p});u.value=f}let g=["all","mine","others"].includes(this.sharedAuthorFilter)?"__any__":this.sharedAuthorFilter;d.value=g,d.onchange=()=>{let f=d.value;f==="__any__"?this.sharedAuthorFilter="all":this.sharedAuthorFilter=f,this.render()}}let l=o.createEl("button",{cls:"stashpad-shared-chip",text:"Has contributors"});this.sharedContribOnly&&l.addClass("is-active"),l.onclick=()=>{this.sharedContribOnly=!this.sharedContribOnly,this.render()};let c=s.filter(d=>{if(this.sharedContribOnly&&d.contributorCount===0)return!1;switch(this.sharedAuthorFilter){case"all":return!0;case"mine":return d.authorId===i;case"others":return d.authorId!==i;default:return d.authorId===this.sharedAuthorFilter}});if(c.length===0){t.createDiv({cls:"stashpad-shared-empty"}).setText("No shared notes match the current filters.");return}for(let d of c){let h=t.createDiv({cls:"stashpad-pinned-row stashpad-shared-row"}),g=h.createSpan({cls:"stashpad-pinned-icon"});(0,oe.setIcon)(g,"users"),d.color&&(g.style.color=d.color);let f=h.createSpan({cls:"stashpad-pinned-label",text:d.title});f.onclick=()=>this.openSharedFromPanel(d.folder,d.id);let p=d.folder.split("/").pop()||d.folder;h.createSpan({cls:"stashpad-pinned-folder",text:p}),d.authorDisplay&&h.createSpan({cls:"stashpad-shared-meta"}).setText(d.authorId===i?`you \xB7 ${d.contributorCount} contributor${d.contributorCount===1?"":"s"}`:`by ${d.authorDisplay}${d.contributorCount>0?` \xB7 ${d.contributorCount} contributor${d.contributorCount===1?"":"s"}`:""}`)}}collectSharedNotes(e){let t=this.plugin.discoverStashpadFolders(),i=new Set(t),s=new Map;for(let o of this.app.vault.getMarkdownFiles()){let a=o.parent?.path?.replace(/\/+$/,"")??"";if(!i.has(a))continue;let l=this.app.metadataCache.getFileCache(o)?.frontmatter??{};l.id===F&&s.set(a,this.extractAuthorId(l.author))}let n=[];for(let o of this.app.vault.getMarkdownFiles()){let a=o.parent?.path?.replace(/\/+$/,"")??"";if(!i.has(a)||a.endsWith("/_authors")||o.path.includes("/_authors/"))continue;let l=this.app.metadataCache.getFileCache(o)?.frontmatter??{};if(typeof l.id!="string"||l.id===F)continue;let c=this.extractAuthorId(l.author),d=Array.isArray(l.contributors)?l.contributors.filter(C=>typeof C=="string"):[],h=s.get(a)??null;if(!(d.length>0||h===e&&(c!==null&&c!==e)))continue;let I=this.titleFromFile(o),m=typeof l.color=="string"?l.color:null;n.push({file:o,folder:a,id:l.id,title:I,color:m,authorId:c,authorDisplay:this.extractAuthorDisplay(l.author)||(c??""),contributorCount:d.length})}return n.sort((o,a)=>{let l=this.app.metadataCache.getFileCache(o.file)?.frontmatter??{},c=this.app.metadataCache.getFileCache(a.file)?.frontmatter??{},d=l.modified??l.created??"";return(c.modified??c.created??"").localeCompare(d)}),n}extractAuthorId(e){if(typeof e!="string")return null;let t=e.match(/-([a-z0-9]{4,12})(?:\.md)?(?:\||\]\])/i);return t?t[1]:null}extractAuthorDisplay(e){if(typeof e!="string")return"";let t=e.match(/\|([^\]]+)\]\]/);if(t)return t[1].trim();let i=e.match(/_authors\/([^\]|]+)-[a-z0-9]{4,12}/i);return i?i[1].replace(/[-_]/g," ").trim():""}async openSharedFromPanel(e,t){await this.plugin.revealNoteByRef(e,t)}renderTasksPanel(e){let t=e.createDiv({cls:"stashpad-panel-tasks"}),s=t.createDiv({cls:"stashpad-task-review-bar"}).createEl("button",{cls:"stashpad-task-review-open"});(0,oe.setIcon)(s.createSpan({cls:"stashpad-btn-icon"}),"maximize-2"),s.createSpan({text:"Daily review"}),s.onclick=()=>new oi(this.app,this.plugin).open();let n=this.collectTasks();if(n.length===0){t.createDiv({cls:"stashpad-tasks-empty"}).setText("No tasks yet \u2014 press G on a note to mark it a task, or D to give it a due date.");return}let o=(this.plugin.settings.authorId??"").trim(),a=D=>!!o&&D.assignedTo.some(b=>b.id===o),l=D=>{let b=this.taskAssignFilter;if(b.startsWith("person:")){let P=b.slice(7);return D.assignedTo.some(O=>O.id===P)}switch(b){case"mine":return a(D);case"others":return D.assignedTo.length>0&&!a(D);case"byme":return!!o&&D.assignedBy?.id===o;case"unassigned":return D.assignedTo.length===0;default:return!0}},c=new Map;for(let D of n){for(let b of D.assignedTo)b.id!==o&&c.set(b.id,b.name);D.assignedBy&&D.assignedBy.id!==o&&c.set(D.assignedBy.id,D.assignedBy.name)}let d=[...c.entries()].map(([D,b])=>({id:D,name:b})).sort((D,b)=>D.name.localeCompare(b.name)),h=t.createDiv({cls:"stashpad-task-assign-bar"});h.createSpan({cls:"stashpad-task-assign-label",text:"Assignment"});let g=h.createEl("select",{cls:"stashpad-task-assign-select"}),f=D=>{let b={all:"Everyone",mine:"Assigned to me",others:"Assigned to others",byme:"Assigned by me",unassigned:"Unassigned"};return D.startsWith("person:")?c.get(D.slice(7))??"Person":b[D]??D},p=D=>{let b=g.createEl("option",{text:f(D),value:D});this.taskAssignFilter===D&&(b.selected=!0)};for(let D of["all","mine","others","byme","unassigned"])p(D);if(d.length>0){let D=g.createEl("optgroup");D.setAttr("label","By person");for(let b of d){let P=D.createEl("option",{text:b.name,value:`person:${b.id}`});this.taskAssignFilter===`person:${b.id}`&&(P.selected=!0)}}g.onchange=()=>{this.taskAssignFilter=g.value,this.render()};let u=[...new Set(n.map(D=>D.folder))].sort((D,b)=>D.localeCompare(b));this.taskFolderFilter!=="all"&&!u.includes(this.taskFolderFilter)&&(this.taskFolderFilter="all");let I=t.createDiv({cls:"stashpad-task-assign-bar"});I.createSpan({cls:"stashpad-task-assign-label",text:"Folder"});let m=I.createEl("select",{cls:"stashpad-task-assign-select"}),C=m.createEl("option",{text:"All folders",value:"all"});this.taskFolderFilter==="all"&&(C.selected=!0);for(let D of u){let b=m.createEl("option",{text:D.split("/").pop()||D,value:D});this.taskFolderFilter===D&&(b.selected=!0)}m.onchange=()=>{this.taskFolderFilter=m.value,this.render()};let B=D=>this.taskFolderFilter==="all"||D.folder===this.taskFolderFilter,w=n.filter(D=>l(D)&&B(D));if(w.length===0){t.createDiv({cls:"stashpad-tasks-empty"}).setText("No tasks match the current filters.");return}let y=new Date;y.setHours(0,0,0,0);let v=y.getTime(),E=v+24*60*60*1e3,Q={overdue:[],today:[],upcoming:[],nodate:[],completed:[]};for(let D of w){if(D.completed){Q.completed.push(D);continue}if(D.due==null){Q.nodate.push(D);continue}D.due<v?Q.overdue.push(D):D.due<E?Q.today.push(D):Q.upcoming.push(D)}let S=(D,b)=>D.due==null&&b.due==null?D.title.localeCompare(b.title):D.due==null?1:b.due==null?-1:D.due-b.due,x=[{key:"overdue",label:"Overdue",icon:"alert-circle"},{key:"today",label:"Due today",icon:"calendar-clock"},{key:"upcoming",label:"Upcoming",icon:"calendar"},{key:"nodate",label:"No date",icon:"inbox"},{key:"completed",label:"Completed",icon:"check-circle-2"}],H=t.createDiv({cls:"stashpad-task-filters"}),G=w.length,N=(D,b,P)=>{let O=H.createEl("button",{cls:"stashpad-task-filter"});this.taskFilter===D&&O.addClass("is-active"),O.createSpan({cls:"stashpad-task-filter-label",text:b}),O.createSpan({cls:"stashpad-task-filter-count",text:String(P)}),O.onclick=()=>{this.taskFilter=D,this.render()}};N("all","All",G),N("overdue","Overdue",Q.overdue.length),N("today","Today",Q.today.length),N("upcoming","Upcoming",Q.upcoming.length),N("nodate","No date",Q.nodate.length),N("completed","Done",Q.completed.length);let R=this.taskFilter==="all"?x:x.filter(D=>D.key===this.taskFilter),U=!1;for(let D of R){let b=Q[D.key];if(b.length!==0){if(U=!0,b.sort(D.key==="completed"?(P,O)=>S(O,P):S),this.taskFilter==="all"){let P=t.createDiv({cls:`stashpad-task-section-header is-${D.key}`});(0,oe.setIcon)(P.createSpan({cls:"stashpad-task-section-icon"}),D.icon),P.createSpan({cls:"stashpad-task-section-name",text:D.label}),P.createSpan({cls:"stashpad-task-section-count",text:String(b.length)})}for(let P of b)this.renderTaskRow(t,P,D.key==="today")}}if(!U&&this.taskFilter!=="all"){t.createDiv({cls:"stashpad-tasks-empty"}).setText(`Nothing in "${R[0]?.label??this.taskFilter}".`);return}U||t.createDiv({cls:"stashpad-tasks-empty"}).setText("No tasks to show.")}renderTaskRow(e,t,i){let s=e.createDiv({cls:"stashpad-pinned-row stashpad-task-row"});t.color&&s.addClass("has-color"),t.completed&&s.addClass("is-completed");let n=s.createSpan({cls:"stashpad-pinned-icon stashpad-task-checkbox"});(0,oe.setIcon)(n,t.completed?"check-square":"square"),t.color&&(n.style.color=t.color),n.title=t.completed?"Mark not done":"Mark done",n.onclick=l=>{l.stopPropagation(),this.toggleTaskCompleted(t)};let o=s.createSpan({cls:"stashpad-pinned-label",text:t.title});if(o.onclick=()=>this.openTaskFromPanel(t.folder,t.id),s.createSpan({cls:"stashpad-task-folder",text:t.folder.split("/").pop()||t.folder}),t.due!=null){let l=s.createSpan({cls:"stashpad-task-due",text:this.formatDueShort(t.due,i)});t.due<Date.now()&&!t.completed&&l.addClass("is-overdue")}else t.dueRaw&&s.createSpan({cls:"stashpad-task-due",text:t.dueRaw});if(t.assignedTo.length>0){let l=(this.plugin.settings.authorId??"").trim(),c=s.createSpan({cls:"stashpad-task-assignees"});for(let d of t.assignedTo){let h=c.createSpan({cls:"stashpad-task-assignee"});l&&d.id===l&&h.addClass("is-me"),h.setText(this.initials(d.name)),h.title=l&&d.id===l?`${d.name} (you)`:d.name}}let a=s.createEl("button",{cls:"stashpad-task-snooze"});(0,oe.setIcon)(a,"alarm-clock"),a.title="Snooze \u2014 reschedule due date",a.onclick=l=>{l.stopPropagation(),this.snoozeTask(t)},s.oncontextmenu=l=>{l.preventDefault();let c=new oe.Menu;c.addItem(d=>d.setTitle("Open").setIcon("arrow-right").onClick(()=>{this.openTaskFromPanel(t.folder,t.id)})),c.addItem(d=>d.setTitle("Snooze (reschedule)\u2026").setIcon("alarm-clock").onClick(()=>{this.snoozeTask(t)})),c.showAtMouseEvent(l)}}snoozeTask(e){let t=e.dueRaw??(e.due!=null?new Date(e.due).toISOString():null);new Oe(this.app,t,i=>{this.app.fileManager.processFrontMatter(e.file,s=>{i.iso===null?delete s.due:(s.due=i.iso,s.task=!0)}).then(()=>this.scheduleRender()).catch(s=>new oe.Notice(`Couldn't snooze: ${s.message}`))},{title:"Snooze \u2014 reschedule",hideAssignees:!0,quickAdjusts:this.plugin.settings.dueQuickAdjusts}).open()}formatDueShort(e,t){return t?ei(e,this.plugin.settings):Bt(e,this.plugin.settings)}async openTaskFromPanel(e,t){await this.plugin.revealNoteByRef(e,t)}async toggleTaskCompleted(e){try{await this.app.fileManager.processFrontMatter(e.file,t=>{t.completed=t.completed!==!0})}catch(t){new oe.Notice(`Couldn't update task: ${t.message}`);return}this.scheduleRender()}initials(e){let t=e.trim().split(/\s+/).filter(Boolean);return t.length===0?"?":t.length===1?t[0].slice(0,2).toUpperCase():(t[0][0]+t[t.length-1][0]).toUpperCase()}collectTasks(){return Ss(this.app,this.plugin)}};async function xs(A){let r=A.workspace.getLeavesOfType(It);if(r.length>0){A.workspace.revealLeaf(r[0]);return}let e=A.workspace.getLeftLeaf(!1);if(!e){new oe.Notice("Stashpad: couldn't open the panels view.");return}await e.setViewState({type:It,active:!0}),A.workspace.revealLeaf(e)}var Ia=require("obsidian");le();var T=require("obsidian");Xi();var Hs=require("obsidian");le();var li=class extends Hs.AbstractInputSuggest{constructor(e,t){super(e,t);this.inputEl=t}getSuggestions(e){let t=e.toLowerCase().split(/\s+/).filter(Boolean),i=o=>t.every(a=>o.toLowerCase().includes(a)),s=[],n=o=>{if(!(o.path!=="/"&&Bs.has(o.name))){o.path!=="/"&&i(o.path)&&s.push(o);for(let a of o.children)a instanceof Hs.TFolder&&n(a)}};return n(this.app.vault.getRoot()),s.slice(0,100)}renderSuggestion(e,t){t.setText(e.path)}selectSuggestion(e){this.setValue(e.path),this.inputEl.dispatchEvent(new Event("input",{bubbles:!0})),this.close()}};var ci=require("obsidian"),Rs=class A extends ci.AbstractInputSuggest{constructor(e,t){super(e,t);this.inputEl=t}static norm(e){return e.replace(/^lucide-/,"")}getSuggestions(e){let t=e.toLowerCase().split(/\s+/).filter(Boolean),i=new Set,s=[];for(let a of(0,ci.getIconIds)()){let l=A.norm(a);i.has(l)||(i.add(l),s.push(l))}let n=a=>t.every(l=>a.toLowerCase().includes(l)),o=t.length?s.filter(n):s;return o.sort(),o.slice(0,t.length?50:300)}renderSuggestion(e,t){t.addClass("stashpad-icon-suggest-item");let i=t.createSpan({cls:"stashpad-icon-suggest-icon"});(0,ci.setIcon)(i,e),t.createSpan({cls:"stashpad-icon-suggest-label",text:e})}selectSuggestion(e){this.setValue(e),this.inputEl.dispatchEvent(new Event("input",{bubbles:!0})),this.close()}};le();ye();var Jt=require("obsidian"),po={create:{label:"Create",desc:"Confirmations after a new note is created."},edit:{label:"Edit",desc:"Edit-related toasts (currently rare)."},delete:{label:"Delete",desc:"Confirmations after deleting one or more notes."},move:{label:"Move",desc:"After reparenting or moving notes between folders."},merge:{label:"Merge",desc:"After combining multiple notes into one."},split:{label:"Split",desc:"After splitting a note in two."},clone:{label:"Clone",desc:"After duplicating notes or subtrees."},complete:{label:"Complete",desc:"When a note is marked complete."},uncomplete:{label:"Uncomplete",desc:"When a note's complete mark is removed."},export:{label:"Export",desc:".stash exports \u2014 success + the action buttons."},import:{label:"Import",desc:".stash imports (both manual and the drop-folder auto-import)."},attachment:{label:"Attachment",desc:"Attachment add / remove notifications."},color:{label:"Color",desc:"Per-note color changes."},reorder:{label:"Reorder",desc:"Drag-reorder and keyboard moveUp/Down/Top/Bottom."},multiplayer:{label:"Multiplayer",desc:"Cross-author activity (someone else touched your notes or vice versa)."},reminder:{label:"Reminder",desc:"Task due-date reminders \u2014 surfaced when a task comes due (fired at launch / periodically)."},system:{label:"System",desc:"Plumbing toasts: backfill progress, integrity warnings, errors not tied to a verb."}},ec={info:4e3,success:4e3,warning:6e3,error:1e4},zr=5e3,Us=class{constructor(r){this.app=r;this.history=[];this.nextId=1;this.historyLimit=zr;this.changeListeners=new Set;this.muted=new Set;this.defaultAuthorId=null}setDefaultAuthorId(r){this.defaultAuthorId=r||null}show(r){let e=r.kind??"info",t=r.category??"system",i={id:this.nextId++,ts:Date.now(),message:r.message,kind:e,category:t,authorId:r.authorId??this.defaultAuthorId??void 0,affectedIds:r.affectedIds?r.affectedIds.slice():[],affectedPaths:r.affectedPaths?r.affectedPaths.slice():[],affectedAuthorIds:r.affectedAuthorIds?r.affectedAuthorIds.slice():[],folder:r.folder,actionLabels:(r.actions??[]).map(o=>o.label)};if(this.pushHistory(i),this.muted.has(t))return null;let s=this.buildContent(r,e),n=r.duration??ec[e];return new Jt.Notice(s,n)}onChange(r){return this.changeListeners.add(r),()=>this.changeListeners.delete(r)}recent(){return this.history.slice().reverse()}clearHistory(){this.history=[],this.emit()}setMuted(r,e){e?this.muted.add(r):this.muted.delete(r)}isMuted(r){return this.muted.has(r)}loadMutedFromList(r){this.muted=new Set(r)}mutedList(){return Array.from(this.muted)}loadHistory(r){let e=this.applyLimit(r.slice());this.history=e;let t=0;for(let i of e)i.id>t&&(t=i.id);this.nextId=Math.max(this.nextId,t+1),this.emit()}setHistoryLimit(r){this.historyLimit=Number.isFinite(r)?r:zr,this.history=this.applyLimit(this.history),this.emit()}applyLimit(r){return this.historyLimit<=0||r.length<=this.historyLimit?r:r.slice(r.length-this.historyLimit)}pushHistory(r){this.history.push(r),this.history=this.applyLimit(this.history),this.emit()}emit(){for(let r of this.changeListeners)try{r()}catch(e){console.warn("[Stashpad] notification listener failed",e)}}buildContent(r,e){let t=document.createDocumentFragment(),i=document.createElement("div");i.className=`stashpad-notice stashpad-notice-${e}`;let s=document.createElement("div");s.className="stashpad-notice-message";let n=r.message.split(/(`[^`\n]+`)/);for(let o of n)if(o.length>1&&o.startsWith("`")&&o.endsWith("`")){let a=document.createElement("code");a.textContent=o.slice(1,-1),s.appendChild(a)}else o.length>0&&s.appendChild(document.createTextNode(o));if(i.appendChild(s),r.actions&&r.actions.length>0){let o=document.createElement("div");o.className="stashpad-notice-actions";for(let a of r.actions){let l=document.createElement("button");l.className="stashpad-notice-action",l.textContent=a.label,l.addEventListener("click",c=>{if(c.stopPropagation(),Promise.resolve().then(()=>a.onClick()).catch(d=>console.warn("[Stashpad] notification action failed",d)),!a.keepOpen){let d=l.closest(".notice");d&&d.parentElement&&d.parentElement.removeChild(d)}}),o.appendChild(l)}i.appendChild(o)}return t.appendChild(i),t}};function Gs(A,r,e){let t=A.vault.getAbstractFileByPath(r);if(!(t instanceof Jt.TFile))return[];let i=[{label:"Reveal",keepOpen:!0,onClick:()=>{let s=A.workspace.getLeavesOfType("file-explorer")[0];if(!s)return;A.workspace.revealLeaf(s),s.view?.revealInFolder?.(t)}}];if(!e){let s=Jt.Platform.isMacOS?"Finder":Jt.Platform.isWin?"File Explorer":"file manager";i.push({label:`Show in ${s}`,keepOpen:!0,onClick:()=>{try{let n=window.require?.("electron")?.shell,a=A.vault.adapter?.getFullPath?.(r);a&&n?.showItemInFolder&&n.showItemInFolder(a)}catch(n){console.warn("[Stashpad] showItemInFolder failed",n)}}})}return i}var Vr=require("obsidian"),go=Vr.Platform.isMacOS;function Zi(A){if(!A)return"(none)";let r=A.replace(/\bMod\b/g,go?"Cmd":"Ctrl");return go&&(r=r.replace(/\bAlt\b/g,"Option")),r}function Wr(A,r,e={allowSingleKey:!0}){let t=A.placeholder,i=A.value;A.placeholder="Press a key\u2026 (Backspace to cancel)",A.value="",A.classList.add("is-recording");let s=!1,n=()=>{A.placeholder=t,A.classList.remove("is-recording"),s||(A.value=i),A.removeEventListener("keydown",o,!0),A.removeEventListener("blur",a)},o=l=>{if(l.key==="Control"||l.key==="Shift"||l.key==="Alt"||l.key==="Meta"||l.key==="OS")return;let c=!!l.code&&(/^Key[A-Z]$/.test(l.code)||/^Digit\d$/.test(l.code));if((l.key==="Dead"||l.key==="Process"||l.key==="Unidentified")&&!c)return;if(l.preventDefault(),l.stopPropagation(),l.key==="Backspace"&&!l.metaKey&&!l.ctrlKey&&!l.altKey&&!l.shiftKey){n();return}let d=[];go?(l.metaKey&&d.push("Mod"),l.ctrlKey&&d.push("Ctrl")):l.ctrlKey&&d.push("Mod"),l.altKey&&d.push("Alt"),l.shiftKey&&d.push("Shift");let h=tc(l.key,l.code);if(!h||d.length===0&&!e.allowSingleKey)return;d.push(h);let g=d.join("+");s=!0,n(),r(g)},a=()=>n();return A.addEventListener("keydown",o,!0),A.addEventListener("blur",a),n}function tc(A,r){if(!A)return"";if(r){let e=/^Key([A-Z])$/.exec(r);if(e)return e[1];let t=/^Digit(\d)$/.exec(r);if(t)return t[1]}return A.length===1?A.toUpperCase():A}ri();Le();function $t(A,r,e,t){function i(s){return s instanceof e?s:new e(function(n){n(s)})}return new(e||(e=Promise))(function(s,n){function o(c){try{l(t.next(c))}catch(d){n(d)}}function a(c){try{l(t.throw(c))}catch(d){n(d)}}function l(c){c.done?s(c.value):i(c.value).then(o,a)}l((t=t.apply(A,r||[])).next())})}var ae=class{constructor(){this.mutex=Promise.resolve()}lock(){let r=()=>{};return this.mutex=this.mutex.then(()=>new Promise(r)),new Promise(e=>{r=e})}dispatch(r){return $t(this,void 0,void 0,function*(){let e=yield this.lock();try{return yield Promise.resolve(r())}finally{e()}})}},uo;function ic(){return typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:global}var Co=ic(),fo=(uo=Co.Buffer)!==null&&uo!==void 0?uo:null,sc=Co.TextEncoder?new Co.TextEncoder:null;function ta(A,r){return(A&15)+(A>>6|A>>3&8)<<4|(r&15)+(r>>6|r>>3&8)}function ia(A,r){let e=r.length>>1;for(let t=0;t<e;t++){let i=t<<1;A[t]=ta(r.charCodeAt(i),r.charCodeAt(i+1))}}function nc(A,r){if(A.length!==r.length*2)return!1;for(let e=0;e<r.length;e++){let t=e<<1;if(r[e]!==ta(A.charCodeAt(t),A.charCodeAt(t+1)))return!1}return!0}var Xr=87,Zr=48;function Bo(A,r,e){let t=0;for(let i=0;i<e;i++){let s=r[i]>>>4;A[t++]=s>9?s+Xr:s+Zr,s=r[i]&15,A[t++]=s>9?s+Xr:s+Zr}return String.fromCharCode.apply(null,A)}var ht=fo!==null?A=>{if(typeof A=="string"){let r=fo.from(A,"utf8");return new Uint8Array(r.buffer,r.byteOffset,r.length)}if(fo.isBuffer(A))return new Uint8Array(A.buffer,A.byteOffset,A.length);if(ArrayBuffer.isView(A))return new Uint8Array(A.buffer,A.byteOffset,A.byteLength);throw new Error("Invalid data type!")}:A=>{if(typeof A=="string")return sc.encode(A);if(ArrayBuffer.isView(A))return new Uint8Array(A.buffer,A.byteOffset,A.byteLength);throw new Error("Invalid data type!")},qe="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",_i=new Uint8Array(256);for(let A=0;A<qe.length;A++)_i[qe.charCodeAt(A)]=A;function jr(A,r=!0){let e=A.length,t=e%3,i=[],s=e-t;for(let n=0;n<s;n+=3){let o=(A[n]<<16&16711680)+(A[n+1]<<8&65280)+(A[n+2]&255),a=qe.charAt(o>>18&63)+qe.charAt(o>>12&63)+qe.charAt(o>>6&63)+qe.charAt(o&63);i.push(a)}if(t===1){let n=A[e-1],o=qe.charAt(n>>2),a=qe.charAt(n<<4&63);i.push(`${o}${a}`),r&&i.push("==")}else if(t===2){let n=(A[e-2]<<8)+A[e-1],o=qe.charAt(n>>10),a=qe.charAt(n>>4&63),l=qe.charAt(n<<2&63);i.push(`${o}${a}${l}`),r&&i.push("=")}return i.join("")}function oc(A){let r=Math.floor(A.length*.75),e=A.length;return A[e-1]==="="&&(r-=1,A[e-2]==="="&&(r-=1)),r}function rc(A){let r=oc(A),e=A.length,t=new Uint8Array(r),i=0;for(let s=0;s<e;s+=4){let n=_i[A.charCodeAt(s)],o=_i[A.charCodeAt(s+1)],a=_i[A.charCodeAt(s+2)],l=_i[A.charCodeAt(s+3)];t[i]=n<<2|o>>4,i+=1,t[i]=(o&15)<<4|a>>2,i+=1,t[i]=(a&3)<<6|l&63,i+=1}return t}var Ls=16*1024,ji=4,ac=new ae,Io=new Map;function sa(A,r){return $t(this,void 0,void 0,function*(){let e=null,t=null,i=!1;if(typeof WebAssembly>"u")throw new Error("WebAssembly is not supported in this environment!");let s=(y,v=0)=>{t.set(y,v)},n=()=>t,o=()=>e.exports,a=y=>{e.exports.Hash_SetMemorySize(y);let v=e.exports.Hash_GetBuffer(),E=e.exports.memory.buffer;t=new Uint8Array(E,v,y)},l=()=>new DataView(e.exports.memory.buffer).getUint32(e.exports.STATE_SIZE,!0),c=ac.dispatch(()=>$t(this,void 0,void 0,function*(){if(!Io.has(A.name)){let v=rc(A.data),E=WebAssembly.compile(v);Io.set(A.name,E)}let y=yield Io.get(A.name);e=yield WebAssembly.instantiate(y,{})})),d=()=>$t(this,void 0,void 0,function*(){e||(yield c);let y=e.exports.Hash_GetBuffer(),v=e.exports.memory.buffer;t=new Uint8Array(v,y,Ls)}),h=(y=null)=>{i=!0,e.exports.Hash_Init(y)},g=y=>{let v=0;for(;v<y.length;){let E=y.subarray(v,v+Ls);v+=E.length,t.set(E),e.exports.Hash_Update(E.length)}},f=y=>{if(!i)throw new Error("update() called before init()");let v=ht(y);g(v)},p=new Uint8Array(r*2),u=(y,v=null)=>{if(!i)throw new Error("digest() called before init()");return i=!1,e.exports.Hash_Final(v),y==="binary"?t.slice(0,r):Bo(p,t,r)},I=()=>{if(!i)throw new Error("save() can only be called after init() and before digest()");let y=e.exports.Hash_GetState(),v=l(),E=e.exports.memory.buffer,Q=new Uint8Array(E,y,v),S=new Uint8Array(ji+v);return ia(S,A.hash),S.set(Q,ji),S},m=y=>{if(!(y instanceof Uint8Array))throw new Error("load() expects an Uint8Array generated by save()");let v=e.exports.Hash_GetState(),E=l(),Q=ji+E,S=e.exports.memory.buffer;if(y.length!==Q)throw new Error(`Bad state length (expected ${Q} bytes, got ${y.length})`);if(!nc(A.hash,y.subarray(0,ji)))throw new Error("This state was written by an incompatible hash implementation");let x=y.subarray(ji);new Uint8Array(S,v,E).set(x),i=!0},C=y=>typeof y=="string"?y.length<Ls/4:y.byteLength<Ls,B=C;switch(A.name){case"argon2":case"scrypt":B=()=>!0;break;case"blake2b":case"blake2s":B=(y,v)=>v<=512&&C(y);break;case"blake3":B=(y,v)=>v===0&&C(y);break;case"xxhash64":case"xxhash3":case"xxhash128":case"crc64":B=()=>!1;break}let w=(y,v=null,E=null)=>{if(!B(y,v))return h(v),f(y),u("hex",E);let Q=ht(y);return t.set(Q),e.exports.Hash_Calculate(Q.length,v,E),Bo(p,t,r)};return yield d(),{getMemory:n,writeMemory:s,getExports:o,setMemorySize:a,init:h,update:f,digest:u,save:I,load:m,calculate:w,hashLength:r}})}var SA=new ae;var lc="argon2",cc="AGFzbQEAAAABKQVgAX8Bf2AAAX9gEH9/f39/f39/f39/f39/f38AYAR/f39/AGACf38AAwYFAAECAwQFBgEBAoCAAgYIAX8BQZCoBAsHQQQGbWVtb3J5AgASSGFzaF9TZXRNZW1vcnlTaXplAAAOSGFzaF9HZXRCdWZmZXIAAQ5IYXNoX0NhbGN1bGF0ZQAECvEyBVgBAn9BACEBAkAgAEEAKAKICCICRg0AAkAgACACayIAQRB2IABBgIB8cSAASWoiAEAAQX9HDQBB/wHADwtBACEBQQBBACkDiAggAEEQdK18NwOICAsgAcALcAECfwJAQQAoAoAIIgANAEEAPwBBEHQiADYCgAhBACgCiAgiAUGAgCBGDQACQEGAgCAgAWsiAEEQdiAAQYCAfHEgAElqIgBAAEF/Rw0AQQAPC0EAQQApA4gIIABBEHStfDcDiAhBACgCgAghAAsgAAvcDgECfiAAIAQpAwAiECAAKQMAIhF8IBFCAYZC/v///x+DIBBC/////w+DfnwiEDcDACAMIBAgDCkDAIVCIIkiEDcDACAIIBAgCCkDACIRfCARQgGGQv7///8fgyAQQv////8Pg358IhA3AwAgBCAQIAQpAwCFQiiJIhA3AwAgACAQIAApAwAiEXwgEEL/////D4MgEUIBhkL+////H4N+fCIQNwMAIAwgECAMKQMAhUIwiSIQNwMAIAggECAIKQMAIhF8IBBC/////w+DIBFCAYZC/v///x+DfnwiEDcDACAEIBAgBCkDAIVCAYk3AwAgASAFKQMAIhAgASkDACIRfCARQgGGQv7///8fgyAQQv////8Pg358IhA3AwAgDSAQIA0pAwCFQiCJIhA3AwAgCSAQIAkpAwAiEXwgEUIBhkL+////H4MgEEL/////D4N+fCIQNwMAIAUgECAFKQMAhUIoiSIQNwMAIAEgECABKQMAIhF8IBBC/////w+DIBFCAYZC/v///x+DfnwiEDcDACANIBAgDSkDAIVCMIkiEDcDACAJIBAgCSkDACIRfCAQQv////8PgyARQgGGQv7///8fg358IhA3AwAgBSAQIAUpAwCFQgGJNwMAIAIgBikDACIQIAIpAwAiEXwgEUIBhkL+////H4MgEEL/////D4N+fCIQNwMAIA4gECAOKQMAhUIgiSIQNwMAIAogECAKKQMAIhF8IBFCAYZC/v///x+DIBBC/////w+DfnwiEDcDACAGIBAgBikDAIVCKIkiEDcDACACIBAgAikDACIRfCAQQv////8PgyARQgGGQv7///8fg358IhA3AwAgDiAQIA4pAwCFQjCJIhA3AwAgCiAQIAopAwAiEXwgEEL/////D4MgEUIBhkL+////H4N+fCIQNwMAIAYgECAGKQMAhUIBiTcDACADIAcpAwAiECADKQMAIhF8IBFCAYZC/v///x+DIBBC/////w+DfnwiEDcDACAPIBAgDykDAIVCIIkiEDcDACALIBAgCykDACIRfCARQgGGQv7///8fgyAQQv////8Pg358IhA3AwAgByAQIAcpAwCFQiiJIhA3AwAgAyAQIAMpAwAiEXwgEEL/////D4MgEUIBhkL+////H4N+fCIQNwMAIA8gECAPKQMAhUIwiSIQNwMAIAsgECALKQMAIhF8IBBC/////w+DIBFCAYZC/v///x+DfnwiEDcDACAHIBAgBykDAIVCAYk3AwAgACAFKQMAIhAgACkDACIRfCARQgGGQv7///8fgyAQQv////8Pg358IhA3AwAgDyAQIA8pAwCFQiCJIhA3AwAgCiAQIAopAwAiEXwgEUIBhkL+////H4MgEEL/////D4N+fCIQNwMAIAUgECAFKQMAhUIoiSIQNwMAIAAgECAAKQMAIhF8IBBC/////w+DIBFCAYZC/v///x+DfnwiEDcDACAPIBAgDykDAIVCMIkiEDcDACAKIBAgCikDACIRfCAQQv////8PgyARQgGGQv7///8fg358IhA3AwAgBSAQIAUpAwCFQgGJNwMAIAEgBikDACIQIAEpAwAiEXwgEUIBhkL+////H4MgEEL/////D4N+fCIQNwMAIAwgECAMKQMAhUIgiSIQNwMAIAsgECALKQMAIhF8IBFCAYZC/v///x+DIBBC/////w+DfnwiEDcDACAGIBAgBikDAIVCKIkiEDcDACABIBAgASkDACIRfCAQQv////8PgyARQgGGQv7///8fg358IhA3AwAgDCAQIAwpAwCFQjCJIhA3AwAgCyAQIAspAwAiEXwgEEL/////D4MgEUIBhkL+////H4N+fCIQNwMAIAYgECAGKQMAhUIBiTcDACACIAcpAwAiECACKQMAIhF8IBFCAYZC/v///x+DIBBC/////w+DfnwiEDcDACANIBAgDSkDAIVCIIkiEDcDACAIIBAgCCkDACIRfCARQgGGQv7///8fgyAQQv////8Pg358IhA3AwAgByAQIAcpAwCFQiiJIhA3AwAgAiAQIAIpAwAiEXwgEEL/////D4MgEUIBhkL+////H4N+fCIQNwMAIA0gECANKQMAhUIwiSIQNwMAIAggECAIKQMAIhF8IBBC/////w+DIBFCAYZC/v///x+DfnwiEDcDACAHIBAgBykDAIVCAYk3AwAgAyAEKQMAIhAgAykDACIRfCARQgGGQv7///8fgyAQQv////8Pg358IhA3AwAgDiAQIA4pAwCFQiCJIhA3AwAgCSAQIAkpAwAiEXwgEUIBhkL+////H4MgEEL/////D4N+fCIQNwMAIAQgECAEKQMAhUIoiSIQNwMAIAMgECADKQMAIhF8IBBC/////w+DIBFCAYZC/v///x+DfnwiEDcDACAOIBAgDikDAIVCMIkiEDcDACAJIBAgCSkDACIRfCAQQv////8PgyARQgGGQv7///8fg358IhA3AwAgBCAQIAQpAwCFQgGJNwMAC98aAQN/QQAhBEEAIAIpAwAgASkDAIU3A5AIQQAgAikDCCABKQMIhTcDmAhBACACKQMQIAEpAxCFNwOgCEEAIAIpAxggASkDGIU3A6gIQQAgAikDICABKQMghTcDsAhBACACKQMoIAEpAyiFNwO4CEEAIAIpAzAgASkDMIU3A8AIQQAgAikDOCABKQM4hTcDyAhBACACKQNAIAEpA0CFNwPQCEEAIAIpA0ggASkDSIU3A9gIQQAgAikDUCABKQNQhTcD4AhBACACKQNYIAEpA1iFNwPoCEEAIAIpA2AgASkDYIU3A/AIQQAgAikDaCABKQNohTcD+AhBACACKQNwIAEpA3CFNwOACUEAIAIpA3ggASkDeIU3A4gJQQAgAikDgAEgASkDgAGFNwOQCUEAIAIpA4gBIAEpA4gBhTcDmAlBACACKQOQASABKQOQAYU3A6AJQQAgAikDmAEgASkDmAGFNwOoCUEAIAIpA6ABIAEpA6ABhTcDsAlBACACKQOoASABKQOoAYU3A7gJQQAgAikDsAEgASkDsAGFNwPACUEAIAIpA7gBIAEpA7gBhTcDyAlBACACKQPAASABKQPAAYU3A9AJQQAgAikDyAEgASkDyAGFNwPYCUEAIAIpA9ABIAEpA9ABhTcD4AlBACACKQPYASABKQPYAYU3A+gJQQAgAikD4AEgASkD4AGFNwPwCUEAIAIpA+gBIAEpA+gBhTcD+AlBACACKQPwASABKQPwAYU3A4AKQQAgAikD+AEgASkD+AGFNwOICkEAIAIpA4ACIAEpA4AChTcDkApBACACKQOIAiABKQOIAoU3A5gKQQAgAikDkAIgASkDkAKFNwOgCkEAIAIpA5gCIAEpA5gChTcDqApBACACKQOgAiABKQOgAoU3A7AKQQAgAikDqAIgASkDqAKFNwO4CkEAIAIpA7ACIAEpA7AChTcDwApBACACKQO4AiABKQO4AoU3A8gKQQAgAikDwAIgASkDwAKFNwPQCkEAIAIpA8gCIAEpA8gChTcD2ApBACACKQPQAiABKQPQAoU3A+AKQQAgAikD2AIgASkD2AKFNwPoCkEAIAIpA+ACIAEpA+AChTcD8ApBACACKQPoAiABKQPoAoU3A/gKQQAgAikD8AIgASkD8AKFNwOAC0EAIAIpA/gCIAEpA/gChTcDiAtBACACKQOAAyABKQOAA4U3A5ALQQAgAikDiAMgASkDiAOFNwOYC0EAIAIpA5ADIAEpA5ADhTcDoAtBACACKQOYAyABKQOYA4U3A6gLQQAgAikDoAMgASkDoAOFNwOwC0EAIAIpA6gDIAEpA6gDhTcDuAtBACACKQOwAyABKQOwA4U3A8ALQQAgAikDuAMgASkDuAOFNwPIC0EAIAIpA8ADIAEpA8ADhTcD0AtBACACKQPIAyABKQPIA4U3A9gLQQAgAikD0AMgASkD0AOFNwPgC0EAIAIpA9gDIAEpA9gDhTcD6AtBACACKQPgAyABKQPgA4U3A/ALQQAgAikD6AMgASkD6AOFNwP4C0EAIAIpA/ADIAEpA/ADhTcDgAxBACACKQP4AyABKQP4A4U3A4gMQQAgAikDgAQgASkDgASFNwOQDEEAIAIpA4gEIAEpA4gEhTcDmAxBACACKQOQBCABKQOQBIU3A6AMQQAgAikDmAQgASkDmASFNwOoDEEAIAIpA6AEIAEpA6AEhTcDsAxBACACKQOoBCABKQOoBIU3A7gMQQAgAikDsAQgASkDsASFNwPADEEAIAIpA7gEIAEpA7gEhTcDyAxBACACKQPABCABKQPABIU3A9AMQQAgAikDyAQgASkDyASFNwPYDEEAIAIpA9AEIAEpA9AEhTcD4AxBACACKQPYBCABKQPYBIU3A+gMQQAgAikD4AQgASkD4ASFNwPwDEEAIAIpA+gEIAEpA+gEhTcD+AxBACACKQPwBCABKQPwBIU3A4ANQQAgAikD+AQgASkD+ASFNwOIDUEAIAIpA4AFIAEpA4AFhTcDkA1BACACKQOIBSABKQOIBYU3A5gNQQAgAikDkAUgASkDkAWFNwOgDUEAIAIpA5gFIAEpA5gFhTcDqA1BACACKQOgBSABKQOgBYU3A7ANQQAgAikDqAUgASkDqAWFNwO4DUEAIAIpA7AFIAEpA7AFhTcDwA1BACACKQO4BSABKQO4BYU3A8gNQQAgAikDwAUgASkDwAWFNwPQDUEAIAIpA8gFIAEpA8gFhTcD2A1BACACKQPQBSABKQPQBYU3A+ANQQAgAikD2AUgASkD2AWFNwPoDUEAIAIpA+AFIAEpA+AFhTcD8A1BACACKQPoBSABKQPoBYU3A/gNQQAgAikD8AUgASkD8AWFNwOADkEAIAIpA/gFIAEpA/gFhTcDiA5BACACKQOABiABKQOABoU3A5AOQQAgAikDiAYgASkDiAaFNwOYDkEAIAIpA5AGIAEpA5AGhTcDoA5BACACKQOYBiABKQOYBoU3A6gOQQAgAikDoAYgASkDoAaFNwOwDkEAIAIpA6gGIAEpA6gGhTcDuA5BACACKQOwBiABKQOwBoU3A8AOQQAgAikDuAYgASkDuAaFNwPIDkEAIAIpA8AGIAEpA8AGhTcD0A5BACACKQPIBiABKQPIBoU3A9gOQQAgAikD0AYgASkD0AaFNwPgDkEAIAIpA9gGIAEpA9gGhTcD6A5BACACKQPgBiABKQPgBoU3A/AOQQAgAikD6AYgASkD6AaFNwP4DkEAIAIpA/AGIAEpA/AGhTcDgA9BACACKQP4BiABKQP4BoU3A4gPQQAgAikDgAcgASkDgAeFNwOQD0EAIAIpA4gHIAEpA4gHhTcDmA9BACACKQOQByABKQOQB4U3A6APQQAgAikDmAcgASkDmAeFNwOoD0EAIAIpA6AHIAEpA6AHhTcDsA9BACACKQOoByABKQOoB4U3A7gPQQAgAikDsAcgASkDsAeFNwPAD0EAIAIpA7gHIAEpA7gHhTcDyA9BACACKQPAByABKQPAB4U3A9APQQAgAikDyAcgASkDyAeFNwPYD0EAIAIpA9AHIAEpA9AHhTcD4A9BACACKQPYByABKQPYB4U3A+gPQQAgAikD4AcgASkD4AeFNwPwD0EAIAIpA+gHIAEpA+gHhTcD+A9BACACKQPwByABKQPwB4U3A4AQQQAgAikD+AcgASkD+AeFNwOIEEGQCEGYCEGgCEGoCEGwCEG4CEHACEHICEHQCEHYCEHgCEHoCEHwCEH4CEGACUGICRACQZAJQZgJQaAJQagJQbAJQbgJQcAJQcgJQdAJQdgJQeAJQegJQfAJQfgJQYAKQYgKEAJBkApBmApBoApBqApBsApBuApBwApByApB0ApB2ApB4ApB6ApB8ApB+ApBgAtBiAsQAkGQC0GYC0GgC0GoC0GwC0G4C0HAC0HIC0HQC0HYC0HgC0HoC0HwC0H4C0GADEGIDBACQZAMQZgMQaAMQagMQbAMQbgMQcAMQcgMQdAMQdgMQeAMQegMQfAMQfgMQYANQYgNEAJBkA1BmA1BoA1BqA1BsA1BuA1BwA1ByA1B0A1B2A1B4A1B6A1B8A1B+A1BgA5BiA4QAkGQDkGYDkGgDkGoDkGwDkG4DkHADkHIDkHQDkHYDkHgDkHoDkHwDkH4DkGAD0GIDxACQZAPQZgPQaAPQagPQbAPQbgPQcAPQcgPQdAPQdgPQeAPQegPQfAPQfgPQYAQQYgQEAJBkAhBmAhBkAlBmAlBkApBmApBkAtBmAtBkAxBmAxBkA1BmA1BkA5BmA5BkA9BmA8QAkGgCEGoCEGgCUGoCUGgCkGoCkGgC0GoC0GgDEGoDEGgDUGoDUGgDkGoDkGgD0GoDxACQbAIQbgIQbAJQbgJQbAKQbgKQbALQbgLQbAMQbgMQbANQbgNQbAOQbgOQbAPQbgPEAJBwAhByAhBwAlByAlBwApByApBwAtByAtBwAxByAxBwA1ByA1BwA5ByA5BwA9ByA8QAkHQCEHYCEHQCUHYCUHQCkHYCkHQC0HYC0HQDEHYDEHQDUHYDUHQDkHYDkHQD0HYDxACQeAIQegIQeAJQegJQeAKQegKQeALQegLQeAMQegMQeANQegNQeAOQegOQeAPQegPEAJB8AhB+AhB8AlB+AlB8ApB+ApB8AtB+AtB8AxB+AxB8A1B+A1B8A5B+A5B8A9B+A8QAkGACUGICUGACkGICkGAC0GIC0GADEGIDEGADUGIDUGADkGIDkGAD0GID0GAEEGIEBACAkACQCADRQ0AA0AgACAEaiIDIAIgBGoiBSkDACABIARqIgYpAwCFIARBkAhqKQMAhSADKQMAhTcDACADQQhqIgMgBUEIaikDACAGQQhqKQMAhSAEQZgIaikDAIUgAykDAIU3AwAgBEEQaiIEQYAIRw0ADAILC0EAIQQDQCAAIARqIgMgAiAEaiIFKQMAIAEgBGoiBikDAIUgBEGQCGopAwCFNwMAIANBCGogBUEIaikDACAGQQhqKQMAhSAEQZgIaikDAIU3AwAgBEEQaiIEQYAIRw0ACwsL5QcMBX8BfgR/An4BfwF+AX8Bfgd/AX4DfwF+AkBBACgCgAgiAiABQQp0aiIDKAIIIAFHDQAgAygCDCEEIAMoAgAhBUEAIAMoAhQiBq03A7gQQQAgBK0iBzcDsBBBACAFIAEgBUECdG4iCGwiCUECdK03A6gQAkACQAJAAkAgBEUNAEF/IQogBUUNASAIQQNsIQsgCEECdCIErSEMIAWtIQ0gBkF/akECSSEOQgAhDwNAQQAgDzcDkBAgD6chEEIAIRFBACEBA0BBACARNwOgECAPIBGEUCIDIA5xIRIgBkEBRiAPUCITIAZBAkYgEUICVHFxciEUQX8gAUEBakEDcSAIbEF/aiATGyEVIAEgEHIhFiABIAhsIRcgA0EBdCEYQgAhGQNAQQBCADcDwBBBACAZNwOYECAYIQECQCASRQ0AQQBCATcDwBBBkBhBkBBBkCBBABADQZAYQZAYQZAgQQAQA0ECIQELAkAgASAITw0AIAQgGaciGmwgF2ogAWohAwNAIANBACAEIAEbQQAgEVAiGxtqQX9qIRwCQAJAIBQNAEEAKAKACCICIBxBCnQiHGohCgwBCwJAIAFB/wBxIgINAEEAQQApA8AQQgF8NwPAEEGQGEGQEEGQIEEAEANBkBhBkBhBkCBBABADCyAcQQp0IRwgAkEDdEGQGGohCkEAKAKACCECCyACIANBCnRqIAIgHGogAiAKKQMAIh1CIIinIAVwIBogFhsiHCAEbCABIAFBACAZIBytUSIcGyIKIBsbIBdqIAogC2ogExsgAUUgHHJrIhsgFWqtIB1C/////w+DIh0gHX5CIIggG61+QiCIfSAMgqdqQQp0akEBEAMgA0EBaiEDIAggAUEBaiIBRw0ACwsgGUIBfCIZIA1SDQALIBFCAXwiEachASARQgRSDQALIA9CAXwiDyAHUg0AC0EAKAKACCECCyAJQQx0QYB4aiEXIAVBf2oiCkUNAgwBC0EAQgM3A6AQQQAgBEF/aq03A5AQQYB4IRcLIAIgF2ohGyAIQQx0IQhBACEcA0AgCCAcQQFqIhxsQYB4aiEEQQAhAQNAIBsgAWoiAyADKQMAIAIgBCABamopAwCFNwMAIANBCGoiAyADKQMAIAIgBCABQQhyamopAwCFNwMAIAFBCGohAyABQRBqIQEgA0H4B0kNAAsgHCAKRw0ACwsgAiAXaiEbQXghAQNAIAIgAWoiA0EIaiAbIAFqIgRBCGopAwA3AwAgA0EQaiAEQRBqKQMANwMAIANBGGogBEEYaikDADcDACADQSBqIARBIGopAwA3AwAgAUEgaiIBQfgHSQ0ACwsL",dc="e4cdc523",Ac={name:lc,data:cc,hash:dc},hc="blake2b",pc="AGFzbQEAAAABEQRgAAF/YAJ/fwBgAX8AYAAAAwoJAAECAwECAgABBQQBAQICBg4CfwFBsIsFC38AQYAICwdwCAZtZW1vcnkCAA5IYXNoX0dldEJ1ZmZlcgAACkhhc2hfRmluYWwAAwlIYXNoX0luaXQABQtIYXNoX1VwZGF0ZQAGDUhhc2hfR2V0U3RhdGUABw5IYXNoX0NhbGN1bGF0ZQAIClNUQVRFX1NJWkUDAQrTOAkFAEGACQvrAgIFfwF+AkAgAUEBSA0AAkACQAJAIAFBgAFBACgC4IoBIgJrIgNKDQAgASEEDAELQQBBADYC4IoBAkAgAkH/AEoNACACQeCJAWohBSAAIQRBACEGA0AgBSAELQAAOgAAIARBAWohBCAFQQFqIQUgAyAGQQFqIgZB/wFxSg0ACwtBAEEAKQPAiQEiB0KAAXw3A8CJAUEAQQApA8iJASAHQv9+Vq18NwPIiQFB4IkBEAIgACADaiEAAkAgASADayIEQYEBSA0AIAIgAWohBQNAQQBBACkDwIkBIgdCgAF8NwPAiQFBAEEAKQPIiQEgB0L/flatfDcDyIkBIAAQAiAAQYABaiEAIAVBgH9qIgVBgAJLDQALIAVBgH9qIQQMAQsgBEEATA0BC0EAIQUDQCAFQQAoAuCKAWpB4IkBaiAAIAVqLQAAOgAAIAQgBUEBaiIFQf8BcUoNAAsLQQBBACgC4IoBIARqNgLgigELC78uASR+QQBBACkD0IkBQQApA7CJASIBQQApA5CJAXwgACkDICICfCIDhULr+obav7X2wR+FQiCJIgRCq/DT9K/uvLc8fCIFIAGFQiiJIgYgA3wgACkDKCIBfCIHIASFQjCJIgggBXwiCSAGhUIBiSIKQQApA8iJAUEAKQOoiQEiBEEAKQOIiQF8IAApAxAiA3wiBYVCn9j52cKR2oKbf4VCIIkiC0K7zqqm2NDrs7t/fCIMIASFQiiJIg0gBXwgACkDGCIEfCIOfCAAKQNQIgV8Ig9BACkDwIkBQQApA6CJASIQQQApA4CJASIRfCAAKQMAIgZ8IhKFQtGFmu/6z5SH0QCFQiCJIhNCiJLznf/M+YTqAHwiFCAQhUIoiSIVIBJ8IAApAwgiEHwiFiAThUIwiSIXhUIgiSIYQQApA9iJAUEAKQO4iQEiE0EAKQOYiQF8IAApAzAiEnwiGYVC+cL4m5Gjs/DbAIVCIIkiGkLx7fT4paf9p6V/fCIbIBOFQiiJIhwgGXwgACkDOCITfCIZIBqFQjCJIhogG3wiG3wiHSAKhUIoiSIeIA98IAApA1giCnwiDyAYhUIwiSIYIB18Ih0gDiALhUIwiSIOIAx8Ih8gDYVCAYkiDCAWfCAAKQNAIgt8Ig0gGoVCIIkiFiAJfCIaIAyFQiiJIiAgDXwgACkDSCIJfCIhIBaFQjCJIhYgGyAchUIBiSIMIAd8IAApA2AiB3wiDSAOhUIgiSIOIBcgFHwiFHwiFyAMhUIoiSIbIA18IAApA2giDHwiHCAOhUIwiSIOIBd8IhcgG4VCAYkiGyAZIBQgFYVCAYkiFHwgACkDcCINfCIVIAiFQiCJIhkgH3wiHyAUhUIoiSIUIBV8IAApA3giCHwiFXwgDHwiIoVCIIkiI3wiJCAbhUIoiSIbICJ8IBJ8IiIgFyAYIBUgGYVCMIkiFSAffCIZIBSFQgGJIhQgIXwgDXwiH4VCIIkiGHwiFyAUhUIoiSIUIB98IAV8Ih8gGIVCMIkiGCAXfCIXIBSFQgGJIhR8IAF8IiEgFiAafCIWIBUgHSAehUIBiSIaIBx8IAl8IhyFQiCJIhV8Ih0gGoVCKIkiGiAcfCAIfCIcIBWFQjCJIhWFQiCJIh4gGSAOIBYgIIVCAYkiFiAPfCACfCIPhUIgiSIOfCIZIBaFQiiJIhYgD3wgC3wiDyAOhUIwiSIOIBl8Ihl8IiAgFIVCKIkiFCAhfCAEfCIhIB6FQjCJIh4gIHwiICAiICOFQjCJIiIgJHwiIyAbhUIBiSIbIBx8IAp8IhwgDoVCIIkiDiAXfCIXIBuFQiiJIhsgHHwgE3wiHCAOhUIwiSIOIBkgFoVCAYkiFiAffCAQfCIZICKFQiCJIh8gFSAdfCIVfCIdIBaFQiiJIhYgGXwgB3wiGSAfhUIwiSIfIB18Ih0gFoVCAYkiFiAVIBqFQgGJIhUgD3wgBnwiDyAYhUIgiSIYICN8IhogFYVCKIkiFSAPfCADfCIPfCAHfCIihUIgiSIjfCIkIBaFQiiJIhYgInwgBnwiIiAjhUIwiSIjICR8IiQgFoVCAYkiFiAOIBd8Ig4gDyAYhUIwiSIPICAgFIVCAYkiFCAZfCAKfCIXhUIgiSIYfCIZIBSFQiiJIhQgF3wgC3wiF3wgBXwiICAPIBp8Ig8gHyAOIBuFQgGJIg4gIXwgCHwiGoVCIIkiG3wiHyAOhUIoiSIOIBp8IAx8IhogG4VCMIkiG4VCIIkiISAdIB4gDyAVhUIBiSIPIBx8IAF8IhWFQiCJIhx8Ih0gD4VCKIkiDyAVfCADfCIVIByFQjCJIhwgHXwiHXwiHiAWhUIoiSIWICB8IA18IiAgIYVCMIkiISAefCIeIBogFyAYhUIwiSIXIBl8IhggFIVCAYkiFHwgCXwiGSAchUIgiSIaICR8IhwgFIVCKIkiFCAZfCACfCIZIBqFQjCJIhogHSAPhUIBiSIPICJ8IAR8Ih0gF4VCIIkiFyAbIB98Iht8Ih8gD4VCKIkiDyAdfCASfCIdIBeFQjCJIhcgH3wiHyAPhUIBiSIPIBsgDoVCAYkiDiAVfCATfCIVICOFQiCJIhsgGHwiGCAOhUIoiSIOIBV8IBB8IhV8IAx8IiKFQiCJIiN8IiQgD4VCKIkiDyAifCAHfCIiICOFQjCJIiMgJHwiJCAPhUIBiSIPIBogHHwiGiAVIBuFQjCJIhUgHiAWhUIBiSIWIB18IAR8IhuFQiCJIhx8Ih0gFoVCKIkiFiAbfCAQfCIbfCABfCIeIBUgGHwiFSAXIBogFIVCAYkiFCAgfCATfCIYhUIgiSIXfCIaIBSFQiiJIhQgGHwgCXwiGCAXhUIwiSIXhUIgiSIgIB8gISAVIA6FQgGJIg4gGXwgCnwiFYVCIIkiGXwiHyAOhUIoiSIOIBV8IA18IhUgGYVCMIkiGSAffCIffCIhIA+FQiiJIg8gHnwgBXwiHiAghUIwiSIgICF8IiEgGyAchUIwiSIbIB18IhwgFoVCAYkiFiAYfCADfCIYIBmFQiCJIhkgJHwiHSAWhUIoiSIWIBh8IBJ8IhggGYVCMIkiGSAfIA6FQgGJIg4gInwgAnwiHyAbhUIgiSIbIBcgGnwiF3wiGiAOhUIoiSIOIB98IAZ8Ih8gG4VCMIkiGyAafCIaIA6FQgGJIg4gFSAXIBSFQgGJIhR8IAh8IhUgI4VCIIkiFyAcfCIcIBSFQiiJIhQgFXwgC3wiFXwgBXwiIoVCIIkiI3wiJCAOhUIoiSIOICJ8IAh8IiIgGiAgIBUgF4VCMIkiFSAcfCIXIBSFQgGJIhQgGHwgCXwiGIVCIIkiHHwiGiAUhUIoiSIUIBh8IAZ8IhggHIVCMIkiHCAafCIaIBSFQgGJIhR8IAR8IiAgGSAdfCIZIBUgISAPhUIBiSIPIB98IAN8Ih2FQiCJIhV8Ih8gD4VCKIkiDyAdfCACfCIdIBWFQjCJIhWFQiCJIiEgFyAbIBkgFoVCAYkiFiAefCABfCIZhUIgiSIbfCIXIBaFQiiJIhYgGXwgE3wiGSAbhUIwiSIbIBd8Ihd8Ih4gFIVCKIkiFCAgfCAMfCIgICGFQjCJIiEgHnwiHiAiICOFQjCJIiIgJHwiIyAOhUIBiSIOIB18IBJ8Ih0gG4VCIIkiGyAafCIaIA6FQiiJIg4gHXwgC3wiHSAbhUIwiSIbIBcgFoVCAYkiFiAYfCANfCIXICKFQiCJIhggFSAffCIVfCIfIBaFQiiJIhYgF3wgEHwiFyAYhUIwiSIYIB98Ih8gFoVCAYkiFiAVIA+FQgGJIg8gGXwgCnwiFSAchUIgiSIZICN8IhwgD4VCKIkiDyAVfCAHfCIVfCASfCIihUIgiSIjfCIkIBaFQiiJIhYgInwgBXwiIiAjhUIwiSIjICR8IiQgFoVCAYkiFiAbIBp8IhogFSAZhUIwiSIVIB4gFIVCAYkiFCAXfCADfCIXhUIgiSIZfCIbIBSFQiiJIhQgF3wgB3wiF3wgAnwiHiAVIBx8IhUgGCAaIA6FQgGJIg4gIHwgC3wiGoVCIIkiGHwiHCAOhUIoiSIOIBp8IAR8IhogGIVCMIkiGIVCIIkiICAfICEgFSAPhUIBiSIPIB18IAZ8IhWFQiCJIh18Ih8gD4VCKIkiDyAVfCAKfCIVIB2FQjCJIh0gH3wiH3wiISAWhUIoiSIWIB58IAx8Ih4gIIVCMIkiICAhfCIhIBogFyAZhUIwiSIXIBt8IhkgFIVCAYkiFHwgEHwiGiAdhUIgiSIbICR8Ih0gFIVCKIkiFCAafCAJfCIaIBuFQjCJIhsgHyAPhUIBiSIPICJ8IBN8Ih8gF4VCIIkiFyAYIBx8Ihh8IhwgD4VCKIkiDyAffCABfCIfIBeFQjCJIhcgHHwiHCAPhUIBiSIPIBggDoVCAYkiDiAVfCAIfCIVICOFQiCJIhggGXwiGSAOhUIoiSIOIBV8IA18IhV8IA18IiKFQiCJIiN8IiQgD4VCKIkiDyAifCAMfCIiICOFQjCJIiMgJHwiJCAPhUIBiSIPIBsgHXwiGyAVIBiFQjCJIhUgISAWhUIBiSIWIB98IBB8IhiFQiCJIh18Ih8gFoVCKIkiFiAYfCAIfCIYfCASfCIhIBUgGXwiFSAXIBsgFIVCAYkiFCAefCAHfCIZhUIgiSIXfCIbIBSFQiiJIhQgGXwgAXwiGSAXhUIwiSIXhUIgiSIeIBwgICAVIA6FQgGJIg4gGnwgAnwiFYVCIIkiGnwiHCAOhUIoiSIOIBV8IAV8IhUgGoVCMIkiGiAcfCIcfCIgIA+FQiiJIg8gIXwgBHwiISAehUIwiSIeICB8IiAgGCAdhUIwiSIYIB98Ih0gFoVCAYkiFiAZfCAGfCIZIBqFQiCJIhogJHwiHyAWhUIoiSIWIBl8IBN8IhkgGoVCMIkiGiAcIA6FQgGJIg4gInwgCXwiHCAYhUIgiSIYIBcgG3wiF3wiGyAOhUIoiSIOIBx8IAN8IhwgGIVCMIkiGCAbfCIbIA6FQgGJIg4gFSAXIBSFQgGJIhR8IAt8IhUgI4VCIIkiFyAdfCIdIBSFQiiJIhQgFXwgCnwiFXwgBHwiIoVCIIkiI3wiJCAOhUIoiSIOICJ8IAl8IiIgGyAeIBUgF4VCMIkiFSAdfCIXIBSFQgGJIhQgGXwgDHwiGYVCIIkiHXwiGyAUhUIoiSIUIBl8IAp8IhkgHYVCMIkiHSAbfCIbIBSFQgGJIhR8IAN8Ih4gGiAffCIaIBUgICAPhUIBiSIPIBx8IAd8IhyFQiCJIhV8Ih8gD4VCKIkiDyAcfCAQfCIcIBWFQjCJIhWFQiCJIiAgFyAYIBogFoVCAYkiFiAhfCATfCIahUIgiSIYfCIXIBaFQiiJIhYgGnwgDXwiGiAYhUIwiSIYIBd8Ihd8IiEgFIVCKIkiFCAefCAFfCIeICCFQjCJIiAgIXwiISAiICOFQjCJIiIgJHwiIyAOhUIBiSIOIBx8IAt8IhwgGIVCIIkiGCAbfCIbIA6FQiiJIg4gHHwgEnwiHCAYhUIwiSIYIBcgFoVCAYkiFiAZfCABfCIXICKFQiCJIhkgFSAffCIVfCIfIBaFQiiJIhYgF3wgBnwiFyAZhUIwiSIZIB98Ih8gFoVCAYkiFiAVIA+FQgGJIg8gGnwgCHwiFSAdhUIgiSIaICN8Ih0gD4VCKIkiDyAVfCACfCIVfCANfCIihUIgiSIjfCIkIBaFQiiJIhYgInwgCXwiIiAjhUIwiSIjICR8IiQgFoVCAYkiFiAYIBt8IhggFSAahUIwiSIVICEgFIVCAYkiFCAXfCASfCIXhUIgiSIafCIbIBSFQiiJIhQgF3wgCHwiF3wgB3wiISAVIB18IhUgGSAYIA6FQgGJIg4gHnwgBnwiGIVCIIkiGXwiHSAOhUIoiSIOIBh8IAt8IhggGYVCMIkiGYVCIIkiHiAfICAgFSAPhUIBiSIPIBx8IAp8IhWFQiCJIhx8Ih8gD4VCKIkiDyAVfCAEfCIVIByFQjCJIhwgH3wiH3wiICAWhUIoiSIWICF8IAN8IiEgHoVCMIkiHiAgfCIgIBggFyAahUIwiSIXIBt8IhogFIVCAYkiFHwgBXwiGCAchUIgiSIbICR8IhwgFIVCKIkiFCAYfCABfCIYIBuFQjCJIhsgHyAPhUIBiSIPICJ8IAx8Ih8gF4VCIIkiFyAZIB18Ihl8Ih0gD4VCKIkiDyAffCATfCIfIBeFQjCJIhcgHXwiHSAPhUIBiSIPIBkgDoVCAYkiDiAVfCAQfCIVICOFQiCJIhkgGnwiGiAOhUIoiSIOIBV8IAJ8IhV8IBN8IiKFQiCJIiN8IiQgD4VCKIkiDyAifCASfCIiICOFQjCJIiMgJHwiJCAPhUIBiSIPIBsgHHwiGyAVIBmFQjCJIhUgICAWhUIBiSIWIB98IAt8IhmFQiCJIhx8Ih8gFoVCKIkiFiAZfCACfCIZfCAJfCIgIBUgGnwiFSAXIBsgFIVCAYkiFCAhfCAFfCIahUIgiSIXfCIbIBSFQiiJIhQgGnwgA3wiGiAXhUIwiSIXhUIgiSIhIB0gHiAVIA6FQgGJIg4gGHwgEHwiFYVCIIkiGHwiHSAOhUIoiSIOIBV8IAF8IhUgGIVCMIkiGCAdfCIdfCIeIA+FQiiJIg8gIHwgDXwiICAhhUIwiSIhIB58Ih4gGSAchUIwiSIZIB98IhwgFoVCAYkiFiAafCAIfCIaIBiFQiCJIhggJHwiHyAWhUIoiSIWIBp8IAp8IhogGIVCMIkiGCAdIA6FQgGJIg4gInwgBHwiHSAZhUIgiSIZIBcgG3wiF3wiGyAOhUIoiSIOIB18IAd8Ih0gGYVCMIkiGSAbfCIbIA6FQgGJIg4gFSAXIBSFQgGJIhR8IAx8IhUgI4VCIIkiFyAcfCIcIBSFQiiJIhQgFXwgBnwiFXwgEnwiIoVCIIkiI3wiJCAOhUIoiSIOICJ8IBN8IiIgGyAhIBUgF4VCMIkiFSAcfCIXIBSFQgGJIhQgGnwgBnwiGoVCIIkiHHwiGyAUhUIoiSIUIBp8IBB8IhogHIVCMIkiHCAbfCIbIBSFQgGJIhR8IA18IiEgGCAffCIYIBUgHiAPhUIBiSIPIB18IAJ8Ih2FQiCJIhV8Ih4gD4VCKIkiDyAdfCABfCIdIBWFQjCJIhWFQiCJIh8gFyAZIBggFoVCAYkiFiAgfCADfCIYhUIgiSIZfCIXIBaFQiiJIhYgGHwgBHwiGCAZhUIwiSIZIBd8Ihd8IiAgFIVCKIkiFCAhfCAIfCIhIB+FQjCJIh8gIHwiICAiICOFQjCJIiIgJHwiIyAOhUIBiSIOIB18IAd8Ih0gGYVCIIkiGSAbfCIbIA6FQiiJIg4gHXwgDHwiHSAZhUIwiSIZIBcgFoVCAYkiFiAafCALfCIXICKFQiCJIhogFSAefCIVfCIeIBaFQiiJIhYgF3wgCXwiFyAahUIwiSIaIB58Ih4gFoVCAYkiFiAVIA+FQgGJIg8gGHwgBXwiFSAchUIgiSIYICN8IhwgD4VCKIkiDyAVfCAKfCIVfCACfCIChUIgiSIifCIjIBaFQiiJIhYgAnwgC3wiAiAihUIwiSILICN8IiIgFoVCAYkiFiAZIBt8IhkgFSAYhUIwiSIVICAgFIVCAYkiFCAXfCANfCINhUIgiSIXfCIYIBSFQiiJIhQgDXwgBXwiBXwgEHwiECAVIBx8Ig0gGiAZIA6FQgGJIg4gIXwgDHwiDIVCIIkiFXwiGSAOhUIoiSIOIAx8IBJ8IhIgFYVCMIkiDIVCIIkiFSAeIB8gDSAPhUIBiSINIB18IAl8IgmFQiCJIg98IhogDYVCKIkiDSAJfCAIfCIJIA+FQjCJIgggGnwiD3wiGiAWhUIoiSIWIBB8IAd8IhAgEYUgDCAZfCIHIA6FQgGJIgwgCXwgCnwiCiALhUIgiSILIAUgF4VCMIkiBSAYfCIJfCIOIAyFQiiJIgwgCnwgE3wiEyALhUIwiSIKIA58IguFNwOAiQFBACADIAYgDyANhUIBiSINIAJ8fCICIAWFQiCJIgUgB3wiBiANhUIoiSIHIAJ8fCICQQApA4iJAYUgBCABIBIgCSAUhUIBiSIDfHwiASAIhUIgiSISICJ8IgkgA4VCKIkiAyABfHwiASAShUIwiSIEIAl8IhKFNwOIiQFBACATQQApA5CJAYUgECAVhUIwiSIQIBp8IhOFNwOQiQFBACABQQApA5iJAYUgAiAFhUIwiSICIAZ8IgGFNwOYiQFBACASIAOFQgGJQQApA6CJAYUgAoU3A6CJAUEAIBMgFoVCAYlBACkDqIkBhSAKhTcDqIkBQQAgASAHhUIBiUEAKQOwiQGFIASFNwOwiQFBACALIAyFQgGJQQApA7iJAYUgEIU3A7iJAQvdAgUBfwF+AX8BfgJ/IwBBwABrIgAkAAJAQQApA9CJAUIAUg0AQQBBACkDwIkBIgFBACgC4IoBIgKsfCIDNwPAiQFBAEEAKQPIiQEgAyABVK18NwPIiQECQEEALQDoigFFDQBBAEJ/NwPYiQELQQBCfzcD0IkBAkAgAkH/AEoNAEEAIQQDQCACIARqQeCJAWpBADoAACAEQQFqIgRBgAFBACgC4IoBIgJrSA0ACwtB4IkBEAIgAEEAKQOAiQE3AwAgAEEAKQOIiQE3AwggAEEAKQOQiQE3AxAgAEEAKQOYiQE3AxggAEEAKQOgiQE3AyAgAEEAKQOoiQE3AyggAEEAKQOwiQE3AzAgAEEAKQO4iQE3AzhBACgC5IoBIgVBAUgNAEEAIQRBACECA0AgBEGACWogACAEai0AADoAACAEQQFqIQQgBSACQQFqIgJB/wFxSg0ACwsgAEHAAGokAAv9AwMBfwF+AX8jAEGAAWsiAiQAQQBBgQI7AfKKAUEAIAE6APGKAUEAIAA6APCKAUGQfiEAA0AgAEGAiwFqQgA3AAAgAEH4igFqQgA3AAAgAEHwigFqQgA3AAAgAEEYaiIADQALQQAhAEEAQQApA/CKASIDQoiS853/zPmE6gCFNwOAiQFBAEEAKQP4igFCu86qptjQ67O7f4U3A4iJAUEAQQApA4CLAUKr8NP0r+68tzyFNwOQiQFBAEEAKQOIiwFC8e30+KWn/aelf4U3A5iJAUEAQQApA5CLAULRhZrv+s+Uh9EAhTcDoIkBQQBBACkDmIsBQp/Y+dnCkdqCm3+FNwOoiQFBAEEAKQOgiwFC6/qG2r+19sEfhTcDsIkBQQBBACkDqIsBQvnC+JuRo7Pw2wCFNwO4iQFBACADp0H/AXE2AuSKAQJAIAFBAUgNACACQgA3A3ggAkIANwNwIAJCADcDaCACQgA3A2AgAkIANwNYIAJCADcDUCACQgA3A0ggAkIANwNAIAJCADcDOCACQgA3AzAgAkIANwMoIAJCADcDICACQgA3AxggAkIANwMQIAJCADcDCCACQgA3AwBBACEEA0AgAiAAaiAAQYAJai0AADoAACAAQQFqIQAgBEEBaiIEQf8BcSABSA0ACyACQYABEAELIAJBgAFqJAALEgAgAEEDdkH/P3EgAEEQdhAECwkAQYAJIAAQAQsGAEGAiQELGwAgAUEDdkH/P3EgAUEQdhAEQYAJIAAQARADCwsLAQBBgAgLBPAAAAA=",gc="c6f286e6",uc={name:hc,data:pc,hash:gc},bA=new ae;function _r(A){return!Number.isInteger(A)||A<8||A>512||A%8!==0?new Error("Invalid variant! Valid values: 8, 16, ..., 512"):null}function fc(A,r){return A|r<<16}function yo(A=512,r=null){if(_r(A))return Promise.reject(_r(A));let e=null,t=A;if(r!==null){if(e=ht(r),e.length>64)return Promise.reject(new Error("Max key length is 64 bytes"));t=fc(A,e.length)}let i=A/8;return sa(uc,i).then(s=>{t>512&&s.writeMemory(e),s.init(t);let n={init:t>512?()=>(s.writeMemory(e),s.init(t),n):()=>(s.init(t),n),update:o=>(s.update(o),n),digest:o=>s.digest(o),save:()=>s.save(),load:o=>(s.load(o),n),blockSize:128,digestSize:i};return n})}function Ic(A,r,e){let t=[`m=${r.memorySize}`,`t=${r.iterations}`,`p=${r.parallelism}`].join(",");return`$argon2${r.hashType}$v=19$${t}$${jr(A,!1)}$${jr(e,!1)}`}var ea=new DataView(new ArrayBuffer(4));function At(A){return ea.setInt32(0,A,!0),new Uint8Array(ea.buffer)}function mo(A,r,e){return $t(this,void 0,void 0,function*(){if(e<=64){let a=yield yo(e*8);return a.update(At(e)),a.update(r),a.digest("binary")}let t=Math.ceil(e/32)-2,i=new Uint8Array(e);A.init(),A.update(At(e)),A.update(r);let s=A.digest("binary");i.set(s.subarray(0,32),0);for(let a=1;a<t;a++)A.init(),A.update(s),s=A.digest("binary"),i.set(s.subarray(0,32),a*32);let n=e-32*t,o;return n===64?(o=A,o.init()):o=yield yo(n*8),o.update(s),s=o.digest("binary"),i.set(s.subarray(0,n),t*32),i})}function mc(A){switch(A){case"d":return 0;case"i":return 1;default:return 2}}function Cc(A){return $t(this,void 0,void 0,function*(){var r;let{parallelism:e,iterations:t,hashLength:i}=A,s=ht(A.password),n=ht(A.salt),o=19,a=mc(A.hashType),{memorySize:l}=A,c=ht((r=A.secret)!==null&&r!==void 0?r:""),[d,h]=yield Promise.all([sa(Ac,1024),yo(512)]);d.setMemorySize(l*1024+1024);let g=new Uint8Array(24),f=new DataView(g.buffer);f.setInt32(0,e,!0),f.setInt32(4,i,!0),f.setInt32(8,l,!0),f.setInt32(12,t,!0),f.setInt32(16,o,!0),f.setInt32(20,a,!0),d.writeMemory(g,l*1024),h.init(),h.update(g),h.update(At(s.length)),h.update(s),h.update(At(n.length)),h.update(n),h.update(At(c.length)),h.update(c),h.update(At(0));let u=Math.floor(l/(e*4))*4,I=new Uint8Array(72),m=h.digest("binary");I.set(m);for(let w=0;w<e;w++){I.set(At(0),64),I.set(At(w),68);let y=w*u,v=yield mo(h,I,1024);d.writeMemory(v,y*1024),y+=1,I.set(At(1),64),v=yield mo(h,I,1024),d.writeMemory(v,y*1024)}let C=new Uint8Array(1024);ia(C,d.calculate(new Uint8Array([]),l));let B=yield mo(h,C,i);if(A.outputType==="hex"){let w=new Uint8Array(i*2);return Bo(w,B,i)}return A.outputType==="encoded"?Ic(n,A,B):B})}var Bc=A=>{var r;if(!A||typeof A!="object")throw new Error("Invalid options parameter. It requires an object.");if(!A.password)throw new Error("Password must be specified");if(A.password=ht(A.password),A.password.length<1)throw new Error("Password must be specified");if(!A.salt)throw new Error("Salt must be specified");if(A.salt=ht(A.salt),A.salt.length<8)throw new Error("Salt should be at least 8 bytes long");if(A.secret=ht((r=A.secret)!==null&&r!==void 0?r:""),!Number.isInteger(A.iterations)||A.iterations<1)throw new Error("Iterations should be a positive number");if(!Number.isInteger(A.parallelism)||A.parallelism<1)throw new Error("Parallelism should be a positive number");if(!Number.isInteger(A.hashLength)||A.hashLength<4)throw new Error("Hash length should be at least 4 bytes.");if(!Number.isInteger(A.memorySize))throw new Error("Memory size should be specified.");if(A.memorySize<8*A.parallelism)throw new Error("Memory size should be at least 8 * parallelism.");if(A.outputType===void 0&&(A.outputType="hex"),!["hex","binary","encoded"].includes(A.outputType))throw new Error(`Insupported output type ${A.outputType}. Valid values: ['hex', 'binary', 'encoded']`)};function wo(A){return $t(this,void 0,void 0,function*(){return Bc(A),Cc(Object.assign(Object.assign({},A),{hashType:"id"}))})}var DA=new ae;var xA=new ae;var NA=new ae;var MA=new ae;var TA=new Uint8Array(8);var PA=new ae;var HA=new ae;var RA=new ae;var UA=new ae;var GA=new ae;var LA=new ae;var OA=new ae;var qA=new ae;var KA=new ae;var JA=new ae;var $A=new ae;var YA=new Uint8Array(8);var zA=new ae;var VA=new Uint8Array(8);var WA=new ae;var XA=new Uint8Array(8);var ZA=new ae;var jA=new ae;var _A=new ae;ye();var Ke=new Uint8Array([83,84,65,83,72,69,78,67]),vo=2,Fo=1,ko=2,ra=3,yc=32,di=12,Eo=32,wc=6e5,Ec=47104,Qc=3,vc=1,ge=A=>A;function Me(A){if(A.length<Ke.length+1)return!1;for(let r=0;r<Ke.length;r++)if(A[r]!==Ke[r])return!1;return!0}function na(A,r,e){A[r]=e>>>24&255,A[r+1]=e>>>16&255,A[r+2]=e>>>8&255,A[r+3]=e&255}function oa(A,r){return A[r]*16777216+(A[r+1]<<16)+(A[r+2]<<8)+A[r+3]}async function Qo(A,r,e){if(e.id===ko)return await wo({password:A,salt:r,parallelism:e.c,iterations:e.b,memorySize:e.a,hashLength:Eo,outputType:"binary"});if(e.id===Fo){let t=await crypto.subtle.importKey("raw",ge(new TextEncoder().encode(A)),"PBKDF2",!1,["deriveBits"]),i=await crypto.subtle.deriveBits({name:"PBKDF2",salt:ge(r),iterations:e.a,hash:"SHA-256"},t,Eo*8);return new Uint8Array(i)}throw new Error(`Unsupported KDF id (${e.id}).`)}async function Os(A){return crypto.subtle.importKey("raw",ge(A),{name:"AES-GCM"},!1,["encrypt","decrypt"])}var So={argon2id:{label:"Argon2id",strong:!0},pbkdf2:{label:"PBKDF2 (fallback)",strong:!1}};async function et(A,r){let e=crypto.getRandomValues(new Uint8Array(yc)),t=crypto.getRandomValues(new Uint8Array(di)),i,s,n;try{i={id:ko,a:Ec,b:Qc,c:vc},n=await Qo(r,e,i),s="argon2id"}catch(d){console.warn("[Stashpad] Argon2id unavailable \u2014 encrypting with PBKDF2 fallback.",d),i={id:Fo,a:wc,b:0,c:0},n=await Qo(r,e,i),s="pbkdf2"}let o=aa(i,e,t),a=await Os(n),l=new Uint8Array(await crypto.subtle.encrypt({name:"AES-GCM",iv:ge(t),additionalData:ge(o)},a,ge(A))),c=new Uint8Array(o.length+l.length);return c.set(o,0),c.set(l,o.length),{data:c,kdf:s}}var es=null;async function ts(){if(es!==null)return es;try{await wo({password:"x",salt:new Uint8Array(16),parallelism:1,iterations:1,memorySize:8,hashLength:16,outputType:"binary"}),es=!0}catch{es=!1}return es}function aa(A,r,e){let t=new Uint8Array(Ke.length+1+1+4+4+1+1+r.length+e.length),i=0;return t.set(Ke,i),i+=Ke.length,t[i++]=vo,t[i++]=A.id,na(t,i,A.a),i+=4,na(t,i,A.b),i+=4,t[i++]=A.c,t[i++]=r.length,t.set(r,i),i+=r.length,t.set(e,i),i+=e.length,t}async function _e(A,r){if(!Me(A))throw new Error("Not an encrypted Stashpad file.");let e=A[Ke.length];if(e===1)return Fc(A,r);if(e!==vo)throw new Error(`Unsupported encrypted .stash version (${e}).`);let t=Ke.length+1,i={id:A[t++],a:0,b:0,c:0};if(i.a=oa(A,t),t+=4,i.b=oa(A,t),t+=4,i.c=A[t++],i.id===ko){if(i.a>2097152||i.b>64||i.c<1||i.c>8)throw new Error("Unsupported KDF parameters (file may be corrupted or malicious).")}else if(i.id===Fo&&i.a>1e7)throw new Error("Unsupported KDF parameters (file may be corrupted or malicious).");let s=A[t++];if(s<8||s>64)throw new Error("Unsupported KDF parameters (file may be corrupted or malicious).");let n=A.slice(t,t+s);t+=s;let o=A.slice(t,t+di);t+=di;let a=A.slice(0,t),l=A.slice(t),c=await Qo(r,n,i),d=await Os(c),h=await crypto.subtle.decrypt({name:"AES-GCM",iv:ge(o),additionalData:ge(a)},d,ge(l));return new Uint8Array(h)}async function Fc(A,r){let e=Ke.length+1,t=A.slice(e,e+16);e+=16;let i=A.slice(e,e+12);e+=12;let s=A.slice(e),n=await crypto.subtle.importKey("raw",ge(new TextEncoder().encode(r)),"PBKDF2",!1,["deriveKey"]),o=await crypto.subtle.deriveKey({name:"PBKDF2",salt:ge(t),iterations:21e4,hash:"SHA-256"},n,{name:"AES-GCM",length:256},!1,["decrypt"]),a=await crypto.subtle.decrypt({name:"AES-GCM",iv:ge(i)},o,ge(s));return new Uint8Array(a)}async function Et(A,r){if(r.length!==Eo)throw new Error("Encryption key must be 32 bytes.");let e=crypto.getRandomValues(new Uint8Array(di)),i=aa({id:ra,a:0,b:0,c:0},new Uint8Array(0),e),s=await Os(r),n=new Uint8Array(await crypto.subtle.encrypt({name:"AES-GCM",iv:ge(e),additionalData:ge(i)},s,ge(A))),o=new Uint8Array(i.length+n.length);return o.set(i,0),o.set(n,i.length),o}async function Fe(A,r){if(!Me(A))throw new Error("Not an encrypted Stashpad file.");let e=A[Ke.length];if(e!==vo)throw new Error(`Unsupported encrypted version (${e}).`);let t=Ke.length+1;if(A[t++]!==ra)throw new Error("Not a raw-key (.stashenc) envelope.");t+=9;let s=A[t++];t+=s;let n=A.slice(t,t+di);t+=di;let o=A.slice(0,t),a=A.slice(t),l=await Os(r),c=await crypto.subtle.decrypt({name:"AES-GCM",iv:ge(n),additionalData:ge(o)},l,ge(a));return new Uint8Array(c)}async function Ai(A,r,e={}){if(!Me(r))return r;if(e.secretId){let i=A.secretStorage,s=null;try{s=i?.getSecret(e.secretId)??null}catch{s=null}if(s)try{return await _e(r,s)}catch{}}let t;for(;;){let i=await new Promise(s=>{new Gi(A,t,!!e.allowLater,s).open()});if(i.kind==="cancel")return null;if(i.kind==="later")return e.onLater?.(),null;try{return await _e(r,i.value)}catch{t="Wrong password or corrupted file. Try again."}}}Le();var qs={name:"ECDH",namedCurve:"P-256"},kc="stashpad-vault-dek-wrap",Sc=new TextEncoder;function bo(A){let r="";for(let e=0;e<A.length;e++)r+=String.fromCharCode(A[e]);return btoa(r)}function Do(A){let r=atob(A),e=new Uint8Array(r.length);for(let t=0;t<r.length;t++)e[t]=r.charCodeAt(t);return e}async function la(){let A=await crypto.subtle.generateKey(qs,!0,["deriveBits"]);return{pubKeySpki:new Uint8Array(await crypto.subtle.exportKey("spki",A.publicKey)),privKeyPkcs8:new Uint8Array(await crypto.subtle.exportKey("pkcs8",A.privateKey))}}function ca(A){return crypto.subtle.importKey("spki",A,qs,!1,[])}function bc(A){return crypto.subtle.importKey("pkcs8",A,qs,!1,["deriveBits"])}async function da(A,r){let e=await crypto.subtle.deriveBits({name:"ECDH",public:r},A,256),t=await crypto.subtle.importKey("raw",e,"HKDF",!1,["deriveKey"]);return crypto.subtle.deriveKey({name:"HKDF",hash:"SHA-256",salt:new Uint8Array(0),info:Sc.encode(kc)},t,{name:"AES-GCM",length:256},!1,["encrypt","decrypt"])}async function Ks(A,r,e){let t=await crypto.subtle.generateKey(qs,!0,["deriveBits"]),i=await da(t.privateKey,await ca(r)),s=crypto.getRandomValues(new Uint8Array(12)),n=new Uint8Array(await crypto.subtle.encrypt({name:"AES-GCM",iv:s},i,A)),o=new Uint8Array(await crypto.subtle.exportKey("spki",t.publicKey));return{recipientId:e,ephPub:bo(o),iv:bo(s),ct:bo(n)}}async function Aa(A,r){let e=await da(await bc(r),await ca(Do(A.ephPub)));return new Uint8Array(await crypto.subtle.decrypt({name:"AES-GCM",iv:Do(A.iv)},e,Do(A.ct)))}var ha=".stashpad/keys.json",Dc=".stashpad",is="_keys";function xo(A){return{v:2,keyId:A,identities:[],slots:[],joinRequests:[]}}var Js=class{constructor(r){this.app=r}get a(){return this.app.vault.adapter}validate(r){let e=r;return!!e&&e.v===2&&typeof e.keyId=="string"&&Array.isArray(e.identities)&&Array.isArray(e.slots)&&Array.isArray(e.joinRequests)}async readValid(r){try{if(!await this.a.exists(r))return null;let e=JSON.parse(await this.a.read(r));return this.validate(e)?e:null}catch{return null}}async load(){let r=await this.readValid(ha);if(r)return r;try{let t=((await this.a.list(is)).files||[]).filter(i=>/\/keys-\d+\.json$/.test(i)).sort();for(let i of t.reverse()){let s=await this.readValid(i);if(s)return s}}catch{}return null}async exists(){return await this.load()!==null}async ensureDir(r){try{await this.a.exists(r)||await this.a.mkdir(r)}catch{}}async save(r){let e=JSON.stringify(r,null,2);await this.ensureDir(Dc),await this.a.write(ha,e),await this.ensureDir(is);for(let t=4;t>=1;t--){let i=`${is}/keys-${t}.json`,s=`${is}/keys-${t+1}.json`;try{await this.a.exists(i)&&await this.a.write(s,await this.a.read(i))}catch{}}try{await this.a.write(`${is}/keys-1.json`,e)}catch{}}};var ss="stashpad-vault-encryption";function ns(){return{wrappedKey:null,kdf:null,identityId:null,identityLabel:null,identityPub:null,identityPrivWrapped:null,identityPrivKdf:null}}function Yt(A){let r="";for(let e=0;e<A.length;e++)r+=String.fromCharCode(A[e]);return btoa(r)}function tt(A){let r=atob(A),e=new Uint8Array(r.length);for(let t=0;t<r.length;t++)e[t]=r.charCodeAt(t);return e}var $s=32,Ys=class{constructor(r,e,t){this.app=r;this.load=e;this.save=t;this.sessionKey=null;this.folderSessionKeys=new Map;this.kf=null;this.keyfiles=new Js(r)}argonProbe(){return ts()}async init(){await this.refresh()}async refresh(){this.kf=await this.keyfiles.load()}secretStore(){return this.app.secretStorage??null}keychainAvailable(){return!!this.secretStore()}appTag(){return(this.app.appId||"default").toLowerCase().replace(/[^a-z0-9]/g,"").slice(0,20)||"default"}keychainId(){return`${ss}-${this.appTag()}`}legacyAppIdKeychainId(){let r=this.app.appId||"default";return`${ss}-${r}`}isRemembered(){try{let r=this.secretStore();return!!(r?.getSecret(this.keychainId())||r?.getSecret(this.legacyAppIdKeychainId()))}catch{return!1}}rememberedPassword(){let r=this.secretStore();if(!r)return null;try{let e=r.getSecret(this.keychainId());if(e)return e}catch{}try{let e=r.getSecret(this.legacyAppIdKeychainId());if(e)return e}catch{}try{return r.getSecret(ss)||null}catch{return null}}async verifyWithKeychain(){let r=this.rememberedPassword();if(!r)return!1;try{return await this.verifyPassword(r)}catch{return!1}}async parkThenSet(r,e){let t=this.secretStore();if(!t)return;let i=[r];try{let s=t.getSecret(r);if(s&&s!==e){let n=Date.now().toString(36),o=`${r.slice(0,64-(n.length+2))}-r${n}`;await t.setSecret(o,s),i.push(o)}}catch{}await t.setSecret(r,e),await this.recordKeychainIds(i)}async recordKeychainIds(r){try{let e=this.load(),t=new Set(e.knownKeychainIds??[]),i=!1;for(let s of r)s&&!t.has(s)&&(t.add(s),i=!0);i&&await this.save({...e,knownKeychainIds:[...t]})}catch{}}keychainCandidateIds(){let r=[this.keychainId(),this.legacyAppIdKeychainId(),ss,...this.load().knownKeychainIds??[]];return[...new Set(r)]}async remember(r){try{await this.parkThenSet(this.keychainId(),r)}catch(e){console.warn("[Stashpad] couldn't save password to keychain",e)}try{let e=this.load();e.keychainForgotten&&await this.save({...e,keychainForgotten:!1})}catch{}}async forgetKeychain(){try{await this.save({...this.load(),keychainForgotten:!0})}catch{}let r=this.secretStore();if(r){try{r.getSecret(this.keychainId())&&await this.parkThenSet(this.keychainId(),"");return}catch{}try{r.removeSecret?await r.removeSecret(this.keychainId()):await r.setSecret(this.keychainId(),"")}catch(e){console.warn("[Stashpad] couldn't clear keychain",e)}}}async tryAutoUnlock(){if(!this.isConfigured()||this.isUnlocked())return this.isUnlocked();if(this.load().keychainForgotten||this.accessState()!=="member"&&!this.hasSharedPassword())return!1;let r=this.secretStore();if(!r)return!1;let e=this.keychainId(),t=new Set;for(let i of this.keychainCandidateIds()){let s=null;try{s=r.getSecret(i)??null}catch{s=null}if(!s||t.has(s))continue;t.add(s);let n=!1;try{n=await this.verifyPassword(s)}catch{n=!1}if(n&&await this.unlock(s))return i!==e&&await this.remember(s),!0}return!1}isConfigured(){return!!this.kf||!!this.load().wrappedKey}isUnlocked(){return this.sessionKey!==null}kdf(){return this.load().identityPrivKdf??this.load().kdf}hasIdentity(){return!!this.load().identityPrivWrapped&&!!this.load().identityId}mySlot(){let r=this.load().identityId;return r?this.kf?.slots.find(e=>e.recipientId===r)??null:null}accessState(){let r=this.load();return!this.kf&&r.wrappedKey?"member":this.kf?this.hasIdentity()&&this.mySlot()?"member":r.identityId&&this.kf.joinRequests.some(e=>e.id===r.identityId)?"pending":"outsider":"none"}amIMember(){return this.accessState()==="member"}async setup(r,e=!1,t){if(await this.refresh(),this.isConfigured())throw new Error("Encryption is already set up in this vault.");if(!r)throw new Error("Password required.");let i=crypto.getRandomValues(new Uint8Array($s)),s=await this.mintIdentity(r,t),n=xo(Z(8));n.identities.push(this.identityRecord(s)),n.slots.push(await Ks(i,tt(s.pub),s.id)),await this.keyfiles.save(n),this.kf=n,this.sessionKey=i,e&&await this.remember(r)}async unlock(r,e=!1){await this.refresh();let t=this.load();if(!this.kf&&t.wrappedKey){let s;try{s=await _e(tt(t.wrappedKey),r)}catch{return!1}let n=await this.mintIdentity(r,t.identityLabel??void 0),o=xo(Z(8));return o.identities.push(this.identityRecord(n)),o.slots.push(await Ks(s,tt(n.pub),n.id)),await this.keyfiles.save(o),this.kf=o,this.sessionKey=s,e&&await this.remember(r),!0}let i=this.hasIdentity()?this.mySlot():null;if(i)try{let s=await _e(tt(t.identityPrivWrapped),r);try{let n=await Aa(i,s);return s.fill(0),n.length!==$s?(n.fill(0),!1):(this.sessionKey=n,e&&await this.remember(r),!0)}catch{s.fill(0)}}catch{}for(let s of(this.kf?.passwordSlots??[]).filter(n=>!n.label.startsWith("[deprecated]")))try{let n=await _e(tt(s.wrapped),r);if(n.length!==$s){n.fill(0);continue}return this.sessionKey=n,e&&await this.remember(r),!0}catch{}return!1}hasSharedPassword(){return(this.kf?.passwordSlots??[]).some(r=>!r.label.startsWith("[deprecated]"))}async setSharedPassword(r,e=!1){if(!this.sessionKey)throw new Error("Unlock encryption first.");if(!r)throw new Error("Password required.");if(await this.refresh(),!this.kf)throw new Error("Encryption is not set up.");let t=(this.kf.passwordSlots??[]).filter(o=>!o.label.startsWith("[deprecated]")),i=this.rememberedPassword();if(i&&t[0])try{await this.parkThenSet(`${this.keychainId()}-d-${t[0].id}`,i)}catch{}let s=await et(this.sessionKey,r),n=(this.kf.passwordSlots??[]).map(o=>o.label.startsWith("[deprecated]")?o:{...o,label:`[deprecated] ${o.label}`});this.kf.passwordSlots=[{id:Z(8),label:"Shared password",wrapped:Yt(s.data),kdf:s.kdf,createdAt:new Date().toISOString()},...n],await this.keyfiles.save(this.kf),(e||this.isRemembered())&&await this.remember(r)}async removeSharedPassword(){await this.refresh(),!(!this.kf||!this.kf.passwordSlots?.length)&&(this.kf.passwordSlots=[],await this.keyfiles.save(this.kf))}owningFolder(r){let e=this.kf?.folderKeys;if(!e)return null;let t=this.cleanFolder(r);for(;t;){if(e[t])return t;let i=t.lastIndexOf("/");if(i<0)break;t=t.slice(0,i)}return null}hasOwnFolderKey(r){return!!this.kf?.folderKeys?.[this.cleanFolder(r)]}folderKeyEntry(r){let e=this.owningFolder(r);return e?this.kf.folderKeys[e]??null:null}hasFolderKey(r){return this.owningFolder(r)!==null}folderPathByKeyId(r){for(let[e,t]of Object.entries(this.kf?.folderKeys??{}))if(t.keyId===r)return e;return null}folderActiveSlots(r){return r.passwordSlots.filter(e=>!e.label.startsWith("[deprecated]"))}isFolderUnlocked(r){let e=this.owningFolder(r);return e?this.folderSessionKeys.has(e):this.isUnlocked()}getFolderKey(r){let e=this.owningFolder(r);if(!e)return this.getSessionKey();let t=this.folderSessionKeys.get(e);return t?t.slice():null}async setupFolderKey(r,e,t,i=!1){if(!e)throw new Error("Password required.");if(await this.refresh(),!this.kf)throw new Error("Set up vault encryption first.");let s=this.cleanFolder(r);if(this.kf.folderKeys?.[s])throw new Error("This folder already has its own key.");let n=this.owningFolder(s);if(n&&n!==s)throw new Error(`A parent folder (\u201C${n.split("/").pop()}\u201D) already has its own password; this folder inherits it.`);let o=crypto.getRandomValues(new Uint8Array($s)),a=await et(o,e),l=Z(8),c={keyId:l,folderPath:s,label:t,kcId:this.folderKcId(t,l),passwordSlots:[{id:Z(8),label:"Folder password",wrapped:Yt(a.data),kdf:a.kdf,createdAt:new Date().toISOString()}],createdAt:new Date().toISOString()};this.kf.folderKeys={...this.kf.folderKeys??{},[s]:c},await this.keyfiles.save(this.kf),this.folderSessionKeys.set(s,o),i&&await this.rememberFolder(this.folderKcIdFor(c),e)}async unlockFolder(r,e,t=!1){await this.refresh();let i=this.cleanFolder(r),s=this.folderKeyEntry(i);if(!s)return!1;for(let n of this.folderActiveSlots(s))try{let o=await _e(tt(n.wrapped),e);this.folderSessionKeys.set(s.folderPath,o);let a=this.folderKcIdFor(s);return(t||this.isFolderRemembered(a))&&await this.rememberFolder(a,e),!0}catch{}return!1}async tryAutoUnlockFolder(r){let e=this.folderKeyEntry(r);if(!e||this.isFolderUnlocked(r))return this.isFolderUnlocked(r);let t=this.folderKcIdFor(e),i=this.rememberedFolderPassword(t);if(i&&await this.unlockFolder(r,i))return!0;let s=this.secretStore();if(!s)return!1;let n=new Set(i?[i]:[]);for(let o of this.keychainCandidateIds()){if(o===t)continue;let a=null;try{a=s.getSecret(o)??null}catch{a=null}if(!(!a||n.has(a))&&(n.add(a),await this.unlockFolder(r,a)))return await this.rememberFolder(t,a),!0}return!1}async changeFolderPassword(r,e,t=!1){if(!e)throw new Error("Password required.");let i=this.cleanFolder(r);await this.refresh();let s=this.folderKeyEntry(i);if(!s||!this.kf)throw new Error("This folder has no key.");let n=s.folderPath,o=this.folderSessionKeys.get(n);if(!o)throw new Error("Unlock this folder first.");let a=s.passwordSlots.filter(f=>!f.label.startsWith("[deprecated]")),l=this.folderKcIdFor(s),c=this.rememberedFolderPassword(l);if(c&&a[0])try{await this.parkThenSet(`${l}-d-${a[0].id}`,c)}catch{}let d=await et(o,e),h=s.passwordSlots.map(f=>f.label.startsWith("[deprecated]")?f:{...f,label:`[deprecated] ${f.label}`}),g={...s,kcId:s.kcId??this.folderKcId(s.label,s.keyId),passwordSlots:[{id:Z(8),label:"Folder password",wrapped:Yt(d.data),kdf:d.kdf,createdAt:new Date().toISOString()},...h]};this.kf.folderKeys={...this.kf.folderKeys??{},[n]:g},await this.keyfiles.save(this.kf),(t||this.isFolderRemembered(l))&&await this.rememberFolder(this.folderKcIdFor(g),e)}async commitFolderRotation(r,e,t,i,s,n=!1){if(!e)throw new Error("Password required.");if(await this.refresh(),!this.kf)throw new Error("Encryption is not set up.");let o=this.cleanFolder(r),a=this.folderKeyEntry(o),l=a?.folderPath??o,c=a?.keyId??Z(8),d=a?this.folderKcIdFor(a):null,h=(a?.passwordSlots??[]).filter(C=>!C.label.startsWith("[deprecated]")),g=d?this.rememberedFolderPassword(d):null;if(g&&h[0])try{await this.parkThenSet(`${d}-d-${h[0].id}`,g)}catch{}let f=await et(t,e),p=(a?.passwordSlots??[]).map(C=>C.label.startsWith("[deprecated]")?C:{...C,label:`[deprecated] ${C.label}`}),u=s??a?.label??`rotated - ${o.split("/").pop()||o}`,I={keyId:c,folderPath:l,label:u,kcId:this.folderKcId(u,c),rotId:i,passwordSlots:[{id:Z(8),label:"Folder password",wrapped:Yt(f.data),kdf:f.kdf,createdAt:new Date().toISOString()},...p],createdAt:a?.createdAt??new Date().toISOString()};this.kf.folderKeys={...this.kf.folderKeys??{},[l]:I},await this.keyfiles.save(this.kf);let m=this.folderSessionKeys.get(l);m&&m!==t&&m.fill(0),this.folderSessionKeys.set(l,t.slice()),(n||d&&this.isFolderRemembered(d))&&await this.rememberFolder(this.folderKcIdFor(I),e)}folderKcId(r,e){return(`sp-${(r||"").toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")}`.slice(0,44)+`-${e.slice(0,6)}`).replace(/-+/g,"-")}folderKcIdFor(r){return r.kcId||`${ss}-f-${r.keyId}`}isFolderRemembered(r){try{return!!this.secretStore()?.getSecret(r)}catch{return!1}}rememberedFolderPassword(r){try{return this.secretStore()?.getSecret(r)||null}catch{return null}}async rememberFolder(r,e){try{await this.parkThenSet(r,e)}catch(t){console.warn("[Stashpad] couldn't save folder password to keychain",t)}}async forgetFolderKeychain(r){let e=this.folderKeyEntry(r);if(!e)return;let t=this.secretStore();if(!t)return;let i=this.folderKcIdFor(e);try{if(t.getSecret(i)){await this.parkThenSet(i,"");return}}catch{}try{t.removeSecret?await t.removeSecret(i):await t.setSecret(i,"")}catch(s){console.warn("[Stashpad] couldn't clear folder keychain",s)}}async verifyPassword(r){let e=this.load();try{if(e.identityPrivWrapped)return(await _e(tt(e.identityPrivWrapped),r)).fill(0),!0;if(e.wrappedKey)return(await _e(tt(e.wrappedKey),r)).fill(0),!0}catch{}return!1}async changePassword(r,e,t=!1){let i=this.load();if(!e)throw new Error("New password required.");if(!i.identityPrivWrapped&&!await this.unlock(r,!1))return!1;let s=this.load(),n;try{n=await _e(tt(s.identityPrivWrapped),r)}catch{return!1}let o=await et(n,e);return n.fill(0),await this.save({...s,identityPrivWrapped:Yt(o.data),identityPrivKdf:o.kdf}),(t||this.isRemembered())&&await this.remember(e),!0}async requestAccess(r,e,t=!1){if(await this.refresh(),!this.kf)throw new Error("This vault has no encryption set up yet.");if(this.amIMember())return;if(!e)throw new Error("Password required.");let i=this.hasIdentity()?{id:this.load().identityId,label:this.load().identityLabel??r,pub:this.load().identityPub}:await this.mintIdentity(e,r);r&&i.label!==r&&(await this.save({...this.load(),identityLabel:r}),i.label=r);let s={id:i.id,label:i.label,pubKey:i.pub,requestedAt:new Date().toISOString()};this.kf.joinRequests=[...this.kf.joinRequests.filter(n=>n.id!==i.id),s],await this.keyfiles.save(this.kf),(t||this.isRemembered())&&await this.remember(e)}pendingJoinRequests(){return this.kf?.joinRequests??[]}members(){return this.kf?.identities??[]}myIdentityId(){return this.load().identityId}async approveJoinRequest(r,e){if(!this.sessionKey)throw new Error("Unlock encryption first.");if(await this.refresh(),!this.kf)return!1;let t=this.kf.joinRequests.find(i=>i.id===r);return t?(this.kf.slots=[...this.kf.slots.filter(i=>i.recipientId!==t.id),await Ks(this.sessionKey,tt(t.pubKey),t.id)],this.kf.identities=[...this.kf.identities.filter(i=>i.id!==t.id),{id:t.id,label:e??t.label,pubKey:t.pubKey,addedAt:new Date().toISOString()}],this.kf.joinRequests=this.kf.joinRequests.filter(i=>i.id!==r),await this.keyfiles.save(this.kf),!0):!1}async removeMember(r){await this.refresh(),this.kf&&(this.kf.slots=this.kf.slots.filter(e=>e.recipientId!==r),this.kf.identities=this.kf.identities.filter(e=>e.id!==r),this.kf.joinRequests=this.kf.joinRequests.filter(e=>e.id!==r),await this.keyfiles.save(this.kf))}async denyJoinRequest(r){await this.refresh(),this.kf&&(this.kf.joinRequests=this.kf.joinRequests.filter(e=>e.id!==r),await this.keyfiles.save(this.kf))}lock(){this.sessionKey&&this.sessionKey.fill(0),this.sessionKey=null;for(let r of this.folderSessionKeys.values())r.fill(0);this.folderSessionKeys.clear()}lockFolder(r){let e=this.owningFolder(r)??this.cleanFolder(r),t=this.folderSessionKeys.get(e);t&&(t.fill(0),this.folderSessionKeys.delete(e))}cleanFolder(r){return(r||"").replace(/\/+$/,"")}async clear(){this.lock(),await this.forgetKeychain();try{let r=this.app.vault.adapter;for(let e of[".stashpad/keys.json"])try{await r.exists(e)&&await r.remove(e)}catch{}try{let t=((await r.list("_keys")).files||[]).filter(i=>/\/keys-\d+\.json$/.test(i));if(t.length){let s=`_keys/removed-${new Date().toISOString().slice(0,19).replace(/[:T]/g,"-")}`;try{await r.exists(s)||await r.mkdir(s)}catch{}for(let n of t){let o=n.split("/").pop();try{await r.write(`${s}/${o}`,await r.read(n)),await r.remove(n)}catch{}}}}catch{}}catch{}this.kf=null,await this.save(ns())}getSessionKey(){return this.sessionKey?this.sessionKey.slice():null}async mintIdentity(r,e){let t=await la(),i=await et(t.privKeyPkcs8,r);t.privKeyPkcs8.fill(0);let s=this.load(),n=s.identityId??Z(8),o=e??s.identityLabel??"This device",a=Yt(t.pubKeySpki);return await this.save({...s,identityId:n,identityLabel:o,identityPub:a,identityPrivWrapped:Yt(i.data),identityPrivKdf:i.kdf}),{id:n,label:o,pub:a}}identityRecord(r){return{id:r.id,label:r.label,pubKey:r.pub,addedAt:new Date().toISOString()}}dispose(){this.lock()}};var os=null,zs=new Set;function Vs(A){if(os!==A){os=A;for(let r of zs)r()}}function pt(){return os}function pa(A){if(os===A){os=null;for(let r of zs)r()}}function ga(A){return zs.add(A),()=>zs.delete(A)}function xc(){return T.Platform.isMacOS?"Finder":T.Platform.isWin?"File Explorer":"file manager"}var hi=[{id:"move",label:"Move (picker)",desc:"Open a fuzzy picker to choose the new parent.",defaultPrimary:"M"},{id:"pickMove",label:"Move (in-list)",desc:"Highlight a note in the list with arrows; Enter sets it as parent.",defaultPrimary:"O"},{id:"merge",label:"Merge",desc:"Concatenate selected notes into the oldest one.",defaultPrimary:"&"},{id:"copy",label:"Copy",desc:"Copy selected note bodies to clipboard.",defaultPrimary:"C"},{id:"copyTree",label:"Copy tree",desc:"Copy the focused note + all descendants, indented.",defaultPrimary:"Y"},{id:"openEditor",label:"Open in editor",desc:"Open the cursor row (or focused note) in a regular Obsidian markdown tab.",defaultPrimary:"E"},{id:"openTab",label:"Open in new Stashpad tab",desc:"Open the cursor row (or focused note) in a new Stashpad tab focused on it.",defaultPrimary:"T"},{id:"split",label:"Split note",desc:"Split the cursor row (or focused note) into two notes at a chosen line.",defaultPrimary:"S"},{id:"copyOutline",label:"Copy as outline",desc:"Copy selection (or cursor row) as a nested ![[embed]] outline.",defaultPrimary:"L"},{id:"toggleSplit",label:"Toggle split-on-newlines",desc:"Default: Mod+/",defaultPrimary:"Mod+/"},{id:"pickDestination",label:"Pick destination",desc:"Default: Mod+D",defaultPrimary:"Mod+D"},{id:"search",label:"Search notes",desc:"Default: Mod+F",defaultPrimary:"Mod+F"},{id:"searchInParent",label:"Search in current parent",desc:"Default: Mod+Alt+F (Mod+Shift+F is taken by Obsidian's global search).",defaultPrimary:"Mod+Alt+F"},{id:"delete",label:"Delete selection",desc:"Default: Mod+Backspace",defaultPrimary:"Mod+Backspace"},{id:"undo",label:"Undo",desc:"Default: Mod+Z (Stashpad-only \u2014 won't fire while typing in the composer).",defaultPrimary:"Mod+Z"},{id:"redo",label:"Redo",desc:"Default: Mod+Shift+Z",defaultPrimary:"Mod+Shift+Z"},{id:"toggleComplete",label:"Toggle complete (strikethrough)",desc:"Default: Mod+Enter or X \u2014 marks selected/focused notes as complete (both chords active).",defaultPrimary:"Mod+Enter",defaultSecondary:"X",defaultUseBoth:!0},{id:"moveUp",label:"Move note up",desc:"Default: Mod+ArrowUp",defaultPrimary:"Mod+ArrowUp"},{id:"moveDown",label:"Move note down",desc:"Default: Mod+ArrowDown",defaultPrimary:"Mod+ArrowDown"},{id:"moveToTop",label:"Move note to top",desc:"Default: Mod+Shift+ArrowUp",defaultPrimary:"Mod+Shift+ArrowUp"},{id:"moveToBottom",label:"Move note to bottom",desc:"Default: Mod+Shift+ArrowDown",defaultPrimary:"Mod+Shift+ArrowDown"},{id:"outdent",label:"Outdent (move to grandparent)",desc:"Default: Mod+[ \u2014 re-parents the selection one level up.",defaultPrimary:"Mod+["},{id:"setColor",label:"Set note color",desc:"Default: Shift+: or ; \u2014 open the color picker for the selection (both chords active).",defaultPrimary:"Shift+:",defaultSecondary:";",defaultUseBoth:!0},{id:"clone",label:"Clone (duplicate / copy) selection",desc:"Default: Mod+Shift+D \u2014 clone selected notes (with their subtrees) as siblings.",defaultPrimary:"Mod+Shift+D"},{id:"forkNote",label:"Fork into a separate note (under a chosen parent)",desc:'Duplicate the cursor row (with its subtree) as a separate note and pick which parent it nests under. Distinct from "Fork as a version" (a draft within a sheet group). No default chord.',defaultPrimary:""},{id:"insertTemplate",label:"Insert template (clone an existing note)",desc:"Pick any note in this Stashpad; clone it (with subtree + attachments) into the current view, retimestamped.",defaultPrimary:""},{id:"toggleExpand",label:"Show more / show less (expand toggle)",desc:"Default: Shift+? \u2014 toggle the clamp on the cursor row (or every selected row).",defaultPrimary:"Shift+?"},{id:"expandAll",label:"Expand all (show every note's full body)",desc:"Un-clamp every note in the current list at once.",defaultPrimary:""},{id:"collapseAll",label:"Collapse all (clamp every note's body)",desc:"Re-clamp every note in the current list at once.",defaultPrimary:""},{id:"exportStash",label:"Export selection to .stash",desc:"Export the selected subtree(s) as a .stash bundle (notes + attachments).",defaultPrimary:""},{id:"importStash",label:"Import .stash file",desc:"Open the .stash bundle picker and import its notes into this Stashpad.",defaultPrimary:""},{id:"pickFolder",label:"Open / switch / create Stashpad folder",desc:"Default: Mod+S \u2014 opens the unified folder picker (reveal, switch, create, convert).",defaultPrimary:"Mod+S"},{id:"cloneStashpadTab",label:"Clone (duplicate / copy) this Stashpad tab",desc:'Open a second tab on the same folder + focus, mirroring the "copy" button in the focused-header actions.',defaultPrimary:""},{id:"selectAll",label:"Select all notes in view",desc:"Default: Mod+A \u2014 adds every visible row to the selection.",defaultPrimary:"Mod+A"},{id:"copyCodeBlock",label:"Copy code from codeblock",desc:"Default: { \u2014 copy the contents of the cursor row's first codeblock (or pick one when multiple exist).",defaultPrimary:"{"},{id:"swapWithParent",label:"Swap with parent (ouroboros)",desc:"Promote the cursor row above its current parent; the parent slides under it (carrying its other children). No default \u2014 bind in this tab.",defaultPrimary:""},{id:"togglePin",label:"Pin / unpin selected note",desc:"Default: P \u2014 toggle the sidebar pin state of the cursor row (or focused note).",defaultPrimary:"P"},{id:"listPin",label:"Pin / unpin to top of list",desc:"Float the cursor row (or selection) to the TOP of its list \u2014 distinct from the sidebar pin. No default chord.",defaultPrimary:""},{id:"toggleTask",label:"Toggle task (todo)",desc:"Default: G \u2014 mark the selection (or cursor row) as a task / todo, or clear it. Tasks appear in the Tasks panel.",defaultPrimary:"G"},{id:"setDue",label:"Set due date\u2026",desc:"Default: D \u2014 open a date+time picker to set (or clear) the due date on the selection. Setting a due date also marks the note as a task.",defaultPrimary:"D"},{id:"jumpToTop",label:"Jump to top of list",desc:"Default: Home \u2014 move the cursor to the first note in the current list.",defaultPrimary:"Home"},{id:"jumpToBottom",label:"Jump to bottom of list",desc:"Default: End \u2014 move the cursor to the last note in the current list.",defaultPrimary:"End"},{id:"commandPalette",label:"Command palette (Stashpad only)",desc:"Default: Mod+K \u2014 open a command palette listing only Stashpad's commands, with Sift search.",defaultPrimary:"Mod+K"},{id:"lockSelection",label:"Encrypt (lock) selection",desc:"Encrypt the selected note(s) + their children into a locked .stashenc bundle in place (prompts to unlock first if needed). No default chord.",defaultPrimary:""},{id:"unlockAll",label:"Decrypt (unlock) locked notes in view",desc:"Decrypt every locked stash shown in the current view back into place, skipping any that can't be read. No default chord.",defaultPrimary:""},{id:"moveToArchive",label:"Move selection to archive",desc:"Move the selected note(s) into this folder's own archive subfolder (drops out of search; plaintext unless this folder encrypts its archive). Undoable. No default chord.",defaultPrimary:""},{id:"encryptDelete",label:"Encrypt & delete selection",desc:"Send the selected note(s) to the encrypted trash (recoverable with your password, Ctrl/Cmd+Z undoable). No default chord.",defaultPrimary:""},{id:"copyNotes",label:"Copy notes (note clipboard)",desc:"Copy the selected note(s) as NOTES: paste in the list to duplicate them (new ids), or anywhere else to paste their text. Skipped when text is highlighted (normal copy wins).",defaultPrimary:"Mod+C"},{id:"cutNotes",label:"Cut notes",desc:"Cut the selected note(s): paste in the list to MOVE them, or in the composer to extract their text and delete the originals (undoable).",defaultPrimary:"Mod+X"},{id:"pasteNotes",label:"Paste notes",desc:"Paste previously copied/cut notes at the cursor row (after it, same parent). Does nothing if the note clipboard is empty.",defaultPrimary:"Mod+V"}];function To(){let A={};for(let r of hi)A[r.id]={primary:r.defaultPrimary,secondary:r.defaultSecondary??"",preferRight:!1,useBoth:!!r.defaultUseBoth};return A}var Nc=!1,Mc=!1,Tc=!1,Qt={folder:"Stashpad",importDropFolder:"",exportFolder:"_exports",autoImport:!1,inheritObsidianExclusions:!0,folderPanelPinnedFraction:.5,folderPanelPinned:[],folderPanelDownranked:[],folderPanelHidden:[],folderPanelPinnedGrouping:"pin-order",enablePerfProfiling:!1,debugTrace:!1,writeRecoveryLinks:!0,useTemplatesFormat:!1,prefixTimestampsOnCopy:!0,splitOnLines:!1,splitMode:"lines",confirmCrossParentDrag:!0,confirmBulkDelete:!0,confirmAttachmentDelete:!0,autofocusComposerAfterSend:!0,focusComposerOnOpen:!1,searchOpensInContext:!0,popoutDuplicates:!0,encryption:ns(),encryptTrash:!1,encryptTrashFilenames:!1,migratedToggleTaskG:!1,migratedArchiveToSubfolders:!1,migratedTrashToSubfolders:!1,reEncryptWatch:[],reEncryptNudge:!1,reEncryptAfterMin:0,dueQuickAdjusts:["5m","15m","30m","1h","1d","1w"],archiveFolders:[],folderEncPrefs:{},folderIcons:{},folderSwitcherIncludePinned:!1,importExcludePrefixes:"_",lockedSubtrees:[],searchOpensInNewTab:!0,pinnedNotes:[],hideMobileToolbarInStashpad:!0,slugStopWords:[],searchIncludedFolders:[],searchExcludedFolders:[],shortcuts:{move:"M",pickMove:"O",merge:"&",copy:"C",copyTree:"Y",openEditor:"E",openTab:"T",split:"S",copyOutline:"L"},mod:{toggleSplit:"Mod+/",pickDestination:"Mod+D",search:"Mod+F",delete:"Mod+Backspace",undo:"Mod+Z",redo:"Mod+Shift+Z",toggleComplete:"Mod+Enter",moveUp:"Mod+ArrowUp",moveDown:"Mod+ArrowDown",moveToTop:"Mod+Shift+ArrowUp",moveToBottom:"Mod+Shift+ArrowDown",outdent:"Mod+[",setColor:"Shift+:"},customPalette:[],colorAliases:{},noteTemplates:{},authorName:"",authorId:"",authorRole:"",authorDepartment:"",showAuthor:!0,showContributors:!0,showLastEdit:!0,viewModes:{},includeAttachmentsInEverything:{},hideChildlessNotes:{},hideCompletedNotes:{},attachmentsOnlyNotes:{},mutedNotificationCategories:[],notificationHistoryLimit:5e3,notifiedDueKeys:[],autoNavOnMoveIn:!1,autoNavOnMoveOut:!1,autoExpandCursorRow:!1,expandBodiesByDefault:!1,autoOpenDetailPanel:!1,doubleClickToFocus:!0,enableSheetVersions:!1,dateDisplayFormat:"locale",dateDisplayTimezone:"",jdIndexScope:"vault",jdIndexScopeFolder:"",jdIndexStashpadFolder:"",jdIndexFile:"Index",jdIndexIncludeStashpadFolders:!1,jdIndexSort:"natural",jdIndexHasBuilt:!1,okfEnabled:!1,okfTemplatePath:"",drafts:{},lastSubmitted:{},bindings:To()},ua=JSON.parse(JSON.stringify(Qt)),Mo=new Set;function X(){return ua}function Zs(A){ua=A;for(let r of Mo)r()}function fa(A){return Mo.add(A),()=>Mo.delete(A)}function js(A){try{let r=A.internalPlugins?.plugins?.templates;if(!r?.enabled)return null;let e=r.instance?.options??{};return{dateFormat:e.dateFormat||"YYYY-MM-DD",timeFormat:e.timeFormat||"HH:mm"}}catch{return null}}var Ws=[{id:"foldersStorage",label:"\u{1F4C1} Folders & Storage"},{id:"importExport",label:"\u{1F504} Import & Export"},{id:"datesTime",label:"\u{1F552} Dates & Time"},{id:"behaviors",label:"\u{1F39B}\uFE0F Behavior"},{id:"notifications",label:"\u{1F514} Notifications"},{id:"encryption",label:"\u{1F512} Encryption"},{id:"authorship",label:"\u2712\uFE0F Authorship"},{id:"templates",label:"\u{1F4C4} Templates"},{id:"organizationSystems",label:"\u{1F5C2}\uFE0F Organization Systems"},{id:"maintenance",label:"\u{1F6E0}\uFE0F Maintenance"},{id:"diagnostics",label:"\u{1FA7A} Diagnostics"},{id:"hotkeys",label:"\u2328\uFE0F Hotkeys"}].sort((A,r)=>{let e=t=>t.replace(/^[^\p{L}\p{N}]+/u,"");return e(A.label).localeCompare(e(r.label))}),No=null;function Pc(A,r){return No||(No=class extends T.SettingPage{constructor(e,t){super(),this.title=e,this._renderFn=t}display(){this.containerEl.empty(),this._renderFn(this.containerEl)}}),new No(A,r)}var Xs=class extends T.PluginSettingTab{constructor(e,t){super(e,t);this.plugin=t;this.pfeSelected=null;this.pfeRerender=null;this.iconPickFolder=null}display(){let{containerEl:e}=this;e.empty();for(let t of Ws){new T.Setting(e).setName(t.label).setHeading();let i=this.itemsForTab(t.id);if(i)for(let s of i){let n=new T.Setting(e);typeof s.render=="function"?s.render(n):(s.name&&n.setName(s.name),s.desc&&n.setDesc(s.desc))}else this.renderTabContent(e,t.id)}}getSettingDefinitions(){return Ws.map(e=>{let t=this.itemsForTab(e.id);return t?{type:"page",name:e.label,items:t}:{type:"page",name:e.label,page:()=>Pc(e.label,i=>this.renderTabContent(i,e.id))}})}itemsForTab(e){switch(e){case"hotkeys":return this.hotkeyItems();case"diagnostics":return this.diagnosticsItems();case"notifications":return this.notificationsItems();case"foldersStorage":return this.buildGeneralCategories().foldersStorage;case"importExport":return this.buildGeneralCategories().importExport;case"datesTime":return this.buildGeneralCategories().datesTime;case"maintenance":return this.buildGeneralCategories().maintenance;case"behaviors":{let t=this.buildGeneralCategories();return[this.headingDef("\u{1F4CB} List & Display"),...t.listDisplay,this.headingDef("\u2195\uFE0F Moving Notes"),...t.movingNotes,this.headingDef("\u270D\uFE0F Composer & Copying"),...t.composerCopy,this.headingDef("\u{1F5D1}\uFE0F Deleting"),...t.deleting,this.headingDef("\u{1FA9F} Windows & Tabs"),...t.windowsTabs,this.headingDef("\u2699\uFE0F Misc"),...t.misc]}case"organizationSystems":return[this.headingDef("\u{1F522} JD Index (Johnny Decimal)"),...this.jdIndexItems(),this.headingDef("\u{1F4DA} Open Knowledge Format (OKF)"),...this.okfItems()];case"encryption":return this.encryptionItems();case"authorship":return this.authorshipItems();case"templates":return this.templatesItems();default:return null}}renderTabContent(e,t){switch(t){case"authorship":this.renderAuthorshipSection(e);break;case"templates":this.renderTemplatesTab(e);break}}diagnosticsItems(){return[this.renderDef("Performance profiling","Record timing for list rendering, body reads, and file writes. Turn on, use Stashpad normally (especially the slow operations), then run \u201CDump performance profile\u201D from the command palette and share the result. Off = zero overhead.",e=>e.addToggle(t=>t.setValue(this.plugin.settings.enablePerfProfiling).onChange(async i=>{this.plugin.settings.enablePerfProfiling=i,await this.plugin.saveSettings()})),["perf","profiling","timing","slow"]),this.renderDef("Debug trace","Record low-level diagnostic lines (e.g. tap coordinates vs the row they resolve to) to an in-memory buffer while you reproduce a bug, then copy them below to share. Local only \u2014 no network, no file writes; zero overhead when off.",e=>e.addToggle(t=>t.setValue(this.plugin.settings.debugTrace).onChange(async i=>{this.plugin.settings.debugTrace=i,await this.plugin.saveSettings()})),["debug","trace","diagnostics","tap","log"]),this.renderDef("Copy / clear debug trace","Copy the captured debug lines to the clipboard (paste them back to share), or clear the buffer to start a fresh capture.",e=>{e.addButton(t=>t.setButtonText("Copy").onClick(async()=>{let i=this.plugin.getDebugTrace();if(!i){new T.Notice("Debug trace is empty \u2014 enable it and reproduce the issue first.");return}try{await navigator.clipboard.writeText(i),new T.Notice("Debug trace copied.")}catch{new T.Notice("Couldn't access clipboard.")}})),e.addButton(t=>t.setButtonText("Clear").onClick(()=>{this.plugin.clearDebugTrace(),new T.Notice("Debug trace cleared.")}))},["debug","trace","copy","clear"]),this.renderDef("Open log file","Append-only history of creates, deletes, parent changes, renames. Stored alongside the plugin's other private files.",e=>e.addButton(t=>t.setButtonText("Open log").onClick(async()=>{let i=this.app.vault.adapter,s=this.plugin.pluginPrivatePath("log.jsonl");if(!await i.exists(s)){new T.Notice("No log yet \u2014 make some changes first.");return}let n=await i.read(s);new ii(this.app,n,s).open()})),["log","history","diagnostics"])]}notificationsItems(){let e=new Set(this.plugin.settings.mutedNotificationCategories??[]),t=Object.keys(po);return[this.renderDef("Notification history limit","Maximum number of notifications kept in the persistent history. Set to 0 for unlimited (the file size grows with usage; expect a few hundred KB per ~5000 entries). Default: 5000.",i=>i.addText(s=>s.setValue(String(this.plugin.settings.notificationHistoryLimit??5e3)).setPlaceholder("5000").onChange(async n=>{let o=parseInt(n,10);Number.isFinite(o)&&(this.plugin.settings.notificationHistoryLimit=o,this.plugin.notifications.setHistoryLimit(o),await this.plugin.saveSettings())})),["notification","history","limit"]),{type:"group",heading:"Mute notification categories",items:t.map(i=>{let s=po[i];return this.renderDef(s.label,s.desc,n=>n.addToggle(o=>o.setValue(!e.has(i)).onChange(async a=>{let l=!a;l?e.add(i):e.delete(i),this.plugin.settings.mutedNotificationCategories=Array.from(e),this.plugin.notifications.setMuted(i,l),await this.plugin.saveSettings()})),["notification","mute","toast","category"])})},this.renderDef("Notification history","Browse the last 200 toasts. Filter by category. Live-updates as new notifications arrive. Muted categories still appear here so you can review what was suppressed.",i=>i.addButton(s=>s.setButtonText("View notification history").onClick(()=>{new qi(this.app,this.plugin.notifications,async n=>{let o=this.app.vault.adapter,a=this.plugin.pluginPrivatePath("log.jsonl");if(!await o.exists(a)){new T.Notice("No log yet \u2014 make some changes first.");return}let l=await o.read(a);new ii(this.app,l,a).open()},this.plugin.settings.authorId||null,n=>this.plugin.lookupNoteAuthorIds(n)).open()})),["notification","history","panel"])]}renderTemplatesTab(e){this.renderColorAliasesSection(e),this.renderNoteTemplatesSection(e)}renderDef(e,t,i,s){return{name:e,desc:t,aliases:s,render:n=>{n.setName(e).setDesc(t);try{i(n)}catch(o){console.error(`[Stashpad] settings control "${e}" failed to render:`,o)}}}}sectionDef(e,t,i,s){return{name:e,desc:t,aliases:s,render:n=>{let o=n.settingEl;o.empty(),o.removeClass("setting-item"),o.addClass("stashpad-settings-section");try{i(o)}catch(a){console.error(`[Stashpad] settings section "${e}" failed to render:`,a)}}}}headingDef(e,t){return this.sectionDef(e,t??"",i=>{let s=new T.Setting(i).setName(e).setHeading();t&&s.setDesc(t)},[e.toLowerCase()])}folderKeyLabel(e){let t=new Date,i=a=>String(a).padStart(2,"0"),s=`${t.getFullYear()}${i(t.getMonth()+1)}${i(t.getDate())}-${i(t.getHours())}${i(t.getMinutes())}`,n=(this.plugin.settings.authorName||"").trim(),o=n?n.split(/\s+/).map(a=>a[0]).join(""):(this.plugin.settings.authorId||"anon").slice(0,4);return`${s} - ${e.split("/").pop()||e} - ${o}`}promptSetFolderPassword(e){new de(this.app,{mode:"setup",offerKeychain:!0,title:`Set password for \u201C${e.split("/").pop()}\u201D`,intro:"A separate password just for this folder. Share it with collaborators out-of-band (a password manager, Signal, in person). There is NO recovery if it's lost.",onSubmit:async({next:t,remember:i})=>{if(!t)return"Enter a password.";try{await this.plugin.encryption.setupFolderKey(e,t,this.folderKeyLabel(e),i)}catch(s){return s.message}return new T.Notice("Folder password set \u2014 share it securely."),this.update?.(),this.pfeRerender?.(),null}}).open()}promptChangeFolderPassword(e){new de(this.app,{mode:"setup",offerKeychain:!0,title:`Change password for \u201C${e.split("/").pop()}\u201D`,intro:"Re-wraps this folder's key under a new password. The OLD password stops working on THIS device right away (un-synced copies elsewhere keep working until they sync). To truly cut off someone who left, you'll want \u201CRotate key\u201D (re-encrypts everything) \u2014 coming soon.",onSubmit:async({next:t,remember:i})=>{if(!t)return"Enter a password.";try{await this.plugin.encryption.changeFolderPassword(e,t,i)}catch(s){return s.message}return new T.Notice("Folder password changed."),this.update?.(),this.pfeRerender?.(),null}}).open()}promptRotateFolderKey(e){let t=e.split("/").pop()||e;new ie(this.app,`Rotate key for \u201C${t}\u201D?`,`This RE-ENCRYPTS every locked note in \u201C${t}\u201D under a brand-new key, then retires the old password so it can no longer decrypt them \u2014 even copies a collaborator synced earlier. Use this when someone should LOSE access (e.g. they left the org). Large folders can take a while. Everyone who should keep access will need the new password.`,"Rotate key",i=>{i&&new de(this.app,{mode:"setup",offerKeychain:!0,title:`New password for \u201C${t}\u201D`,intro:"The new password to use after rotating. Share it only with people who should keep access.",onSubmit:async({next:s,remember:n})=>s?await this.plugin.rotateFolderKey(e,s,n)<0?"Rotation failed \u2014 see the notice.":(this.update?.(),this.pfeRerender?.(),null):"Enter a password."}).open()}).open()}promptUnlockFolder(e){new de(this.app,{mode:"unlock",offerKeychain:!0,title:`Unlock \u201C${e.split("/").pop()}\u201D`,intro:"Enter this folder's password.",onSubmit:async({current:t,remember:i})=>await this.plugin.encryption.unlockFolder(e,t,i)?(new T.Notice("Folder unlocked."),this.update?.(),this.pfeRerender?.(),null):"Wrong password. Try again."}).open()}encryptionOrOnboard(){return this.plugin.encryption?.isConfigured?.()?!0:(new T.Notice("That setting needs vault encryption, which isn't set up yet \u2014 let's set it up first.",6e3),new de(this.app,{mode:"setup",offerKeychain:!0,title:"Set up encryption",intro:"One password protects what you encrypt in this vault. It's stored only on this device \u2014 there is NO recovery if you lose it.",onSubmit:async({next:e,remember:t})=>{if(!e)return"Enter a password.";try{await this.plugin.encryption.setup(e,t,this.plugin.settings.authorName||"This device")}catch(i){return i.message}return new T.Notice("Encryption set up \u2014 now switch on the setting you wanted."),this.update?.(),this.pfeRerender?.(),null}}).open(),!1)}renderPerFolderEncryption(e){if(!this.plugin.encryption.isConfigured()){e.createEl("p",{cls:"setting-item-description"}).setText("Set up vault encryption above first. Per-folder passwords layer on top: a folder with its own password uses a separate key; folders without one use the vault password.");return}let i=this.plugin.discoverStashpadFolders();if(i.length===0){e.createEl("p",{cls:"setting-item-description"}).setText("No Stashpad folders found yet.");return}(!this.pfeSelected||!i.includes(this.pfeSelected))&&(this.pfeSelected=i[0]);let s=e.createDiv({cls:"stashpad-folderenc-panel"});this.pfeRerender=()=>{s.empty(),this.renderFolderEncPanel(s,this.pfeSelected)},new T.Setting(e).setName("Folder").setDesc("Pick a folder to configure its password, archive, and trash options. Everything below applies to the selected folder only.").addDropdown(n=>{for(let o of i)n.addOption(o,o);n.setValue(this.pfeSelected),n.onChange(o=>{this.pfeSelected=o,this.pfeRerender?.()})}),e.appendChild(s),this.renderFolderEncPanel(s,this.pfeSelected)}renderFolderEncPanel(e,t){let i=this.plugin.encryption,s=i.hasOwnFolderKey(t),n=i.folderKeyEntry(t)?.folderPath??null,o=!!n&&!s,a=i.isFolderUnlocked(t),l=(this.plugin.settings.folderEncPrefs??{})[t]??{},c=s?a?"Has its own password \xB7 unlocked":"Has its own password \xB7 locked":o?`Inherits \u201C${n.split("/").pop()||n}\u201D's password \xB7 ${a?"unlocked":"locked"}`:"Uses the vault password",d=new T.Setting(e).setName("Password").setDesc(c);o?d.addButton(p=>p.setButtonText("Manage on parent folder").onClick(()=>{this.pfeSelected=n,this.update?.(),this.pfeRerender?.()})):s?a?(d.addButton(p=>p.setButtonText("Change password\u2026").onClick(()=>this.promptChangeFolderPassword(t))),d.addButton(p=>{p.setButtonText("Rotate key\u2026").onClick(()=>this.promptRotateFolderKey(t)),p.buttonEl.addClass("mod-warning")})):d.addButton(p=>p.setButtonText("Unlock\u2026").setCta().onClick(()=>this.promptUnlockFolder(t))):d.addButton(p=>p.setButtonText("Set folder password\u2026").setCta().onClick(()=>this.promptSetFolderPassword(t)));let h=async p=>{this.plugin.settings.folderEncPrefs={...this.plugin.settings.folderEncPrefs??{},[t]:{...(this.plugin.settings.folderEncPrefs??{})[t]??{},...p}},await this.plugin.saveSettings()};new T.Setting(e).setName("Trash handling").setDesc("Where notes deleted from this folder go.").addDropdown(p=>p.addOption("","Stashpad trash (default)").addOption("obsidian","Obsidian native trash").setValue(l.trashHandling==="obsidian"?"obsidian":"").onChange(u=>h({trashHandling:u||void 0})));let g=(p,u,I,m,C=!1)=>{new T.Setting(e).setName(p).addToggle(B=>B.setValue(!!l[u]).onChange(async w=>{if(w&&C){if(!this.encryptionOrOnboard()){this.pfeRerender?.();return}if(!await this.plugin.ensureFolderUnlocked(t)){this.pfeRerender?.();return}}let y={};y[u]=w,w||(y[I]=!1),await h(y),m&&await m(w),this.pfeRerender?.()})),new T.Setting(e).setName(`\u21B3 ${p} \u2014 hide filenames`).setClass("stashpad-subsetting").addToggle(B=>{B.setValue(!!l[I]),B.setDisabled(!l[u]),B.onChange(async w=>{let y={};y[I]=w,await h(y),this.pfeRerender?.()})})};g("Encrypt this folder's notes","encryptContent","encryptFilenames",async p=>{p?await this.plugin.lockFolder(t):await this.plugin.unlockFolder(t);let u=(this.plugin.settings.lockedSubtrees??[]).some(I=>(I.folder||"").replace(/\/+$/,"")===t);await h({encryptContent:u,...u?{}:{encryptFilenames:!1}})}),g("Encrypt archived notes","archiveEncryptContent","archiveEncryptFilenames",async p=>{if(!p)return;let u=this.plugin.archivedPlainNotesIn(`${t}/archive`).length;u!==0&&new ie(this.app,`Encrypt the ${u} note${u===1?"":"s"} already archived?`,`\u201C${t.split("/").pop()}\u201D's archive already holds ${u} plaintext note${u===1?"":"s"}. Encrypt ${u===1?"it":"them"} now too? (Skipping leaves ${u===1?"it":"them"} readable; only notes archived from now on get encrypted.)`,"Encrypt them",async I=>{if(!I)return;let m=await this.plugin.encryptExistingArchiveNotes(t);new T.Notice(m>0?`Encrypted ${m} archived note${m===1?"":"s"}.`:"Nothing was encrypted (cancelled or already locked)."),this.pfeRerender?.()}).open()},!0),g("Encrypt trashed notes","trashEncryptContent","trashEncryptFilenames",void 0,!0),new T.Setting(e).setName("Auto re-encrypt after idle minutes").setDesc("Override the global auto-re-encrypt timer for this folder. Blank = use global; 0 = off for this folder. A cancellable countdown always shows first, and open notes are never touched.").addText(p=>{p.setPlaceholder("global"),p.setValue(l.reEncryptAfterMin==null?"":String(l.reEncryptAfterMin)),p.onChange(u=>{let I=u.trim();{let m=Number(I);I===""?h({reEncryptAfterMin:void 0}):Number.isFinite(m)&&m>=0?h({reEncryptAfterMin:Math.floor(m)}):p.setValue(l.reEncryptAfterMin==null?"":String(l.reEncryptAfterMin))}})});let f=this.plugin.discoverStashpadFolders().filter(p=>p!==t&&p.startsWith(t+"/")).sort();if(f.length){new T.Setting(e).setName("Subfolders").setDesc(`${f.length} subfolder${f.length===1?"":"s"} \u2014 they use \u201C${t.split("/").pop()}\u201D's password. Encrypt each individually.`).setHeading();for(let p of f){let u=(this.plugin.settings.folderEncPrefs??{})[p]??{},I=async B=>{this.plugin.settings.folderEncPrefs={...this.plugin.settings.folderEncPrefs??{},[p]:{...(this.plugin.settings.folderEncPrefs??{})[p]??{},...B}},await this.plugin.saveSettings()},m=(this.plugin.settings.lockedSubtrees??[]).some(B=>(B.folder||"").replace(/\/+$/,"")===p),C=p.slice(t.length+1);new T.Setting(e).setName(C).addToggle(B=>B.setValue(m).onChange(async w=>{w?await this.plugin.lockFolder(p):await this.plugin.unlockFolder(p);let y=(this.plugin.settings.lockedSubtrees??[]).some(v=>(v.folder||"").replace(/\/+$/,"")===p);await I({encryptContent:y,...y?{}:{encryptFilenames:!1}}),this.update?.(),this.pfeRerender?.()})),new T.Setting(e).setName(`\u21B3 ${C} \u2014 hide filenames`).setClass("stashpad-subsetting").addToggle(B=>{B.setValue(!!u.encryptFilenames),B.setDisabled(!m),B.onChange(w=>void I({encryptFilenames:w}))})}}}buildGeneralCategories(){let e=async()=>this.plugin.saveSettings(),t=(s,n,o,a,l)=>this.renderDef(s,n,c=>c.addToggle(d=>d.setValue(o()).onChange(async h=>{a(h),await e()})),l),i={foldersStorage:[],importExport:[],datesTime:[],listDisplay:[],movingNotes:[],deleting:[],composerCopy:[],windowsTabs:[],maintenance:[],misc:[]};i.foldersStorage.push(this.renderDef("Stashpad notes folder","Vault-relative folder where Stashpad stores its notes and attachments. Created on demand.",s=>{s.addText(n=>{new li(this.app,n.inputEl),n.setValue(this.plugin.settings.folder).setPlaceholder("Stashpad").onChange(async o=>{let a=(o||"").trim().replace(/^\/+|\/+$/g,"")||Qt.folder,l=a.split("/").filter(Boolean).pop()??"";if(new Set([this.plugin.settings.importDropFolder,this.plugin.settings.exportFolder,"_attachments","_processed"].map(d=>(d??"").trim().replace(/^\/+|\/+$/g,"")).filter(Boolean)).has(l)){new T.Notice(`"${a}" uses a reserved Stashpad subfolder name. Pick something else.`);return}this.plugin.settings.folder=a,await e()})})},["folder","path","location","notes"])),i.importExport.push(t("Auto-import dropped files","When on, any file you drop directly into a Stashpad folder is imported automatically: markdown becomes a note (the original is archived to .archive); other files move to _attachments with a note that links to them. Large drops ask for confirmation first.",()=>this.plugin.settings.autoImport,s=>{this.plugin.settings.autoImport=s},["import","drop","auto"])),i.foldersStorage.push(t("Inherit Obsidian's excluded files","Also hide files matching Obsidian's \u201CExcluded files\u201D list (Settings \u2192 Files & Links) from Stashpad's link autocomplete and file surfaces \u2014 so you manage exclusions in one place. Plugin-internal formats like .edtz are always excluded regardless.",()=>this.plugin.settings.inheritObsidianExclusions,s=>{this.plugin.settings.inheritObsidianExclusions=s},["excluded","ignore","files"])),i.foldersStorage.push(t("Include pinned notes in the folder switcher","When on, the folder switcher / creator (the folder button and the \u201COpen or switch Stashpad folder\u201D command) also lists your pinned notes, so you can jump straight to one. Off keeps the picker focused on folders.",()=>this.plugin.settings.folderSwitcherIncludePinned,s=>{this.plugin.settings.folderSwitcherIncludePinned=s},["pinned","switcher","folder","picker","jump"])),i.foldersStorage.push(this.renderDef("Folder tab icon","Give a folder its own Lucide icon (e.g. rocket, star, book-open) shown on its tab, the folder switcher, and its folder-panel row. Pick a folder, then start typing in the icon box \u2014 matching icons appear with previews; pick one. Blank = the default icon. Set per folder.",s=>{let n=this.plugin.discoverStashpadFolders();if(n.length===0){s.setDesc("No Stashpad folders found yet.");return}(!this.iconPickFolder||!n.includes(this.iconPickFolder))&&(this.iconPickFolder=n[0]);let o=null,a=s.controlEl.createSpan({cls:"stashpad-folder-icon-preview"}),l=c=>{a.empty();let d=c.trim();d&&(0,T.setIcon)(a,d)};s.addDropdown(c=>{for(let d of n)c.addOption(d,d.split("/").pop()||d);c.setValue(this.iconPickFolder),c.onChange(d=>{this.iconPickFolder=d;let h=this.plugin.getFolderIcon(d)??"";o?.setValue(h),l(h)})}),s.addText(c=>{o=c,c.setPlaceholder("type to search\u2026 (e.g. rocket)"),new Rs(this.app,c.inputEl),c.setValue(this.plugin.getFolderIcon(this.iconPickFolder)??""),c.onChange(async d=>{l(d),await this.plugin.setFolderIcon(this.iconPickFolder,d.trim())})}),l(this.plugin.getFolderIcon(this.iconPickFolder)??"")},["icon","folder","tab","lucide","emoji","switcher"])),i.importExport.push(this.renderDef("Dedicated import subfolder (optional)","Optional. A subfolder (relative to each Stashpad folder) where dropped .stash files auto-import. Leave blank to just drop files into the Stashpad folder itself (recommended). Suggested name: _imports.",s=>s.addText(n=>n.setValue(this.plugin.settings.importDropFolder).setPlaceholder("_imports (leave blank to use the folder root)").onChange(async o=>{this.plugin.settings.importDropFolder=(o||"").trim().replace(/^\/+|\/+$/g,""),await e()})),["import","subfolder"])),i.importExport.push(this.renderDef("Stash export subfolder","Subfolder name (relative to each Stashpad folder) where exports land. Must differ from the import subfolder above.",s=>s.addText(n=>n.setValue(this.plugin.settings.exportFolder).setPlaceholder("_exports").onChange(async o=>{this.plugin.settings.exportFolder=(o||"").trim().replace(/^\/+|\/+$/g,"")||Qt.exportFolder,await e()})),["export","stash","subfolder"])),i.importExport.push(this.renderDef("Exclude subfolders by prefix","Comma-separated name prefixes (default \u201C_\u201D). A subfolder whose name starts with any of these \u2014 at any depth \u2014 is NOT surfaced as a Stashpad folder or imported (it stays local). To encrypt such a folder, right-click it in Obsidian's file explorer \u2192 \u201C\u{1F512} Encrypt with Stashpad\u201D.",s=>s.addText(n=>n.setValue(this.plugin.settings.importExcludePrefixes??"_").setPlaceholder("_").onChange(async o=>{this.plugin.settings.importExcludePrefixes=o,await e()})),["import","exclude","prefix","subfolder","underscore","ignore"])),i.maintenance.push(this.renderDef("Rebootstrap existing Stashpad folders","Walk every folder that has a home note: ensure infrastructure (_imports, _exports, drafts file), backfill the redundant parentLink + children frontmatter fields, rename any note whose filename slug no longer matches its body's first line, AND migrate legacy attachment filenames to the new name-first format (`photo-<id>.png`). Safe to run anytime; skip-if-equal means already-synced notes are no-op writes.",s=>s.addButton(n=>n.setButtonText("Rebootstrap now").onClick(async()=>{n.setDisabled(!0).setButtonText("Working\u2026");try{await this.plugin.runRebootstrapWithUI()}catch{}finally{n.setDisabled(!1).setButtonText("Rebootstrap now")}})),["rebootstrap","rebuild","repair","backfill","slug"])),i.maintenance.push(this.renderDef("Write recovery navigation links","Maintain the redundant parentLink/children frontmatter so you can walk the hierarchy from raw Markdown if the index ever breaks. On a slow / network drive this is a big per-move cost (several round-trips each); turn it off there for snappier moves \u2014 Rebootstrap rebuilds the fields on demand, and your notes' canonical structure (id/parent) is unaffected either way.",s=>s.addToggle(n=>n.setValue(this.plugin.settings.writeRecoveryLinks).onChange(async o=>{this.plugin.settings.writeRecoveryLinks=o,await e()})),["recovery","parentlink","children","frontmatter"]));{let s=null,n=()=>{s&&s.setText(`Sample: ${Xe(Date.now(),this.plugin.settings)}`)};i.datesTime.push(this.renderDef("Date display format","How created / edited timestamps and due dates are shown \u2014 in the notes list, the Tasks panel, and the detail panel. Overridden by the Templates-plugin formats when the option below is on.",o=>{o.addDropdown(a=>{a.addOption("locale","Locale, short (Mar 5, 9:00 AM)"),a.addOption("long","Locale, long (Thursday, March 5\u2026)"),a.addOption("iso","ISO (2026-03-05 09:00)"),a.addOption("us","US (3/5/2026, 9:00 AM)"),a.addOption("eu","EU (5/3/2026, 09:00)"),a.setValue(this.plugin.settings.dateDisplayFormat??"locale"),a.onChange(async l=>{this.plugin.settings.dateDisplayFormat=l,await e(),n()})})},["date","format","display"])),i.datesTime.push(this.renderDef("Use Templates plugin date/time formats","Use the date/time formats configured in the core Templates plugin instead of the Date display format above. Off = the Date display format above is used everywhere.",o=>{o.addToggle(l=>l.setValue(this.plugin.settings.useTemplatesFormat).onChange(async c=>{this.plugin.settings.useTemplatesFormat=c,await e(),n()}));let a=js(this.app);o.descEl.createDiv({cls:"stashpad-settings-note"}).setText(a?`Templates plugin: date = "${a.dateFormat}", time = "${a.timeFormat}"`:"Templates plugin not enabled.")},["templates","date","time","format"])),i.datesTime.push(this.renderDef("Display timezone","IANA timezone name (e.g. America/New_York, Europe/London, Asia/Kolkata). Leave blank to use your system timezone.",o=>{o.addText(a=>{a.setPlaceholder("(system timezone)"),a.setValue(this.plugin.settings.dateDisplayTimezone??""),a.onChange(async l=>{this.plugin.settings.dateDisplayTimezone=(l||"").trim(),await e(),n()})})},["timezone","tz","date","iana"])),i.datesTime.push(this.renderDef("Quick due-date adjustments","Comma-separated relative amounts shown as quick +/- buttons in the due-date and snooze pickers (e.g. 5m, 15m, 1h, 1d, 1w). Units: m=minutes, h=hours, d=days, w=weeks. A +/- flip in the picker toggles add vs subtract. Leave blank to hide the row.",o=>{o.addText(a=>{a.setPlaceholder("5m, 15m, 1h, 1d, 1w"),a.setValue((this.plugin.settings.dueQuickAdjusts??[]).join(", ")),a.onChange(async l=>{let c=l.split(",").map(d=>d.trim()).filter(d=>/^\d+\s*[mhdw]$/i.test(d)).map(d=>d.replace(/\s+/g,"").toLowerCase());this.plugin.settings.dueQuickAdjusts=c,await e()})})},["quick","adjust","snooze","due","preset","increment","decrement"])),i.datesTime.push({name:"Date sample",searchable:!1,render:o=>{let a=o.settingEl;a.empty(),a.removeClass("setting-item"),a.addClass("stashpad-settings-section"),s=a.createDiv({cls:"setting-item-description stashpad-settings-note stashpad-date-sample"}),n()}})}return i.movingNotes.push(t("Navigate into parent after moving a note IN","When you move a note onto another note via the in-list move picker (drag-onto-sibling), automatically drill into the new parent so you can see the moved note in its new home. Off = stay focused where you were.",()=>this.plugin.settings.autoNavOnMoveIn,s=>{this.plugin.settings.autoNavOnMoveIn=s},["navigate","move","in"])),i.movingNotes.push(t("Navigate to destination after moving a note OUT","When you outdent a note, move it via the cross-parent picker, or send it to Home, automatically drill into the destination parent. Off = stay focused where you were.",()=>this.plugin.settings.autoNavOnMoveOut,s=>{this.plugin.settings.autoNavOnMoveOut=s},["navigate","move","out"])),i.listDisplay.push(t("Double-click a note to open it","Double-click (or double-tap on mobile) a note in the list to focus/open it \u2014 the same as pressing \u2192 or clicking the enter arrow. Single click still just selects. On by default.",()=>this.plugin.settings.doubleClickToFocus,s=>{this.plugin.settings.doubleClickToFocus=s},["double","click","open","focus"])),i.misc.push(t("Sheet versions (alternate drafts)",`Treat notes that share a 'sheet-group' frontmatter id as alternate versions of one item: only the active version shows as a row, and its siblings collapse into a tab bar at the bottom of that row. Use "Fork as version" on a note to start. Off by default \u2014 when off, no note is ever hidden by this feature and the commands do nothing.`,()=>this.plugin.settings.enableSheetVersions,s=>{this.plugin.settings.enableSheetVersions=s},["sheet","version","draft","alternate","fork"])),i.listDisplay.push(t("Auto-open the detail panel","Open the right-sidebar Stashpad detail panel automatically whenever a Stashpad view becomes active. The panel shows the cursored note's body, metadata, and children. Off = open manually via ribbon or command palette.",()=>this.plugin.settings.autoOpenDetailPanel,s=>{this.plugin.settings.autoOpenDetailPanel=s},["detail","panel","sidebar"])),i.listDisplay.push(t("Expand the cursor row's body automatically","As you arrow-key through the list, the row under the cursor temporarily un-clamps to show its full body. Moving away re-collapses it. Doesn't affect the persistent 'Show more' state \u2014 this is a transient view-only effect.",()=>this.plugin.settings.autoExpandCursorRow,s=>{this.plugin.settings.autoExpandCursorRow=s},["expand","cursor","body"])),i.listDisplay.push(t("Expand note bodies by default","Show every note's full body by default instead of clamping long notes. The per-note 'Show more / show less' toggle and the Expand-all / Collapse-all commands then work in reverse \u2014 they let you collapse individual notes back down. Off = bodies clamp by default (expand is opt-in).",()=>this.plugin.settings.expandBodiesByDefault,s=>{this.plugin.settings.expandBodiesByDefault=s},["expand","collapse","default","body","clamp"])),i.movingNotes.push(t("Confirm cross-parent drag-and-drop","When dragging notes onto a note that has a different parent, ask before re-parenting (turn off to allow direct moves).",()=>this.plugin.settings.confirmCrossParentDrag,s=>{this.plugin.settings.confirmCrossParentDrag=s},["confirm","drag","drop","reparent"])),i.deleting.push(t("Confirm bulk deletes","Warn before deletes that affect more than one note \u2014 multi-selection delete OR deleting a note that has descendants. A single childless note with no attachments never prompts. Off = those deletes apply immediately (undo still recovers everything).",()=>this.plugin.settings.confirmBulkDelete,s=>{this.plugin.settings.confirmBulkDelete=s},["confirm","delete","bulk"])),i.deleting.push(t("Offer to delete attachments with note",`When a note references attachments, the delete modal includes an "Also delete attachments" checkbox so orphaned files don't pile up in your vault. Attachments are detected from both ![[\u2026]] embeds in the body and the frontmatter attachments: list. Off = attachments are always preserved on delete (no checkbox shown), and a single childless note with attachments deletes silently.`,()=>this.plugin.settings.confirmAttachmentDelete,s=>{this.plugin.settings.confirmAttachmentDelete=s},["delete","attachment","orphan"])),i.foldersStorage.push(this.sectionDef("Cross-Stashpad Search Scope","Toggle each Stashpad's pill to choose whether its notes contribute to cross-folder search. Excluded folders are still valid move destinations. Also: create a new Stashpad.",s=>{let n=this.plugin.discoverStashpadFolders();if(new T.Setting(s).setName("Cross-Stashpad Search Scope").setDesc("Toggle each Stashpad's pill to choose whether its notes contribute to cross-folder search. Excluded folders are still valid move destinations \u2014 their notes just don't appear in search results from elsewhere."),n.length===0)s.createEl("p",{cls:"setting-item-description"}).setText('No Stashpads found in this vault yet. A Stashpad is just a folder that contains a Stashpad-shaped note (frontmatter has both `id` and `parent`). Easiest way: open Stashpad (ribbon icon or command "Reveal or open Stashpad") \u2014 it auto-creates the default folder on first use. Or create one below.');else{let a=s.createDiv({cls:"stashpad-folder-list"});for(let l of n)this.renderFolderScopeRow(a,l)}let o=null;new T.Setting(s).setName("Create a new Stashpad").setDesc("Type a vault-relative folder path. The folder is created (with intermediates) and seeded with a Home note so Stashpad recognizes it.").addText(a=>{a.setPlaceholder("my-stashpad"),o=a.inputEl}).addButton(a=>a.setButtonText("Create").setCta().onClick(async()=>{let l=(o?.value??"").trim().replace(/^\/+|\/+$/g,"");if(!l){new T.Notice("Enter a folder name first.");return}try{await this.plugin.createNewStashpad(l),new T.Notice(`Created Stashpad "${l}".`),o&&(o.value=""),await this.plugin.waitForStashpadFolder(l,2e3),this.update?.()}catch(c){new T.Notice(`Couldn't create: ${c.message}`)}}))},["search","scope","exclude","include","create","new","stashpad","folder"])),i.foldersStorage.push(this.sectionDef("Folder Panel Placement","Pin, downrank, or hide folders in the Stashpad folder panel. Restore hidden folders here or from the panel's \u201CHidden\u201D section.",s=>{new T.Setting(s).setName("Folder Panel Placement").setDesc("Folders you've pinned, downranked, or hidden in the Stashpad folder panel. Pin/downrank from a folder's right-click menu in the panel; restore here or from the panel's \u201CHidden\u201D section."),this.renderFolderPlacementList(s)},["folder","panel","pin","pinned","downrank","hide","hidden","restore","placement"])),i.foldersStorage.push(this.headingDef("\u{1F3F7}\uFE0F Note Titles")),i.foldersStorage.push(this.renderDef("Slug stop-words","Words removed from auto-generated note titles (filenames). One per line.",s=>{let n=null,o=(this.plugin.settings.slugStopWords?.length?this.plugin.settings.slugStopWords:je).join(` +`);s.addTextArea(a=>{a.setValue(o),n=a.inputEl,n.rows=6,n.setCssStyles({fontFamily:"var(--font-monospace)"}),a.onChange(async l=>{this.plugin.settings.slugStopWords=(l||"").split(/\r?\n/).map(c=>c.trim().toLowerCase()).filter(Boolean),await e()})}).addExtraButton(a=>a.setIcon("rotate-ccw").setTooltip("Reset to defaults").onClick(async()=>{this.plugin.settings.slugStopWords=[...je],n&&(n.value=je.join(` +`)),await e()}))},["slug","stopwords","filename","title","note titles"])),i.composerCopy.push(t("Autofocus composer after sending","After Enter-submitting a note, return focus to the composer so you can keep typing. Off keeps focus in the list \u2014 useful if you want arrow keys to work without an extra click.",()=>this.plugin.settings.autofocusComposerAfterSend,s=>{this.plugin.settings.autofocusComposerAfterSend=s},["composer","focus","send"])),i.composerCopy.push(t("Focus composer when opening a note","Focus the composer when you open or switch into a Stashpad view. OFF (default) focuses the LIST instead, so arrow-key navigation works right away instead of the composer grabbing focus every time. (Separate from 'after sending' above.)",()=>this.plugin.settings.focusComposerOnOpen,s=>{this.plugin.settings.focusComposerOnOpen=s},["composer","focus","open","navigate","list"])),i.windowsTabs.push(t("Search opens the note in its list (in context)","When you pick a search result, open the LIST that contains the note (focus its parent) and scroll to the note \u2014 so you see it in context instead of landing on the focused-note header. On by default.",()=>this.plugin.settings.searchOpensInContext,s=>{this.plugin.settings.searchOpensInContext=s},["search","context","list","scroll","parent"])),i.windowsTabs.push(t("Open in new window \u2014 duplicate tab","ON: the new-window button (in the time-filter row) duplicates the current Stashpad tab \u2014 original stays open in the main window. OFF: the leaf is MOVED to the new window, closing the original tab.",()=>this.plugin.settings.popoutDuplicates,s=>{this.plugin.settings.popoutDuplicates=s},["popout","window","duplicate"])),i.windowsTabs.push(t("Search results open in a new tab","When you pick a result in the Search modal, open it in a new Stashpad tab instead of navigating the current tab. Applies to same-folder and cross-Stashpad results alike. On by default.",()=>this.plugin.settings.searchOpensInNewTab,s=>{this.plugin.settings.searchOpensInNewTab=s},["search","new tab","results","open"])),i.composerCopy.push(t("Prefix timestamps when copying","Include each note's timestamp before its body when copying with C or Y.",()=>this.plugin.settings.prefixTimestampsOnCopy,s=>{this.plugin.settings.prefixTimestampsOnCopy=s},["copy","timestamp","prefix"])),i}encryptionItems(){let e=this.plugin.encryption,t=[];return t.push(this.sectionDef("Vault Encryption","Set one password to encrypt content in this vault. Stored only on this device \u2014 there is no recovery if you lose it.",i=>{i.addClass("stashpad-encryption-section");let s=i.createDiv({cls:"stashpad-beta-row"});s.createEl("span",{cls:"stashpad-beta-badge",text:"BETA"}),s.createEl("span",{cls:"stashpad-beta-note",text:"Encryption is in beta \u2014 keep your own backups of anything important."});let n=i.createEl("div",{cls:"stashpad-enc-warning"});n.createEl("p").setText("\u26A0\uFE0F AI-built, NOT human-audited. This encryption was written by an AI assistant \u2014 not reviewed or security-audited by a human. Treat it as best-effort protection against a casual snoop, not a guarantee \u2014 and always keep your own unencrypted backups of anything important."),n.createEl("p").setText("There is NO recovery. If you lose a password, anything encrypted under it (notes, archived items, encrypted-trash items) is gone for good. Locking, encrypted archiving, and secure-deleting permanently remove the plaintext \u2014 the encrypted copy is the only one left. (A plaintext archive only de-indexes notes from search; it does NOT encrypt them.)"),n.createEl("p").setText("Each device unlocks with its own password (it never leaves the device); collaborators get access by device approval, not a shared password. If everyone with access loses their password, the content is unrecoverable. While encrypting or decrypting, avoid having a sync/cloud service write the vault mid-operation \u2014 it can corrupt files.");let o=i.createEl("details",{cls:"stashpad-enc-info"});o.createEl("summary",{text:"\u2139\uFE0F How Stashpad encryption works (concepts)"});let a=(p,u)=>{let I=o.createEl("p",{cls:"stashpad-enc-info-item"});I.createEl("strong",{text:`${p} \u2014 `}),I.appendText(u)};a("Vault password vs per-folder passwords","One vault password protects everything by default. A folder can also get its OWN password (a separate key) so you can share just that folder with collaborators; subfolders inherit their nearest keyed ancestor's password. Folders without their own key use the vault password."),a("Encrypt notes vs Hide filenames","\u201CEncrypt \u2026 notes\u201D scrambles the note body into a locked .stashenc blob (the plaintext is removed). \u201CHide filenames\u201D goes further: it replaces the on-disk name AND the placeholder title with a generic label, so a glance at the vault doesn't reveal WHAT is locked. Whether a title is hidden is decided when the note is locked."),a("Archive: plaintext vs encrypted","Every folder has its own \u201Carchive\u201D subfolder. Archiving is a plain move by default \u2014 the notes drop out of cross-folder search but stay readable. Turn on \u201CEncrypt archived notes\u201D for a folder to lock notes as they're archived instead."),a("Trash: Stashpad vs Obsidian","Deleting a note normally sends it to that folder's own \u201Ctrash\u201D subfolder \u2014 encrypted if the folder encrypts its trash \u2014 and it's recoverable from the Trash view. Set a folder's \u201CTrash handling\u201D to \u201CObsidian native trash\u201D to use Obsidian's plain deleted-files behavior instead (Stashpad can't encrypt or recover those)."),a("No recovery","There is no backdoor and no reset. If a password is lost, everything encrypted under it is gone \u2014 so keep your own unencrypted backups of anything you can't afford to lose.");let l=()=>e.argonProbe(),c=()=>this.plugin.settings.authorName?.trim()||"This device",d=e.accessState();e.refresh().then(()=>{e.accessState()!==d&&this.update?.()});let h=d;if(h==="none"){new T.Setting(i).setName("Encryption").setDesc("Not set up yet in this vault.").addButton(p=>p.setButtonText("Set up password\u2026").setCta().onClick(()=>{new de(this.app,{mode:"setup",offerKeychain:!0,kdfProbe:l,onSubmit:async({next:u,remember:I})=>{if(!u)return"Enter a password.";try{await e.setup(u,I,c())}catch(m){return m.message}return new T.Notice("Encryption set up \u2014 unlocked for this session."),this.update?.(),null}}).open()}));return}if(h==="outsider"&&!e.isUnlocked()){e.hasSharedPassword()&&new T.Setting(i).setName("This vault is encrypted").setDesc("Enter the shared password (ask whoever set it up \u2014 they'll send it via a password manager or secure message).").addButton(p=>p.setButtonText("Unlock with shared password\u2026").setCta().onClick(()=>{new de(this.app,{mode:"unlock",offerKeychain:!0,onSubmit:async({current:u,remember:I})=>await e.unlock(u,I)?(new T.Notice("Encryption unlocked."),this.update?.(),null):"Wrong password (or the keyfile hasn't synced here yet)."}).open()})),new T.Setting(i).setName(e.hasSharedPassword()?"Or request device approval":"This vault is encrypted by a collaborator").setDesc("Request access \u2014 pick a password for THIS device, then ask an existing member to approve it. Once approved (and the keyfile syncs to you), you'll unlock with that password. No shared secret.").addButton(p=>p.setButtonText("Request access\u2026").onClick(()=>{new de(this.app,{mode:"setup",offerKeychain:!0,kdfProbe:l,onSubmit:async({next:u,remember:I})=>{if(!u)return"Choose a password for this device.";try{await e.requestAccess(c(),u,I)}catch(m){return m.message}return new T.Notice("Access requested. An existing member can now approve this device."),this.update?.(),null}}).open()}));return}if(h==="pending"){new T.Setting(i).setName("Access requested \u2014 waiting for approval").setDesc("An existing member needs to approve this device. After they do and the keyfile syncs here, reopen this page to unlock with the password you chose.").addButton(p=>p.setButtonText("Try unlock now").setCta().onClick(()=>{new de(this.app,{mode:"unlock",offerKeychain:!0,onSubmit:async({current:u,remember:I})=>await e.unlock(u,I)?(new T.Notice("Encryption unlocked."),this.update?.(),null):"Not approved yet (or wrong password). Ask a member to approve this device, then try again."}).open()})).addButton(p=>p.setButtonText("Cancel request").onClick(async()=>{let u=e.myIdentityId();u&&await e.denyJoinRequest(u),new T.Notice("Access request cancelled."),this.update?.()}));return}let g=e.kdf()==="argon2id"?"Argon2id":e.kdf()==="pbkdf2"?"PBKDF2 (fallback)":"",f=e.isRemembered()?" \xB7 remembered on this device":"";if(new T.Setting(i).setName("Status").setDesc(`${e.isUnlocked()?"Set up \xB7 unlocked this session":"Set up \xB7 locked"}${g?` \xB7 ${g}`:""}${f}`),e.isUnlocked()||new T.Setting(i).setName("Unlock").setDesc("Enter your password to use encryption this session.").addButton(p=>p.setButtonText("Unlock\u2026").setCta().onClick(()=>{new de(this.app,{mode:"unlock",offerKeychain:!0,onSubmit:async({current:u,remember:I})=>await e.unlock(u,I)?(new T.Notice("Encryption unlocked."),this.update?.(),null):"Wrong password. Try again."}).open()})),Tc&&e.keychainAvailable()&&e.isRemembered()&&new T.Setting(i).setName("Forget password on this device").setDesc("Drops ONLY the copy saved in this device's keychain \u2014 your encryption stays set up and nothing is decrypted or deleted. You'll just re-type the password next session. (To turn encryption off entirely, use \u201CRemove encryption\u201D below.)").addButton(p=>p.setButtonText("Forget on this device").onClick(async()=>{await e.forgetKeychain(),new T.Notice("Removed from this device's keychain \u2014 encryption still set up."),this.update?.()})),e.amIMember()&&new T.Setting(i).setName("Change this device's password").setDesc("Re-wraps THIS device's key under a new password \u2014 doesn't re-encrypt files or affect other people.").addButton(p=>p.setButtonText("Change\u2026").onClick(()=>{new de(this.app,{mode:"change",offerKeychain:!0,kdfProbe:l,onSubmit:async({current:u,next:I,remember:m})=>await e.changePassword(u,I,m)?(new T.Notice("Password changed."),this.update?.(),null):"Wrong current password. Try again."}).open()})),e.amIMember()&&new T.Setting(i).setName("Remove encryption").setDesc("Erases the key for this vault so you can start fresh. If anything is still encrypted, you'll be offered to decrypt it (needs your password) or \u2014 if you've lost the password \u2014 permanently delete it. Then just type the confirmation phrase; no password needed.").addButton(p=>{let u=async()=>{let m=await this.plugin.encryptionStateStrict();if(m.live){let B=()=>{new ie(this.app,"Delete all locked content forever?","This PERMANENTLY destroys every locked note and folder \u2014 there is NO decrypted copy and NO recovery. Only do this if you've lost the password and want to start fresh.","Delete locked content forever",async y=>{if(!y)return;let v=await this.plugin.purgeAllLockedContent();new T.Notice(`Deleted ${v} locked item${v===1?"":"s"}.`),u()}).open()},w=async()=>{if(await this.plugin.unlockAllInVault(),(await this.plugin.encryptionStateStrict()).live){new T.Notice("Some notes are still locked (decryption was cancelled or failed) \u2014 removal cancelled.",1e4);return}u()};new ie(this.app,"Encrypted notes are still locked",`Some notes or folders are still encrypted (locked) and have no plaintext copy. Before removing encryption: \u2022 Decrypt everything \u2014 unlock them back to normal notes (needs your password). -\u2022 Delete locked content \u2014 permanently destroy them (only if you've lost the password).`,"Decrypt everything",m=>{m?I():f()},"Delete locked content").open();return}let p=(f,I)=>{new Hi(this.app,{title:"Remove encryption?",body:`This erases the encryption key for this vault. No locked notes/folders exist, so your live content is safe.${f} You'll need to set a new password to encrypt again later, and anything previously exported with this key stays locked to its passphrase.`,phrase:"REMOVE ENCRYPTION",confirmText:"Remove encryption",onConfirm:async()=>{let m=await this.plugin.encryptionStateStrict();if(m.live){new M.Notice("Locked notes appeared while this dialog was open \u2014 removal cancelled. Decrypt everything first.",1e4);return}if(m.trash&&!I){new M.Notice("Encrypted trash appeared while this dialog was open \u2014 removal cancelled. Re-open Remove encryption to handle it.",1e4);return}let C=await this.plugin.listDeletedTrash();for(let B of C)await this.plugin.purgeDeletedAt(B.blob);await e.clear(),new M.Notice("Encryption removed."),this.update?.()}}).open()};if(u.trash){let f=async()=>{let m=await this.plugin.restoreAllTrash();if((await this.plugin.encryptionStateStrict()).trash){new M.Notice("Some encrypted-trash items couldn't be decrypted \u2014 removal cancelled. Check the trash tab and try again.",1e4);return}p(m>0?` (${m} trash item${m===1?"":"s"} were decrypted back to their folders.)`:"",!0)},I=()=>{new ie(this.app,"Discard encrypted trash?","The encrypted-trash items will be PERMANENTLY lost the moment the key is erased \u2014 there is no recovery. Continue?","Confirm",m=>{m&&p(" \u26A0\uFE0F The encrypted trash is being permanently discarded.",!0)}).open()};new ie(this.app,"Encrypted notes in the trash",`There are encrypted, deleted notes in the trash. Removing encryption makes them PERMANENTLY unreadable. +\u2022 Delete locked content \u2014 permanently destroy them (only if you've lost the password).`,"Decrypt everything",y=>{y?w():B()},"Delete locked content").open();return}let C=(B,w)=>{new Li(this.app,{title:"Remove encryption?",body:`This erases the encryption key for this vault. No locked notes/folders exist, so your live content is safe.${B} You'll need to set a new password to encrypt again later, and anything previously exported with this key stays locked to its passphrase.`,phrase:"REMOVE ENCRYPTION",confirmText:"Remove encryption",onConfirm:async()=>{let y=await this.plugin.encryptionStateStrict();if(y.live){new T.Notice("Locked notes appeared while this dialog was open \u2014 removal cancelled. Decrypt everything first.",1e4);return}if(y.trash&&!w){new T.Notice("Encrypted trash appeared while this dialog was open \u2014 removal cancelled. Re-open Remove encryption to handle it.",1e4);return}let v=await this.plugin.listDeletedTrash();for(let E of v)await this.plugin.purgeDeletedAt(E.blob);await e.clear(),new T.Notice("Encryption removed."),this.update?.()}}).open()};if(m.trash){let B=async()=>{let y=await this.plugin.restoreAllTrash();if((await this.plugin.encryptionStateStrict()).trash){new T.Notice("Some encrypted-trash items couldn't be decrypted \u2014 removal cancelled. Check the trash tab and try again.",1e4);return}C(y>0?` (${y} trash item${y===1?"":"s"} were decrypted back to their folders.)`:"",!0)},w=()=>{new ie(this.app,"Discard encrypted trash?","The encrypted-trash items will be PERMANENTLY lost the moment the key is erased \u2014 there is no recovery. Continue?","Confirm",y=>{y&&C(" \u26A0\uFE0F The encrypted trash is being permanently discarded.",!0)}).open()};new ie(this.app,"Encrypted notes in the trash",`There are encrypted, deleted notes in the trash. Removing encryption makes them PERMANENTLY unreadable. \u2022 Decrypt Trash \u2014 restore them to their original folders first (recommended). -\u2022 Discard trash \u2014 let them be permanently erased with the key.`,"Decrypt Trash",m=>{m?f():I()},"Discard trash").open();return}p("",!1)};h.setButtonText("Remove\u2026").onClick(()=>void g()),h.buttonEl.addClass("mod-warning")}),new M.Setting(i).setName("Sharing").setHeading(),e.isUnlocked()?e.hasSharedPassword()?new M.Setting(i).setName("Shared password").setDesc("ON \u2014 anyone who knows it can unlock this vault (no approval). Share it via a password manager or secure message; don't send it in the clear.").addButton(h=>h.setButtonText("Change\u2026").onClick(()=>{new de(this.app,{mode:"setup",offerKeychain:!0,kdfProbe:n,title:"Change shared password",intro:"Everyone who unlocks with the shared password will need the new one. Re-share it securely after changing.",onSubmit:async({next:g,remember:u})=>{if(!g)return"Enter a password.";try{await e.setSharedPassword(g,u)}catch(p){return p.message}return new M.Notice("Shared password updated."),this.update?.(),null}}).open()})).addButton(h=>{h.setButtonText("Turn off").onClick(async()=>{await e.removeSharedPassword(),new M.Notice("Shared password turned off."),this.update?.()}),h.buttonEl.addClass("mod-warning")}):new M.Setting(i).setName("Shared password").setDesc("OFF \u2014 set one passphrase that everyone types to unlock (the simplest way to share). Anyone who knows it can unlock; turning it off later doesn't claw back copies already synced elsewhere.").addButton(h=>h.setButtonText("Set shared password\u2026").onClick(()=>{new de(this.app,{mode:"setup",offerKeychain:!0,kdfProbe:n,title:"Set shared password",intro:"One passphrase everyone types to unlock this vault. Anyone who knows it gets in \u2014 share it ONLY through a password manager or secure message, never in the clear. You can turn it off later.",onSubmit:async({next:g,remember:u})=>{if(!g)return"Enter a password.";try{await e.setSharedPassword(g,u)}catch(p){return p.message}return new M.Notice("Shared password set \u2014 share it securely with your collaborators."),this.update?.(),null}}).open()})):i.createEl("p",{cls:"setting-item-description"}).setText("Unlock encryption above to manage how this vault is shared."),pc)if(new M.Setting(i).setName("Collaborators (device approval)").setHeading(),i.createEl("p",{cls:"setting-item-description"}).setText("Everyone who can unlock this vault, and devices waiting for access. Approving a request shares the vault key with that device \u2014 it never sees a password. Removing a member revokes future access (existing synced copies they already hold aren't clawed back \u2014 rotate the key for that, a future feature)."),new M.Setting(i).setName("Synced from the vault keyfile").addButton(g=>g.setButtonText("Refresh").onClick(async()=>{await e.refresh(),this.update?.()})),!e.isUnlocked())i.createEl("p",{cls:"setting-item-description"}).setText("Unlock encryption above to approve or remove collaborators.");else{let g=e.myIdentityId();for(let p of e.members()){let f=p.id===g,I=new M.Setting(i).setName(`${p.label}${f?" \u2014 this device":""}`).setDesc(f?"You":"Member");f||I.addButton(m=>{m.setButtonText("Remove").onClick(async()=>{await e.removeMember(p.id),new M.Notice(`Removed ${p.label}. (Not full revocation without rotating the key.)`),this.update?.()}),m.buttonEl.addClass("mod-warning")})}let u=e.pendingJoinRequests();if(u.length===0)i.createEl("p",{cls:"setting-item-description"}).setText("No pending access requests.");else for(let p of u)new M.Setting(i).setName(p.label).setDesc("Wants access to this vault").addButton(f=>f.setButtonText("Approve").setCta().onClick(async()=>{try{await e.approveJoinRequest(p.id)}catch(I){new M.Notice(I.message);return}new M.Notice(`Approved ${p.label} \u2014 they can unlock once the keyfile syncs to them.`),this.update?.()})).addButton(f=>f.setButtonText("Deny").onClick(async()=>{await e.denyJoinRequest(p.id),new M.Notice("Request denied."),this.update?.()}))}},["encryption","encrypt","password","passphrase","lock","unlock","key","security","private","collaborator","share","team","member","approve"])),t.push(this.headingDef("Trash & title defaults","Vault-wide defaults for deleted notes and locked-note titles. Per-folder overrides live under \u201CPer-Folder Passwords\u201D below.")),t.push(this.renderDef("Encrypt items sent to trash","When ON, deleting a note sends it to Stashpad's encrypted trash (recoverable with your password) instead of a plaintext trash. Default OFF.",i=>i.addToggle(s=>s.setValue(this.plugin.settings.encryptTrash).onChange(async n=>{if(n&&!this.encryptionOrOnboard()){this.update?.();return}this.plugin.settings.encryptTrash=n,await this.plugin.saveSettings()})),["trash","delete","encrypt"])),t.push(this.renderDef("Encrypt trash filenames","Hide the filename + origin folder of encrypted-trashed items (opaque names on disk; shown under \u201CHidden\u201D in the trash tab). Default OFF so you can still tell what to restore when working outside the app.",i=>i.addToggle(s=>s.setValue(this.plugin.settings.encryptTrashFilenames).onChange(async n=>{if(n&&!this.encryptionOrOnboard()){this.update?.();return}this.plugin.settings.encryptTrashFilenames=n,await this.plugin.saveSettings()})),["trash","filename","encrypt"])),t.push(this.renderDef("Follow Obsidian's trash setting instead","OFF (recommended): encrypted-deleted notes go to Stashpad's own \u201C_deleted/\u201D store \u2014 the only trash location Stashpad fully controls, so it can encrypt, list, and restore them. ON: deletes follow Obsidian's \u201CDeleted files\u201D setting instead (system/OS trash or permanent). \u26A0\uFE0F Stashpad CANNOT encrypt or recover notes that go to the system trash \u2014 so the encrypted trash + recoverable trash view won't apply. Only turn this on if you specifically want Obsidian's native trash behavior.",i=>i.addToggle(s=>s.setValue(this.plugin.settings.encryptTrashFollowObsidian??!1).onChange(async n=>{this.plugin.settings.encryptTrashFollowObsidian=n||void 0,await this.plugin.saveSettings()})),["trash","obsidian","system","delete","encrypt"])),t.push(this.renderDef("Auto-lock after idle minutes","Forget the password from memory after this many idle minutes (0 = never). Re-prompts on the next encryption action.",i=>i.addText(s=>s.setValue(String(this.plugin.settings.encryptionIdleLockMinutes??0)).onChange(async n=>{let o=Math.max(0,Math.floor(Number(n)||0));this.plugin.settings.encryptionIdleLockMinutes=o,await this.plugin.saveSettings()})),["auto-lock","idle","timeout","lock"])),t.push(this.renderDef("Hide titles of locked notes (default)","The DEFAULT for hiding \u{1F512} locked-placeholder titles \u2014 used by any folder/trash that doesn't set its own \u201Chide filenames\u201D option in Per-Folder Passwords below (those override this). Shows a generic label instead of the note's title so a glance doesn't reveal what's locked. Default OFF.",i=>i.addToggle(s=>s.setValue(this.plugin.settings.hideLockedTitles??!1).onChange(async n=>{if(n&&!this.encryptionOrOnboard()){this.update?.();return}this.plugin.settings.hideLockedTitles=n,await this.plugin.saveSettings(),this.plugin.refreshAllStashpadViews?.()})),["title","hide","private","lock","placeholder","visibility"])),t.push(this.headingDef("Archive","Archive folders are de-indexed from cross-folder search; mark a folder as an archive (and set whether it encrypts) under \u201CPer-Folder Passwords\u201D below.")),t.push(this.renderDef("Default archive folder",`Where the "Move selection to archive" command sends notes (they're auto-encrypted on arrival). Leaving this blank is fine \u2014 the command will just show you a list of your archive folders to pick from each time (or use the only one if you have a single archive). Mark a folder as an archive via the folder panel \u2192 right-click \u2192 "Mark as archive".`,i=>{let s=this.plugin.settings.archiveFolders??[];i.addDropdown(n=>{n.addOption("",s.length?"\u2014 pick from list each time \u2014":"\u2014 no archive folders yet \u2014");for(let a of s)n.addOption(a,a);let o=this.plugin.settings.defaultArchiveFolder??"";n.setValue(s.includes(o)?o:""),n.onChange(async a=>{this.plugin.settings.defaultArchiveFolder=a||void 0,await this.plugin.saveSettings()})})},["archive","default","move","encrypt","folder"])),t.push(this.sectionDef("Per-Folder Passwords","Give a folder its own password (a separate key) so you can share just that folder with collaborators. Folders without their own password use the vault password.",i=>{this.renderPerFolderEncryption(i)},["folder","per-folder","password","encrypt","key","share","collaborator","archive","unlock","lock"])),t}hotkeyItems(){let e={name:"Hotkeys",desc:"Each command has up to two slots. Click a slot and press a key (or chord) to bind it; press Backspace (delete on Mac) to cancel without binding; or click \u2715 to clear an existing binding. A \u21BA icon appears on any slot that differs from its shipped default \u2014 click it to revert that slot. When both slots are set, the pill on the right decides which one is active.",searchable:!1},t=ci.map(i=>({name:i.label,desc:i.desc,aliases:["hotkey","shortcut","keybind","binding","key"],render:s=>this.renderBindingRow(s,i)}));return[e,...t]}renderJdIndexSection(e){let t=new M.Setting(e).setName("JD Index Builder").setHeading();t.settingEl.id="stashpad-jd-index-section";let i=e.createEl("div",{cls:"stashpad-settings-section"}).createEl("p",{cls:"setting-item-description"});i.appendText("Builds a Johnny-Decimal-style index inside a designated Stashpad folder. Two commands:"),i.createEl("br"),i.createEl("strong",{text:"Preview"}),i.appendText(" overwrites the designated folder\u2019s HOME note body with the would-be hierarchy + everything that didn\u2019t match. Frontmatter is preserved; everything below it is replaced."),i.createEl("br"),i.createEl("strong",{text:"Build"}),i.appendText(" creates an actual hierarchy of Stashpad notes (one per prefix), with child\u2192parent relationships matching the dotted segments."),i.createEl("br"),i.appendText("Matches strict prefixes only: all-digits ("),i.createEl("code",{text:"10 Life"}),i.appendText(") or alphanumeric-with-dots ("),i.createEl("code",{text:"1.2 Family"}),i.appendText(", "),i.createEl("code",{text:"animal.duck.yellow Eggs"}),i.appendText("). Mixed schemes sort numbers first, then alphabetically.");let s=this.plugin.discoverStashpadFolders();new M.Setting(e).setName("Scope").setDesc("Scan the whole vault, or restrict to a single folder + its descendants.").addDropdown(l=>{l.addOption("vault","Entire vault"),l.addOption("folder","Single folder"),l.setValue(this.plugin.settings.jdIndexScope??"vault"),l.onChange(async c=>{this.plugin.settings.jdIndexScope=c==="folder"?"folder":"vault",await this.plugin.saveSettings(),this.display()})}),(this.plugin.settings.jdIndexScope??"vault")==="folder"&&new M.Setting(e).setName("Scope folder").setDesc("Vault-relative path. Leave empty to fall back to the entire vault.").addText(l=>{new oi(this.app,l.inputEl),l.setPlaceholder("Path/To/Folder"),l.setValue(this.plugin.settings.jdIndexScopeFolder??""),l.onChange(async c=>{this.plugin.settings.jdIndexScopeFolder=(c||"").trim().replace(/^\/+|\/+$/g,""),await this.plugin.saveSettings()})}),new M.Setting(e).setName("Include Stashpad folders in scan").setDesc("By default, notes inside any known Stashpad folder are excluded \u2014 the index destination shouldn't index itself, and other Stashpad folders are usually already organized. Toggle on if you want them included anyway.").addToggle(l=>{l.setValue(this.plugin.settings.jdIndexIncludeStashpadFolders===!0),l.onChange(async c=>{this.plugin.settings.jdIndexIncludeStashpadFolders=c,await this.plugin.saveSettings(),this.display()})}),new M.Setting(e).setName("Designated Stashpad folder for Index").setDesc("Required. Must be a Stashpad folder. The index hierarchy is built here. New notes are created; nothing is deleted.").addText(l=>{new oi(this.app,l.inputEl),l.setPlaceholder(s[0]??"(pick a Stashpad folder)"),l.setValue(this.plugin.settings.jdIndexStashpadFolder??""),l.onChange(async c=>{this.plugin.settings.jdIndexStashpadFolder=(c||"").trim().replace(/^\/+|\/+$/g,""),await this.plugin.saveSettings()})}),new M.Setting(e).setName("Sort").setDesc("Order of entries within the same depth. Natural: numbers first then alphabetical (recommended). Created: by source file's creation time \u2014 handy when prefixes are word-only and don't carry ordering.").addDropdown(l=>{l.addOption("natural","Natural (numeric \u2192 alphabetical)"),l.addOption("created","By creation time"),l.setValue(this.plugin.settings.jdIndexSort??"natural"),l.onChange(async c=>{this.plugin.settings.jdIndexSort=c==="created"?"created":"natural",await this.plugin.saveSettings()})});let n=ni(this.app,this.plugin,this.plugin.settings),o=e.createEl("div",{cls:"stashpad-settings-section"}).createEl("p",{cls:"setting-item-description"}),a=n.skippedStashpadNotes.length>0?` (${n.skippedStashpadNotes.length} Stashpad-folder note${n.skippedStashpadNotes.length===1?"":"s"} excluded by default)`:"";o.setText(`Preview: ${n.indexed.length} note${n.indexed.length===1?"":"s"} would be indexed, ${n.nonIndex.length} would NOT be indexed${a}.`),new M.Setting(e).setName("Actions").setClass("stashpad-jd-actions").setDesc("Preview aggressively overwrites the designated folder's HOME note body (frontmatter preserved). Build creates Stashpad notes (existing notes with the same jdPrefix are updated, not duplicated).").addButton(l=>{l.setButtonText("Preview"),l.setTooltip("Overwrites the designated Stashpad folder's HOME note body with the preview."),l.onClick(async()=>{try{let c=await Qs(this.app,this.plugin,this.plugin.settings);if(c.error==="no-dest"){new M.Notice("Set a Designated Stashpad folder for Index first.",5e3);return}if(c.error==="no-home"){new M.Notice(`"${this.plugin.settings.jdIndexStashpadFolder}" doesn't have a Stashpad home note. Open the folder in Stashpad first (it creates one automatically).`,7e3);return}Es(this.app,c),this.display()}catch(c){console.error("[stashpad] preview failed",c),new M.Notice(`Preview failed: ${c?.message??c}`,8e3)}})}).addButton(l=>{l.setButtonText("Build Stashpad notes"),l.setCta(),l.setTooltip("Create the Stashpad-note hierarchy. Existing notes with matching jdPrefix are updated."),l.onClick(()=>{if(!(this.plugin.settings.jdIndexStashpadFolder??"").trim().replace(/^\/+|\/+$/g,"")){new M.Notice("Set a Designated Stashpad folder for Index first.",5e3);return}new Oi(this.app,this.plugin,this.plugin.settings,n.indexed.length,async()=>{try{let h=await Xn(this.app,this.plugin,this.plugin.settings);if(h.error==="no-dest"){new M.Notice("Set a Designated Stashpad folder for Index first.",5e3);return}if(h.error==="dest-not-stashpad"){new M.Notice(`"${h.destFolder}" isn't a known Stashpad folder. Pick a real Stashpad folder (or create one first).`,7e3);return}this.plugin.settings.jdIndexHasBuilt=!0,await this.plugin.saveSettings(),new M.Notice(`Built: ${h.created} created, ${h.updated} updated, ${h.skipped} skipped \u2192 ${h.destFolder}`,6e3),this.display()}catch(h){console.error("[stashpad] build failed",h),new M.Notice(`Build failed: ${h?.message??h}`,8e3)}}).open()})}).addButton(l=>{l.setButtonText(`Reveal in ${hc()}`),l.setTooltip("Open the designated Stashpad folder in your OS file browser."),l.onClick(async()=>{let c=(this.plugin.settings.jdIndexStashpadFolder??"").trim().replace(/^\/+|\/+$/g,"");if(!c){new M.Notice("Set a Designated Stashpad folder for Index first.",5e3);return}if(!this.app.vault.getAbstractFileByPath(c)){new M.Notice(`Folder "${c}" doesn't exist yet.`,5e3);return}try{let h=this.app.vault.adapter.basePath;if(h){let{shell:g}=window.require?.("electron")??{},u=`${h}/${c}`;g?.openPath?.(u)}else new M.Notice("Reveal in file system not supported on this platform.",4e3)}catch(h){new M.Notice(`Couldn't open folder: ${h?.message??h}`,5e3)}})})}renderColorAliasesSection(e){let t=this.plugin.discoverStashpadFolders();if(t.length===0){new M.Setting(e).setName("Color aliases per Stashpad").setDesc("No Stashpads discovered yet \u2014 create one above first.");return}let i=(()=>{let o=ht()?.noteFolder;return o&&t.includes(o)?o:t[0]})();new M.Setting(e).setName("Color aliases per Stashpad").setDesc("Which Stashpad's colors to label.").addDropdown(o=>{for(let a of t)o.addOption(a,a);o.setValue(i),o.onChange(a=>{i=a,n()})}),e.createEl("p",{cls:"setting-item-description",text:"Give each per-note color a friendly name. Filters and pickers display the alias instead of the hex code; the underlying color stays the same. The same hex in two Stashpads can have different aliases."});let s=e.createDiv({cls:"stashpad-color-aliases-list"}),n=()=>{s.empty();let o=this.plugin.collectColorsInFolder(i),a=new Map(o.map(h=>[h.hex,h.count])),l=this.plugin.settings.colorAliases?.[i.replace(/\/+$/,"")]??{},c=new Set([...a.keys(),...Object.keys(l)]);if(c.size===0){s.createEl("p",{cls:"setting-item-description",text:`No colors used or aliased in "${i}" yet. Set a per-note color (Shift+: or right-click \u2192 Set color) and it'll appear here.`});return}let d=[...c].map(h=>({hex:h,count:a.get(h)??0}));d.sort((h,g)=>g.count-h.count||h.hex.localeCompare(g.hex));for(let h of d)this.renderColorAliasRow(s,i,h.hex,h.count,n)};n()}authorshipItems(){let e=[];e.push(this.renderDef("Author name","Your display name. Used in the note footer + as the author/contributor link target. Leave blank to opt out (notes won't be stamped).",i=>i.addText(s=>s.setValue(this.plugin.settings.authorName).onChange(async n=>{this.plugin.settings.authorName=n.trim(),this.plugin.settings.authorName&&!this.plugin.settings.authorId&&(this.plugin.settings.authorId=Z()),await this.plugin.saveSettings(),await this.plugin.syncAuthorFilesToName()})),["author","name","identity","stamp"])),e.push(this.renderDef("Author id (auto-assigned)","Stable id appended to your name on links so coworkers with the same name don't collide. Generated once and shouldn't change. To reset it, clear and retype your author name.",i=>i.addText(s=>s.setValue(this.plugin.settings.authorId).setDisabled(!0)),["author","id"])),e.push(this.renderDef("Title / role",'Optional. Shown on your author page (e.g. "Engineer", "PM", "Designer").',i=>i.addText(s=>s.setValue(this.plugin.settings.authorRole).onChange(async n=>{this.plugin.settings.authorRole=n.trim(),await this.plugin.saveSettings(),await this.plugin.syncAuthorFilesToName()})),["role","title","job"])),e.push(this.renderDef("Department / team",'Optional. Shown on your author page (e.g. "Engineering", "Growth").',i=>i.addText(s=>s.setValue(this.plugin.settings.authorDepartment).onChange(async n=>{this.plugin.settings.authorDepartment=n.trim(),await this.plugin.saveSettings(),await this.plugin.syncAuthorFilesToName()})),["department","team"]));let t=(i,s,n,o)=>this.renderDef(i,"",a=>a.addToggle(l=>l.setValue(s()).onChange(async c=>{n(c),await this.plugin.saveSettings()})),o);return e.push(t("Show author in note footer",()=>this.plugin.settings.showAuthor,i=>{this.plugin.settings.showAuthor=i},["author","footer","show"])),e.push(t("Show contributors in note footer",()=>this.plugin.settings.showContributors,i=>{this.plugin.settings.showContributors=i},["contributors","footer","show"])),e.push(t("Show last edit time in note footer",()=>this.plugin.settings.showLastEdit,i=>{this.plugin.settings.showLastEdit=i},["last edit","modified","footer","time"])),e.push(this.sectionDef("Folders You've Worked In","Folders where you've authored or contributed notes. Click one to open it.",i=>this.renderAuthoredFolders(i),["folders","authored","contributed","worked"])),e.push(this.sectionDef("Known Authors","Everyone the plugin has seen, with role/department + rename history; rebuild/restore the registry.",i=>this.renderKnownAuthorsSection(i),["authors","registry","rename","known","rebuild"])),e}renderAuthoredFolders(e){new M.Setting(e).setName("Folders you've worked in").setHeading();let t=this.plugin.collectAuthoredFolders();if(t.length===0){e.createEl("p",{cls:"setting-item-description",text:"No authored or contributed folders yet."});return}let i=e.createDiv({cls:"stashpad-authored-folders-list"});for(let s of t){let n=i.createDiv({cls:"stashpad-authored-folder-row"}),o=n.createEl("a",{cls:"stashpad-authored-folder-link",text:s.folder});o.onclick=l=>{l.preventDefault(),this.plugin.activateViewForFolder(s.folder)};let a=[];s.authored>0&&a.push(`authored ${s.authored}`),s.contributed>0&&a.push(`contributed to ${s.contributed}`),n.createSpan({cls:"stashpad-authored-folder-counts",text:` \xB7 ${a.join(", ")}`})}}templatesItems(){return[this.sectionDef("Color Aliases","Give your note colors friendly names, per Stashpad folder.",e=>this.renderColorAliasesSection(e),["color","colour","alias","name","swatch","palette","label"]),this.sectionDef("Note Templates","Per-Stashpad note templates \u2014 content stamped into new notes.",e=>this.renderNoteTemplatesSection(e),["template","note","default","boilerplate","snippet"])]}jdIndexItems(){return[this.sectionDef("JD Index (Johnny Decimal)","Build a Johnny-Decimal-style index from dotted-prefix note titles \u2014 set the scope, preview, then build.",e=>this.renderJdIndexSection(e),["jd","johnny","decimal","index","scope","build","preview","hierarchy","folder"])]}okfItems(){return[this.sectionDef("Open Knowledge Format (OKF)","Turn a Stashpad folder into a browsable OKF bundle \u2014 markdown concept files with OKF frontmatter, a generated index.md, and relative-markdown cross-links \u2014 that LLMs/agents can read. Complements (never replaces) Stashpad's own frontmatter and links.",e=>this.renderOkfSection(e),["okf","open knowledge format","knowledge","catalog","index","export","bundle","tarball","agent","google"])]}appendCode(e,t){t.split(/`([^`]+)`/g).forEach((i,s)=>{s%2===1?e.createEl("code",{text:i}):i&&e.appendText(i)})}codeDesc(e){let t=document.createDocumentFragment();return this.appendCode(t,e),t}renderOkfSection(e){if(e.createDiv({cls:"stashpad-beta-row"}).createEl("span",{cls:"stashpad-beta-badge",text:"BETA"}),new M.Setting(e).setName("Enable OKF").setDesc(this.codeDesc("Master switch. When on, you choose which folders use OKF by assigning the OKF template to them in Stashpad's own Templates section (the \u{1F4C4} Templates settings here \u2014 NOT Obsidian's core Templates plugin) \u2014 all / some / none, your call. Those folders then get OKF frontmatter and a maintained `index.md`. Turning this off leaves existing OKF files in place; it just stops maintaining them.")).addToggle(i=>i.setValue(this.plugin.settings.okfEnabled).onChange(async s=>{if(this.plugin.settings.okfEnabled=s,await this.plugin.saveSettings(),s)try{await this.plugin.ensureOkfTemplate()}catch(n){console.warn("[Stashpad] OKF template create failed",n)}new M.Notice(s?`OKF on. Next: assign the template "${this.plugin.okfTemplatePathOrDefault()}" to a folder \u2014 use \u201CCreate template + open Templates\u201D below. Heads-up: OKF frontmatter + index.md refresh automatically but NOT instantly (a few seconds after changes); hit Rebuild for an immediate pass.`:"OKF disabled.",s?0:4e3),this.update?.()})),this.plugin.settings.okfEnabled){let i=this.plugin.okfTemplatePathOrDefault(),s=this.plugin.okfActiveFolders().length,n=e.createEl("div",{cls:"setting-item-description stashpad-okf-howto"});n.createEl("p",{text:"How to use OKF in a folder:"});let o=n.createEl("ol");if(this.appendCode(o.createEl("li"),`Open Stashpad's \u{1F4C4} Templates section (here in Stashpad's settings \u2014 not Obsidian's core Templates plugin) and set a folder's template to \`${i}\` (archive folders are skipped).`),this.appendCode(o.createEl("li"),"Hit Rebuild below to write OKF frontmatter (`okfParent`/`okfChildren` + `okfType`/`okfTitle`/`okfTimestamp`) and generate that folder's `index.md`."),this.appendCode(o.createEl("li"),"Right-click a note (or a selection) \u2192 \u201CExport as OKF\u2026\u201D to save a `.zip` / `.tar.gz` bundle (or `.stash`)."),n.createEl("p",{cls:"stashpad-okf-soon",text:"OKF frontmatter + index.md refresh automatically a few seconds after you add, move, or delete notes \u2014 NOT instantly. Use Rebuild for an immediate pass."}),s===0){let a=e.createEl("p",{cls:"stashpad-okf-cta"});this.appendCode(a,"\u{1F449} No folder is using OKF yet. Click \u201CCreate template + open Templates\u201D below, then set a folder's template to `"+i+"`.")}else n.createEl("p",{cls:"stashpad-okf-soon",text:`Currently ${s} folder${s===1?"":"s"} actively using OKF.`});new M.Setting(e).setName("Assign OKF to folders").setDesc(this.codeDesc(`Creates the OKF template if needed (never duplicates it), then opens Templates \u2014 set a folder's template to \`${i}\` there.`)).addButton(a=>{a.setButtonText("Create template + open Templates").setCta(),a.onClick(async()=>{let l;try{l=await this.plugin.ensureOkfTemplate()}catch(c){new M.Notice(`Couldn't create the OKF template: ${c.message}`);return}new M.Notice(`OKF template ready at "${l}" \u2014 set a folder's template to that path.`),this.update?.(),this.openSettingsPage("Templates")})}),new M.Setting(e).setName("Rebuild OKF frontmatter").setDesc(this.codeDesc("Write/refresh OKF fields for every folder using the OKF template \u2014 `okfParent`/`okfChildren` relative links (managed) plus `okfType`/`okfTitle`/`okfTimestamp` defaults (yours to edit after). Heads-up: adding, moving, or deleting notes already auto-refreshes the folder, but NOT instantly \u2014 it waits ~a few seconds after you stop. Use this button for an immediate rebuild (e.g. right after first assigning the template). Complements Stashpad's own links; nothing is removed.")).addButton(a=>a.setButtonText("Rebuild now").onClick(async()=>{let l=await this.plugin.rebuildAllOkf();new M.Notice(l.folders===0?"No folders use the OKF template yet \u2014 assign it in Templates first.":`OKF: updated ${l.written} of ${l.checked} notes across ${l.folders} folder${l.folders===1?"":"s"}.`),this.update?.()}))}let t=new M.Setting(e).setName("Learn about OKF").setDesc("Google's open, vendor-neutral spec for sharing curated knowledge with agents.");t.addButton(i=>i.setButtonText("Spec / repo").onClick(()=>window.open("https://github.com/GoogleCloudPlatform/knowledge-catalog/tree/main/okf"))),t.addButton(i=>i.setButtonText("Announcement").onClick(()=>window.open("https://cloud.google.com/blog/products/data-analytics/how-the-open-knowledge-format-can-improve-data-sharing/")))}openSettingsPage(e){let t=()=>new M.Notice(`Open Settings \u2192 Stashpad \u2192 ${e}.`);try{let i=this.app.setting;if(!i?.openTabById){t();return}i.openTabById("stashpad"),window.setTimeout(()=>{let s=i.modalEl?.querySelector(".vertical-tab-content");if(!s){t();return}let o=Array.from(s.querySelectorAll("*")).find(a=>a.childElementCount===0&&a.textContent?.trim()===e&&!a.closest(".vertical-tab-header"))?.closest("[class*='nav'], .setting-item, button, a");o&&!o.closest(".vertical-tab-header")?o.click():t()},60)}catch{t()}}renderAuthorshipSection(e){new M.Setting(e).setName("Authorship").setHeading(),e.createEl("p",{cls:"setting-item-description",text:"Stamp each new note with your name. If the vault is later shared (e.g. a coworker opens it with --config pointing at their own settings folder), every modification automatically tracks contributors on top of the original author. Names link to per-user pages in <stashpad>/_authors/."}),new M.Setting(e).setName("Author name").setDesc("Your display name. Used in the note footer + as the author/contributor link target. Leave blank to opt out (notes won't be stamped).").addText(i=>{i.setValue(this.plugin.settings.authorName).onChange(async s=>{this.plugin.settings.authorName=s.trim(),this.plugin.settings.authorName&&!this.plugin.settings.authorId&&(this.plugin.settings.authorId=Z()),await this.plugin.saveSettings(),await this.plugin.syncAuthorFilesToName()})}),new M.Setting(e).setName("Author id (auto-assigned)").setDesc("Stable id appended to your name on links so coworkers with the same name don't collide. Generated once and shouldn't change. If you really need to reset it, clear and retype your author name above.").addText(i=>{i.setValue(this.plugin.settings.authorId).setDisabled(!0)}),new M.Setting(e).setName("Title / role").setDesc('Optional. Shown on your author page (e.g. "Engineer", "PM", "Designer").').addText(i=>{i.setValue(this.plugin.settings.authorRole).onChange(async s=>{this.plugin.settings.authorRole=s.trim(),await this.plugin.saveSettings(),await this.plugin.syncAuthorFilesToName()})}),new M.Setting(e).setName("Department / team").setDesc('Optional. Shown on your author page (e.g. "Engineering", "Growth").').addText(i=>{i.setValue(this.plugin.settings.authorDepartment).onChange(async s=>{this.plugin.settings.authorDepartment=s.trim(),await this.plugin.saveSettings(),await this.plugin.syncAuthorFilesToName()})}),new M.Setting(e).setName("Show author in note footer").addToggle(i=>i.setValue(this.plugin.settings.showAuthor).onChange(async s=>{this.plugin.settings.showAuthor=s,await this.plugin.saveSettings()})),new M.Setting(e).setName("Show contributors in note footer").addToggle(i=>i.setValue(this.plugin.settings.showContributors).onChange(async s=>{this.plugin.settings.showContributors=s,await this.plugin.saveSettings()})),new M.Setting(e).setName("Show last edit time in note footer").addToggle(i=>i.setValue(this.plugin.settings.showLastEdit).onChange(async s=>{this.plugin.settings.showLastEdit=s,await this.plugin.saveSettings()}));let t=this.plugin.collectAuthoredFolders();if(t.length>0){new M.Setting(e).setName("Folders You've Worked In").setHeading();let i=e.createDiv({cls:"stashpad-authored-folders-list"});for(let s of t){let n=i.createDiv({cls:"stashpad-authored-folder-row"}),o=n.createEl("a",{cls:"stashpad-authored-folder-link",text:s.folder});o.onclick=l=>{l.preventDefault(),this.plugin.activateViewForFolder(s.folder)};let a=[];s.authored>0&&a.push(`authored ${s.authored}`),s.contributed>0&&a.push(`contributed to ${s.contributed}`),n.createSpan({cls:"stashpad-authored-folder-counts",text:` \xB7 ${a.join(", ")}`})}}this.renderKnownAuthorsSection(e)}renderKnownAuthorsSection(e){new M.Setting(e).setName("Known Authors (registry)").setHeading(),e.createEl("div",{cls:"setting-item-description",text:"A rebuildable cache of every author Stashpad has seen, with rename history. Not a source of truth \u2014 the author id stored in each note is authoritative. Use it to recover deleted author pages or audit name changes."}),new M.Setting(e).setName("Registry maintenance").setDesc("Rebuild scans the whole vault to reconstruct the list. Restore regenerates any deleted author pages across every Stashpad folder.").addButton(s=>s.setButtonText("Rebuild").onClick(async()=>{s.setDisabled(!0).setButtonText("Rebuilding\u2026");try{let n=await this.plugin.rebuildAuthorRegistry();new M.Notice(`Author registry rebuilt: ${n.total} author(s).`)}catch(n){new M.Notice(`Rebuild failed: ${n.message}`)}s.setDisabled(!1).setButtonText("Rebuild"),this.display()})).addButton(s=>s.setButtonText("Restore missing pages").onClick(async()=>{s.setDisabled(!0).setButtonText("Restoring\u2026");try{let n=await this.plugin.restoreMissingAuthorStubs();new M.Notice(n.created>0?`Restored ${n.created} author page(s).`:"No missing author pages.")}catch(n){new M.Notice(`Restore failed: ${n.message}`)}s.setDisabled(!1).setButtonText("Restore missing pages")}));let t=this.plugin.authorRegistry.all();if(t.length===0){e.createEl("div",{cls:"setting-item-description",text:"No authors recorded yet. Rebuild to scan the vault."});return}let i=e.createDiv({cls:"stashpad-known-authors-list"});for(let s of t){let n=i.createDiv({cls:"stashpad-known-author-row"}),o=n.createDiv({cls:"stashpad-known-author-main"});o.createSpan({cls:"stashpad-known-author-name",text:s.name||"(unnamed)"});let a=[];if(s.role&&a.push(s.role),s.department&&a.push(s.department),a.push(`id ${s.id}`),o.createSpan({cls:"stashpad-known-author-meta",text:` \xB7 ${a.join(" \xB7 ")}`}),s.renames&&s.renames.length>0){let l=n.createDiv({cls:"stashpad-known-author-history"}),c=s.renames.map(d=>`${d.from} \u2192 ${d.to}`).join(", ");l.setText(`Renamed: ${c}`)}}}renderNoteTemplatesSection(e){let t=this.plugin.discoverStashpadFolders();if(t.length===0)return;if(new M.Setting(e).setName("Note templates per Stashpad").setDesc("Pick a markdown file to use as the default template for new notes in each Stashpad. The template's frontmatter becomes the new note's frontmatter (id/parent/created/attachments are always set by Stashpad). If the body contains {{body}}, that's where the user-typed body goes; otherwise the user body is followed by the template body."),this.plugin.settings.okfEnabled){let n=this.plugin.okfTemplatePathOrDefault();this.appendCode(e.createEl("p",{cls:"setting-item-description"}),`\u{1F4A1} OKF tip: type \`${n}\` into a folder's template field below to turn that folder into an OKF bundle (OKF frontmatter + a maintained \`index.md\`). Assign it to all, some, or none of your folders \u2014 it's per-folder. Manage OKF itself in Settings \u2192 OKF.`)}let i=e.createDiv({cls:"stashpad-note-templates-list"}),s=n=>{let o=n.replace(/\/+$/,""),a=i.createDiv({cls:"stashpad-note-template-row"});a.createSpan({cls:"stashpad-note-template-folder"}).setText(n);let c=a.createDiv({cls:"stashpad-note-template-input-wrap"}),d=c.createEl("input",{type:"text",cls:"stashpad-note-template-input",attr:{placeholder:"path/to/template.md (leave blank to disable)"}});d.value=(this.plugin.settings.noteTemplates??{})[o]??"";let h=c.createDiv({cls:"stashpad-note-template-suggest"});h.setCssStyles({display:"none"});let g=[],u=[],p=-1,f=()=>h.style.display!=="none"&&g.length>0,I=Q=>{p=Q,u.forEach((S,x)=>S.toggleClass("is-active",x===Q)),Q>=0&&u[Q]&&u[Q].scrollIntoView({block:"nearest"})},m=()=>{h.setCssStyles({display:"none"}),p=-1},C=async Q=>{d.value=Q,await E(),m()},B=a.createDiv({cls:"stashpad-note-template-warn"});B.setCssStyles({display:"none"});let w=()=>this.app.vault.getMarkdownFiles().map(Q=>Q.path).filter(Q=>!/\/(_imports|_exports|_attachments|\.stashpad)\//.test(Q)).sort(),y=()=>{h.empty(),u=[];let Q=d.value.trim().toLowerCase().split(/\s+/).filter(Boolean),S=x=>{let H=x.toLowerCase();return Q.every(G=>H.includes(G))};if(g=w().filter(x=>S(x)).slice(0,12),g.length===0){m();return}h.setCssStyles({display:""}),g.forEach((x,H)=>{let G=h.createDiv({cls:"stashpad-note-template-suggest-item",text:x});u.push(G),G.addEventListener("mousemove",()=>I(H)),G.addEventListener("mousedown",async N=>{N.preventDefault(),await C(x)})}),p=p>=0&&p<g.length?p:-1,p>=0&&I(p)},E=async()=>{let Q=d.value.trim(),S={...this.plugin.settings.noteTemplates??{}};Q?S[o]=Q:delete S[o],this.plugin.settings.noteTemplates=S,await this.plugin.saveSettings(),v()},v=()=>{B.empty(),B.setCssStyles({display:"none"});let Q=d.value.trim();if(!Q)return;let S=this.app.vault.getAbstractFileByPath(Q);if(!S||S.extension!=="md"){B.setCssStyles({display:""}),B.setText(`\u26A0 "${Q}" is not a markdown file in this vault.`);return}let x=this.app.metadataCache.getFileCache(S)?.frontmatter??{},G=Ct.filter(N=>{let U=x[N];return!(U==null||typeof U=="string"&&U.trim()===""||Array.isArray(U)&&U.length===0)});G.length!==0&&(B.setCssStyles({display:""}),B.setText(`\u26A0 Template defines ${G.join(", ")} \u2014 Stashpad always sets ${G.length===1?"this":"these"} on new notes, so the template value${G.length===1?"":"s"} will be ignored.`))};d.addEventListener("focus",y),d.addEventListener("input",()=>{p=-1,y()}),d.addEventListener("blur",()=>{setTimeout(m,150)}),d.addEventListener("change",()=>{E()}),d.addEventListener("keydown",Q=>{if(Q.key==="ArrowDown")Q.preventDefault(),f()?I((p+1)%g.length):(y(),g.length&&I(0));else if(Q.key==="ArrowUp"){if(!f())return;Q.preventDefault(),I((p-1+g.length)%g.length)}else if(Q.key==="Enter")f()&&p>=0&&(Q.preventDefault(),C(g[p]));else if(Q.key==="Escape")f()&&(Q.preventDefault(),m());else if(Q.key==="Tab"&&!Q.shiftKey){if(!f())return;let S=g[p>=0?p:0],x=d.value,H;if(S.toLowerCase().startsWith(x.toLowerCase())){let G=S.indexOf("/",x.length);H=G>=0?S.slice(0,G+1):S}else H=S;H&&H!==x&&(Q.preventDefault(),d.value=H,p=-1,y(),g.length===1&&I(0))}}),v()};for(let n of t)s(n)}renderColorAliasRow(e,t,i,s,n){let o=e.createDiv({cls:"stashpad-color-alias-row"});s===0&&o.addClass("is-unused");let a=o.createSpan({cls:"stashpad-color-alias-swatch"});a.setCssStyles({background:i}),a.title="Click to bulk-recolor every note of this color in this Stashpad",a.onclick=()=>{let h=this.plugin.settings.customPalette??[];new Rt(this.app,i,h,async g=>{if((g??null)===null&&s===0){await this.plugin.setColorAlias(t,i,""),n();return}if(g&&g.toLowerCase()===i){n();return}let u=await this.plugin.recolorAllInFolder(t,i,g??null);if(u>0)new M.Notice(`Recolored ${u} note${u===1?"":"s"}.`);else if(s===0){let p=this.plugin.getColorAlias(t,i);p&&(await this.plugin.setColorAlias(t,i,""),g&&await this.plugin.setColorAlias(t,g,p))}n()},async g=>{let u=(this.plugin.settings.customPalette??[]).filter(p=>p.toLowerCase()!==g.toLowerCase());return this.plugin.settings.customPalette=u,await this.plugin.saveSettings(),u}).open()};let l=o.createDiv({cls:"stashpad-color-alias-meta"});l.createSpan({cls:"stashpad-color-alias-hex",text:i}),l.createSpan({cls:"stashpad-color-alias-count",text:s===0?"\xB7 unused":`\xB7 ${s} note${s===1?"":"s"}`});let c=o.createEl("input",{type:"text",cls:"stashpad-color-alias-input",attr:{placeholder:"Alias (optional)"}});c.value=this.plugin.getColorAlias(t,i)??"",c.onchange=async()=>{await this.plugin.setColorAlias(t,i,c.value),!c.value.trim()&&s===0&&n()};let d=o.createEl("button",{cls:"stashpad-color-alias-del",text:"\xD7",attr:{title:"Delete alias"}});c.value||d.setCssStyles({visibility:"hidden"}),d.onclick=async()=>{await this.plugin.setColorAlias(t,i,""),s===0?n():(c.value="",d.setCssStyles({visibility:"hidden"}))}}renderFolderScopeRow(e,t){let i=e.createDiv({cls:"stashpad-folder-row"});i.createSpan({cls:"stashpad-folder-row-label",text:t});let s=i.createSpan({cls:"stashpad-folder-row-state"}),n=i.createDiv({cls:"stashpad-binding-pill"});n.setAttribute("role","switch"),n.setAttribute("tabindex","0");let o=n.createDiv({cls:"stashpad-binding-pill-knob"}),a=()=>(this.plugin.settings.searchExcludedFolders??[]).includes(t),l=()=>{let d=a();n.toggleClass("is-right",d),n.setAttribute("aria-checked",String(d)),o.setText(d?"X":"\u2713"),s.setText(d?"Excluded":"Included"),s.toggleClass("is-excluded",d),n.title=d?"Excluded \u2014 notes here won't appear in cross-Stashpad search. Click to include.":"Included \u2014 notes here appear in cross-Stashpad search. Click to exclude."},c=async()=>{let d=new Set(this.plugin.settings.searchExcludedFolders??[]);d.has(t)?d.delete(t):d.add(t),this.plugin.settings.searchExcludedFolders=[...d].sort(),l(),await this.plugin.saveSettings()};n.onclick=()=>void c(),n.onkeydown=d=>{(d.key===" "||d.key==="Enter")&&(d.preventDefault(),c())},l()}renderFolderPlacementList(e){let t=this.plugin.settings,i=[{key:"folderPanelPinned",label:"Pinned",action:"Unpin"},{key:"folderPanelDownranked",label:"Downranked",action:"Reset"},{key:"folderPanelHidden",label:"Hidden",action:"Unhide"}];if(!i.some(o=>(t[o.key]??[]).length>0)){e.createEl("p",{cls:"setting-item-description"}).setText("No folders customized yet. Right-click a folder in the Stashpad folder panel to pin, downrank, or hide it.");return}let n=async o=>{t.folderPanelPinned=(t.folderPanelPinned??[]).filter(a=>a!==o),t.folderPanelDownranked=(t.folderPanelDownranked??[]).filter(a=>a!==o),t.folderPanelHidden=(t.folderPanelHidden??[]).filter(a=>a!==o),await this.plugin.saveSettings(),this.update?.()};for(let o of i){let a=[...t[o.key]??[]].sort();if(a.length===0)continue;e.createEl("div",{cls:"stashpad-folder-placement-group",text:`${o.label} (${a.length})`});let l=e.createDiv({cls:"stashpad-folder-list"});for(let c of a){let d=l.createDiv({cls:"stashpad-folder-row"});d.createSpan({cls:"stashpad-folder-row-label",text:c});let h=d.createEl("button",{text:o.action});h.onclick=()=>void n(c)}}}renderBindingRow(e,t){e.setName(t.label).setDesc(t.desc),e.settingEl.addClass("stashpad-binding-row");let i=()=>this.plugin.settings.bindings[t.id],s,n,o=()=>{},a=async g=>{},l=e.controlEl.createDiv({cls:"stashpad-binding-seg",attr:{role:"group","aria-label":"Active binding"}}),c={left:l.createEl("button",{cls:"stashpad-binding-seg-btn",text:"Left"}),both:l.createEl("button",{cls:"stashpad-binding-seg-btn",text:"Both"}),right:l.createEl("button",{cls:"stashpad-binding-seg-btn",text:"Right"})};["left","both","right"].forEach(g=>{c[g].onclick=()=>void a(g)});let d=e.controlEl.createDiv({cls:"stashpad-binding-slots"}),h=g=>{let u=d.createDiv({cls:"stashpad-binding-slot"}),p=u.createEl("input",{type:"text"});p.readOnly=!0,p.placeholder="Click & press a key",p.value=$i(i()[g]),p.classList.add("stashpad-binding-input");let f=()=>{p.size=Math.max(3,p.value.length||p.placeholder.length)};f();let I=g==="primary"?t.defaultPrimary:t.defaultSecondary??"";p.onclick=()=>{Ta(p,async y=>{this.plugin.settings.bindings[t.id][g]=y,p.value=$i(y),f(),await this.plugin.saveSettings(),o(),w()})};let m=u.createDiv({cls:"stashpad-binding-slot-btns"}),C=m.createEl("button",{cls:"stashpad-binding-clear",text:"\xD7"});C.title="Clear this slot",C.onclick=async()=>{this.plugin.settings.bindings[t.id][g]="",p.value="",f(),await this.plugin.saveSettings(),o(),w()};let B=m.createEl("button",{cls:"stashpad-binding-revert"});(0,M.setIcon)(B,"rotate-ccw");let w=()=>{let E=i()[g]!==I;B.toggleClass("is-hidden",!E),B.title=I?`Revert to default (${$i(I)})`:"Revert to default (no binding)"};return B.onclick=async()=>{this.plugin.settings.bindings[t.id][g]=I,p.value=$i(I),f(),await this.plugin.saveSettings(),o(),w()},w(),p};s=h("primary"),n=h("secondary"),a=async g=>{let u=this.plugin.settings.bindings[t.id];u.primary&&u.secondary&&(g==="both"?u.useBoth=!0:(u.useBoth=!1,u.preferRight=g==="right"),await this.plugin.saveSettings(),o())},o=()=>{let g=i(),u=!!(g.primary&&g.secondary),p=g.useBoth?"both":g.preferRight?"right":"left";["left","both","right"].forEach(f=>{c[f].toggleClass("is-active",u&&f===p),c[f].disabled=!u}),l.toggleClass("is-disabled",!u),l.title=u?"Which binding is active \u2014 Left, Both, or Right":"Bind both slots to choose Left / Both / Right"},o()}};var di=class{constructor(r,e){this.app=r;this.ta=e;this.popupEl=null;this.items=[];this.activeIdx=0;this.state=null;this.fileIndex=[];this.tagIndex=[];this.indexBuilt=!1;this.vaultListeners=[];this.scope=null;this.onInput=()=>{let r=this.detectTrigger();if(!r){this.close();return}this.openFor(r)};this.onBlur=()=>{setTimeout(()=>this.close(),120)};this.onDocEscape=r=>{r.key==="Escape"&&(!this.state||!this.items.length||(r.preventDefault(),r.stopPropagation(),r.stopImmediatePropagation(),this.close()))};this.onKeyDown=r=>{!this.state||!this.items.length||(r.key==="ArrowDown"?(r.preventDefault(),this.activeIdx=(this.activeIdx+1)%this.items.length,this.refreshActive()):r.key==="ArrowUp"?(r.preventDefault(),this.activeIdx=(this.activeIdx-1+this.items.length)%this.items.length,this.refreshActive()):r.key==="Enter"||r.key==="Tab"?(r.preventDefault(),r.stopPropagation(),this.commit()):r.key==="Escape"&&(r.preventDefault(),r.stopPropagation(),r.stopImmediatePropagation(),this.close()))}}attach(){this.ta.addEventListener("input",this.onInput),this.ta.addEventListener("keydown",this.onKeyDown,!0),this.ta.addEventListener("blur",this.onBlur);let r=this.ta.ownerDocument??document;r.addEventListener("keydown",this.onDocEscape,!0),this.vaultListeners.push(()=>r.removeEventListener("keydown",this.onDocEscape,!0)),this.buildIndex();let e=()=>{this.indexBuilt=!1},t=this.app.vault;t.on("create",e),t.on("delete",e),t.on("rename",e),this.vaultListeners.push(()=>t.off("create",e),()=>t.off("delete",e),()=>t.off("rename",e))}isOpen(){return!!this.state&&this.items.length>0}detach(){this.close(),this.ta.removeEventListener("input",this.onInput),this.ta.removeEventListener("keydown",this.onKeyDown,!0),this.ta.removeEventListener("blur",this.onBlur);for(let r of this.vaultListeners)r();this.vaultListeners=[]}buildIndex(){if(this.indexBuilt)return;let r=X().inheritObsidianExclusions,e=r?this.app.vault.getConfig?.("userIgnoreFilters"):void 0;this.fileIndex=this.app.vault.getFiles().filter(i=>!da(i.path)&&!Aa(i.path)&&!(r&&ha(i.path,e))).map(i=>{let s=i.extension==="md",n=s?i.basename:i.name,o=s?i.basename:i.name;return{label:n,lower:n.toLowerCase(),insertText:o,file:i}});let t=this.app.metadataCache.getTags?.()??{};this.tagIndex=Object.keys(t).sort((i,s)=>(t[s]||0)-(t[i]||0)),this.indexBuilt=!0}detectTrigger(){let r=this.ta.value,e=this.ta.selectionStart;if(e==null)return null;let t=r.slice(0,e),i=t.match(/\[\[([^\]\[\n]*)$/);if(i){let n=i[1];return{kind:"link",query:n,replaceStart:e-n.length-2,replaceEnd:e}}let s=t.match(/(^|\s)#([A-Za-z0-9_/\-]+)$/);if(s){let n=s[2];return{kind:"tag",query:n,replaceStart:e-n.length-1,replaceEnd:e}}return null}buildItems(r){this.buildIndex();let e=r.query.toLowerCase().trim(),t=e?e.split(/\s+/).filter(Boolean):[],i=s=>{if(!t.length)return!0;for(let n of t)if(!s.includes(n))return!1;return!0};return r.kind==="link"?this.fileIndex.filter(n=>i(n.lower)).slice(0,50).map(n=>({label:n.label,insert:`[[${n.insertText}]]`,subtitle:n.file.path})):this.tagIndex.filter(n=>i(n.toLowerCase())).slice(0,30).map(n=>({label:n,insert:n,subtitle:""}))}openFor(r){if(this.state=r,this.items=this.buildItems(r),this.activeIdx=0,!this.items.length){this.close();return}this.renderPopup(),this.pushScope()}pushScope(){if(this.scope)return;let r=new sr.Scope(this.app.scope);r.register([],"Escape",e=>(e.preventDefault(),this.close(),!1)),this.scope=r,this.app.keymap?.pushScope(r)}popScope(){if(this.scope){try{this.app.keymap?.popScope(this.scope)}catch{}this.scope=null}}renderPopup(){if(!this.popupEl){let e=this.ta.ownerDocument??document;this.popupEl=e.body.createDiv({cls:"stashpad-composer-suggest"}),this.popupEl.tabIndex=-1,this.popupEl.addEventListener("mousedown",t=>t.preventDefault())}let r=this.popupEl;r.empty();for(let e=0;e<this.items.length;e++){let t=this.items[e],i=r.createDiv({cls:"stashpad-composer-suggest-row"});e===this.activeIdx&&i.addClass("is-active"),i.createSpan({cls:"stashpad-composer-suggest-label",text:t.label}),t.subtitle&&i.createSpan({cls:"stashpad-composer-suggest-sub",text:t.subtitle}),i.onmousedown=s=>{s.preventDefault(),this.activeIdx=e,this.commit()}}this.position()}refreshActive(){if(!this.popupEl)return;let r=this.popupEl.children;for(let t=0;t<r.length;t++)r[t].toggleClass("is-active",t===this.activeIdx);let e=r[this.activeIdx];e&&e.scrollIntoView({block:"nearest"})}position(){if(!this.popupEl)return;let r=this.ta.getBoundingClientRect(),e=this.popupEl.offsetHeight||200,t=r.top-e-4,i=r.left;this.popupEl.style.left=`${Math.max(8,i)}px`,this.popupEl.style.top=`${Math.max(8,t)}px`,this.popupEl.style.minWidth=`${Math.min(360,r.width)}px`}commit(){if(!this.state||!this.items.length)return;let r=this.items[this.activeIdx];if(!r)return;let e=this.ta.value.slice(0,this.state.replaceStart),t=this.ta.value.slice(this.state.replaceEnd),i=r.insert;this.ta.value=e+i+t;let s=e.length+i.length;this.ta.setSelectionRange(s,s),this.ta.dispatchEvent(new Event("input",{bubbles:!0})),this.close(),this.ta.focus()}close(){this.popupEl&&(this.popupEl.remove(),this.popupEl=null),this.state=null,this.items=[],this.activeIdx=0,this.popScope()}};le();var qs=class extends Fe.ItemView{constructor(e,t){super(e);this.plugin=t;this.displayedPath=null;this.unsubscribeSelection=null;this.renderTimer=null;this.renderToken=0;this.expanded=new Set;this.composerAutocomplete=null;this.composerInputEl=null;this.composerDraft="";this.composerDraftForId=null;this.displayedId=null;this.unsubscribeContent=null}getViewType(){return xt}getDisplayText(){return"Stashpad detail"}getIcon(){return"panel-right"}async onOpen(){this.render(),this.unsubscribeSelection=this.plugin.onStashpadSelectionChange(()=>{this.displayedId=null,this.scheduleRender()}),this.unsubscribeContent=this.plugin.onStashpadContentChange(()=>this.scheduleRender()),this.registerEvent(this.app.workspace.on("active-leaf-change",e=>{!e||e===this.leaf||e.view?.getViewType?.()===z&&this.scheduleRender()})),this.registerEvent(this.app.vault.on("modify",e=>{this.displayedPath&&e.path===this.displayedPath&&this.scheduleRender()})),this.registerEvent(this.app.metadataCache.on("changed",e=>{this.displayedPath&&e.path===this.displayedPath&&this.scheduleRender()}))}async onClose(){this.unsubscribeSelection?.(),this.unsubscribeSelection=null,this.unsubscribeContent?.(),this.unsubscribeContent=null,this.composerAutocomplete?.detach(),this.composerAutocomplete=null,this.composerInputEl=null,this.renderTimer!=null&&(window.clearTimeout(this.renderTimer),this.renderTimer=null)}scheduleRender(){this.renderTimer==null&&(this.renderTimer=window.setTimeout(()=>{this.renderTimer=null,this.containerEl.isConnected&&this.render()},60))}resolveDisplayed(){if(this.displayedId){let i=this.plugin.lastActiveStashpadLeaf?.view;if(i?.getViewType?.()===z&&i.tree?.get){let s=i.tree.get(this.displayedId);if(s?.file)return{folder:i.noteFolder,id:this.displayedId,file:s.file}}this.displayedId=null}let e=this.plugin.getActiveStashpadSelection();if(e)return this.displayedId=e.id,e;let t=this.plugin.lastActiveStashpadLeaf?.view;if(t?.getViewType?.()===z&&t.tree?.get&&t.focusId){let i=t.tree.get(t.focusId);if(i?.file)return{folder:t.noteFolder,id:t.focusId,file:i.file}}return null}async render(){let e=++this.renderToken,t=this.contentEl,i=!!this.composerInputEl&&document.activeElement===this.composerInputEl,s=this.composerInputEl?.selectionStart??null;this.composerAutocomplete?.detach(),this.composerAutocomplete=null,this.composerInputEl=null,t.empty(),t.addClass("stashpad-detail-root");let n=this.resolveDisplayed();if(!n){this.displayedPath=null;let f=t.createDiv({cls:"stashpad-detail-empty"});(0,Fe.setIcon)(f.createSpan({cls:"stashpad-detail-empty-icon"}),"panel-right"),f.createSpan({cls:"stashpad-detail-empty-text",text:"No Stashpad note selected. Cursor or click a note in a Stashpad list to inspect it here."});return}let o=n.file;this.displayedPath=o.path;let a=this.app.metadataCache.getFileCache(o)?.frontmatter??{},l=t.createDiv({cls:"stashpad-detail-scroll"}),c=l.createDiv({cls:"stashpad-detail-header"}),d=c.createDiv({cls:"stashpad-detail-titlerow"});d.createDiv({cls:"stashpad-detail-title",text:this.titleFor(o)});let h=d.createEl("button",{cls:"stashpad-detail-open-btn",attr:{"aria-label":"Open in Stashpad tab"}});(0,Fe.setIcon)(h,"arrow-up-right"),h.title="Open this note in a new Stashpad tab",h.onclick=()=>{this.openInStashpad(n.folder,n.id)};let g=c.createDiv({cls:"stashpad-detail-metarow"});if(n.folder&&g.createSpan({cls:"stashpad-detail-meta-chip",text:n.folder.split("/").pop()||n.folder}),typeof a.color=="string"&&a.color){let f=g.createSpan({cls:"stashpad-detail-meta-color"});f.style.background=a.color,f.title=a.color}if(a.completed===!0&&g.createSpan({cls:"stashpad-detail-meta-chip is-completed",text:"\u2713 completed"}),typeof a.due=="string"||typeof a.due=="number"){let f=Date.parse(String(a.due)),I=Number.isNaN(f)?String(a.due):ct(f,this.plugin.settings);g.createSpan({cls:"stashpad-detail-meta-chip is-due",text:`due ${I}`})}if(Array.isArray(a.tags))for(let f of a.tags.filter(I=>typeof I=="string"))g.createSpan({cls:"stashpad-detail-meta-tag",text:`#${f.replace(/^#/,"")}`});let u=l.createDiv({cls:"stashpad-detail-body"});try{let f=await this.app.vault.cachedRead(o),I=this.stripFrontmatter(f);await Fe.MarkdownRenderer.render(this.app,I,u,o.path,this)}catch(f){u.createDiv({cls:"stashpad-detail-error",text:`Couldn't read \`${o.path}\`: ${f.message}`})}if(e!==this.renderToken)return;this.renderFooterMeta(l,o,a,n),this.renderChildren(l,n.folder,n.id),this.renderComposer(t,n);let p=this.composerInputEl;if(i&&p&&(p.focus(),s!=null)){let f=Math.min(s,p.value.length);try{p.setSelectionRange(f,f)}catch{}}}renderComposer(e,t){let i=e.createDiv({cls:"stashpad-detail-composer"}),s=i.createEl("textarea",{cls:"stashpad-detail-composer-input",attr:{placeholder:"Add a child note\u2026",rows:"2"}});this.composerInputEl=s,this.composerDraftForId===t.id?s.value=this.composerDraft:(this.composerDraft="",this.composerDraftForId=t.id),s.addEventListener("input",()=>{this.composerDraft=s.value,this.composerDraftForId=t.id}),s.addEventListener("keydown",o=>{if(o.key==="Enter"&&!o.shiftKey&&!o.isComposing){if(this.composerAutocomplete?.isOpen?.())return;o.preventDefault(),this.submitComposer(t)}});let n=i.createEl("button",{cls:"stashpad-detail-composer-send",attr:{"aria-label":"Add child note"}});(0,Fe.setIcon)(n.createSpan({cls:"stashpad-detail-composer-send-icon"}),"plus"),n.createSpan({cls:"stashpad-detail-composer-send-text",text:"Add child note"}),n.title="Add child note (Enter)",n.onclick=()=>void this.submitComposer(t),this.composerAutocomplete=new di(this.app,s),this.composerAutocomplete.attach()}async submitComposer(e){let t=(this.composerInputEl?.value??"").trim();if(!t)return;let i=this.plugin.lastActiveStashpadLeaf?.view;if(!i||i.getViewType?.()!==z||typeof i.createNoteUnder!="function"){new Fe.Notice("Open a Stashpad view to add notes.");return}try{await i.createNoteUnder(t,e.id)}catch(s){new Fe.Notice(`Couldn't add note: ${s.message}`);return}this.composerDraft="",this.composerDraftForId=e.id,this.composerInputEl&&(this.composerInputEl.value=""),await this.render(),setTimeout(()=>this.composerInputEl?.focus(),0)}renderChildren(e,t,i){let s=this.plugin.lastActiveStashpadLeaf?.view;if(!s||s.getViewType?.()!==z)return;let n=s.tree;if(!n?.getChildren)return;let o=n.getChildren(i);if(!o||o.length===0)return;let a=e.createDiv({cls:"stashpad-detail-children"});a.createDiv({cls:"stashpad-detail-children-header",text:`Children (${o.length})`});let l=a.createDiv({cls:"stashpad-detail-children-list"});for(let c of o)c.file&&this.renderDetailChildRow(l,s,n,t,c,0)}renderDetailChildRow(e,t,i,s,n,o){if(!n.file)return;let a=this.app.metadataCache.getFileCache(n.file)?.frontmatter??{},l=typeof a.color=="string"?a.color:null,c=a.completed===!0,d=i.getChildren(n.id),h=d.length>0,g=`${s}|${n.id}`,u=this.expanded.has(g),p=e.createDiv({cls:"stashpad-detail-child-row"});c&&p.addClass("is-completed"),o>0&&(p.style.paddingLeft=`${o*16}px`),p.draggable=!0,p.dataset.id=n.id,p.addEventListener("dragstart",B=>{B.dataTransfer?.setData("text/plain",n.id),B.dataTransfer&&(B.dataTransfer.effectAllowed="move"),p.addClass("is-dragging")}),p.addEventListener("dragend",()=>p.removeClass("is-dragging")),p.addEventListener("dragover",B=>{B.preventDefault(),B.dataTransfer&&(B.dataTransfer.dropEffect="move");let w=p.getBoundingClientRect(),y=B.clientY-w.top<w.height/2;p.toggleClass("drop-before",y),p.toggleClass("drop-after",!y)}),p.addEventListener("dragleave",()=>{p.removeClass("drop-before"),p.removeClass("drop-after")}),p.addEventListener("drop",B=>{B.preventDefault(),p.removeClass("drop-before"),p.removeClass("drop-after");let w=B.dataTransfer?.getData("text/plain");if(!w||w===n.id)return;let y=p.getBoundingClientRect(),E=B.clientY-y.top<y.height/2;typeof t.reorderToTarget=="function"&&t.reorderToTarget([w],n.id,E?"before":"after")}),p.oncontextmenu=B=>{B.preventDefault();let w=i.get(n.id);w&&typeof t.openNoteMenu=="function"&&t.openNoteMenu(B,w)};let f=p.createSpan({cls:"stashpad-detail-child-toggle"});if(h&&(Bt(f,d.length,u),f.onclick=B=>{B.stopPropagation(),this.expanded.has(g)?this.expanded.delete(g):this.expanded.add(g),this.render()}),Vt(a,"task")||a.task===!0||a.completed!==void 0){let B=p.createSpan({cls:"stashpad-detail-child-checkbox"});(0,Fe.setIcon)(B,c?"check-square":"square"),B.title=c?"Mark not done":"Mark done",B.addEventListener("mousedown",w=>w.stopPropagation()),B.addEventListener("dblclick",w=>{w.preventDefault(),w.stopPropagation()}),B.onclick=w=>{w.preventDefault(),w.stopPropagation();let y=i.get(n.id);y&&typeof t.toggleCompletedForNode=="function"&&t.toggleCompletedForNode(y)}}let m=p.createSpan({cls:"stashpad-detail-child-icon"});(0,Fe.setIcon)(m,h?"folder-tree":"file-text"),l&&(m.style.color=l);let C=p.createSpan({cls:"stashpad-detail-child-title",text:this.titleFor(n.file)});if(C.onclick=()=>{t?.navigateTo&&t.navigateTo(n.id)},h&&u)for(let B of d)this.renderDetailChildRow(e,t,i,s,B,o+1)}renderFooterMeta(e,t,i,s){let n=e.createDiv({cls:"stashpad-detail-footer-meta"}),o=[];if(typeof i.author=="string"&&i.author&&o.push({label:"Author",value:this.stripWikiLink(i.author)}),Array.isArray(i.contributors)){let a=i.contributors.filter(l=>typeof l=="string").map(l=>this.stripWikiLink(l));a.length>0&&o.push({label:"Contributors",value:a.join(", ")})}if(typeof i.modified=="string"&&o.push({label:"Modified",value:this.formatTime(i.modified)}),typeof i.created=="string"&&o.push({label:"Created",value:this.formatTime(i.created)}),o.push({label:"Path",value:t.path}),o.push({label:"ID",value:s.id}),o.length===0){n.remove();return}for(let a of o){let l=n.createDiv({cls:"stashpad-detail-meta-line"});l.createSpan({cls:"stashpad-detail-meta-key",text:a.label}),l.createSpan({cls:"stashpad-detail-meta-val",text:a.value})}}titleFor(e){return e.basename.replace(/-[a-z0-9]{4,12}$/,"").replace(/-/g," ")}stripFrontmatter(e){if(!e.startsWith("---"))return e;let t=e.indexOf(` ----`,3);return t<0?e:e.slice(t+4).replace(/^\n+/,"")}stripWikiLink(e){let t=e.match(/^\[\[(.+?)(?:\|(.+))?\]\]$/);return t?t[2]??(t[1].split("/").pop()??t[1]).replace(/\.md$/,""):e}formatTime(e){let t=Date.parse(e);return Number.isFinite(t)?ct(t,this.plugin.settings):e}async openInStashpad(e,t){try{let i=await this.plugin.activateViewForFolder(e);t!==k&&this.plugin.navigateLeafTo(i,e,t)}catch(i){new Fe.Notice(`Couldn't open: ${i.message}`)}}};async function Ks(A){let r=A.workspace.getLeavesOfType(xt);if(r.length>0){A.workspace.revealLeaf(r[0]);return}let e=A.workspace.getRightLeaf(!1);if(!e){new Fe.Notice("Stashpad: couldn't open the detail panel.");return}await e.setViewState({type:xt,active:!0}),A.workspace.revealLeaf(e)}var F=require("obsidian");le();var Et=require("obsidian");le();function fc(A,r){let e=A.vault.getAbstractFileByPath(r);if(!(e instanceof Et.TFolder))return[];let t=[],i=[e];for(;i.length;){let s=i.pop();for(let n of s.children)n instanceof Et.TFile?n.extension==="md"&&t.push(n):n instanceof Et.TFolder&&(Si.has(n.name)||i.push(n))}return t}var Js=class{constructor(r){this.app=r;this.nodes=new Map;this.byPath=new Map;this.listeners=new Set;this.orderProvider=null;this.synthetic=new Map;this.coalesceTimer=null;this.nodes.set(k,{id:k,parent:null,children:[],file:null,created:""})}setOrderProvider(r){this.orderProvider=r}onChange(r){return this.listeners.add(r),()=>this.listeners.delete(r)}emit(){for(let r of this.listeners)r()}rebuild(r){r!==void 0&&(this.currentFolder=r);let e=this.currentFolder,t=this.byPath,i=this.nodes;this.nodes=new Map,this.byPath=new Map,this.nodes.set(k,{id:k,parent:null,children:[],file:null,created:""});let s=e?fc(this.app,e):this.app.vault.getMarkdownFiles();for(let n of s){let o=this.app.metadataCache.getFileCache(n)?.frontmatter,a=o?.id;if(!a){let c=t.get(n.path),d=c?i.get(c):void 0,h=this.synthetic.get(n.path),g=d??h;g&&(this.nodes.set(g.id,{...g,children:[],file:n}),this.byPath.set(n.path,g.id));continue}if(this.synthetic.delete(n.path),a===k){let c=this.nodes.get(k);c.file=n,c.created=o?.created??"",this.byPath.set(n.path,k);continue}let l=o?.parent??null;l===a&&(l=null),this.nodes.set(a,{id:a,parent:l??k,children:[],file:n,created:o?.created??""}),this.byPath.set(n.path,a)}for(let n of this.nodes.values()){if(n.id===k)continue;let o=n.parent??k;(this.nodes.get(o)??this.nodes.get(k)).children.push(n.id)}for(let n of this.nodes.values())if(n.children.sort((o,a)=>{let l=this.nodes.get(o),c=this.nodes.get(a);return(l.created||"").localeCompare(c.created||"")}),this.orderProvider){let o=this.orderProvider(n.id);if(o.length>0){let a=new Map;o.forEach((l,c)=>a.set(l,c)),n.children.sort((l,c)=>{let d=a.has(l)?a.get(l):1/0,h=a.has(c)?a.get(c):1/0;return d===h?0:d-h})}}this.emit()}insertSynthetic(r){if(!r.file)return;let e=r.file.path;this.synthetic.set(e,r),this.nodes.set(r.id,r),this.byPath.set(e,r.id);let t=r.parent??k,i=this.nodes.get(t);i&&!i.children.includes(r.id)&&i.children.push(r.id),this.emit()}get(r){return this.nodes.get(r)}getRoot(){return this.nodes.get(k)}getChildren(r){let e=this.nodes.get(r);return e?e.children.map(t=>this.nodes.get(t)).filter(t=>!!t):[]}fileBackedCount(){let r=0;for(let e of this.nodes.values())e.file&&r++;return r}pathTo(r){let e=[],t=new Set,i=this.nodes.get(r);for(;i&&i.id!==k&&!t.has(i.id);)t.add(i.id),e.unshift(i),i=i.parent?this.nodes.get(i.parent):void 0;return e}idForPath(r){return this.byPath.get(r)}snapshot(){let r={};for(let e of this.nodes.values())e.id===k||!e.file||(r[e.id]={parent:e.parent===k?null:e.parent,path:e.file.path});return r}hookMetadataCache(r){let e=l=>{let c=this.currentFolder;return c?typeof l!="string"?!1:l===c||l.startsWith(c+"/"):!0},t=!1,i=()=>{t=!0,this.coalesceTimer==null&&(this.coalesceTimer=window.setTimeout(()=>{this.coalesceTimer=null,t&&(t=!1,this.emit(),r())},16))},s=l=>{l instanceof Et.TFile&&e(l.path)&&l.extension==="md"&&(this.applyChange(l),i())},n=l=>{l instanceof Et.TFile&&e(l.path)&&l.extension==="md"&&this.applyChange(l)&&i()},o=l=>{let c=l?.path;typeof c=="string"&&e(c)&&this.applyDelete(c)&&i()},a=(l,c)=>{if(!(l instanceof Et.TFile))return;let d=typeof c=="string"?c:null;!e(l.path)&&!e(d)||this.applyRename(l,d??"")&&i()};return this.app.metadataCache.on("changed",s),this.app.vault.on("create",n),this.app.vault.on("delete",o),this.app.vault.on("rename",a),()=>{this.app.metadataCache.off("changed",s),this.app.vault.off("create",n),this.app.vault.off("delete",o),this.app.vault.off("rename",a),this.coalesceTimer!=null&&(window.clearTimeout(this.coalesceTimer),this.coalesceTimer=null)}}applyChange(r){if(Wt(r.path))return this.byPath.has(r.path)?this.applyDelete(r.path):!1;let e=this.app.metadataCache.getFileCache(r)?.frontmatter,t=e?.id,i=this.byPath.get(r.path);if(!t){if(i){let c=this.nodes.get(i);if(c&&c.file!==r)return c.file=r,!0}return!1}if(this.synthetic.delete(r.path),t===k){let c=this.nodes.get(k),d=e?.created??"",h=!1;return c.file!==r&&(c.file=r,h=!0),c.created!==d&&(c.created=d,h=!0),this.byPath.get(r.path)!==k&&(this.byPath.set(r.path,k),h=!0),h}let s=e?.parent??k,n=e?.created??"";if(!(s===k||this.nodes.has(s)))return this.rebuild(),!0;if(i&&i!==t)return this.rebuild(),!0;let a=this.nodes.get(t);if(!a){let c={id:t,parent:s,children:[],file:r,created:n};return this.nodes.set(t,c),this.byPath.set(r.path,t),this.attachToParent(c),this.resortChildrenOf(s),!0}let l=!1;if(a.file!==r&&(a.file=r,l=!0),a.created!==n&&(a.created=n,this.resortChildrenOf(a.parent??k),l=!0),a.parent!==s){let c=a.parent??k;this.detachFromParent(a),a.parent=s,this.attachToParent(a),this.resortChildrenOf(c),this.resortChildrenOf(s),l=!0}return l}applyDelete(r){let e=this.byPath.get(r);if(!e)return!1;let t=this.nodes.get(e);if(!t)return this.byPath.delete(r),this.synthetic.delete(r),!0;this.detachFromParent(t);let i=this.nodes.get(k);for(let s of t.children){let n=this.nodes.get(s);n&&(n.parent=k,i.children.includes(s)||i.children.push(s))}return t.children.length>0&&this.resortChildrenOf(k),this.nodes.delete(e),this.byPath.delete(r),this.synthetic.delete(r),!0}applyRename(r,e){let t=this.currentFolder,i=!t||e===t||e.startsWith(t+"/"),s=!t||r.path===t||r.path.startsWith(t+"/");if(!i&&!s)return!1;if(i&&!s)return this.applyDelete(e);if(!i&&s)return this.applyChange(r);if(Wt(r.path))return this.byPath.has(e)?this.applyDelete(e):!1;let n=this.byPath.get(e);if(!n)return this.applyChange(r);this.byPath.delete(e),this.byPath.set(r.path,n);let o=this.nodes.get(n);return o&&(o.file=r),!0}detachFromParent(r){let e=this.nodes.get(r.parent??k);if(!e)return;let t=e.children.indexOf(r.id);t>=0&&e.children.splice(t,1)}attachToParent(r){let e=this.nodes.get(r.parent??k);e||(e=this.nodes.get(k)),e.children.includes(r.id)||e.children.push(r.id)}resortChildrenOf(r){let e=this.nodes.get(r);if(e&&(e.children.sort((t,i)=>{let s=this.nodes.get(t),n=this.nodes.get(i);return(s.created||"").localeCompare(n.created||"")}),this.orderProvider)){let t=this.orderProvider(r);if(t.length>0){let i=new Map;t.forEach((s,n)=>i.set(s,n)),e.children.sort((s,n)=>{let o=i.has(s)?i.get(s):1/0,a=i.has(n)?i.get(n):1/0;return o===a?0:o-a})}}}};var wo=class{constructor(){this.enabled=!1;this.buckets=new Map}record(r,e){if(!this.enabled)return;let t=this.buckets.get(r);t||(t={count:0,total:0,max:0},this.buckets.set(r,t)),t.count+=1,t.total+=e,e>t.max&&(t.max=e)}time(r,e){if(!this.enabled)return e();let t=performance.now();try{return e()}finally{this.record(r,performance.now()-t)}}async timeAsync(r,e){if(!this.enabled)return e();let t=performance.now();try{return await e()}finally{this.record(r,performance.now()-t)}}reset(){this.buckets.clear()}hasData(){return this.buckets.size>0}report(){let r=[...this.buckets.entries()].map(([t,i])=>({label:t,...i,avg:i.total/i.count})).sort((t,i)=>i.total-t.total);if(r.length===0)return"Stashpad perf: no samples (enable profiling, then use the app).";let e=["**Stashpad performance profile**","","| label | count | total (ms) | avg (ms) | max (ms) |","| --- | ---: | ---: | ---: | ---: |"];for(let t of r)e.push(`| \`${t.label}\` | ${t.count} | ${t.total.toFixed(0)} | ${t.avg.toFixed(1)} | ${t.max.toFixed(0)} |`);return e.join(` -`)}},ke=new wo;var nr=".stashpad-order.json",Ai=class{constructor(r){this.app=r;this.cache=new Map;this.pendingTimers=new Map;this.writeInFlight=new Map}async load(r){if(this.cache.has(r))return this.cache.get(r);let e=`${r}/${nr}`,t=this.app.vault.adapter,i={};try{if(await t.exists(e)){let s=await t.read(e),n=JSON.parse(s);if(n&&typeof n=="object"&&!Array.isArray(n))for(let[o,a]of Object.entries(n))Array.isArray(a)&&a.every(l=>typeof l=="string")&&(i[o]=a)}}catch(s){console.warn("Stashpad: order load failed",s)}return this.cache.set(r,i),i}async save(r){this.scheduleWrite(r)}async flush(r){let e=this.pendingTimers.get(r);e!=null&&(window.clearTimeout(e),this.pendingTimers.delete(r)),await this.writeNow(r)}scheduleWrite(r){let e=this.pendingTimers.get(r);e!=null&&window.clearTimeout(e);let t=window.setTimeout(()=>{this.pendingTimers.delete(r),this.writeNow(r)},150);this.pendingTimers.set(r,t)}async writeNow(r){let t=(this.writeInFlight.get(r)??Promise.resolve()).then(()=>this.doWrite(r));this.writeInFlight.set(r,t);try{await t}finally{this.writeInFlight.get(r)===t&&this.writeInFlight.delete(r)}}async doWrite(r){let e=this.cache.get(r)??{},t={};for(let[n,o]of Object.entries(e))o.length>0&&(t[n]=o);this.cache.set(r,t);let i=`${r}/${nr}`,s=this.app.vault.adapter;try{if(Object.keys(t).length===0)try{await s.remove(i)}catch{}else await s.write(i,JSON.stringify(t,null,2))}catch(n){console.warn("Stashpad: order save failed",n)}}getOrder(r,e){return this.cache.get(r)?.[e]?.slice()??[]}setOrder(r,e,t){let i=this.cache.get(r)??{};i[e]=t.slice(),this.cache.set(r,i)}appendChild(r,e,t){let i=this.cache.get(r)??{},s=i[e]??[];s.includes(t)||s.push(t),i[e]=s,this.cache.set(r,i)}removeChild(r,e){let t=this.cache.get(r);if(t)for(let i of Object.values(t)){let s=i.indexOf(e);s>=0&&i.splice(s,1)}}invalidate(r){this.cache.delete(r)}};var hi={manual:"Manual","created-asc":"Created \u2014 oldest first","created-desc":"Created \u2014 newest first","modified-asc":"Modified \u2014 oldest first","modified-desc":"Modified \u2014 newest first","title-az":"Title \u2014 A\u2192Z","title-za":"Title \u2014 Z\u2192A"},Qo=["manual","created-asc","created-desc","modified-asc","modified-desc","title-az","title-za"],Ic=new Set(Qo),or=".stashpad-sort.json",Os=class{constructor(r){this.app=r;this.cache=new Map;this.pendingTimers=new Map;this.writeInFlight=new Map}async load(r){if(this.cache.has(r))return this.cache.get(r);let e=`${r}/${or}`,t=this.app.vault.adapter,i={};try{if(await t.exists(e)){let s=JSON.parse(await t.read(e));if(s&&typeof s=="object"&&!Array.isArray(s))for(let[n,o]of Object.entries(s))typeof o=="string"&&Ic.has(o)&&(i[n]=o)}}catch(s){console.warn("Stashpad: sort load failed",s)}return this.cache.set(r,i),i}async save(r){this.scheduleWrite(r)}async flush(r){let e=this.pendingTimers.get(r);e!=null&&(window.clearTimeout(e),this.pendingTimers.delete(r)),await this.writeNow(r)}scheduleWrite(r){let e=this.pendingTimers.get(r);e!=null&&window.clearTimeout(e);let t=window.setTimeout(()=>{this.pendingTimers.delete(r),this.writeNow(r)},150);this.pendingTimers.set(r,t)}async writeNow(r){let t=(this.writeInFlight.get(r)??Promise.resolve()).then(()=>this.doWrite(r));this.writeInFlight.set(r,t);try{await t}finally{this.writeInFlight.get(r)===t&&this.writeInFlight.delete(r)}}async doWrite(r){let e=this.cache.get(r)??{},t=`${r}/${or}`,i=this.app.vault.adapter;try{if(Object.keys(e).length===0)try{await i.remove(t)}catch{}else await i.write(t,JSON.stringify(e,null,2))}catch(s){console.warn("Stashpad: sort save failed",s)}}getMode(r,e){return this.cache.get(r)?.[e]??"manual"}setMode(r,e,t){let i=this.cache.get(r)??{};t==="manual"?delete i[e]:i[e]=t,this.cache.set(r,i)}removeParent(r,e){let t=this.cache.get(r);t&&delete t[e]}invalidate(r){this.cache.delete(r)}};le();var vt="parentLink",pi="children";var zs=class zs{constructor(r,e){this.app=r;this.getTree=e;this.pending=new Set;this.timer=null;this.activityListeners=new Set;this.errorListeners=new Set}onActivity(r){return this.activityListeners.add(r),()=>this.activityListeners.delete(r)}emitActivity(){let r=this.pending.size;for(let e of this.activityListeners)try{e(r)}catch(t){console.warn("[Stashpad] fmSync activity listener failed",t)}}schedule(r){if(!r||!X().writeRecoveryLinks)return;let e=this.pending.size;this.pending.add(r),this.pending.size!==e&&this.emitActivity(),this.kick()}scheduleParentChange(r,e,t){this.schedule(r),e&&this.schedule(e),this.schedule(t)}scheduleSubtree(r){let e=this.getTree(),t=i=>{this.schedule(i);for(let s of e.getChildren(i))t(s.id)};t(r)}scheduleParentOfDeleted(r){r&&this.schedule(r)}async flush(){for(this.timer!=null&&(window.clearTimeout(this.timer),this.timer=null);this.pending.size>0;){let r=this.pending.values().next().value;if(r===void 0)break;this.pending.delete(r),this.emitActivity(),await this.syncOne(r)}}pendingCount(){return this.pending.size}onError(r){return this.errorListeners.add(r),()=>this.errorListeners.delete(r)}emitError(r,e){for(let t of this.errorListeners)try{t(r,e)}catch(i){console.warn("[Stashpad] fmSync error listener failed",i)}}reset(){this.timer!=null&&(window.clearTimeout(this.timer),this.timer=null),this.pending.clear()}kick(){this.timer!=null||this.pending.size===0||(this.timer=window.setTimeout(()=>this.tick(),zs.PACING_MS))}async tick(){this.timer=null;let r=this.pending.values().next().value;if(r!==void 0){this.pending.delete(r),this.emitActivity();try{await this.syncOne(r)}catch(e){console.warn("[Stashpad] frontmatter sync tick failed",e)}this.pending.size>0&&this.kick()}}wouldWrite(r){let t=this.getTree().get(r);if(!t||!t.file)return!1;let i=this.computeParentLink(t),s=this.computeChildrenLinks(t),n=this.app.metadataCache.getFileCache(t.file)?.frontmatter,o=n&&typeof n[vt]=="string"?n[vt]:null,a=n?.[pi],l=Array.isArray(a)?a.filter(c=>typeof c=="string"):[];if((o??null)!==(i??null)||l.length!==s.length)return!0;for(let c=0;c<l.length;c++)if(l[c]!==s[c])return!0;return!1}async syncOne(r){let t=this.getTree().get(r);if(!t||!t.file||!this.wouldWrite(r))return;let i=this.computeParentLink(t),s=this.computeChildrenLinks(t);try{await ke.timeAsync("write.fmSync",()=>this.app.fileManager.processFrontMatter(t.file,n=>{i?n[vt]=i:delete n[vt],s.length>0?n[pi]=s:delete n[pi]}))}catch(n){console.warn("[Stashpad] frontmatter sync failed",t.file?.path,n),this.emitError(t.file.path,n)}}computeParentLink(r){if(r.id===k)return null;let e=this.getTree();if(!r.parent||r.parent===k){let i=e.getRoot();return i&&i.id!==r.id&&i.file?Ys(i.file.path):null}let t=e.get(r.parent);return t?.file?Ys(t.file.path):null}computeChildrenLinks(r){return this.getTree().getChildren(r.id).filter(t=>!!t.file).map(t=>Ys(t.file.path))}};zs.PACING_MS=100;var $s=zs;async function gi(A,r){let e=r.endsWith("/")?r:r+"/",t=new Map,i=new Map;for(let c of A.vault.getMarkdownFiles()){if(c.path!==r&&!c.path.startsWith(e))continue;let d=A.metadataCache.getFileCache(c)?.frontmatter,h=typeof d?.id=="string"?d.id:null;if(!h)continue;let g=typeof d?.parent=="string"?d.parent:k;t.set(h,{file:c,id:h,parent:g});let u=i.get(g)??[];u.push(h),i.set(g,u)}let s=c=>Ys(c.file.path),n=c=>{if(c.id===k)return null;if(c.parent===k||!c.parent){let h=t.get(k);return h?s(h):null}let d=t.get(c.parent);return d?s(d):null},o=c=>{let d=i.get(c.id)??[],h=[];for(let g of d){let u=t.get(g);u&&h.push(s(u))}return h},a=0,l=0;for(let c of t.values()){a+=1;let d=n(c),h=o(c),g=A.metadataCache.getFileCache(c.file)?.frontmatter,u=g&&typeof g[vt]=="string"?g[vt]:null,p=g?.[pi],f=Array.isArray(p)?p.filter(C=>typeof C=="string"):[],I=(u??null)===(d??null),m=f.length===h.length&&f.every((C,B)=>C===h[B]);if(!(I&&m))try{await A.fileManager.processFrontMatter(c.file,C=>{d?C[vt]=d:delete C[vt],h.length>0?C[pi]=h:delete C[pi]}),l+=1,await new Promise(C=>setTimeout(C,50))}catch(C){console.warn("[Stashpad] rebootstrap fm sync failed",c.file.path,C)}}return{checked:a,written:l}}function Ys(A){return`[[${A.replace(/\.md$/i,"")}]]`}qe();si();var Vs=class{constructor(r,e){this.tree=r;this.log=e}async sweep(r){let e=await this.log.readState(),t=this.tree.snapshot(),i=n=>{if(!r)return!0;let o=r.replace(/\/+$/,"");return n===o||n.startsWith(o+"/")};for(let[n,o]of Object.entries(t)){let a=e[n];a?a.parent!==o.parent?await this.log.append({type:"parent_change",id:n,payload:{from:a.parent,to:o.parent}}):a.path!==o.path&&await this.log.append({type:"rename",id:n,payload:{from:a.path,to:o.path}}):await this.log.append({type:"create",id:n,payload:{path:o.path,parent:o.parent}})}for(let[n,o]of Object.entries(e))!t[n]&&i(o.path)&&await this.log.append({type:"missing",id:n,payload:{lastPath:o.path}});let s={};for(let[n,o]of Object.entries(e))i(o.path)||(s[n]=o);for(let[n,o]of Object.entries(t))s[n]=o;await this.log.writeState(s)}};var ue=require("obsidian");le();Jn();var ye=ue.moment;function Eo(A){let r=A.trim().toLowerCase();if(!r)return null;let e=Date.now(),t=p=>ye(p).startOf("day").valueOf(),i=/\b(\d{1,2})(?::(\d{2}))?\s*(am|pm)?\b/,s=0,n=!1,o=r,a=i.exec(r);if(a&&(a[3]||a[2])){let p=parseInt(a[1],10),f=a[2]?parseInt(a[2],10):0,I=a[3];I==="pm"&&p<12&&(p+=12),I==="am"&&p===12&&(p=0),p>=0&&p<24&&f>=0&&f<60&&(s=(p*3600+f*60)*1e3,n=!0,o=r.replace(a[0]," ").replace(/\s+/g," ").trim())}let l=p=>n?p+s:p;if(o===""||o==="today")return l(t(e));if(o==="yesterday")return l(t(e)-864e5);if(o==="tomorrow")return l(t(e)+864e5);let c=["sunday","monday","tuesday","wednesday","thursday","friday","saturday"],d=["sun","mon","tue","wed","thu","fri","sat"],h=c.indexOf(o)>=0?c.indexOf(o):d.indexOf(o);if(h>=0){let p=ye().startOf("day"),I=(p.day()-h+7)%7;return l(p.subtract(I,"days").valueOf())}let g=/^(\d+)\s*([dwmy])$/.exec(o);if(g){let p=parseInt(g[1],10),f={d:"days",w:"weeks",m:"months",y:"years"}[g[2]];return l(ye().subtract(p,f).startOf("day").valueOf())}let u=ye(o,["YYYY-MM-DD","YYYY/MM/DD","MM-DD-YYYY","MM/DD/YYYY","M-D-YYYY","M/D/YYYY"],!0);return u.isValid()?l(u.startOf("day").valueOf()):null}function vo(A){let r={text:[],filters:{}},e=(A||"").trim();if(!e)return r;let t=/\b(in|before|after|on):\s*(?:\[([^\]]*)\]|([^]*?)(?=\s+(?:in|before|after|on):|$))/gi,i=e,s;for(;(s=t.exec(e))!=null;){let n=s[1].toLowerCase(),o=(s[2]!==void 0?s[2]:s[3]??"").trim();if(o){if(n==="in")r.filters.in=o.toLowerCase();else if(n==="before"){let a=Eo(o);a!=null&&(r.filters.before=a)}else if(n==="after"){let a=Eo(o);a!=null&&(r.filters.after=a)}else if(n==="on"){let a=Eo(o);if(a!=null)if(/\b\d{1,2}:\d{2}\b|\b\d{1,2}\s*(?:am|pm)\b/i.test(o))r.filters.on={start:a-6e4,end:a+6e4};else{let c=ye(a).startOf("day").valueOf();r.filters.on={start:c,end:c+864e5}}}i=i.replace(s[0]," ")}}for(let n of i.split(/\s+/))n&&r.text.push(n.toLowerCase());return r}var tt=class A extends ue.SuggestModal{constructor(e,t,i,s){super(e);this.tree=t;this.titleFn=i;this.opts=s;this.notes=[];this.includeExcluded=!1;this.pendingCleanups=[];this.crossFolderIndex=null;this.whenBuilderEl=null;this.whenMode="on";this.whenSingleText="";this.whenBetweenEndText="";this.setPlaceholder(s.placeholder??(s.mode==="search"?"Search notes\u2026":"Pick a note\u2026")),this.loadAll()}onClose(){for(;this.pendingCleanups.length>0;){let e=this.pendingCleanups.pop();try{e&&e()}catch{}}try{this.opts.onClose?.()}catch{}}loadAll(){let e=this.tree.getRoot(),t=e.file?.parent?.name?.trim()??"",i=t?`Home \u2014 ${t}`:"Home",s=(n,o)=>{let a=this.tree.get(n);a?.file&&n!==k?this.notes.push({node:a,title:`${" ".repeat(o)}${this.titleFn(a)}`,body:""}):a?.file&&n===k&&this.notes.push({node:a,title:i,body:""});for(let l of this.tree.getChildren(n))s(l.id,o+1)};e.file&&this.notes.push({node:e,title:i,body:""});for(let n of this.tree.getChildren(k))s(n.id,1);if(this.opts.lockedNotes)for(let n of this.opts.lockedNotes()){let o=(this.opts.hideLockedTitles??!1)||!n.title;this.notes.push({node:null,title:o?"Locked note":n.title,body:"",locked:n})}for(let n of this.notes)n.node?.file&&this.app.vault.cachedRead(n.node.file).then(o=>{n.body=this.stripFm(o)});if(this.opts.crossFolderNotes){let n=this.opts.crossFolderNotes();for(let o of n)this.notes.push({node:null,title:o.title,body:o.body,cross:o});for(let o of this.notes)!o.cross||o.body||o.cross.file&&this.app.vault.cachedRead(o.cross.file).then(a=>{o.body=this.stripFm(a)})}}createdMsFor(e){if(e.node?.created){let t=Date.parse(e.node.created);if(!Number.isNaN(t))return t}if(e.locked?.created){let t=Date.parse(e.locked.created);if(!Number.isNaN(t))return t}return e.cross?.file?.stat?.ctime!=null?e.cross.file.stat.ctime:null}stripFm(e){if(!e.startsWith("---"))return e;let t=e.indexOf(` ----`,3);return t===-1?e:e.slice(t+4).replace(/^\r?\n/,"")}getSuggestions(e){let t=e.trim().toLowerCase(),i=vo(e),s=i.text;console.log("[Stashpad] search query parsed",{query:e,text:i.text,filters:i.filters});let n=m=>{if(!s.length)return!0;for(let C of s)if(!m.includes(C))return!1;return!0},o=null,a=null;if(i.filters.in){let m=i.filters.in.split(/\s+/).filter(Boolean);o=new Set,a=new Map;let C=y=>m.every(E=>y.toLowerCase().includes(E)),B=y=>{if(!o.has(y)){o.add(y);for(let E of this.tree.getChildren(y))B(E.id)}};for(let y of this.notes)!y.node||y.cross||C(y.title)&&B(y.node.id);let w=new Map;for(let y of this.notes){if(!y.cross)continue;let E=w.get(y.cross.folder)??[];E.push(y),w.set(y.cross.folder,E)}for(let[y,E]of w){let v=new Map;for(let x of E){let H=x.cross?.parentId??null;if(!H)continue;let G=v.get(H)??[];G.push(x.cross.id),v.set(H,G)}let Q=new Set;a.set(y,Q);let S=x=>{if(!Q.has(x)){Q.add(x);for(let H of v.get(x)??[])S(H)}};for(let x of E)C(x.title)&&S(x.cross.id)}}let l=m=>{let C=i.filters;if(o||a)if(m.cross){let B=a?.get(m.cross.folder);if(!B||!B.has(m.cross.id))return!1}else if(m.node){if(!o||!o.has(m.node.id))return!1}else return!1;if(C.before||C.after||C.on){let B=this.createdMsFor(m);if(B==null||C.before!=null&&B>=C.before||C.after!=null&&B<=C.after||C.on&&(B<C.on.start||B>=C.on.end))return!1}return!0},c=m=>n(m.toLowerCase()),d=this.notes.filter(m=>!m.cross),h=this.notes.filter(m=>m.cross),g=(m,C)=>({id:m.locked?`locked:${m.locked.blob}`:m.cross?`cross:${m.cross.folder}:${m.cross.id}`:m.node.id,label:m.title,node:m.node,kind:"note",bodyPreview:this.previewFromBody(m.body,C),matchLine:C,crossFolder:m.cross?.folder,crossFile:m.cross?.file,crossId:m.cross?.id,locked:m.locked}),u=m=>{let C=[];for(let B of m)if(l(B))if(this.opts.mode==="search"){if(!t){C.push(g(B,-1));continue}let w=n(B.title.toLowerCase()),y=B.body.split(/\r?\n/),E=[];for(let Q=0;Q<y.length;Q++)c(y[Q])&&E.push(Q);let v=E.length>0||n(B.body.toLowerCase());if(!w&&!v)continue;if(E.length===0)C.push(g(B,-1));else{let Q=[],S=-100;for(let H of E)H-S>5&&Q.push(H),S=H;let x=5;for(let H of Q.slice(0,x))C.push(g(B,H))}}else{if(t&&!n(B.title.toLowerCase())&&!n(B.body.toLowerCase()))continue;C.push(g(B,-1))}return C},p=u(d),f=[...p];if(this.opts.mode==="search"?t?!0:p.length<10:!!t){let m=u(h);this.opts.mode==="search"&&!t&&(f.length=Math.min(f.length,50)),f.push(...m)}if(this.opts.folderResults){let m=this.opts.folderResults(),C={id:"__folder_picker__",label:"Open folder in a new tab\u2026",node:null,kind:"folder-open"};m.length>0&&(t?m.some(B=>{let w=B.split("/").pop()??B;return n(`${B.toLowerCase()} ${w.toLowerCase()}`)})&&f.push(C):f.unshift(C))}if(this.opts.allowCreate&&t&&!f.some(m=>m.label.trim().toLowerCase()===t)&&f.push({id:"__create__",label:`Create new: "${e}"`,node:null,kind:"create"}),i.text.join("").length>=3){let m=B=>B.kind==="note"&&(B.id===k||B.crossId===k)&&n(B.label.toLowerCase()),C=f.filter(m);if(C.length>0){let B=f.filter(w=>!m(w));f.splice(0,f.length,...C,...B)}}return this.opts.excludedFolderNotes&&!this.includeExcluded&&f.push({id:"__search_excluded__",label:"Search excluded Stashpad folders",node:null,kind:"search-excluded"}),f}loadExcludedNotes(){if(this.includeExcluded||!this.opts.excludedFolderNotes)return;this.includeExcluded=!0;for(let t of this.opts.excludedFolderNotes())this.notes.push({node:null,title:t.title,body:t.body,cross:t});for(let t of this.notes)!t.cross||t.body||!t.cross.file||this.app.vault.cachedRead(t.cross.file).then(i=>{t.body=this.stripFm(i)});let e=this.inputEl;e&&(e.dispatchEvent(new Event("input",{bubbles:!0})),setTimeout(()=>e.dispatchEvent(new Event("input",{bubbles:!0})),250))}previewFromBody(e,t){let i=e.split(/\r?\n/);if(t<0)return i.filter(a=>a.trim().length>0).slice(0,3).join(` +\u2022 Discard trash \u2014 let them be permanently erased with the key.`,"Decrypt Trash",y=>{y?B():w()},"Discard trash").open();return}C("",!1)},I=!1;p.setButtonText("Remove\u2026").onClick(async()=>{if(!I){I=!0,p.setDisabled(!0),p.buttonEl.addClass("stashpad-btn-loading");try{await u()}finally{I=!1,p.setDisabled(!1),p.buttonEl.removeClass("stashpad-btn-loading")}}}),p.buttonEl.addClass("mod-warning")}),(Mc||e.hasSharedPassword())&&(new T.Setting(i).setName("Sharing").setHeading(),e.isUnlocked()?e.hasSharedPassword()?new T.Setting(i).setName("Shared password").setDesc("ON \u2014 anyone who knows it can unlock this vault (no approval). Share it via a password manager or secure message; don't send it in the clear.").addButton(p=>p.setButtonText("Change\u2026").onClick(()=>{new de(this.app,{mode:"setup",offerKeychain:!0,kdfProbe:l,title:"Change shared password",intro:"Everyone who unlocks with the shared password will need the new one. Re-share it securely after changing.",onSubmit:async({next:u,remember:I})=>{if(!u)return"Enter a password.";try{await e.setSharedPassword(u,I)}catch(m){return m.message}return new T.Notice("Shared password updated."),this.update?.(),null}}).open()})).addButton(p=>{p.setButtonText("Turn off").onClick(async()=>{await e.removeSharedPassword(),new T.Notice("Shared password turned off."),this.update?.()}),p.buttonEl.addClass("mod-warning")}):new T.Setting(i).setName("Shared password").setDesc("OFF \u2014 set one passphrase that everyone types to unlock (the simplest way to share). Anyone who knows it can unlock; turning it off later doesn't claw back copies already synced elsewhere.").addButton(p=>p.setButtonText("Set shared password\u2026").onClick(()=>{new de(this.app,{mode:"setup",offerKeychain:!0,kdfProbe:l,title:"Set shared password",intro:"One passphrase everyone types to unlock this vault. Anyone who knows it gets in \u2014 share it ONLY through a password manager or secure message, never in the clear. You can turn it off later.",onSubmit:async({next:u,remember:I})=>{if(!u)return"Enter a password.";try{await e.setSharedPassword(u,I)}catch(m){return m.message}return new T.Notice("Shared password set \u2014 share it securely with your collaborators."),this.update?.(),null}}).open()})):i.createEl("p",{cls:"setting-item-description"}).setText("Unlock encryption above to manage how this vault is shared.")),Nc)if(new T.Setting(i).setName("Collaborators (device approval)").setHeading(),i.createEl("p",{cls:"setting-item-description"}).setText("Everyone who can unlock this vault, and devices waiting for access. Approving a request shares the vault key with that device \u2014 it never sees a password. Removing a member revokes future access (existing synced copies they already hold aren't clawed back \u2014 rotate the key for that, a future feature)."),new T.Setting(i).setName("Synced from the vault keyfile").addButton(u=>u.setButtonText("Refresh").onClick(async()=>{await e.refresh(),this.update?.()})),!e.isUnlocked())i.createEl("p",{cls:"setting-item-description"}).setText("Unlock encryption above to approve or remove collaborators.");else{let u=e.myIdentityId();for(let m of e.members()){let C=m.id===u,B=new T.Setting(i).setName(`${m.label}${C?" \u2014 this device":""}`).setDesc(C?"You":"Member");C||B.addButton(w=>{w.setButtonText("Remove").onClick(async()=>{await e.removeMember(m.id),new T.Notice(`Removed ${m.label}. (Not full revocation without rotating the key.)`),this.update?.()}),w.buttonEl.addClass("mod-warning")})}let I=e.pendingJoinRequests();if(I.length===0)i.createEl("p",{cls:"setting-item-description"}).setText("No pending access requests.");else for(let m of I)new T.Setting(i).setName(m.label).setDesc("Wants access to this vault").addButton(C=>C.setButtonText("Approve").setCta().onClick(async()=>{try{await e.approveJoinRequest(m.id)}catch(B){new T.Notice(B.message);return}new T.Notice(`Approved ${m.label} \u2014 they can unlock once the keyfile syncs to them.`),this.update?.()})).addButton(C=>C.setButtonText("Deny").onClick(async()=>{await e.denyJoinRequest(m.id),new T.Notice("Request denied."),this.update?.()}))}},["encryption","encrypt","password","passphrase","lock","unlock","key","security","private","collaborator","share","team","member","approve"])),t.push(this.headingDef("Trash & title defaults","Vault-wide defaults for deleted notes and locked-note titles. Per-folder overrides live under \u201CPer-Folder Passwords\u201D below.")),t.push(this.renderDef("Encrypt items sent to trash","When ON, deleting a note sends it to Stashpad's encrypted trash (recoverable with your password) instead of a plaintext trash. Default OFF.",i=>i.addToggle(s=>s.setValue(this.plugin.settings.encryptTrash).onChange(async n=>{if(n&&!this.encryptionOrOnboard()){this.update?.();return}this.plugin.settings.encryptTrash=n,await this.plugin.saveSettings()})),["trash","delete","encrypt"])),t.push(this.renderDef("Encrypt trash filenames","Hide the filename + origin folder of encrypted-trashed items (opaque names on disk; shown under \u201CHidden\u201D in the trash tab). Default OFF so you can still tell what to restore when working outside the app.",i=>i.addToggle(s=>s.setValue(this.plugin.settings.encryptTrashFilenames).onChange(async n=>{if(n&&!this.encryptionOrOnboard()){this.update?.();return}this.plugin.settings.encryptTrashFilenames=n,await this.plugin.saveSettings()})),["trash","filename","encrypt"])),t.push(this.renderDef("Remind me about unlocked encrypted notes","On startup, if notes that WERE encrypted are still sitting plaintext (you unlocked them, or restored them from the trash), show a notice with a Review button that opens the \u201CPreviously encrypted\u201D view. Nothing is ever re-encrypted automatically.",i=>i.addToggle(s=>s.setValue(this.plugin.settings.reEncryptNudge??!1).onChange(async n=>{this.plugin.settings.reEncryptNudge=n,await this.plugin.saveSettings()})),["remind","nudge","re-encrypt","watchlist","unlocked","sweep"])),t.push(this.renderDef("Auto re-encrypt after idle minutes (global)","0 = OFF (default). After an unlocked, previously-encrypted note sits idle this long, Stashpad shows a \u201CRe-encrypting in 10s\u2026 [Keep unlocked]\u201D countdown you can cancel, then re-locks it. It NEVER re-encrypts a note you have open in an editor. Set per-folder overrides under \u201CPer-Folder Passwords\u201D; per-note in the \u201CPreviously encrypted\u201D view.",i=>i.addText(s=>s.setValue(String(this.plugin.settings.reEncryptAfterMin??0)).onChange(async n=>{{let o=Number(n.trim());if(n.trim()==="")this.plugin.settings.reEncryptAfterMin=0;else if(Number.isFinite(o)&&o>=0)this.plugin.settings.reEncryptAfterMin=Math.floor(o);else{s.setValue(String(this.plugin.settings.reEncryptAfterMin??0));return}await this.plugin.saveSettings()}})),["auto","re-encrypt","idle","timer","peek","lock"])),t.push(this.headingDef("Archive","Every folder has its own \u201Carchive\u201D subfolder: \u201CMove selection to archive\u201D tucks notes into it, archived notes drop out of search, and everything shows up in the aggregated Archived view. Whether a folder's archive is encrypted is set per folder under \u201CPer-Folder Passwords\u201D below (\u201CEncrypt archived notes\u201D).")),t.push(this.sectionDef("Per-Folder Passwords","Give a folder its own password (a separate key) so you can share just that folder with collaborators. Folders without their own password use the vault password.",i=>{this.renderPerFolderEncryption(i)},["folder","per-folder","password","encrypt","key","share","collaborator","archive","unlock","lock"])),t}hotkeyItems(){let e={name:"Hotkeys",desc:"Each command has up to two slots. Click a slot and press a key (or chord) to bind it; press Backspace (delete on Mac) to cancel without binding; or click \u2715 to clear an existing binding. A \u21BA icon appears on any slot that differs from its shipped default \u2014 click it to revert that slot. When both slots are set, the pill on the right decides which one is active.",searchable:!1},t=hi.map(i=>({name:i.label,desc:i.desc,aliases:["hotkey","shortcut","keybind","binding","key"],render:s=>this.renderBindingRow(s,i)}));return[e,...t]}renderJdIndexSection(e){let t=new T.Setting(e).setName("JD Index Builder").setHeading();t.settingEl.id="stashpad-jd-index-section";let i=e.createEl("div",{cls:"stashpad-settings-section"}).createEl("p",{cls:"setting-item-description"});i.appendText("Builds a Johnny-Decimal-style index inside a designated Stashpad folder. Two commands:"),i.createEl("br"),i.createEl("strong",{text:"Preview"}),i.appendText(" overwrites the designated folder\u2019s HOME note body with the would-be hierarchy + everything that didn\u2019t match. Frontmatter is preserved; everything below it is replaced."),i.createEl("br"),i.createEl("strong",{text:"Build"}),i.appendText(" creates an actual hierarchy of Stashpad notes (one per prefix), with child\u2192parent relationships matching the dotted segments."),i.createEl("br"),i.appendText("Matches strict prefixes only: all-digits ("),i.createEl("code",{text:"10 Life"}),i.appendText(") or alphanumeric-with-dots ("),i.createEl("code",{text:"1.2 Family"}),i.appendText(", "),i.createEl("code",{text:"animal.duck.yellow Eggs"}),i.appendText("). Mixed schemes sort numbers first, then alphabetically.");let s=this.plugin.discoverStashpadFolders();new T.Setting(e).setName("Scope").setDesc("Scan the whole vault, or restrict to a single folder + its descendants.").addDropdown(l=>{l.addOption("vault","Entire vault"),l.addOption("folder","Single folder"),l.setValue(this.plugin.settings.jdIndexScope??"vault"),l.onChange(async c=>{this.plugin.settings.jdIndexScope=c==="folder"?"folder":"vault",await this.plugin.saveSettings(),this.display()})}),(this.plugin.settings.jdIndexScope??"vault")==="folder"&&new T.Setting(e).setName("Scope folder").setDesc("Vault-relative path. Leave empty to fall back to the entire vault.").addText(l=>{new li(this.app,l.inputEl),l.setPlaceholder("Path/To/Folder"),l.setValue(this.plugin.settings.jdIndexScopeFolder??""),l.onChange(async c=>{this.plugin.settings.jdIndexScopeFolder=(c||"").trim().replace(/^\/+|\/+$/g,""),await this.plugin.saveSettings()})}),new T.Setting(e).setName("Include Stashpad folders in scan").setDesc("By default, notes inside any known Stashpad folder are excluded \u2014 the index destination shouldn't index itself, and other Stashpad folders are usually already organized. Toggle on if you want them included anyway.").addToggle(l=>{l.setValue(this.plugin.settings.jdIndexIncludeStashpadFolders===!0),l.onChange(async c=>{this.plugin.settings.jdIndexIncludeStashpadFolders=c,await this.plugin.saveSettings(),this.display()})}),new T.Setting(e).setName("Designated Stashpad folder for Index").setDesc("Required. Must be a Stashpad folder. The index hierarchy is built here. New notes are created; nothing is deleted.").addText(l=>{new li(this.app,l.inputEl),l.setPlaceholder(s[0]??"(pick a Stashpad folder)"),l.setValue(this.plugin.settings.jdIndexStashpadFolder??""),l.onChange(async c=>{this.plugin.settings.jdIndexStashpadFolder=(c||"").trim().replace(/^\/+|\/+$/g,""),await this.plugin.saveSettings()})}),new T.Setting(e).setName("Sort").setDesc("Order of entries within the same depth. Natural: numbers first then alphabetical (recommended). Created: by source file's creation time \u2014 handy when prefixes are word-only and don't carry ordering.").addDropdown(l=>{l.addOption("natural","Natural (numeric \u2192 alphabetical)"),l.addOption("created","By creation time"),l.setValue(this.plugin.settings.jdIndexSort??"natural"),l.onChange(async c=>{this.plugin.settings.jdIndexSort=c==="created"?"created":"natural",await this.plugin.saveSettings()})});let n=ai(this.app,this.plugin,this.plugin.settings),o=e.createEl("div",{cls:"stashpad-settings-section"}).createEl("p",{cls:"setting-item-description"}),a=n.skippedStashpadNotes.length>0?` (${n.skippedStashpadNotes.length} Stashpad-folder note${n.skippedStashpadNotes.length===1?"":"s"} excluded by default)`:"";o.setText(`Preview: ${n.indexed.length} note${n.indexed.length===1?"":"s"} would be indexed, ${n.nonIndex.length} would NOT be indexed${a}.`),new T.Setting(e).setName("Actions").setClass("stashpad-jd-actions").setDesc("Preview aggressively overwrites the designated folder's HOME note body (frontmatter preserved). Build creates Stashpad notes (existing notes with the same jdPrefix are updated, not duplicated).").addButton(l=>{l.setButtonText("Preview"),l.setTooltip("Overwrites the designated Stashpad folder's HOME note body with the preview."),l.onClick(async()=>{try{let c=await Ms(this.app,this.plugin,this.plugin.settings);if(c.error==="no-dest"){new T.Notice("Set a Designated Stashpad folder for Index first.",5e3);return}if(c.error==="no-home"){new T.Notice(`"${this.plugin.settings.jdIndexStashpadFolder}" doesn't have a Stashpad home note. Open the folder in Stashpad first (it creates one automatically).`,7e3);return}Ts(this.app,c),this.display()}catch(c){console.error("[stashpad] preview failed",c),new T.Notice(`Preview failed: ${c?.message??c}`,8e3)}})}).addButton(l=>{l.setButtonText("Build Stashpad notes"),l.setCta(),l.setTooltip("Create the Stashpad-note hierarchy. Existing notes with matching jdPrefix are updated."),l.onClick(()=>{if(!(this.plugin.settings.jdIndexStashpadFolder??"").trim().replace(/^\/+|\/+$/g,"")){new T.Notice("Set a Designated Stashpad folder for Index first.",5e3);return}new Wi(this.app,this.plugin,this.plugin.settings,n.indexed.length,async()=>{try{let h=await Ao(this.app,this.plugin,this.plugin.settings);if(h.error==="no-dest"){new T.Notice("Set a Designated Stashpad folder for Index first.",5e3);return}if(h.error==="dest-not-stashpad"){new T.Notice(`"${h.destFolder}" isn't a known Stashpad folder. Pick a real Stashpad folder (or create one first).`,7e3);return}this.plugin.settings.jdIndexHasBuilt=!0,await this.plugin.saveSettings(),new T.Notice(`Built: ${h.created} created, ${h.updated} updated, ${h.skipped} skipped \u2192 ${h.destFolder}`,6e3),this.display()}catch(h){console.error("[stashpad] build failed",h),new T.Notice(`Build failed: ${h?.message??h}`,8e3)}}).open()})}).addButton(l=>{l.setButtonText(`Reveal in ${xc()}`),l.setTooltip("Open the designated Stashpad folder in your OS file browser."),l.onClick(async()=>{let c=(this.plugin.settings.jdIndexStashpadFolder??"").trim().replace(/^\/+|\/+$/g,"");if(!c){new T.Notice("Set a Designated Stashpad folder for Index first.",5e3);return}if(!this.app.vault.getAbstractFileByPath(c)){new T.Notice(`Folder "${c}" doesn't exist yet.`,5e3);return}try{let h=this.app.vault.adapter.basePath;if(h){let{shell:g}=window.require?.("electron")??{},f=`${h}/${c}`;g?.openPath?.(f)}else new T.Notice("Reveal in file system not supported on this platform.",4e3)}catch(h){new T.Notice(`Couldn't open folder: ${h?.message??h}`,5e3)}})})}renderColorAliasesSection(e){let t=this.plugin.discoverStashpadFolders();if(t.length===0){new T.Setting(e).setName("Color aliases per Stashpad").setDesc("No Stashpads discovered yet \u2014 create one above first.");return}let i=(()=>{let o=pt()?.noteFolder;return o&&t.includes(o)?o:t[0]})();new T.Setting(e).setName("Color aliases per Stashpad").setDesc("Which Stashpad's colors to label.").addDropdown(o=>{for(let a of t)o.addOption(a,a);o.setValue(i),o.onChange(a=>{i=a,n()})}),e.createEl("p",{cls:"setting-item-description",text:"Give each per-note color a friendly name. Filters and pickers display the alias instead of the hex code; the underlying color stays the same. The same hex in two Stashpads can have different aliases."});let s=e.createDiv({cls:"stashpad-color-aliases-list"}),n=()=>{s.empty();let o=this.plugin.collectColorsInFolder(i),a=new Map(o.map(h=>[h.hex,h.count])),l=this.plugin.settings.colorAliases?.[i.replace(/\/+$/,"")]??{},c=new Set([...a.keys(),...Object.keys(l)]);if(c.size===0){s.createEl("p",{cls:"setting-item-description",text:`No colors used or aliased in "${i}" yet. Set a per-note color (Shift+: or right-click \u2192 Set color) and it'll appear here.`});return}let d=[...c].map(h=>({hex:h,count:a.get(h)??0}));d.sort((h,g)=>g.count-h.count||h.hex.localeCompare(g.hex));for(let h of d)this.renderColorAliasRow(s,i,h.hex,h.count,n)};n()}authorshipItems(){let e=[];e.push(this.renderDef("Author name","Your display name. Used in the note footer + as the author/contributor link target. Leave blank to opt out (notes won't be stamped).",i=>i.addText(s=>s.setValue(this.plugin.settings.authorName).onChange(async n=>{this.plugin.settings.authorName=n.trim(),this.plugin.settings.authorName&&!this.plugin.settings.authorId&&(this.plugin.settings.authorId=Z()),await this.plugin.saveSettings(),await this.plugin.syncAuthorFilesToName()})),["author","name","identity","stamp"])),e.push(this.renderDef("Author id (auto-assigned)","Stable id appended to your name on links so coworkers with the same name don't collide. Generated once and shouldn't change. To reset it, clear and retype your author name.",i=>i.addText(s=>s.setValue(this.plugin.settings.authorId).setDisabled(!0)),["author","id"])),e.push(this.renderDef("Title / role",'Optional. Shown on your author page (e.g. "Engineer", "PM", "Designer").',i=>i.addText(s=>s.setValue(this.plugin.settings.authorRole).onChange(async n=>{this.plugin.settings.authorRole=n.trim(),await this.plugin.saveSettings(),await this.plugin.syncAuthorFilesToName()})),["role","title","job"])),e.push(this.renderDef("Department / team",'Optional. Shown on your author page (e.g. "Engineering", "Growth").',i=>i.addText(s=>s.setValue(this.plugin.settings.authorDepartment).onChange(async n=>{this.plugin.settings.authorDepartment=n.trim(),await this.plugin.saveSettings(),await this.plugin.syncAuthorFilesToName()})),["department","team"]));let t=(i,s,n,o)=>this.renderDef(i,"",a=>a.addToggle(l=>l.setValue(s()).onChange(async c=>{n(c),await this.plugin.saveSettings()})),o);return e.push(t("Show author in note footer",()=>this.plugin.settings.showAuthor,i=>{this.plugin.settings.showAuthor=i},["author","footer","show"])),e.push(t("Show contributors in note footer",()=>this.plugin.settings.showContributors,i=>{this.plugin.settings.showContributors=i},["contributors","footer","show"])),e.push(t("Show last edit time in note footer",()=>this.plugin.settings.showLastEdit,i=>{this.plugin.settings.showLastEdit=i},["last edit","modified","footer","time"])),e.push(this.sectionDef("Folders You've Worked In","Folders where you've authored or contributed notes. Click one to open it.",i=>this.renderAuthoredFolders(i),["folders","authored","contributed","worked"])),e.push(this.sectionDef("Known Authors","Everyone the plugin has seen, with role/department + rename history; rebuild/restore the registry.",i=>this.renderKnownAuthorsSection(i),["authors","registry","rename","known","rebuild"])),e}renderAuthoredFolders(e){new T.Setting(e).setName("Folders you've worked in").setHeading();let t=this.plugin.collectAuthoredFolders();if(t.length===0){e.createEl("p",{cls:"setting-item-description",text:"No authored or contributed folders yet."});return}let i=e.createDiv({cls:"stashpad-authored-folders-list"});for(let s of t){let n=i.createDiv({cls:"stashpad-authored-folder-row"}),o=n.createEl("a",{cls:"stashpad-authored-folder-link",text:s.folder});o.onclick=l=>{l.preventDefault(),this.plugin.activateViewForFolder(s.folder)};let a=[];s.authored>0&&a.push(`authored ${s.authored}`),s.contributed>0&&a.push(`contributed to ${s.contributed}`),n.createSpan({cls:"stashpad-authored-folder-counts",text:` \xB7 ${a.join(", ")}`})}}templatesItems(){return[this.sectionDef("Color Aliases","Give your note colors friendly names, per Stashpad folder.",e=>this.renderColorAliasesSection(e),["color","colour","alias","name","swatch","palette","label"]),this.sectionDef("Note Templates","Per-Stashpad note templates \u2014 content stamped into new notes.",e=>this.renderNoteTemplatesSection(e),["template","note","default","boilerplate","snippet"])]}jdIndexItems(){return[this.sectionDef("JD Index (Johnny Decimal)","Build a Johnny-Decimal-style index from dotted-prefix note titles \u2014 set the scope, preview, then build.",e=>this.renderJdIndexSection(e),["jd","johnny","decimal","index","scope","build","preview","hierarchy","folder"])]}okfItems(){return[this.sectionDef("Open Knowledge Format (OKF)","Turn a Stashpad folder into a browsable OKF bundle \u2014 markdown concept files with OKF frontmatter, a generated index.md, and relative-markdown cross-links \u2014 that LLMs/agents can read. Complements (never replaces) Stashpad's own frontmatter and links.",e=>this.renderOkfSection(e),["okf","open knowledge format","knowledge","catalog","index","export","bundle","tarball","agent","google"])]}appendCode(e,t){t.split(/`([^`]+)`/g).forEach((i,s)=>{s%2===1?e.createEl("code",{text:i}):i&&e.appendText(i)})}codeDesc(e){let t=document.createDocumentFragment();return this.appendCode(t,e),t}renderOkfSection(e){if(e.createDiv({cls:"stashpad-beta-row"}).createEl("span",{cls:"stashpad-beta-badge",text:"BETA"}),new T.Setting(e).setName("Enable OKF").setDesc(this.codeDesc("Master switch. When on, you choose which folders use OKF by assigning the OKF template to them in Stashpad's own Templates section (the \u{1F4C4} Templates settings here \u2014 NOT Obsidian's core Templates plugin) \u2014 all / some / none, your call. Those folders then get OKF frontmatter and a maintained `index.md`. Turning this off leaves existing OKF files in place; it just stops maintaining them.")).addToggle(i=>i.setValue(this.plugin.settings.okfEnabled).onChange(async s=>{if(this.plugin.settings.okfEnabled=s,await this.plugin.saveSettings(),s)try{await this.plugin.ensureOkfTemplate()}catch(n){console.warn("[Stashpad] OKF template create failed",n)}new T.Notice(s?`OKF on. Next: assign the template "${this.plugin.okfTemplatePathOrDefault()}" to a folder \u2014 use \u201CCreate template + open Templates\u201D below. Heads-up: OKF frontmatter + index.md refresh automatically but NOT instantly (a few seconds after changes); hit Rebuild for an immediate pass.`:"OKF disabled.",s?0:4e3),this.update?.()})),this.plugin.settings.okfEnabled){let i=this.plugin.okfTemplatePathOrDefault(),s=this.plugin.okfActiveFolders().length,n=e.createEl("div",{cls:"setting-item-description stashpad-okf-howto"});n.createEl("p",{text:"How to use OKF in a folder:"});let o=n.createEl("ol");if(this.appendCode(o.createEl("li"),`Open Stashpad's \u{1F4C4} Templates section (here in Stashpad's settings \u2014 not Obsidian's core Templates plugin) and set a folder's template to \`${i}\` (archive folders are skipped).`),this.appendCode(o.createEl("li"),"Hit Rebuild below to write OKF frontmatter (`okfParent`/`okfChildren` + `okfType`/`okfTitle`/`okfTimestamp`) and generate that folder's `index.md`."),this.appendCode(o.createEl("li"),"Right-click a note (or a selection) \u2192 \u201CExport as OKF\u2026\u201D to save a `.zip` / `.tar.gz` bundle (or `.stash`)."),n.createEl("p",{cls:"stashpad-okf-soon",text:"OKF frontmatter + index.md refresh automatically a few seconds after you add, move, or delete notes \u2014 NOT instantly. Use Rebuild for an immediate pass."}),s===0){let a=e.createEl("p",{cls:"stashpad-okf-cta"});this.appendCode(a,"\u{1F449} No folder is using OKF yet. Click \u201CCreate template + open Templates\u201D below, then set a folder's template to `"+i+"`.")}else n.createEl("p",{cls:"stashpad-okf-soon",text:`Currently ${s} folder${s===1?"":"s"} actively using OKF.`});new T.Setting(e).setName("Assign OKF to folders").setDesc(this.codeDesc(`Creates the OKF template if needed (never duplicates it), then opens Templates \u2014 set a folder's template to \`${i}\` there.`)).addButton(a=>{a.setButtonText("Create template + open Templates").setCta(),a.onClick(async()=>{let l;try{l=await this.plugin.ensureOkfTemplate()}catch(c){new T.Notice(`Couldn't create the OKF template: ${c.message}`);return}new T.Notice(`OKF template ready at "${l}" \u2014 set a folder's template to that path.`),this.update?.(),this.openSettingsPage("Templates")})}),new T.Setting(e).setName("Rebuild OKF frontmatter").setDesc(this.codeDesc("Write/refresh OKF fields for every folder using the OKF template \u2014 `okfParent`/`okfChildren` relative links (managed) plus `okfType`/`okfTitle`/`okfTimestamp` defaults (yours to edit after). Heads-up: adding, moving, or deleting notes already auto-refreshes the folder, but NOT instantly \u2014 it waits ~a few seconds after you stop. Use this button for an immediate rebuild (e.g. right after first assigning the template). Complements Stashpad's own links; nothing is removed.")).addButton(a=>a.setButtonText("Rebuild now").onClick(async()=>{let l=await this.plugin.rebuildAllOkf();new T.Notice(l.folders===0?"No folders use the OKF template yet \u2014 assign it in Templates first.":`OKF: updated ${l.written} of ${l.checked} notes across ${l.folders} folder${l.folders===1?"":"s"}.`),this.update?.()}))}let t=new T.Setting(e).setName("Learn about OKF").setDesc("Google's open, vendor-neutral spec for sharing curated knowledge with agents.");t.addButton(i=>i.setButtonText("Spec / repo").onClick(()=>window.open("https://github.com/GoogleCloudPlatform/knowledge-catalog/tree/main/okf"))),t.addButton(i=>i.setButtonText("Announcement").onClick(()=>window.open("https://cloud.google.com/blog/products/data-analytics/how-the-open-knowledge-format-can-improve-data-sharing/")))}openSettingsPage(e){let t=()=>new T.Notice(`Open Settings \u2192 Stashpad \u2192 ${e}.`);try{let i=this.app.setting;if(!i?.openTabById){t();return}i.openTabById("stashpad"),window.setTimeout(()=>{let s=i.modalEl?.querySelector(".vertical-tab-content");if(!s){t();return}let o=Array.from(s.querySelectorAll("*")).find(a=>a.childElementCount===0&&a.textContent?.trim()===e&&!a.closest(".vertical-tab-header"))?.closest("[class*='nav'], .setting-item, button, a");o&&!o.closest(".vertical-tab-header")?o.click():t()},60)}catch{t()}}renderAuthorshipSection(e){new T.Setting(e).setName("Authorship").setHeading(),e.createEl("p",{cls:"setting-item-description",text:"Stamp each new note with your name. If the vault is later shared (e.g. a coworker opens it with --config pointing at their own settings folder), every modification automatically tracks contributors on top of the original author. Names link to per-user pages in <stashpad>/_authors/."}),new T.Setting(e).setName("Author name").setDesc("Your display name. Used in the note footer + as the author/contributor link target. Leave blank to opt out (notes won't be stamped).").addText(i=>{i.setValue(this.plugin.settings.authorName).onChange(async s=>{this.plugin.settings.authorName=s.trim(),this.plugin.settings.authorName&&!this.plugin.settings.authorId&&(this.plugin.settings.authorId=Z()),await this.plugin.saveSettings(),await this.plugin.syncAuthorFilesToName()})}),new T.Setting(e).setName("Author id (auto-assigned)").setDesc("Stable id appended to your name on links so coworkers with the same name don't collide. Generated once and shouldn't change. If you really need to reset it, clear and retype your author name above.").addText(i=>{i.setValue(this.plugin.settings.authorId).setDisabled(!0)}),new T.Setting(e).setName("Title / role").setDesc('Optional. Shown on your author page (e.g. "Engineer", "PM", "Designer").').addText(i=>{i.setValue(this.plugin.settings.authorRole).onChange(async s=>{this.plugin.settings.authorRole=s.trim(),await this.plugin.saveSettings(),await this.plugin.syncAuthorFilesToName()})}),new T.Setting(e).setName("Department / team").setDesc('Optional. Shown on your author page (e.g. "Engineering", "Growth").').addText(i=>{i.setValue(this.plugin.settings.authorDepartment).onChange(async s=>{this.plugin.settings.authorDepartment=s.trim(),await this.plugin.saveSettings(),await this.plugin.syncAuthorFilesToName()})}),new T.Setting(e).setName("Show author in note footer").addToggle(i=>i.setValue(this.plugin.settings.showAuthor).onChange(async s=>{this.plugin.settings.showAuthor=s,await this.plugin.saveSettings()})),new T.Setting(e).setName("Show contributors in note footer").addToggle(i=>i.setValue(this.plugin.settings.showContributors).onChange(async s=>{this.plugin.settings.showContributors=s,await this.plugin.saveSettings()})),new T.Setting(e).setName("Show last edit time in note footer").addToggle(i=>i.setValue(this.plugin.settings.showLastEdit).onChange(async s=>{this.plugin.settings.showLastEdit=s,await this.plugin.saveSettings()}));let t=this.plugin.collectAuthoredFolders();if(t.length>0){new T.Setting(e).setName("Folders You've Worked In").setHeading();let i=e.createDiv({cls:"stashpad-authored-folders-list"});for(let s of t){let n=i.createDiv({cls:"stashpad-authored-folder-row"}),o=n.createEl("a",{cls:"stashpad-authored-folder-link",text:s.folder});o.onclick=l=>{l.preventDefault(),this.plugin.activateViewForFolder(s.folder)};let a=[];s.authored>0&&a.push(`authored ${s.authored}`),s.contributed>0&&a.push(`contributed to ${s.contributed}`),n.createSpan({cls:"stashpad-authored-folder-counts",text:` \xB7 ${a.join(", ")}`})}}this.renderKnownAuthorsSection(e)}renderKnownAuthorsSection(e){new T.Setting(e).setName("Known Authors (registry)").setHeading(),e.createEl("div",{cls:"setting-item-description",text:"A rebuildable cache of every author Stashpad has seen, with rename history. Not a source of truth \u2014 the author id stored in each note is authoritative. Use it to recover deleted author pages or audit name changes."}),new T.Setting(e).setName("Registry maintenance").setDesc("Rebuild scans the whole vault to reconstruct the list. Restore regenerates any deleted author pages across every Stashpad folder.").addButton(s=>s.setButtonText("Rebuild").onClick(async()=>{s.setDisabled(!0).setButtonText("Rebuilding\u2026");try{let n=await this.plugin.rebuildAuthorRegistry();new T.Notice(`Author registry rebuilt: ${n.total} author(s).`)}catch(n){new T.Notice(`Rebuild failed: ${n.message}`)}s.setDisabled(!1).setButtonText("Rebuild"),this.display()})).addButton(s=>s.setButtonText("Restore missing pages").onClick(async()=>{s.setDisabled(!0).setButtonText("Restoring\u2026");try{let n=await this.plugin.restoreMissingAuthorStubs();new T.Notice(n.created>0?`Restored ${n.created} author page(s).`:"No missing author pages.")}catch(n){new T.Notice(`Restore failed: ${n.message}`)}s.setDisabled(!1).setButtonText("Restore missing pages")}));let t=this.plugin.authorRegistry.all();if(t.length===0){e.createEl("div",{cls:"setting-item-description",text:"No authors recorded yet. Rebuild to scan the vault."});return}let i=e.createDiv({cls:"stashpad-known-authors-list"});for(let s of t){let n=i.createDiv({cls:"stashpad-known-author-row"}),o=n.createDiv({cls:"stashpad-known-author-main"});o.createSpan({cls:"stashpad-known-author-name",text:s.name||"(unnamed)"});let a=[];if(s.role&&a.push(s.role),s.department&&a.push(s.department),a.push(`id ${s.id}`),o.createSpan({cls:"stashpad-known-author-meta",text:` \xB7 ${a.join(" \xB7 ")}`}),s.renames&&s.renames.length>0){let l=n.createDiv({cls:"stashpad-known-author-history"}),c=s.renames.map(d=>`${d.from} \u2192 ${d.to}`).join(", ");l.setText(`Renamed: ${c}`)}}}renderNoteTemplatesSection(e){let t=this.plugin.discoverStashpadFolders();if(t.length===0)return;if(new T.Setting(e).setName("Note templates per Stashpad").setDesc("Pick a markdown file to use as the default template for new notes in each Stashpad. The template's frontmatter becomes the new note's frontmatter (id/parent/created/attachments are always set by Stashpad). If the body contains {{body}}, that's where the user-typed body goes; otherwise the user body is followed by the template body."),this.plugin.settings.okfEnabled){let n=this.plugin.okfTemplatePathOrDefault();this.appendCode(e.createEl("p",{cls:"setting-item-description"}),`\u{1F4A1} OKF tip: type \`${n}\` into a folder's template field below to turn that folder into an OKF bundle (OKF frontmatter + a maintained \`index.md\`). Assign it to all, some, or none of your folders \u2014 it's per-folder. Manage OKF itself in Settings \u2192 OKF.`)}let i=e.createDiv({cls:"stashpad-note-templates-list"}),s=n=>{let o=n.replace(/\/+$/,""),a=i.createDiv({cls:"stashpad-note-template-row"});a.createSpan({cls:"stashpad-note-template-folder"}).setText(n);let c=a.createDiv({cls:"stashpad-note-template-input-wrap"}),d=c.createEl("input",{type:"text",cls:"stashpad-note-template-input",attr:{placeholder:"path/to/template.md (leave blank to disable)"}});d.value=(this.plugin.settings.noteTemplates??{})[o]??"";let h=c.createDiv({cls:"stashpad-note-template-suggest"});h.setCssStyles({display:"none"});let g=[],f=[],p=-1,u=()=>h.style.display!=="none"&&g.length>0,I=Q=>{p=Q,f.forEach((S,x)=>S.toggleClass("is-active",x===Q)),Q>=0&&f[Q]&&f[Q].scrollIntoView({block:"nearest"})},m=()=>{h.setCssStyles({display:"none"}),p=-1},C=async Q=>{d.value=Q,await v(),m()},B=a.createDiv({cls:"stashpad-note-template-warn"});B.setCssStyles({display:"none"});let w=()=>this.app.vault.getMarkdownFiles().map(Q=>Q.path).filter(Q=>!/\/(_imports|_exports|_attachments|\.stashpad)\//.test(Q)).sort(),y=()=>{h.empty(),f=[];let Q=d.value.trim().toLowerCase().split(/\s+/).filter(Boolean),S=x=>{let H=x.toLowerCase();return Q.every(G=>H.includes(G))};if(g=w().filter(x=>S(x)).slice(0,12),g.length===0){m();return}h.setCssStyles({display:""}),g.forEach((x,H)=>{let G=h.createDiv({cls:"stashpad-note-template-suggest-item",text:x});f.push(G),G.addEventListener("mousemove",()=>I(H)),G.addEventListener("mousedown",async N=>{N.preventDefault(),await C(x)})}),p=p>=0&&p<g.length?p:-1,p>=0&&I(p)},v=async()=>{let Q=d.value.trim(),S={...this.plugin.settings.noteTemplates??{}};Q?S[o]=Q:delete S[o],this.plugin.settings.noteTemplates=S,await this.plugin.saveSettings(),E()},E=()=>{B.empty(),B.setCssStyles({display:"none"});let Q=d.value.trim();if(!Q)return;let S=this.app.vault.getAbstractFileByPath(Q);if(!S||S.extension!=="md"){B.setCssStyles({display:""}),B.setText(`\u26A0 "${Q}" is not a markdown file in this vault.`);return}let x=this.app.metadataCache.getFileCache(S)?.frontmatter??{},G=Ve.filter(N=>{let R=x[N];return!(R==null||typeof R=="string"&&R.trim()===""||Array.isArray(R)&&R.length===0)});G.length!==0&&(B.setCssStyles({display:""}),B.setText(`\u26A0 Template defines ${G.join(", ")} \u2014 Stashpad always sets ${G.length===1?"this":"these"} on new notes, so the template value${G.length===1?"":"s"} will be ignored.`))};d.addEventListener("focus",y),d.addEventListener("input",()=>{p=-1,y()}),d.addEventListener("blur",()=>{setTimeout(m,150)}),d.addEventListener("change",()=>{v()}),d.addEventListener("keydown",Q=>{if(Q.key==="ArrowDown")Q.preventDefault(),u()?I((p+1)%g.length):(y(),g.length&&I(0));else if(Q.key==="ArrowUp"){if(!u())return;Q.preventDefault(),I((p-1+g.length)%g.length)}else if(Q.key==="Enter")u()&&p>=0&&(Q.preventDefault(),C(g[p]));else if(Q.key==="Escape")u()&&(Q.preventDefault(),m());else if(Q.key==="Tab"&&!Q.shiftKey){if(!u())return;let S=g[p>=0?p:0],x=d.value,H;if(S.toLowerCase().startsWith(x.toLowerCase())){let G=S.indexOf("/",x.length);H=G>=0?S.slice(0,G+1):S}else H=S;H&&H!==x&&(Q.preventDefault(),d.value=H,p=-1,y(),g.length===1&&I(0))}}),E()};for(let n of t)s(n)}renderColorAliasRow(e,t,i,s,n){let o=e.createDiv({cls:"stashpad-color-alias-row"});s===0&&o.addClass("is-unused");let a=o.createSpan({cls:"stashpad-color-alias-swatch"});a.setCssStyles({background:i}),a.title="Click to bulk-recolor every note of this color in this Stashpad",a.onclick=()=>{let h=this.plugin.settings.customPalette??[];new Kt(this.app,i,h,async g=>{if((g??null)===null&&s===0){await this.plugin.setColorAlias(t,i,""),n();return}if(g&&g.toLowerCase()===i){n();return}let f=await this.plugin.recolorAllInFolder(t,i,g??null);if(f>0)new T.Notice(`Recolored ${f} note${f===1?"":"s"}.`);else if(s===0){let p=this.plugin.getColorAlias(t,i);p&&(await this.plugin.setColorAlias(t,i,""),g&&await this.plugin.setColorAlias(t,g,p))}n()},async g=>{let f=(this.plugin.settings.customPalette??[]).filter(p=>p.toLowerCase()!==g.toLowerCase());return this.plugin.settings.customPalette=f,await this.plugin.saveSettings(),f}).open()};let l=o.createDiv({cls:"stashpad-color-alias-meta"});l.createSpan({cls:"stashpad-color-alias-hex",text:i}),l.createSpan({cls:"stashpad-color-alias-count",text:s===0?"\xB7 unused":`\xB7 ${s} note${s===1?"":"s"}`});let c=o.createEl("input",{type:"text",cls:"stashpad-color-alias-input",attr:{placeholder:"Alias (optional)"}});c.value=this.plugin.getColorAlias(t,i)??"",c.onchange=async()=>{await this.plugin.setColorAlias(t,i,c.value),!c.value.trim()&&s===0&&n()};let d=o.createEl("button",{cls:"stashpad-color-alias-del",text:"\xD7",attr:{title:"Delete alias"}});c.value||d.setCssStyles({visibility:"hidden"}),d.onclick=async()=>{await this.plugin.setColorAlias(t,i,""),s===0?n():(c.value="",d.setCssStyles({visibility:"hidden"}))}}renderFolderScopeRow(e,t){let i=e.createDiv({cls:"stashpad-folder-row"});i.createSpan({cls:"stashpad-folder-row-label",text:t});let s=i.createSpan({cls:"stashpad-folder-row-state"}),n=i.createDiv({cls:"stashpad-binding-pill"});n.setAttribute("role","switch"),n.setAttribute("tabindex","0");let o=n.createDiv({cls:"stashpad-binding-pill-knob"}),a=()=>(this.plugin.settings.searchExcludedFolders??[]).includes(t),l=()=>{let d=a();n.toggleClass("is-right",d),n.setAttribute("aria-checked",String(d)),o.setText(d?"X":"\u2713"),s.setText(d?"Excluded":"Included"),s.toggleClass("is-excluded",d),n.title=d?"Excluded \u2014 notes here won't appear in cross-Stashpad search. Click to include.":"Included \u2014 notes here appear in cross-Stashpad search. Click to exclude."},c=async()=>{let d=new Set(this.plugin.settings.searchExcludedFolders??[]);d.has(t)?d.delete(t):d.add(t),this.plugin.settings.searchExcludedFolders=[...d].sort(),l(),await this.plugin.saveSettings()};n.onclick=()=>void c(),n.onkeydown=d=>{(d.key===" "||d.key==="Enter")&&(d.preventDefault(),c())},l()}renderFolderPlacementList(e){let t=this.plugin.settings,i=[{key:"folderPanelPinned",label:"Pinned",action:"Unpin"},{key:"folderPanelDownranked",label:"Downranked",action:"Reset"},{key:"folderPanelHidden",label:"Hidden",action:"Unhide"}];if(!i.some(o=>(t[o.key]??[]).length>0)){e.createEl("p",{cls:"setting-item-description"}).setText("No folders customized yet. Right-click a folder in the Stashpad folder panel to pin, downrank, or hide it.");return}let n=async o=>{t.folderPanelPinned=(t.folderPanelPinned??[]).filter(a=>a!==o),t.folderPanelDownranked=(t.folderPanelDownranked??[]).filter(a=>a!==o),t.folderPanelHidden=(t.folderPanelHidden??[]).filter(a=>a!==o),await this.plugin.saveSettings(),this.update?.()};for(let o of i){let a=[...t[o.key]??[]].sort();if(a.length===0)continue;e.createEl("div",{cls:"stashpad-folder-placement-group",text:`${o.label} (${a.length})`});let l=e.createDiv({cls:"stashpad-folder-list"});for(let c of a){let d=l.createDiv({cls:"stashpad-folder-row"});d.createSpan({cls:"stashpad-folder-row-label",text:c});let h=d.createEl("button",{text:o.action});h.onclick=()=>void n(c)}}}renderBindingRow(e,t){e.setName(t.label).setDesc(t.desc),e.settingEl.addClass("stashpad-binding-row");let i=()=>this.plugin.settings.bindings[t.id],s,n,o=()=>{},a=async g=>{},l=e.controlEl.createDiv({cls:"stashpad-binding-seg",attr:{role:"group","aria-label":"Active binding"}}),c={left:l.createEl("button",{cls:"stashpad-binding-seg-btn",text:"Left"}),both:l.createEl("button",{cls:"stashpad-binding-seg-btn",text:"Both"}),right:l.createEl("button",{cls:"stashpad-binding-seg-btn",text:"Right"})};["left","both","right"].forEach(g=>{c[g].onclick=()=>void a(g)});let d=e.controlEl.createDiv({cls:"stashpad-binding-slots"}),h=g=>{let f=d.createDiv({cls:"stashpad-binding-slot"}),p=f.createEl("input",{type:"text"});p.readOnly=!0,p.placeholder="Click & press a key",p.value=Zi(i()[g]),p.classList.add("stashpad-binding-input");let u=()=>{p.size=Math.max(3,p.value.length||p.placeholder.length)};u();let I=g==="primary"?t.defaultPrimary:t.defaultSecondary??"";p.onclick=()=>{Wr(p,async y=>{this.plugin.settings.bindings[t.id][g]=y,p.value=Zi(y),u(),await this.plugin.saveSettings(),o(),w()})};let m=f.createDiv({cls:"stashpad-binding-slot-btns"}),C=m.createEl("button",{cls:"stashpad-binding-clear",text:"\xD7"});C.title="Clear this slot",C.onclick=async()=>{this.plugin.settings.bindings[t.id][g]="",p.value="",u(),await this.plugin.saveSettings(),o(),w()};let B=m.createEl("button",{cls:"stashpad-binding-revert"});(0,T.setIcon)(B,"rotate-ccw");let w=()=>{let v=i()[g]!==I;B.toggleClass("is-hidden",!v),B.title=I?`Revert to default (${Zi(I)})`:"Revert to default (no binding)"};return B.onclick=async()=>{this.plugin.settings.bindings[t.id][g]=I,p.value=Zi(I),u(),await this.plugin.saveSettings(),o(),w()},w(),p};s=h("primary"),n=h("secondary"),a=async g=>{let f=this.plugin.settings.bindings[t.id];f.primary&&f.secondary&&(g==="both"?f.useBoth=!0:(f.useBoth=!1,f.preferRight=g==="right"),await this.plugin.saveSettings(),o())},o=()=>{let g=i(),f=!!(g.primary&&g.secondary),p=g.useBoth?"both":g.preferRight?"right":"left";["left","both","right"].forEach(u=>{c[u].toggleClass("is-active",f&&u===p),c[u].disabled=!f}),l.toggleClass("is-disabled",!f),l.title=f?"Which binding is active \u2014 Left, Both, or Right":"Bind both slots to choose Left / Both / Right"},o()}};var pi=class{constructor(r,e){this.app=r;this.ta=e;this.popupEl=null;this.items=[];this.activeIdx=0;this.state=null;this.fileIndex=[];this.tagIndex=[];this.indexBuilt=!1;this.vaultListeners=[];this.scope=null;this.onInput=()=>{let r=this.detectTrigger();if(!r){this.close();return}this.openFor(r)};this.onBlur=()=>{setTimeout(()=>this.close(),120)};this.onDocEscape=r=>{r.key==="Escape"&&(!this.state||!this.items.length||(r.preventDefault(),r.stopPropagation(),r.stopImmediatePropagation(),this.close()))};this.onKeyDown=r=>{!this.state||!this.items.length||(r.key==="ArrowDown"?(r.preventDefault(),this.activeIdx=(this.activeIdx+1)%this.items.length,this.refreshActive()):r.key==="ArrowUp"?(r.preventDefault(),this.activeIdx=(this.activeIdx-1+this.items.length)%this.items.length,this.refreshActive()):r.key==="Enter"||r.key==="Tab"?(r.preventDefault(),r.stopPropagation(),this.commit()):r.key==="Escape"&&(r.preventDefault(),r.stopPropagation(),r.stopImmediatePropagation(),this.close()))}}attach(){this.ta.addEventListener("input",this.onInput),this.ta.addEventListener("keydown",this.onKeyDown,!0),this.ta.addEventListener("blur",this.onBlur);let r=this.ta.ownerDocument??document;r.addEventListener("keydown",this.onDocEscape,!0),this.vaultListeners.push(()=>r.removeEventListener("keydown",this.onDocEscape,!0)),this.buildIndex();let e=()=>{this.indexBuilt=!1},t=this.app.vault;t.on("create",e),t.on("delete",e),t.on("rename",e),this.vaultListeners.push(()=>t.off("create",e),()=>t.off("delete",e),()=>t.off("rename",e))}isOpen(){return!!this.state&&this.items.length>0}detach(){this.close(),this.ta.removeEventListener("input",this.onInput),this.ta.removeEventListener("keydown",this.onKeyDown,!0),this.ta.removeEventListener("blur",this.onBlur);for(let r of this.vaultListeners)r();this.vaultListeners=[]}buildIndex(){if(this.indexBuilt)return;let r=X().inheritObsidianExclusions,e=r?this.app.vault.getConfig?.("userIgnoreFilters"):void 0;this.fileIndex=this.app.vault.getFiles().filter(i=>!vr(i.path)&&!Fr(i.path)&&!(r&&kr(i.path,e))).map(i=>{let s=i.extension==="md",n=s?i.basename:i.name,o=s?i.basename:i.name;return{label:n,lower:n.toLowerCase(),insertText:o,file:i}});let t=this.app.metadataCache.getTags?.()??{};this.tagIndex=Object.keys(t).sort((i,s)=>(t[s]||0)-(t[i]||0)),this.indexBuilt=!0}detectTrigger(){let r=this.ta.value,e=this.ta.selectionStart;if(e==null)return null;let t=r.slice(0,e),i=t.match(/\[\[([^\]\[\n]*)$/);if(i){let n=i[1];return{kind:"link",query:n,replaceStart:e-n.length-2,replaceEnd:e}}let s=t.match(/(^|\s)#([A-Za-z0-9_/\-]+)$/);if(s){let n=s[2];return{kind:"tag",query:n,replaceStart:e-n.length-1,replaceEnd:e}}return null}buildItems(r){this.buildIndex();let e=r.query.toLowerCase().trim(),t=e?e.split(/\s+/).filter(Boolean):[],i=s=>{if(!t.length)return!0;for(let n of t)if(!s.includes(n))return!1;return!0};return r.kind==="link"?this.fileIndex.filter(n=>i(n.lower)).slice(0,50).map(n=>({label:n.label,insert:`[[${n.insertText}]]`,subtitle:n.file.path})):this.tagIndex.filter(n=>i(n.toLowerCase())).slice(0,30).map(n=>({label:n,insert:n,subtitle:""}))}openFor(r){if(this.state=r,this.items=this.buildItems(r),this.activeIdx=0,!this.items.length){this.close();return}this.renderPopup(),this.pushScope()}pushScope(){if(this.scope)return;let r=new Ia.Scope(this.app.scope);r.register([],"Escape",e=>(e.preventDefault(),this.close(),!1)),this.scope=r,this.app.keymap?.pushScope(r)}popScope(){if(this.scope){try{this.app.keymap?.popScope(this.scope)}catch{}this.scope=null}}renderPopup(){if(!this.popupEl){let e=this.ta.ownerDocument??document;this.popupEl=e.body.createDiv({cls:"stashpad-composer-suggest"}),this.popupEl.tabIndex=-1,this.popupEl.addEventListener("mousedown",t=>t.preventDefault())}let r=this.popupEl;r.empty();for(let e=0;e<this.items.length;e++){let t=this.items[e],i=r.createDiv({cls:"stashpad-composer-suggest-row"});e===this.activeIdx&&i.addClass("is-active"),i.createSpan({cls:"stashpad-composer-suggest-label",text:t.label}),t.subtitle&&i.createSpan({cls:"stashpad-composer-suggest-sub",text:t.subtitle}),i.onmousedown=s=>{s.preventDefault(),this.activeIdx=e,this.commit()}}this.position()}refreshActive(){if(!this.popupEl)return;let r=this.popupEl.children;for(let t=0;t<r.length;t++)r[t].toggleClass("is-active",t===this.activeIdx);let e=r[this.activeIdx];e&&e.scrollIntoView({block:"nearest"})}position(){if(!this.popupEl)return;let r=this.ta.getBoundingClientRect(),e=this.popupEl.offsetHeight||200,t=r.top-e-4,i=r.left;this.popupEl.style.left=`${Math.max(8,i)}px`,this.popupEl.style.top=`${Math.max(8,t)}px`,this.popupEl.style.minWidth=`${Math.min(360,r.width)}px`}commit(){if(!this.state||!this.items.length)return;let r=this.items[this.activeIdx];if(!r)return;let e=this.ta.value.slice(0,this.state.replaceStart),t=this.ta.value.slice(this.state.replaceEnd),i=r.insert;this.ta.value=e+i+t;let s=e.length+i.length;this.ta.setSelectionRange(s,s),this.ta.dispatchEvent(new Event("input",{bubbles:!0})),this.close(),this.ta.focus()}close(){this.popupEl&&(this.popupEl.remove(),this.popupEl=null),this.state=null,this.items=[],this.activeIdx=0,this.popScope()}};le();function ma(A){return/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(A)}var _s=class extends ke.ItemView{constructor(e,t){super(e);this.plugin=t;this.displayedPath=null;this.unsubscribeSelection=null;this.renderTimer=null;this.renderToken=0;this.expanded=new Set;this.composerAutocomplete=null;this.composerInputEl=null;this.composerDraft="";this.composerDraftForId=null;this.displayedId=null;this.unsubscribeContent=null}getViewType(){return Pt}getDisplayText(){return"Stashpad detail"}getIcon(){return"panel-right"}async onOpen(){this.render(),this.unsubscribeSelection=this.plugin.onStashpadSelectionChange(()=>{this.displayedId=null,this.scheduleRender()}),this.unsubscribeContent=this.plugin.onStashpadContentChange(()=>this.scheduleRender()),this.registerEvent(this.app.workspace.on("active-leaf-change",e=>{!e||e===this.leaf||e.view?.getViewType?.()===z&&this.scheduleRender()})),this.registerEvent(this.app.vault.on("modify",e=>{this.displayedPath&&e.path===this.displayedPath&&this.scheduleRender()})),this.registerEvent(this.app.metadataCache.on("changed",e=>{this.displayedPath&&e.path===this.displayedPath&&this.scheduleRender()}))}async onClose(){this.unsubscribeSelection?.(),this.unsubscribeSelection=null,this.unsubscribeContent?.(),this.unsubscribeContent=null,this.composerAutocomplete?.detach(),this.composerAutocomplete=null,this.composerInputEl=null,this.renderTimer!=null&&(window.clearTimeout(this.renderTimer),this.renderTimer=null)}scheduleRender(){this.renderTimer==null&&(this.renderTimer=window.setTimeout(()=>{this.renderTimer=null,this.containerEl.isConnected&&this.render()},60))}resolveDisplayed(){if(this.displayedId){let i=this.plugin.lastActiveStashpadLeaf?.view;if(i?.getViewType?.()===z&&i.tree?.get){let s=i.tree.get(this.displayedId);if(s?.file)return{folder:i.noteFolder,id:this.displayedId,file:s.file}}this.displayedId=null}let e=this.plugin.getActiveStashpadSelection();if(e)return this.displayedId=e.id,e;let t=this.plugin.lastActiveStashpadLeaf?.view;if(t?.getViewType?.()===z&&t.tree?.get&&t.focusId){let i=t.tree.get(t.focusId);if(i?.file)return{folder:t.noteFolder,id:t.focusId,file:i.file}}return null}async render(){let e=++this.renderToken,t=this.contentEl,i=!!this.composerInputEl&&document.activeElement===this.composerInputEl,s=this.composerInputEl?.selectionStart??null;this.composerAutocomplete?.detach(),this.composerAutocomplete=null,this.composerInputEl=null,t.empty(),t.addClass("stashpad-detail-root");let n=this.resolveDisplayed();if(!n){this.displayedPath=null;let u=t.createDiv({cls:"stashpad-detail-empty"});(0,ke.setIcon)(u.createSpan({cls:"stashpad-detail-empty-icon"}),"panel-right"),u.createSpan({cls:"stashpad-detail-empty-text",text:"No Stashpad note selected. Cursor or click a note in a Stashpad list to inspect it here."});return}let o=n.file;this.displayedPath=o.path;let a=this.app.metadataCache.getFileCache(o)?.frontmatter??{},l=t.createDiv({cls:"stashpad-detail-scroll"}),c=l.createDiv({cls:"stashpad-detail-header"}),d=c.createDiv({cls:"stashpad-detail-titlerow"});d.createDiv({cls:"stashpad-detail-title",text:this.titleFor(o)});let h=d.createEl("button",{cls:"stashpad-detail-open-btn",attr:{"aria-label":"Open in Stashpad tab"}});(0,ke.setIcon)(h,"arrow-up-right"),h.title="Open this note in a new Stashpad tab",h.onclick=()=>{this.openInStashpad(n.folder,n.id)};let g=c.createDiv({cls:"stashpad-detail-metarow"});if(n.folder&&g.createSpan({cls:"stashpad-detail-meta-chip",text:n.folder.split("/").pop()||n.folder}),typeof a.color=="string"&&ma(a.color)){let u=g.createSpan({cls:"stashpad-detail-meta-color"});u.style.background=a.color,u.title=a.color}if(a.completed===!0&&g.createSpan({cls:"stashpad-detail-meta-chip is-completed",text:"\u2713 completed"}),typeof a.due=="string"||typeof a.due=="number"){let u=Date.parse(String(a.due)),I=Number.isNaN(u)?String(a.due):Xe(u,this.plugin.settings);g.createSpan({cls:"stashpad-detail-meta-chip is-due",text:`due ${I}`})}if(Array.isArray(a.tags))for(let u of a.tags.filter(I=>typeof I=="string"))g.createSpan({cls:"stashpad-detail-meta-tag",text:`#${u.replace(/^#/,"")}`});let f=l.createDiv({cls:"stashpad-detail-body"});try{let u=await this.app.vault.cachedRead(o),I=this.stripFrontmatter(u);await ke.MarkdownRenderer.render(this.app,I,f,o.path,this)}catch(u){f.createDiv({cls:"stashpad-detail-error",text:`Couldn't read \`${o.path}\`: ${u.message}`})}if(e!==this.renderToken)return;this.renderFooterMeta(l,o,a,n),this.renderChildren(l,n.folder,n.id),this.renderComposer(t,n);let p=this.composerInputEl;if(i&&p&&(p.focus(),s!=null)){let u=Math.min(s,p.value.length);try{p.setSelectionRange(u,u)}catch{}}}renderComposer(e,t){let i=e.createDiv({cls:"stashpad-detail-composer"}),s=i.createEl("textarea",{cls:"stashpad-detail-composer-input",attr:{placeholder:"Add a child note\u2026",rows:"2"}});this.composerInputEl=s,this.composerDraftForId===t.id?s.value=this.composerDraft:(this.composerDraft="",this.composerDraftForId=t.id),s.addEventListener("input",()=>{this.composerDraft=s.value,this.composerDraftForId=t.id}),s.addEventListener("keydown",o=>{if(o.key==="Enter"&&!o.shiftKey&&!o.isComposing){if(this.composerAutocomplete?.isOpen?.())return;o.preventDefault(),this.submitComposer(t)}});let n=i.createEl("button",{cls:"stashpad-detail-composer-send",attr:{"aria-label":"Add child note"}});(0,ke.setIcon)(n.createSpan({cls:"stashpad-detail-composer-send-icon"}),"plus"),n.createSpan({cls:"stashpad-detail-composer-send-text",text:"Add child note"}),n.title="Add child note (Enter)",n.onclick=()=>void this.submitComposer(t),this.composerAutocomplete=new pi(this.app,s),this.composerAutocomplete.attach()}async submitComposer(e){let t=(this.composerInputEl?.value??"").trim();if(!t)return;let i=this.plugin.lastActiveStashpadLeaf?.view;if(!i||i.getViewType?.()!==z||typeof i.createNoteUnder!="function"){new ke.Notice("Open a Stashpad view to add notes.");return}try{await i.createNoteUnder(t,e.id)}catch(s){new ke.Notice(`Couldn't add note: ${s.message}`);return}this.composerDraft="",this.composerDraftForId=e.id,this.composerInputEl&&(this.composerInputEl.value=""),await this.render(),setTimeout(()=>this.composerInputEl?.focus(),0)}renderChildren(e,t,i){let s=this.plugin.lastActiveStashpadLeaf?.view;if(!s||s.getViewType?.()!==z)return;let n=s.tree;if(!n?.getChildren)return;let o=n.getChildren(i);if(!o||o.length===0)return;let a=e.createDiv({cls:"stashpad-detail-children"});a.createDiv({cls:"stashpad-detail-children-header",text:`Children (${o.length})`});let l=a.createDiv({cls:"stashpad-detail-children-list"});for(let c of o)c.file&&this.renderDetailChildRow(l,s,n,t,c,0)}renderDetailChildRow(e,t,i,s,n,o,a=new Set){if(!n.file||a.has(n.id))return;let l=this.app.metadataCache.getFileCache(n.file)?.frontmatter??{},c=typeof l.color=="string"&&ma(l.color)?l.color:null,d=l.completed===!0,h=i.getChildren(n.id),g=h.length>0,f=`${s}|${n.id}`,p=this.expanded.has(f),u=e.createDiv({cls:"stashpad-detail-child-row"});d&&u.addClass("is-completed"),o>0&&(u.style.paddingLeft=`${o*16}px`),u.draggable=!0,u.dataset.id=n.id,u.addEventListener("dragstart",w=>{w.dataTransfer?.setData("text/plain",n.id),w.dataTransfer&&(w.dataTransfer.effectAllowed="move"),u.addClass("is-dragging")}),u.addEventListener("dragend",()=>u.removeClass("is-dragging")),u.addEventListener("dragover",w=>{w.preventDefault(),w.dataTransfer&&(w.dataTransfer.dropEffect="move");let y=u.getBoundingClientRect(),v=w.clientY-y.top<y.height/2;u.toggleClass("drop-before",v),u.toggleClass("drop-after",!v)}),u.addEventListener("dragleave",()=>{u.removeClass("drop-before"),u.removeClass("drop-after")}),u.addEventListener("drop",w=>{w.preventDefault(),u.removeClass("drop-before"),u.removeClass("drop-after");let y=w.dataTransfer?.getData("text/plain");if(!y||y===n.id)return;let v=u.getBoundingClientRect(),E=w.clientY-v.top<v.height/2;typeof t.reorderToTarget=="function"&&t.reorderToTarget([y],n.id,E?"before":"after")}),u.oncontextmenu=w=>{w.preventDefault();let y=i.get(n.id);y&&typeof t.openNoteMenu=="function"&&t.openNoteMenu(w,y)};let I=u.createSpan({cls:"stashpad-detail-child-toggle"});if(g&&(yt(I,h.length,p),I.onclick=w=>{w.stopPropagation(),this.expanded.has(f)?this.expanded.delete(f):this.expanded.add(f),this.render()}),_t(l,"task")||l.task===!0||l.completed!==void 0){let w=u.createSpan({cls:"stashpad-detail-child-checkbox"});(0,ke.setIcon)(w,d?"check-square":"square"),w.title=d?"Mark not done":"Mark done",w.addEventListener("mousedown",y=>y.stopPropagation()),w.addEventListener("dblclick",y=>{y.preventDefault(),y.stopPropagation()}),w.onclick=y=>{y.preventDefault(),y.stopPropagation();let v=i.get(n.id);v&&typeof t.toggleCompletedForNode=="function"&&t.toggleCompletedForNode(v)}}let C=u.createSpan({cls:"stashpad-detail-child-icon"});(0,ke.setIcon)(C,g?"folder-tree":"file-text"),c&&(C.style.color=c);let B=u.createSpan({cls:"stashpad-detail-child-title",text:this.titleFor(n.file)});if(B.onclick=()=>{t?.navigateTo&&t.navigateTo(n.id)},g&&p){let w=new Set(a).add(n.id);for(let y of h)this.renderDetailChildRow(e,t,i,s,y,o+1,w)}}renderFooterMeta(e,t,i,s){let n=e.createDiv({cls:"stashpad-detail-footer-meta"}),o=[];if(typeof i.author=="string"&&i.author&&o.push({label:"Author",value:this.stripWikiLink(i.author)}),Array.isArray(i.contributors)){let a=i.contributors.filter(l=>typeof l=="string").map(l=>this.stripWikiLink(l));a.length>0&&o.push({label:"Contributors",value:a.join(", ")})}if(typeof i.modified=="string"&&o.push({label:"Modified",value:this.formatTime(i.modified)}),typeof i.created=="string"&&o.push({label:"Created",value:this.formatTime(i.created)}),o.push({label:"Path",value:t.path}),o.push({label:"ID",value:s.id}),o.length===0){n.remove();return}for(let a of o){let l=n.createDiv({cls:"stashpad-detail-meta-line"});l.createSpan({cls:"stashpad-detail-meta-key",text:a.label}),l.createSpan({cls:"stashpad-detail-meta-val",text:a.value})}}titleFor(e){return e.basename.replace(/-[a-z0-9]{4,12}$/,"").replace(/-/g," ")}stripFrontmatter(e){if(!e.startsWith("---"))return e;let t=e.indexOf(` +---`,3);return t<0?e:e.slice(t+4).replace(/^\n+/,"")}stripWikiLink(e){let t=e.match(/^\[\[(.+?)(?:\|(.+))?\]\]$/);return t?t[2]??(t[1].split("/").pop()??t[1]).replace(/\.md$/,""):e}formatTime(e){let t=Date.parse(e);return Number.isFinite(t)?Xe(t,this.plugin.settings):e}async openInStashpad(e,t){try{let i=await this.plugin.activateViewForFolder(e);t!==F&&this.plugin.navigateLeafTo(i,e,t)}catch(i){new ke.Notice(`Couldn't open: ${i.message}`)}}};async function en(A){let r=A.workspace.getLeavesOfType(Pt);if(r.length>0){A.workspace.revealLeaf(r[0]);return}let e=A.workspace.getRightLeaf(!1);if(!e){new ke.Notice("Stashpad: couldn't open the detail panel.");return}await e.setViewState({type:Pt,active:!0}),A.workspace.revealLeaf(e)}var k=require("obsidian");le();var vt=require("obsidian");le();function Hc(A,r){let e=A.vault.getAbstractFileByPath(r);if(!(e instanceof vt.TFolder))return[];let t=[],i=[e];for(;i.length;){let s=i.pop();for(let n of s.children)n instanceof vt.TFile?n.extension==="md"&&t.push(n):n instanceof vt.TFolder&&(Qr(n.name)||i.push(n))}return t}var tn=class{constructor(r){this.app=r;this.nodes=new Map;this.byPath=new Map;this.listeners=new Set;this.orderProvider=null;this.synthetic=new Map;this.coalesceTimer=null;this.nodes.set(F,{id:F,parent:null,children:[],file:null,created:""})}setOrderProvider(r){this.orderProvider=r}onChange(r){return this.listeners.add(r),()=>this.listeners.delete(r)}emit(){for(let r of this.listeners)r()}rebuild(r){r!==void 0&&(this.currentFolder=r);let e=this.currentFolder,t=this.byPath,i=this.nodes;this.nodes=new Map,this.byPath=new Map,this.nodes.set(F,{id:F,parent:null,children:[],file:null,created:""});let s=e?Hc(this.app,e):this.app.vault.getMarkdownFiles();for(let n of s){let o=this.app.metadataCache.getFileCache(n)?.frontmatter,a=o?.id;if(!a){let c=t.get(n.path),d=c?i.get(c):void 0,h=this.synthetic.get(n.path),g=d??h;g&&(this.nodes.set(g.id,{...g,children:[],file:n}),this.byPath.set(n.path,g.id));continue}if(this.synthetic.delete(n.path),a===F){let c=this.nodes.get(F);c.file=n,c.created=o?.created??"",this.byPath.set(n.path,F);continue}let l=o?.parent??null;l===a&&(l=null),this.nodes.set(a,{id:a,parent:l??F,children:[],file:n,created:o?.created??""}),this.byPath.set(n.path,a)}for(let n of this.nodes.values()){if(n.id===F)continue;let o=new Set([n.id]),a=n.parent??F;for(;a!==F&&this.nodes.has(a);){if(o.has(a)){n.parent=F;break}o.add(a),a=this.nodes.get(a).parent??F}}for(let n of this.nodes.values()){if(n.id===F)continue;let o=n.parent??F;(this.nodes.get(o)??this.nodes.get(F)).children.push(n.id)}for(let n of this.nodes.values())if(n.children.sort((o,a)=>{let l=this.nodes.get(o),c=this.nodes.get(a);return(l.created||"").localeCompare(c.created||"")}),this.orderProvider){let o=this.orderProvider(n.id);if(o.length>0){let a=new Map;o.forEach((l,c)=>a.set(l,c)),n.children.sort((l,c)=>{let d=a.has(l)?a.get(l):1/0,h=a.has(c)?a.get(c):1/0;return d===h?0:d-h})}}this.emit()}insertSynthetic(r){if(!r.file)return;let e=r.file.path;this.synthetic.set(e,r),this.nodes.set(r.id,r),this.byPath.set(e,r.id);let t=r.parent??F,i=this.nodes.get(t);i&&!i.children.includes(r.id)&&i.children.push(r.id),this.emit()}get(r){return this.nodes.get(r)}getRoot(){return this.nodes.get(F)}getChildren(r){let e=this.nodes.get(r);return e?e.children.map(t=>this.nodes.get(t)).filter(t=>!!t):[]}fileBackedCount(){let r=0;for(let e of this.nodes.values())e.file&&r++;return r}pathTo(r){let e=[],t=new Set,i=this.nodes.get(r);for(;i&&i.id!==F&&!t.has(i.id);)t.add(i.id),e.unshift(i),i=i.parent?this.nodes.get(i.parent):void 0;return e}idForPath(r){return this.byPath.get(r)}snapshot(){let r={};for(let e of this.nodes.values())e.id===F||!e.file||(r[e.id]={parent:e.parent===F?null:e.parent,path:e.file.path});return r}hookMetadataCache(r){let e=l=>{let c=this.currentFolder;return c?typeof l!="string"?!1:l===c||l.startsWith(c+"/"):!0},t=!1,i=()=>{t=!0,this.coalesceTimer==null&&(this.coalesceTimer=window.setTimeout(()=>{this.coalesceTimer=null,t&&(t=!1,this.emit(),r())},16))},s=l=>{l instanceof vt.TFile&&e(l.path)&&l.extension==="md"&&(this.applyChange(l),i())},n=l=>{l instanceof vt.TFile&&e(l.path)&&l.extension==="md"&&this.applyChange(l)&&i()},o=l=>{let c=l?.path;typeof c=="string"&&e(c)&&this.applyDelete(c)&&i()},a=(l,c)=>{if(!(l instanceof vt.TFile))return;let d=typeof c=="string"?c:null;!e(l.path)&&!e(d)||this.applyRename(l,d??"")&&i()};return this.app.metadataCache.on("changed",s),this.app.vault.on("create",n),this.app.vault.on("delete",o),this.app.vault.on("rename",a),()=>{this.app.metadataCache.off("changed",s),this.app.vault.off("create",n),this.app.vault.off("delete",o),this.app.vault.off("rename",a),this.coalesceTimer!=null&&(window.clearTimeout(this.coalesceTimer),this.coalesceTimer=null)}}applyChange(r){if(Ht(r.path))return this.byPath.has(r.path)?this.applyDelete(r.path):!1;let e=this.app.metadataCache.getFileCache(r)?.frontmatter,t=e?.id,i=this.byPath.get(r.path);if(!t){if(i){let c=this.nodes.get(i);if(c&&c.file!==r)return c.file=r,!0}return!1}if(this.synthetic.delete(r.path),t===F){let c=this.nodes.get(F),d=e?.created??"",h=!1;return c.file!==r&&(c.file=r,h=!0),c.created!==d&&(c.created=d,h=!0),this.byPath.get(r.path)!==F&&(this.byPath.set(r.path,F),h=!0),h}let s=e?.parent??F;s===t&&(s=F);let n=e?.created??"";if(!(s===F||this.nodes.has(s)))return this.rebuild(),!0;if(i&&i!==t)return this.rebuild(),!0;let a=this.nodes.get(t);if(!a){let c={id:t,parent:s,children:[],file:r,created:n};return this.nodes.set(t,c),this.byPath.set(r.path,t),this.attachToParent(c),this.resortChildrenOf(s),!0}let l=!1;if(a.file!==r&&(a.file=r,l=!0),a.created!==n&&(a.created=n,this.resortChildrenOf(a.parent??F),l=!0),a.parent!==s){let c=a.parent??F;this.detachFromParent(a),a.parent=s,this.attachToParent(a),this.resortChildrenOf(c),this.resortChildrenOf(s),l=!0}return l}applyDelete(r){let e=this.byPath.get(r);if(!e)return!1;let t=this.nodes.get(e);if(!t)return this.byPath.delete(r),this.synthetic.delete(r),!0;this.detachFromParent(t);let i=this.nodes.get(F);for(let s of t.children){let n=this.nodes.get(s);n&&(n.parent=F,i.children.includes(s)||i.children.push(s))}return t.children.length>0&&this.resortChildrenOf(F),this.nodes.delete(e),this.byPath.delete(r),this.synthetic.delete(r),!0}applyRename(r,e){let t=this.currentFolder,i=!t||e===t||e.startsWith(t+"/"),s=!t||r.path===t||r.path.startsWith(t+"/");if(!i&&!s)return!1;if(i&&!s)return this.applyDelete(e);if(!i&&s)return this.applyChange(r);if(Ht(r.path))return this.byPath.has(e)?this.applyDelete(e):!1;let n=this.byPath.get(e);if(!n)return this.applyChange(r);this.byPath.delete(e),this.byPath.set(r.path,n);let o=this.nodes.get(n);return o&&(o.file=r),!0}detachFromParent(r){let e=this.nodes.get(r.parent??F);if(!e)return;let t=e.children.indexOf(r.id);t>=0&&e.children.splice(t,1)}attachToParent(r){let e=this.nodes.get(r.parent??F);e||(e=this.nodes.get(F)),e.children.includes(r.id)||e.children.push(r.id)}resortChildrenOf(r){let e=this.nodes.get(r);if(e&&(e.children.sort((t,i)=>{let s=this.nodes.get(t),n=this.nodes.get(i);return(s.created||"").localeCompare(n.created||"")}),this.orderProvider)){let t=this.orderProvider(r);if(t.length>0){let i=new Map;t.forEach((s,n)=>i.set(s,n)),e.children.sort((s,n)=>{let o=i.has(s)?i.get(s):1/0,a=i.has(n)?i.get(n):1/0;return o===a?0:o-a})}}}};var Po=class{constructor(){this.enabled=!1;this.buckets=new Map}record(r,e){if(!this.enabled)return;let t=this.buckets.get(r);t||(t={count:0,total:0,max:0},this.buckets.set(r,t)),t.count+=1,t.total+=e,e>t.max&&(t.max=e)}time(r,e){if(!this.enabled)return e();let t=performance.now();try{return e()}finally{this.record(r,performance.now()-t)}}async timeAsync(r,e){if(!this.enabled)return e();let t=performance.now();try{return await e()}finally{this.record(r,performance.now()-t)}}reset(){this.buckets.clear()}hasData(){return this.buckets.size>0}report(){let r=[...this.buckets.entries()].map(([t,i])=>({label:t,...i,avg:i.total/i.count})).sort((t,i)=>i.total-t.total);if(r.length===0)return"Stashpad perf: no samples (enable profiling, then use the app).";let e=["**Stashpad performance profile**","","| label | count | total (ms) | avg (ms) | max (ms) |","| --- | ---: | ---: | ---: | ---: |"];for(let t of r)e.push(`| \`${t.label}\` | ${t.count} | ${t.total.toFixed(0)} | ${t.avg.toFixed(1)} | ${t.max.toFixed(0)} |`);return e.join(` +`)}},Se=new Po;var Ft={min:6e4,minute:6e4,m:6e4,h:36e5,hr:36e5,hour:36e5,d:864e5,day:864e5,w:6048e5,wk:6048e5,week:6048e5};function Ho(A){if(typeof A=="number")return A>0?A:null;if(!A)return null;let r=String(A).trim().toLowerCase().match(/^(\d+(?:\.\d+)?)\s*([a-z]+)?$/);if(!r)return null;let e=parseFloat(r[1]),t=(r[2]||"min").replace(/s$/,""),i=Ft[t];return i&&e>0?e*i:null}var Ca=["sunday","monday","tuesday","wednesday","thursday","friday","saturday"];function Ba(A,r){let e=new Date(A),t=e.getDate();return e.setMonth(e.getMonth()+r),e.getDate()<t&&e.setDate(0),e.getTime()}function ya(A,r){let e=new Date(A);return e.setFullYear(e.getFullYear()+r),e.getTime()}function Rc(A,r){let e=new Date(A);do e.setDate(e.getDate()+1);while(e.getDay()!==r);return e.getTime()}function Uc(A){let r=new Date(A);do r.setDate(r.getDate()+1);while(r.getDay()===0||r.getDay()===6);return r.getTime()}function sn(A){if(!A)return null;let r=String(A).trim().toLowerCase();if(!r)return null;let e="due";/\b(when done|after completion|on completion|from completion)\b/.test(r)&&(e="completion",r=r.replace(/\b(when done|after completion|on completion|from completion)\b/g,"").trim()),r=r.replace(/^every\s+/,"").replace(/\s+/g," ").trim();let t=(n,o)=>({next:n,anchor:e,label:o});if(/^(first|1st) of (the )?month$/.test(r))return t(n=>{let o=new Date(n);return o.setMonth(o.getMonth()+1,1),o.getTime()},"first of the month");let i=Ca.indexOf(r.replace(/s$/,""));if(i>=0)return t(n=>Rc(n,i),`every ${Ca[i]}`);if(r==="weekday"||r==="weekdays"||r==="business day"||r==="business days")return t(Uc,"every weekday");if(r==="daily"||r==="day")return t(n=>n+Ft.day,"every day");if(r==="weekly"||r==="week")return t(n=>n+Ft.week,"every week");if(r==="monthly"||r==="month")return t(n=>Ba(n,1),"every month");if(r==="yearly"||r==="annually"||r==="year")return t(n=>ya(n,1),"every year");if(r==="hourly"||r==="hour")return t(n=>n+Ft.h,"every hour");let s=r.match(/^(\d+)\s*([a-z]+)$/);if(s){let n=parseInt(s[1],10),o=s[2].replace(/s$/,"");if(n>0){if(o==="day")return t(a=>a+n*Ft.day,`every ${n} days`);if(o==="week"||o==="wk")return t(a=>a+n*Ft.week,`every ${n} weeks`);if(o==="month"||o==="mo")return t(a=>Ba(a,n),`every ${n} months`);if(o==="year"||o==="yr")return t(a=>ya(a,n),`every ${n} years`);if(o==="hour"||o==="hr"||o==="h")return t(a=>a+n*Ft.h,`every ${n} hours`);if(o==="min"||o==="minute"||o==="m")return t(a=>a+n*Ft.min,`every ${n} minutes`)}}return null}function nn(A,r,e){let t=A.anchor==="completion"?e:r??e,i=A.next(t),s=0;for(;i<=e&&s++<5e3;)i=A.next(i);if(i<=e){i=A.next(e);let n=0;for(;i<=e&&n++<5e3;)i=A.next(i)}return i}var wa=".stashpad-order.json",gi=class{constructor(r){this.app=r;this.cache=new Map;this.pendingTimers=new Map;this.writeInFlight=new Map}async load(r){if(this.cache.has(r))return this.cache.get(r);let e=`${r}/${wa}`,t=this.app.vault.adapter,i={};try{if(await t.exists(e)){let s=await t.read(e),n=JSON.parse(s);if(n&&typeof n=="object"&&!Array.isArray(n))for(let[o,a]of Object.entries(n))Array.isArray(a)&&a.every(l=>typeof l=="string")&&(i[o]=a)}}catch(s){console.warn("Stashpad: order load failed",s)}return this.cache.set(r,i),i}async save(r){this.scheduleWrite(r)}async flush(r){let e=this.pendingTimers.get(r);e!=null&&(window.clearTimeout(e),this.pendingTimers.delete(r)),await this.writeNow(r)}scheduleWrite(r){let e=this.pendingTimers.get(r);e!=null&&window.clearTimeout(e);let t=window.setTimeout(()=>{this.pendingTimers.delete(r),this.writeNow(r)},150);this.pendingTimers.set(r,t)}async writeNow(r){let t=(this.writeInFlight.get(r)??Promise.resolve()).then(()=>this.doWrite(r));this.writeInFlight.set(r,t);try{await t}finally{this.writeInFlight.get(r)===t&&this.writeInFlight.delete(r)}}async doWrite(r){let e=this.cache.get(r)??{},t=`${r}/${wa}`,i=this.app.vault.adapter;try{if(await i.exists(t)){let n=JSON.parse(await i.read(t));if(n&&typeof n=="object"&&!Array.isArray(n))for(let[o,a]of Object.entries(n))!(o in e)&&Array.isArray(a)&&a.every(l=>typeof l=="string")&&(e[o]=a)}}catch{}let s={};for(let[n,o]of Object.entries(e))o.length>0&&(s[n]=o);this.cache.set(r,s);try{if(Object.keys(s).length===0)try{await i.remove(t)}catch{}else await i.write(t,JSON.stringify(s,null,2))}catch(n){console.warn("Stashpad: order save failed",n)}}getOrder(r,e){return this.cache.get(r)?.[e]?.slice()??[]}setOrder(r,e,t){let i=this.cache.get(r)??{};i[e]=t.slice(),this.cache.set(r,i)}appendChild(r,e,t){let i=this.cache.get(r)??{},s=i[e]??[];s.includes(t)||s.push(t),i[e]=s,this.cache.set(r,i)}removeChild(r,e){let t=this.cache.get(r);if(t)for(let i of Object.values(t)){let s=i.indexOf(e);s>=0&&i.splice(s,1)}}invalidate(r){this.cache.delete(r)}};var ui={manual:"Manual","created-asc":"Created \u2014 oldest first","created-desc":"Created \u2014 newest first","modified-asc":"Modified \u2014 oldest first","modified-desc":"Modified \u2014 newest first","title-az":"Title \u2014 A\u2192Z","title-za":"Title \u2014 Z\u2192A"},Ro=["manual","created-asc","created-desc","modified-asc","modified-desc","title-az","title-za"],Ea=new Set(Ro),Qa=".stashpad-sort.json",on=class{constructor(r){this.app=r;this.cache=new Map;this.pendingTimers=new Map;this.writeInFlight=new Map}async load(r){if(this.cache.has(r))return this.cache.get(r);let e=`${r}/${Qa}`,t=this.app.vault.adapter,i={};try{if(await t.exists(e)){let s=JSON.parse(await t.read(e));if(s&&typeof s=="object"&&!Array.isArray(s))for(let[n,o]of Object.entries(s))typeof o=="string"&&Ea.has(o)&&(i[n]=o)}}catch(s){console.warn("Stashpad: sort load failed",s)}return this.cache.set(r,i),i}async save(r){this.scheduleWrite(r)}async flush(r){let e=this.pendingTimers.get(r);e!=null&&(window.clearTimeout(e),this.pendingTimers.delete(r)),await this.writeNow(r)}scheduleWrite(r){let e=this.pendingTimers.get(r);e!=null&&window.clearTimeout(e);let t=window.setTimeout(()=>{this.pendingTimers.delete(r),this.writeNow(r)},150);this.pendingTimers.set(r,t)}async writeNow(r){let t=(this.writeInFlight.get(r)??Promise.resolve()).then(()=>this.doWrite(r));this.writeInFlight.set(r,t);try{await t}finally{this.writeInFlight.get(r)===t&&this.writeInFlight.delete(r)}}async doWrite(r){let e=this.cache.get(r)??{},t=`${r}/${Qa}`,i=this.app.vault.adapter;try{if(await i.exists(t)){let s=JSON.parse(await i.read(t));if(s&&typeof s=="object"&&!Array.isArray(s))for(let[n,o]of Object.entries(s))!(n in e)&&typeof o=="string"&&Ea.has(o)&&(e[n]=o)}}catch{}try{if(Object.keys(e).length===0)try{await i.remove(t)}catch{}else await i.write(t,JSON.stringify(e,null,2))}catch(s){console.warn("Stashpad: sort save failed",s)}}getMode(r,e){return this.cache.get(r)?.[e]??"manual"}setMode(r,e,t){let i=this.cache.get(r)??{};t==="manual"?delete i[e]:i[e]=t,this.cache.set(r,i)}removeParent(r,e){let t=this.cache.get(r);t&&delete t[e]}invalidate(r){this.cache.delete(r)}};le();var kt="parentLink",fi="children";var ln=class ln{constructor(r,e){this.app=r;this.getTree=e;this.pending=new Set;this.timer=null;this.activityListeners=new Set;this.errorListeners=new Set}onActivity(r){return this.activityListeners.add(r),()=>this.activityListeners.delete(r)}emitActivity(){let r=this.pending.size;for(let e of this.activityListeners)try{e(r)}catch(t){console.warn("[Stashpad] fmSync activity listener failed",t)}}schedule(r){if(!r||!X().writeRecoveryLinks)return;let e=this.pending.size;this.pending.add(r),this.pending.size!==e&&this.emitActivity(),this.kick()}scheduleParentChange(r,e,t){this.schedule(r),e&&this.schedule(e),this.schedule(t)}scheduleSubtree(r){let e=this.getTree(),t=i=>{this.schedule(i);for(let s of e.getChildren(i))t(s.id)};t(r)}scheduleParentOfDeleted(r){r&&this.schedule(r)}async flush(){for(this.timer!=null&&(window.clearTimeout(this.timer),this.timer=null);this.pending.size>0;){let r=this.pending.values().next().value;if(r===void 0)break;this.pending.delete(r),this.emitActivity(),await this.syncOne(r)}}pendingCount(){return this.pending.size}onError(r){return this.errorListeners.add(r),()=>this.errorListeners.delete(r)}emitError(r,e){for(let t of this.errorListeners)try{t(r,e)}catch(i){console.warn("[Stashpad] fmSync error listener failed",i)}}reset(){this.timer!=null&&(window.clearTimeout(this.timer),this.timer=null),this.pending.clear()}kick(){this.timer!=null||this.pending.size===0||(this.timer=window.setTimeout(()=>this.tick(),ln.PACING_MS))}async tick(){this.timer=null;let r=this.pending.values().next().value;if(r!==void 0){this.pending.delete(r),this.emitActivity();try{await this.syncOne(r)}catch(e){console.warn("[Stashpad] frontmatter sync tick failed",e)}this.pending.size>0&&this.kick()}}wouldWrite(r){let t=this.getTree().get(r);if(!t||!t.file)return!1;let i=this.computeParentLink(t),s=this.computeChildrenLinks(t),n=this.app.metadataCache.getFileCache(t.file)?.frontmatter,o=n&&typeof n[kt]=="string"?n[kt]:null,a=n?.[fi],l=Array.isArray(a)?a.filter(c=>typeof c=="string"):[];if((o??null)!==(i??null)||l.length!==s.length)return!0;for(let c=0;c<l.length;c++)if(l[c]!==s[c])return!0;return!1}async syncOne(r){let t=this.getTree().get(r);if(!t||!t.file||!this.wouldWrite(r))return;let i=this.computeParentLink(t),s=this.computeChildrenLinks(t);try{await Se.timeAsync("write.fmSync",()=>this.app.fileManager.processFrontMatter(t.file,n=>{i?n[kt]=i:delete n[kt],s.length>0?n[fi]=s:delete n[fi]}))}catch(n){console.warn("[Stashpad] frontmatter sync failed",t.file?.path,n),this.emitError(t.file.path,n)}}computeParentLink(r){if(r.id===F)return null;let e=this.getTree();if(!r.parent||r.parent===F){let i=e.getRoot();return i&&i.id!==r.id&&i.file?rn(i.file.path):null}let t=e.get(r.parent);return t?.file?rn(t.file.path):null}computeChildrenLinks(r){return this.getTree().getChildren(r.id).filter(t=>!!t.file).map(t=>rn(t.file.path))}};ln.PACING_MS=100;var an=ln;async function Ii(A,r){let e=r.endsWith("/")?r:r+"/",t=new Map,i=new Map;for(let c of A.vault.getMarkdownFiles()){if(c.path!==r&&!c.path.startsWith(e))continue;let d=A.metadataCache.getFileCache(c)?.frontmatter,h=typeof d?.id=="string"?d.id:null;if(!h)continue;let g=typeof d?.parent=="string"?d.parent:F;t.set(h,{file:c,id:h,parent:g});let f=i.get(g)??[];f.push(h),i.set(g,f)}let s=c=>rn(c.file.path),n=c=>{if(c.id===F)return null;if(c.parent===F||!c.parent){let h=t.get(F);return h?s(h):null}let d=t.get(c.parent);return d?s(d):null},o=c=>{let d=i.get(c.id)??[],h=[];for(let g of d){let f=t.get(g);f&&h.push(s(f))}return h},a=0,l=0;for(let c of t.values()){a+=1;let d=n(c),h=o(c),g=A.metadataCache.getFileCache(c.file)?.frontmatter,f=g&&typeof g[kt]=="string"?g[kt]:null,p=g?.[fi],u=Array.isArray(p)?p.filter(C=>typeof C=="string"):[],I=(f??null)===(d??null),m=u.length===h.length&&u.every((C,B)=>C===h[B]);if(!(I&&m))try{await A.fileManager.processFrontMatter(c.file,C=>{d?C[kt]=d:delete C[kt],h.length>0?C[fi]=h:delete C[fi]}),l+=1,await new Promise(C=>setTimeout(C,50))}catch(C){console.warn("[Stashpad] rebootstrap fm sync failed",c.file.path,C)}}return{checked:a,written:l}}function rn(A){return`[[${A.replace(/\.md$/i,"")}]]`}Le();ri();var cn=class{constructor(r,e){this.tree=r;this.log=e;this.sweepChain=Promise.resolve()}async sweep(r){let e=this.sweepChain.then(()=>this.doSweep(r),()=>this.doSweep(r));return this.sweepChain=e.catch(()=>{}),e}async doSweep(r){let e=await this.log.readState(),t=this.tree.snapshot(),i=n=>{if(!r)return!0;let o=r.replace(/\/+$/,"");return n===o||n.startsWith(o+"/")};for(let[n,o]of Object.entries(t)){let a=e[n];a?a.parent!==o.parent?await this.log.append({type:"parent_change",id:n,payload:{from:a.parent,to:o.parent}}):a.path!==o.path&&await this.log.append({type:"rename",id:n,payload:{from:a.path,to:o.path}}):await this.log.append({type:"create",id:n,payload:{path:o.path,parent:o.parent}})}for(let[n,o]of Object.entries(e))!t[n]&&i(o.path)&&await this.log.append({type:"missing",id:n,payload:{lastPath:o.path}});let s={};for(let[n,o]of Object.entries(e))i(o.path)||(s[n]=o);for(let[n,o]of Object.entries(t))s[n]=o;await this.log.writeState(s)}};var ue=require("obsidian");le();so();var we=ue.moment;function Uo(A){let r=A.trim().toLowerCase();if(!r)return null;let e=Date.now(),t=p=>we(p).startOf("day").valueOf(),i=/\b(\d{1,2})(?::(\d{2}))?\s*(am|pm)?\b/,s=0,n=!1,o=r,a=i.exec(r);if(a&&(a[3]||a[2])){let p=parseInt(a[1],10),u=a[2]?parseInt(a[2],10):0,I=a[3];I==="pm"&&p<12&&(p+=12),I==="am"&&p===12&&(p=0),p>=0&&p<24&&u>=0&&u<60&&(s=(p*3600+u*60)*1e3,n=!0,o=r.replace(a[0]," ").replace(/\s+/g," ").trim())}let l=p=>n?p+s:p;if(o===""||o==="today")return l(t(e));if(o==="yesterday")return l(t(e)-864e5);if(o==="tomorrow")return l(t(e)+864e5);let c=["sunday","monday","tuesday","wednesday","thursday","friday","saturday"],d=["sun","mon","tue","wed","thu","fri","sat"],h=c.indexOf(o)>=0?c.indexOf(o):d.indexOf(o);if(h>=0){let p=we().startOf("day"),I=(p.day()-h+7)%7;return l(p.subtract(I,"days").valueOf())}let g=/^(\d+)\s*([dwmy])$/.exec(o);if(g){let p=parseInt(g[1],10),u={d:"days",w:"weeks",m:"months",y:"years"}[g[2]];return l(we().subtract(p,u).startOf("day").valueOf())}let f=we(o,["YYYY-MM-DD","YYYY/MM/DD","MM-DD-YYYY","MM/DD/YYYY","M-D-YYYY","M/D/YYYY"],!0);return f.isValid()?l(f.startOf("day").valueOf()):null}function Go(A){let r={text:[],filters:{}},e=(A||"").trim();if(!e)return r;let t=/\b(in|before|after|on):\s*(?:\[([^\]]*)\]|([^]*?)(?=\s+(?:in|before|after|on):|$))/gi,i=e,s;for(;(s=t.exec(e))!=null;){let n=s[1].toLowerCase(),o=(s[2]!==void 0?s[2]:s[3]??"").trim();if(o){if(n==="in")r.filters.in=o.toLowerCase();else if(n==="before"){let a=Uo(o);a!=null&&(r.filters.before=a)}else if(n==="after"){let a=Uo(o);a!=null&&(r.filters.after=a)}else if(n==="on"){let a=Uo(o);if(a!=null)if(/\b\d{1,2}:\d{2}\b|\b\d{1,2}\s*(?:am|pm)\b/i.test(o))r.filters.on={start:a-6e4,end:a+6e4};else{let c=we(a).startOf("day").valueOf();r.filters.on={start:c,end:c+864e5}}}i=i.replace(s[0]," ")}}for(let n of i.split(/\s+/))n&&r.text.push(n.toLowerCase());return r}var it=class A extends ue.SuggestModal{constructor(e,t,i,s){super(e);this.tree=t;this.titleFn=i;this.opts=s;this.notes=[];this.includeExcluded=!1;this.pendingCleanups=[];this.crossFolderIndex=null;this.whenBuilderEl=null;this.whenMode="on";this.whenSingleText="";this.whenBetweenEndText="";this.setPlaceholder(s.placeholder??(s.mode==="search"?"Search notes\u2026":"Pick a note\u2026")),this.loadAll()}onClose(){for(;this.pendingCleanups.length>0;){let e=this.pendingCleanups.pop();try{e&&e()}catch{}}try{this.opts.onClose?.()}catch{}}loadAll(){let e=this.tree.getRoot(),t=e.file?.parent?.name?.trim()??"",i=t?`Home \u2014 ${t}`:"Home",s=(n,o)=>{let a=this.tree.get(n);a?.file&&n!==F?this.notes.push({node:a,title:`${" ".repeat(o)}${this.titleFn(a)}`,body:""}):a?.file&&n===F&&this.notes.push({node:a,title:i,body:""});for(let l of this.tree.getChildren(n))s(l.id,o+1)};e.file&&this.notes.push({node:e,title:i,body:""});for(let n of this.tree.getChildren(F))s(n.id,1);if(this.opts.lockedNotes)for(let n of this.opts.lockedNotes()){let o=(this.opts.hideLockedTitles??!1)||!n.title;this.notes.push({node:null,title:o?"Locked note":n.title,body:"",locked:n})}for(let n of this.notes)n.node?.file&&this.app.vault.cachedRead(n.node.file).then(o=>{n.body=this.stripFm(o)});if(this.opts.crossFolderNotes){let n=this.opts.crossFolderNotes();for(let o of n)this.notes.push({node:null,title:o.title,body:o.body,cross:o});for(let o of this.notes)!o.cross||o.body||o.cross.file&&this.app.vault.cachedRead(o.cross.file).then(a=>{o.body=this.stripFm(a)})}}createdMsFor(e){if(e.node?.created){let t=Date.parse(e.node.created);if(!Number.isNaN(t))return t}if(e.locked?.created){let t=Date.parse(e.locked.created);if(!Number.isNaN(t))return t}return e.cross?.file?.stat?.ctime!=null?e.cross.file.stat.ctime:null}stripFm(e){if(!e.startsWith("---"))return e;let t=e.indexOf(` +---`,3);return t===-1?e:e.slice(t+4).replace(/^\r?\n/,"")}getSuggestions(e){let t=e.trim().toLowerCase(),i=Go(e),s=i.text,n=m=>{if(!s.length)return!0;for(let C of s)if(!m.includes(C))return!1;return!0},o=null,a=null;if(i.filters.in){let m=i.filters.in.split(/\s+/).filter(Boolean);o=new Set,a=new Map;let C=y=>m.every(v=>y.toLowerCase().includes(v)),B=y=>{if(!o.has(y)){o.add(y);for(let v of this.tree.getChildren(y))B(v.id)}};for(let y of this.notes)!y.node||y.cross||C(y.title)&&B(y.node.id);let w=new Map;for(let y of this.notes){if(!y.cross)continue;let v=w.get(y.cross.folder)??[];v.push(y),w.set(y.cross.folder,v)}for(let[y,v]of w){let E=new Map;for(let x of v){let H=x.cross?.parentId??null;if(!H)continue;let G=E.get(H)??[];G.push(x.cross.id),E.set(H,G)}let Q=new Set;a.set(y,Q);let S=x=>{if(!Q.has(x)){Q.add(x);for(let H of E.get(x)??[])S(H)}};for(let x of v)C(x.title)&&S(x.cross.id)}}let l=m=>{let C=i.filters;if(o||a)if(m.cross){let B=a?.get(m.cross.folder);if(!B||!B.has(m.cross.id))return!1}else if(m.node){if(!o||!o.has(m.node.id))return!1}else return!1;if(C.before||C.after||C.on){let B=this.createdMsFor(m);if(B==null||C.before!=null&&B>=C.before||C.after!=null&&B<=C.after||C.on&&(B<C.on.start||B>=C.on.end))return!1}return!0},c=m=>n(m.toLowerCase()),d=this.notes.filter(m=>!m.cross),h=this.notes.filter(m=>m.cross),g=(m,C)=>({id:m.locked?`locked:${m.locked.blob}`:m.cross?`cross:${m.cross.folder}:${m.cross.id}`:m.node.id,label:m.title,node:m.node,kind:"note",bodyPreview:this.previewFromBody(m.body,C),matchLine:C,crossFolder:m.cross?.folder,crossFile:m.cross?.file,crossId:m.cross?.id,locked:m.locked}),f=m=>{let C=[];for(let B of m)if(l(B))if(this.opts.mode==="search"){if(!t){C.push(g(B,-1));continue}let w=n(B.title.toLowerCase()),y=B.body.split(/\r?\n/),v=[];for(let Q=0;Q<y.length;Q++)c(y[Q])&&v.push(Q);let E=v.length>0||n(B.body.toLowerCase());if(!w&&!E)continue;if(v.length===0)C.push(g(B,-1));else{let Q=[],S=-100;for(let H of v)H-S>5&&Q.push(H),S=H;let x=5;for(let H of Q.slice(0,x))C.push(g(B,H))}}else{if(t&&!n(B.title.toLowerCase())&&!n(B.body.toLowerCase()))continue;C.push(g(B,-1))}return C},p=f(d),u=[...p];if(this.opts.mode==="search"?t?!0:p.length<10:!!t){let m=f(h);this.opts.mode==="search"&&!t&&(u.length=Math.min(u.length,50)),u.push(...m)}if(this.opts.folderResults){let m=this.opts.folderResults(),C={id:"__folder_picker__",label:"Open folder in a new tab\u2026",node:null,kind:"folder-open"};m.length>0&&(t?m.some(B=>{let w=B.split("/").pop()??B;return n(`${B.toLowerCase()} ${w.toLowerCase()}`)})&&u.push(C):u.unshift(C))}if(this.opts.allowCreate&&t&&!u.some(m=>m.label.trim().toLowerCase()===t)&&u.push({id:"__create__",label:`Create new: "${e}"`,node:null,kind:"create"}),i.text.join("").length>=3){let m=B=>B.kind==="note"&&(B.id===F||B.crossId===F)&&n(B.label.toLowerCase()),C=u.filter(m);if(C.length>0){let B=u.filter(w=>!m(w));u.splice(0,u.length,...C,...B)}}return this.opts.excludedFolderNotes&&!this.includeExcluded&&u.push({id:"__search_excluded__",label:"Search excluded Stashpad folders",node:null,kind:"search-excluded"}),u}loadExcludedNotes(){if(this.includeExcluded||!this.opts.excludedFolderNotes)return;this.includeExcluded=!0;for(let t of this.opts.excludedFolderNotes())this.notes.push({node:null,title:t.title,body:t.body,cross:t});for(let t of this.notes)!t.cross||t.body||!t.cross.file||this.app.vault.cachedRead(t.cross.file).then(i=>{t.body=this.stripFm(i)});let e=this.inputEl;e&&(e.dispatchEvent(new Event("input",{bubbles:!0})),setTimeout(()=>e.dispatchEvent(new Event("input",{bubbles:!0})),250))}previewFromBody(e,t){let i=e.split(/\r?\n/);if(t<0)return i.filter(a=>a.trim().length>0).slice(0,3).join(` `);let s=Math.max(0,t-2),n=Math.min(i.length,t+3);return i.slice(s,n).filter(o=>o.trim().length>0).join(` -`)}renderSuggestion(e,t){if(t.addClass("stashpad-suggest-item"),e.kind==="create"){t.createDiv({cls:"stashpad-suggest-create",text:e.label});return}if(e.kind==="folder-open"){t.addClass("is-folder-open"),t.createDiv({cls:"stashpad-suggest-title",text:e.label}),e.folder?t.createDiv({cls:"stashpad-suggest-preview",text:e.folder}):t.createDiv({cls:"stashpad-suggest-preview",text:"Click to choose a folder\u2026"});return}if(e.kind==="search-excluded"){t.addClass("is-search-excluded");let u=t.createDiv({cls:"stashpad-suggest-title stashpad-search-excluded-row"}),p=u.createSpan({cls:"stashpad-search-excluded-icon"});(0,ue.setIcon)(p,"folder-search"),u.createSpan({text:e.label}),t.createDiv({cls:"stashpad-suggest-preview",text:"Include folders you've excluded from search (e.g. to move a note there)."});return}if(e.locked){t.addClass("stashpad-suggest-row","is-locked");let u=t.createDiv({cls:"stashpad-suggest-body"}),p=u.createDiv({cls:"stashpad-suggest-title stashpad-suggest-locked"});(0,ue.setIcon)(p.createSpan({cls:"stashpad-suggest-locked-icon"}),"lock");let f=vo(this.inputEl?.value??"").text;this.highlightInto(p.createSpan(),e.label,f),u.createDiv({cls:"stashpad-suggest-preview",text:e.locked.count>1?`${e.locked.count} notes \xB7 locked`:"Encrypted \u2014 locked"});return}e.crossFolder&&t.addClass("is-cross-folder"),t.addClass("stashpad-suggest-row");let i=t.createDiv({cls:"stashpad-suggest-body"}),s=this.notes.find(u=>e.crossFolder?u.cross?.folder===e.crossFolder&&u.cross?.id===e.crossId:u.node?.id===e.id),n=vo(this.inputEl?.value??"").text,o=this.firstLineOfBody(s?.body??"")||e.label.trim(),a=i.createDiv({cls:"stashpad-suggest-title"});this.highlightInto(a,o,n);let l=e.crossFolder??this.opts.localFolder??null;if(e.bodyPreview&&e.matchLine!=null&&e.matchLine>=0){let u=i.createDiv({cls:"stashpad-suggest-preview stashpad-suggest-snippet"});this.highlightInto(u,e.bodyPreview,n)}let c="";if(e.crossFolder?c=s?.cross?.parentBlurb??"":c=this.parentBlurbFor(e.node),c){let u=i.createDiv({cls:"stashpad-suggest-preview"});u.appendText("Parent: "),this.highlightInto(u,c,n)}let d=e.crossFolder?this.buildCrossBreadcrumb(e):this.buildLocalBreadcrumb(e.node),h=e.crossFolder??this.opts.localFolder??null;if(h||d){let u=i.createDiv({cls:"stashpad-suggest-breadcrumb"}),f=[h?h.split("/").pop()||h:"",d].filter(I=>I&&I.length>0).join(" \u203A ");f&&u.setText(f)}let g=s?this.createdMsFor(s):null;if(g!=null||l){let u=t.createDiv({cls:"stashpad-suggest-rail"});if(g!=null){let p=u.createDiv({cls:"stashpad-suggest-time"});p.setText(this.formatRelativeTime(g)),p.title=ye(g).format("YYYY-MM-DD HH:mm")}l&&u.createDiv({cls:"stashpad-suggest-folder-badge",text:l.split("/").pop()||l})}}formatRelativeTime(e){let t=ye(e),i=ye().startOf("day").diff(ye(e).startOf("day"),"days");return i===0?t.format("h:mm a"):i===1?"yesterday":i>1&&i<7?`${i}d ago`:i>=7&&i<30?`${Math.floor(i/7)}w ago`:i>=30&&i<365?t.format("MMM D"):t.format("MMM D, YYYY")}highlightInto(e,t,i){if(!t)return;let s=i.filter(c=>c&&c.length>0);if(!s.length){e.appendText(t);return}let n=t.toLowerCase(),o=[];for(let c of s){let d=0;for(;(d=n.indexOf(c,d))!==-1;)o.push([d,d+c.length]),d+=c.length}if(!o.length){e.appendText(t);return}o.sort((c,d)=>c[0]-d[0]);let a=[];for(let c of o){let d=a[a.length-1];d&&c[0]<=d[1]?d[1]=Math.max(d[1],c[1]):a.push([c[0],c[1]])}let l=0;for(let[c,d]of a)c>l&&e.appendText(t.slice(l,c)),e.createSpan({cls:"stashpad-suggest-match",text:t.slice(c,d)}),l=d;l<t.length&&e.appendText(t.slice(l))}buildLocalBreadcrumb(e){if(!e||!e.parent||e.parent===k)return"";let t=[],i=new Set([e.id]),s=this.tree.get(e.parent);for(;s&&s.id!==k&&!i.has(s.id);)i.add(s.id),t.unshift(this.titleFn(s)),s=s.parent&&s.parent!==k?this.tree.get(s.parent):void 0;return t.join(" \u203A ")}buildCrossBreadcrumb(e){if(!e.crossFolder)return"";if(!this.crossFolderIndex){this.crossFolderIndex=new Map;for(let l of this.notes){if(!l.cross)continue;let c=this.crossFolderIndex.get(l.cross.folder);c||(c=new Map,this.crossFolderIndex.set(l.cross.folder,c)),c.set(l.cross.id,l.cross)}}let t=this.crossFolderIndex.get(e.crossFolder);if(!t)return"";let i=e.crossId??e.id.replace(/^cross:/,""),s=t.get(i);if(!s)return"";let n=[],o=new Set([s.id]),a=s.parentId?t.get(s.parentId):null;for(;a&&!o.has(a.id);)o.add(a.id),n.unshift(a.title),a=a.parentId?t.get(a.parentId):null;return n.join(" \u203A ")}firstLineOfBody(e){if(!e)return"";let t=e.split(/\r?\n/);for(let i of t){let s=i.trim();if(s)return s}return""}parentBlurbFor(e){if(!e||!e.parent||e.parent===k)return"";let t=this.tree.get(e.parent);if(!t||!t.file)return"";let i=this.notes.find(s=>s.node?.id===t.id);return i?this.firstLineOfBody(i.body):""}onOpen(){if(super.onOpen(),this.opts.showFilterChips&&!ue.Platform.isMobile){let e=this.inputEl,t=this.resultContainerEl;if(!e||!t)return;this.mountFilterChipRow(e,t);return}if(ue.Platform.isMobile){let e=this.inputEl;e&&e.focus()}}mountFilterChipRow(e,t){e.addEventListener("keydown",c=>{if(c.key==="["){c.preventDefault();let d=e.selectionStart??e.value.length,h=e.selectionEnd??d,g=e.value.slice(0,d),u=e.value.slice(d,h),p=e.value.slice(h);e.value=`${g}[${u}]${p}`;let f=d+1+u.length;e.setSelectionRange(f,f),e.dispatchEvent(new Event("input",{bubbles:!0}));return}if(c.key==="]"){let d=e.selectionStart??-1;d>=0&&e.value[d]==="]"&&(c.preventDefault(),e.setSelectionRange(d+1,d+1));return}if(c.key==="Backspace"&&!c.metaKey&&!c.ctrlKey&&!c.altKey&&e.selectionStart===e.selectionEnd){let d=e.selectionStart??-1;if(d>0&&e.value[d-1]==="["&&e.value[d]==="]"){c.preventDefault();let h=e.value.slice(0,d-1),g=e.value.slice(d+1);e.value=`${h}${g}`,e.setSelectionRange(d-1,d-1),e.dispatchEvent(new Event("input",{bubbles:!0}))}}});let i=document.createElement("div");i.className="stashpad-search-filter-row";let s=document.createElement("span");s.className="stashpad-search-filter-label",s.textContent="Filters:",i.appendChild(s);let n=document.createElement("button");n.className="stashpad-search-filter-chip",n.type="button",n.textContent="in:[parent note]",n.title='Insert "in:" \u2014 filter by parent note.',n.addEventListener("mousedown",c=>{c.preventDefault()}),n.addEventListener("click",c=>{c.preventDefault(),this.appendFilterToInput(e,"in","")}),n.addEventListener("keydown",c=>{(c.key==="Enter"||c.key===" ")&&(c.preventDefault(),c.stopPropagation(),n.click())}),i.appendChild(n);let o=document.createElement("button");o.className="stashpad-search-filter-chip stashpad-search-filter-when",o.type="button",o.textContent="when:[date / day / timeframe]",o.title="Open the time-filter builder (Before / On / After / Between).",o.addEventListener("mousedown",c=>{c.preventDefault()}),o.addEventListener("click",c=>{c.preventDefault(),this.toggleWhenBuilder(e,i)}),o.addEventListener("keydown",c=>{(c.key==="Enter"||c.key===" ")&&(c.preventDefault(),c.stopPropagation(),o.click())}),i.appendChild(o),t.parentElement?.insertBefore(i,t);let a=new ue.Scope(this.scope),l=c=>{let d=document.activeElement;return d?.classList?.contains("stashpad-search-filter-chip")?(c.preventDefault(),d.click(),!1):c.key==="Enter"&&!c.isComposing?(c.preventDefault(),this.selectActiveSuggestion?.(c),!1):!0};a.register([],"Enter",l),a.register([]," ",l),a.register(["Mod"],"f",c=>{c.preventDefault();let d=this.inputEl;return d&&(d.focus(),d.select()),!1}),a.register([],"Tab",c=>{let d=this.modalEl,h=this.inputEl;if(!d)return!0;let g=Array.from(d.querySelectorAll('input, button, select, textarea, a[href], [tabindex]:not([tabindex="-1"])')).filter(f=>!f.hasAttribute("disabled")&&f.tabIndex!==-1&&f.offsetParent!==null);if(g.length===0)return!0;let u=document.activeElement,p=u?g.indexOf(u):-1;if(c.shiftKey){if(p<=0)return c.preventDefault(),g[g.length-1].focus(),!1}else if(p===g.length-1)return c.preventDefault(),(h??g[0]).focus(),!1;return!0}),this.app.keymap?.pushScope(a),this.pendingCleanups.push(()=>{try{this.app.keymap?.popScope(a)}catch{}})}appendFilterToInput(e,t,i){let s=e.value,o=`${s.length>0&&!/\s$/.test(s)?" ":""}${t}: [`,a="]",l=i;e.value=`${s}${o}${l}${a}`;let c=s.length+o.length+l.length;if(e.setSelectionRange(c,c+0),!l){let d=s.length+o.length;e.setSelectionRange(d,d)}e.focus(),e.dispatchEvent(new Event("input",{bubbles:!0}))}toggleWhenBuilder(e,t){let i=this.modalEl;if(this.whenBuilderEl){this.whenBuilderEl.remove(),this.whenBuilderEl=null,i?.removeClass("is-when-builder-open");return}i?.addClass("is-when-builder-open");let s=document.createElement("div");s.className="stashpad-when-builder";let n=document.createElement("div");n.className="stashpad-when-tabs";let o=[{id:"before",label:"Before"},{id:"on",label:"On"},{id:"after",label:"After"},{id:"between",label:"Between"}],a={};for(let f of o){let I=document.createElement("button");I.type="button",I.className="stashpad-when-tab",I.textContent=f.label,this.whenMode===f.id&&I.addClass("is-active"),I.addEventListener("mousedown",m=>m.preventDefault()),I.addEventListener("click",m=>{if(m.preventDefault(),this.whenMode===f.id)return;let C=this.whenMode;C!=="between"&&f.id==="between"?this.whenBetweenEndText="":C==="between"&&f.id!=="between"&&(!this.whenSingleText&&this.whenBetweenEndText&&(this.whenSingleText=this.whenBetweenEndText),this.whenBetweenEndText=""),this.whenMode=f.id;for(let B in a)a[B].toggleClass("is-active",B===f.id);p()}),a[f.id]=I,n.appendChild(I)}s.appendChild(n);let l=document.createElement("div");l.className="stashpad-when-body",s.appendChild(l);let c=(f,I,m="single")=>{let C=ye().format("YYYY-MM-DD"),B=!I||I===C;if(m==="betweenStart")return"00:00";if(m==="betweenEnd")return"23:59";switch(f){case"after":return"00:00";case"on":return B?ye().format("HH:mm"):"23:59";case"before":return B?ye().format("HH:mm"):"23:59";default:return"23:59"}},d=(f,I)=>{f.addEventListener("keydown",m=>{m.key==="Enter"?(m.preventDefault(),m.stopPropagation(),I()):m.key==="Escape"&&(m.preventDefault(),m.stopPropagation(),this.closeWhenBuilder())})},h=(f,I)=>{let m=f.selectionStart??f.value.length,C=f.selectionEnd??f.value.length,w=`${m>0&&!/\s$/.test(f.value.slice(0,m))?" ":""}${I}`,y=f.value.slice(0,m)+w+f.value.slice(C);f.value=y;let E=m+w.length;f.setSelectionRange(E,E),f.focus(),f.dispatchEvent(new Event("input",{bubbles:!0}))},g=(f,I)=>{let C=f.slice(0,I).match(/(\d{4}-\d{2}-\d{2})(?!.*\d{4}-\d{2}-\d{2})/);return C?C[1]:null},u=(f,I,m,C)=>{let B=l.createDiv({cls:"stashpad-when-input-wrap has-icons"}),w=B.createDiv({cls:"stashpad-when-icon-slot"}),y=w.createEl("button",{cls:"stashpad-when-icon stashpad-when-icon-cal"});y.type="button",y.title="Pick date",(0,ue.setIcon)(y,"calendar-days");let E=w.createEl("input",{cls:"stashpad-when-native",attr:{type:"date"}});E.tabIndex=-1;let v=B.createDiv({cls:"stashpad-when-icon-slot"}),Q=v.createEl("button",{cls:"stashpad-when-icon stashpad-when-icon-clock"});Q.type="button",Q.title="Pick time",(0,ue.setIcon)(Q,"clock");let S=B.createDiv({cls:"stashpad-when-icon-slot"}),x=S.createEl("button",{cls:"stashpad-when-icon stashpad-when-icon-day"});x.type="button",x.title="Pick day",(0,ue.setIcon)(x,"calendar");let H=B.createDiv({cls:"stashpad-when-icon-slot"}),G=H.createEl("button",{cls:"stashpad-when-icon stashpad-when-icon-tf"});G.type="button",G.title="Pick timeframe",(0,ue.setIcon)(G,"history");let N=B.createEl("input",{cls:"stashpad-when-input",attr:{type:"text",placeholder:f}});N.value=I(),N.addEventListener("input",()=>m(N.value));let U=B.createEl("button",{cls:"stashpad-when-clear"});U.type="button",U.textContent="\xD7",U.title="Clear";for(let q of[y,Q,x,G,U])q.addEventListener("mousedown",L=>L.preventDefault());let R=null,D=(q,L)=>{R&&R();let K=q.createDiv({cls:"stashpad-when-popover"}),W=q.ownerDocument??document,$=new ue.Scope(this.scope),Y=null,_=te=>{Y=te},ce=!1,ae=te=>{!K.contains(te.target)&&te.target!==q&&!q.contains(te.target)&&se()},se=()=>{if(!ce){ce=!0,K.remove(),W.removeEventListener("mousedown",ae,!0);try{this.app.keymap?.popScope($)}catch{}R===se&&(R=null)}};R=se,$.register([],"Escape",te=>(te.preventDefault(),se(),!1)),$.register([],"Enter",te=>(te.preventDefault(),Y&&Y(),!1));try{L(K,se,_)}catch(te){console.error("[stashpad] popover fill threw:",te),K.remove(),R=null;return}this.app.keymap?.pushScope($),this.pendingCleanups.push(se),setTimeout(()=>W.addEventListener("mousedown",ae,!0),0)};G.addEventListener("click",q=>{q.preventDefault(),D(H,(L,K,W)=>{let $=this.whenMode==="on",Y=(ne,Ee)=>{if(!$)return`${ne}${Ee}`;let Dt={d:"days",w:"weeks",m:"months",y:"years"}[Ee];return ye().subtract(ne,Dt).format("YYYY-MM-DD")},_=L.createDiv({cls:"stashpad-when-pop-row"}),ce=[{label:"Last 7d",n:7,unit:"d"},{label:"Last 30d",n:30,unit:"d"},{label:"This week",n:1,unit:"w"},{label:"This month",n:1,unit:"m"}];for(let ne of ce){let Ee=_.createEl("button",{cls:"stashpad-when-pop-chip",text:ne.label});Ee.type="button",Ee.addEventListener("mousedown",Dt=>Dt.preventDefault()),Ee.addEventListener("click",Dt=>{Dt.preventDefault(),h(N,Y(ne.n,ne.unit)),K()})}let ae=L.createDiv({cls:"stashpad-when-pop-row stashpad-when-pop-builder"});ae.createSpan({text:"Last",cls:"stashpad-when-pop-label"});let se=ae.createEl("input",{cls:"stashpad-when-pop-number",attr:{type:"number",min:"1",max:"999",value:"7"}}),te=ae.createEl("select",{cls:"stashpad-when-pop-unit"});for(let ne of[{v:"d",label:"days"},{v:"w",label:"weeks"},{v:"m",label:"months"},{v:"y",label:"years"}]){let Ee=te.createEl("option",{text:ne.label});Ee.value=ne.v}let pe=ae.createEl("button",{cls:"stashpad-when-pop-chip stashpad-when-pop-go",text:"Insert"});pe.type="button";let He=()=>{let ne=parseInt(se.value,10);!Number.isFinite(ne)||ne<1||(h(N,Y(ne,te.value)),K())};pe.addEventListener("mousedown",ne=>ne.preventDefault()),pe.addEventListener("click",ne=>{ne.preventDefault(),He()}),W(He),se.focus(),se.select()})});let b=(q,L,K,W,$)=>{D(L,(Y,_,ce)=>{gs(Y,{seedH:K,seedM:W,seedPeriod:$,close:_,setOnEnter:ce,onFinalize:ae=>h(q,Ba(ae))})})};x.addEventListener("click",q=>{q.preventDefault(),D(S,(L,K,W)=>{let $=L.createDiv({cls:"stashpad-when-pop-row"});for(let se of["Today","Yesterday"]){let te=$.createEl("button",{cls:"stashpad-when-pop-chip",text:se});te.type="button",te.addEventListener("mousedown",pe=>pe.preventDefault()),te.addEventListener("click",pe=>{pe.preventDefault(),h(N,se),K()})}let Y=L.createDiv({cls:"stashpad-when-pop-row stashpad-when-pop-days"}),_=["Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"],ae=(ye().day()+6)%7;for(let se=0;se<_.length;se++){let te=_[se],pe=Y.createEl("button",{cls:"stashpad-when-pop-chip",text:te.slice(0,3)});pe.type="button",pe.title=te,se===ae&&pe.addClass("is-today"),pe.addEventListener("mousedown",He=>He.preventDefault()),pe.addEventListener("click",He=>{if(He.preventDefault(),C==="betweenEnd"){let ne=se,Ee=ye().startOf("day"),Dt=(Ee.day()+6)%7,Mn=(ne-Dt+7)%7;Mn===0&&(Mn=7);let ul=Ee.clone().add(Mn,"days").format("YYYY-MM-DD");h(N,ul)}else h(N,te);K()})}})});let P=(q,L)=>{q.value||(q.value=L);let K=q;if(typeof K.showPicker=="function")try{K.showPicker();return}catch{}q.focus(),q.click()};return y.addEventListener("click",q=>{q.preventDefault(),P(E,ye().format("YYYY-MM-DD"))}),E.addEventListener("change",()=>{E.value&&h(N,E.value)}),Q.addEventListener("click",q=>{q.preventDefault();let L=N.selectionStart??N.value.length,K=g(N.value,L),W=c(this.whenMode,K,C),[$,Y]=W.split(":"),_=parseInt($,10),ce=parseInt(Y,10),ae=_%12;ae===0&&(ae=12);let se=_>=12?"pm":"am";b(N,v,ae,ce,se)}),U.addEventListener("click",q=>{q.preventDefault(),N.value="",m(""),N.focus()}),N},p=()=>{if(l.empty(),this.whenMode==="between"){let f=u("start (date / day / timeframe)",()=>this.whenSingleText,y=>{this.whenSingleText=y},"betweenStart"),I=u("end (date / day / timeframe)",()=>this.whenBetweenEndText,y=>{this.whenBetweenEndText=y},"betweenEnd"),m=l.createDiv({cls:"stashpad-when-actions"}),C=m.createEl("button",{cls:"stashpad-when-insert",text:"Insert"});C.type="button",C.addEventListener("mousedown",y=>y.preventDefault());let B=()=>{let y=f.value.trim(),E=I.value.trim();y&&this.appendFilterToInput(e,"after",y),E&&this.appendFilterToInput(e,"before",E),this.closeWhenBuilder()};C.addEventListener("click",y=>{y.preventDefault(),B()});let w=m.createEl("button",{cls:"stashpad-when-cancel",text:"Cancel"});w.type="button",w.addEventListener("mousedown",y=>y.preventDefault()),w.addEventListener("click",y=>{y.preventDefault(),this.closeWhenBuilder()}),d(f,B),d(I,B),f.focus(),f.setSelectionRange(f.value.length,f.value.length)}else{let f=u("date / day / timeframe",()=>this.whenSingleText,w=>{this.whenSingleText=w},"single"),I=l.createDiv({cls:"stashpad-when-actions"}),m=I.createEl("button",{cls:"stashpad-when-insert",text:"Insert"});m.type="button",m.addEventListener("mousedown",w=>w.preventDefault());let C=()=>{let w=f.value.trim();w&&this.appendFilterToInput(e,this.whenMode,w),this.closeWhenBuilder()};m.addEventListener("click",w=>{w.preventDefault(),C()});let B=I.createEl("button",{cls:"stashpad-when-cancel",text:"Cancel"});B.type="button",B.addEventListener("mousedown",w=>w.preventDefault()),B.addEventListener("click",w=>{w.preventDefault(),this.closeWhenBuilder()}),d(f,C),f.focus(),f.setSelectionRange(f.value.length,f.value.length)}};p(),t.parentElement?.insertBefore(s,t.nextSibling),this.whenBuilderEl=s}closeWhenBuilder(){if(!this.whenBuilderEl)return;this.whenBuilderEl.remove(),this.whenBuilderEl=null,this.modalEl?.removeClass("is-when-builder-open")}selectSuggestion(e,t){if(e&&e.kind==="search-excluded"){this.loadExcludedNotes();return}super.selectSuggestion(e,t)}onChooseSuggestion(e){if(e.kind==="create"&&this.opts.onCreate){this.opts.onCreate(this.inputEl?.value??"");return}if(e.kind==="folder-open"&&e.id==="__folder_picker__"){let t=this.opts.folderResults?.()??[];if(!t.length)return;let i=this.inputEl?.value??"",s=!1,n=g=>{s=!0;let u=g.split("/").pop()??g;this.opts.onPick({id:`folder:${g}`,label:`Open folder \u201C${u}\u201D in a new tab`,node:null,kind:"folder-open",folder:g})},o=new Fo(this.app,t,n),a=o.onClose.bind(o),l=this.tree,c=this.titleFn,d=this.opts,h=this.app;o.onClose=()=>{if(a(),!s){let g=new A(h,l,c,d);g.open(),setTimeout(()=>{let u=g.inputEl;u&&(i&&(u.value=i,u.dispatchEvent(new Event("input",{bubbles:!0}))),u.focus(),u.setSelectionRange(u.value.length,u.value.length))},0)}},o.open();return}this.opts.onPick(e)}},Fo=class extends ue.FuzzySuggestModal{constructor(e,t,i){super(e);this.folders=t;this.onChosen=i;this.setPlaceholder("Open which folder?")}getItems(){return this.folders}getItemText(e){let t=e.split("/").pop()??e;return t===e?e:`${t} \u2014 ${e}`}onChooseItem(e){this.onChosen(e)}};var ar=require("obsidian");le();var Ws=class extends ar.SuggestModal{constructor(r){super(r),this.setPlaceholder("Run a Stashpad command\u2026");let e=this.app.commands?.commands??{};this.commands=Object.keys(e).filter(t=>t.startsWith("stashpad:")).filter(t=>t!=="stashpad:stashpad-command-palette").map(t=>({id:t,name:mc(e[t]?.name??t)})).sort((t,i)=>t.name.localeCompare(i.name))}getSuggestions(r){return this.commands.filter(e=>hs(r,e.name))}renderSuggestion(r,e){e.createDiv({text:r.name,cls:"stashpad-cmd-palette-name"})}onChooseSuggestion(r){this.app.commands?.executeCommandById?.(r.id)}};function mc(A){return A.replace(/^(?:\s*Stashpad:\s*)+/i,"").trim()||A}Be();var ko=require("obsidian");function Cc(A,r){return!r||A.metaKey||A.ctrlKey||A.altKey?!1:A.key.toLowerCase()===r.toLowerCase()}function ui(A,r){return r?r.includes("+")?Bc(A,r):Cc(A,r):!1}function J(A,r){if(!r)return!1;let{primary:e,secondary:t,preferRight:i,useBoth:s}=r;return e&&t?s?ui(A,e)||ui(A,t):i?ui(A,t):ui(A,e):ui(A,e)||ui(A,t)}function es(A){if(!A)return"";let r=ko.Platform.isMacOS;return A.split("+").map(e=>{let t=e.trim();return t?t.toLowerCase()==="mod"?r?"Cmd":"Ctrl":t.toLowerCase()==="alt"?r?"Opt":"Alt":t.length===1?t.toUpperCase():t:""}).filter(Boolean).join("+")}function Bc(A,r){if(!r)return!1;let e=r.split("+").map(h=>h.trim()).filter(Boolean);if(e.length<2)return!1;let t=e[e.length-1].toLowerCase(),i=new Set(e.slice(0,-1).map(h=>h.toLowerCase())),s=i.has("mod"),n=i.has("ctrl")||i.has("control"),o=i.has("cmd")||i.has("meta")||i.has("command"),a=i.has("alt")||i.has("option"),l=i.has("shift"),d=ko.Platform.isMacOS?A.metaKey:A.ctrlKey;return s&&!d||n&&!A.ctrlKey||o&&!A.metaKey||a!==A.altKey||l!==A.shiftKey||!s&&(!n&&A.ctrlKey||!o&&A.metaKey)?!1:A.key.toLowerCase()===t}var Jt=require("obsidian");Be();var Ue=class Ue{constructor(r){this.host=r;this.knownBodies=new Map;this.contribTimers=new Map;this.recentSelfWrites=new Map;this.lastExternalModify=new Map}dispose(){for(let r of this.contribTimers.values())clearTimeout(r);this.contribTimers.clear(),this.knownBodies.clear(),this.recentSelfWrites.clear(),this.lastExternalModify.clear()}currentAuthorLink(){let r=(this.host.plugin.settings.authorName??"").trim(),e=(this.host.plugin.settings.authorId??"").trim();if(!r||!e)return null;let t=r.replace(/[^\w\- ]+/g,"").trim().replace(/\s+/g,"-")||"author",i=`${this.host.noteFolder}/_authors/${t}-${e}.md`,s=r.replace(/[\[\]|]/g,"").trim()||t;return{link:`[[${i}|${s}]]`,path:i,name:r,id:e}}claimSelectedAsAuthor(){this.claimAuthorship({scope:"selection",contributorMode:!1})}claimFolderAsAuthor(){this.claimAuthorship({scope:"folder",contributorMode:!1})}claimSelectedWithContributor(){this.claimAuthorship({scope:"selection",contributorMode:!0})}claimFolderWithContributor(){this.claimAuthorship({scope:"folder",contributorMode:!0})}fileBackedNotesInFolder(){let r=this.host.noteFolder.replace(/\/+$/,"");return this.host.app.vault.getMarkdownFiles().filter(e=>{let t=e.parent?.path?.replace(/\/+$/,"")??"";if(t!==r&&!t.startsWith(r+"/")||e.path.includes("/_authors/"))return!1;let i=this.host.app.metadataCache.getFileCache(e)?.frontmatter;return typeof i?.id=="string"&&!!i.id})}async pacedFrontmatter(r,e){for(let s=0;s<r.length;s++){let n=this.host.app.vault.getAbstractFileByPath(r[s]);if(n instanceof Jt.TFile){this.recentSelfWrites.set(n.path,Date.now());try{await this.host.app.fileManager.processFrontMatter(n,o=>e(o,r[s]))}catch(o){console.warn("[Stashpad] claim: frontmatter write failed",r[s],o)}}(s+1)%25===0&&await new Promise(o=>setTimeout(o,30))}}async claimAuthorship(r){let e=this.currentAuthorLink();if(!e){new Jt.Notice("Set your author name in Stashpad settings first.");return}let t=`-${e.id}`,i=r.scope==="selection"?this.host.getActionTargets().map(g=>g.file).filter(g=>!!g):this.fileBackedNotesInFolder();if(i.length===0){new Jt.Notice(r.scope==="selection"?"No notes selected.":"No notes in this folder.");return}let s=[],n=[];for(let g of i){let u=this.host.app.metadataCache.getFileCache(g)?.frontmatter,p=typeof u?.author=="string"?u.author:"";if(!p.trim()){s.push(g.path);continue}if(p.includes(t)||!r.contributorMode)continue;(Array.isArray(u?.contributors)?u.contributors.filter(I=>typeof I=="string"):[]).some(I=>I.includes(t))||n.push(g.path)}let o=s.length+n.length;if(o===0){new Jt.Notice("Nothing to claim \u2014 those notes are already authored by you.");return}if(r.scope==="folder"){let g=[`Stamp yourself as author on ${s.length} unauthored note(s)`];if(n.length&&g.push(`and as a contributor on ${n.length} already-authored note(s)`),!await new Promise(p=>{new ie(this.host.app,"Claim authorship",`${g.join(" ")}? -Existing authors are never overwritten. This can be undone.`,"Claim",p).open()}))return}let a=this.host.noteFolder,l=e.link,c=new Set,d=async()=>{await this.pacedFrontmatter(s,(g,u)=>{typeof g.author=="string"&&g.author.trim()||(g.author=l),Array.isArray(g.contributors)&&g.contributors.some(p=>typeof p=="string"&&p.includes(t))&&(g.contributors=g.contributors.filter(p=>!(typeof p=="string"&&p.includes(t))),c.add(u))}),await this.pacedFrontmatter(n,g=>{let u=Array.isArray(g.contributors)?g.contributors.filter(p=>typeof p=="string"):[];u.some(p=>p.includes(t))||u.push(l),g.contributors=u})};this.ensureAuthorFile(e),await d(),this.host.plugin.getUndoStack(a).push({label:`Claim authorship (${o} note${o===1?"":"s"})`,undo:async()=>{await this.pacedFrontmatter(s,(g,u)=>{if(typeof g.author=="string"&&g.author.includes(t)&&delete g.author,c.has(u)){let p=Array.isArray(g.contributors)?g.contributors.filter(f=>typeof f=="string"):[];p.some(f=>f.includes(t))||p.push(l),g.contributors=p}}),await this.pacedFrontmatter(n,g=>{Array.isArray(g.contributors)&&(g.contributors=g.contributors.filter(u=>!(typeof u=="string"&&u.includes(t))))}),this.host.debouncedRender()},redo:async()=>{c.clear(),await d(),this.host.debouncedRender()}});let h=[];s.length&&h.push(`authored ${s.length}`),n.length&&h.push(`contributing to ${n.length}`),new Jt.Notice(`Claimed authorship: ${h.join(", ")}. Undo available.`),this.host.debouncedRender()}async ensureAuthorFile(r){try{r.id&&this.host.plugin.authorRegistry.record({id:r.id,name:r.name});let e=`${this.host.noteFolder}/_authors`;if(await this.host.ensureFolder(e),await this.host.app.vault.adapter.exists(r.path))return;let t=r.id===(this.host.plugin.settings.authorId??"").trim(),i=this.host.plugin.buildAuthorStub({id:r.id,name:r.name,role:t?this.host.plugin.settings.authorRole:void 0,department:t?this.host.plugin.settings.authorDepartment:void 0},new Date().toISOString());await this.host.app.vault.create(r.path,i)}catch(e){console.warn("[Stashpad] ensureAuthorFile failed",e)}}collectAuthorIds(r){let e=new Set,t=i=>{if(typeof i!="string")return null;let s=i.match(/-([a-z0-9]{4,12})(?:\.md)?(?:\||\]\])/i);return s?s[1]:null};for(let i of r){if(!i.file)continue;let s=this.host.app.metadataCache.getFileCache(i.file)?.frontmatter;if(!s)continue;let n=t(s.author);if(n&&e.add(n),Array.isArray(s.contributors))for(let o of s.contributors){let a=t(o);a&&e.add(a)}}return Array.from(e)}pruneContribMaps(){let r=Date.now(),e=Ue.EXTERNAL_WRITE_GRACE_MS*20,t=Ue.EXTERNAL_QUIESCENCE_MS*12;for(let[i,s]of this.recentSelfWrites)r-s>e&&this.recentSelfWrites.delete(i);for(let[i,s]of this.lastExternalModify)r-s>t&&this.lastExternalModify.delete(i);if(this.knownBodies.size>64)for(let i of this.knownBodies.keys())this.host.tree.idForPath(i)||this.knownBodies.delete(i)}noteModify(r){let e=Date.now(),t=this.recentSelfWrites.get(r.path);t!==void 0&&e-t<Ue.EXTERNAL_WRITE_GRACE_MS||this.lastExternalModify.set(r.path,e),this.scheduleContribution(r)}scheduleContribution(r){let e=this.contribTimers.get(r.path);e&&clearTimeout(e);let t=this.isFileActivelyEdited(r),i=Ue.CONTRIB_DEBOUNCE_MS+(t?Ue.CONTRIB_ACTIVE_EDITOR_BONUS_MS:0),s=setTimeout(()=>{if(this.contribTimers.delete(r.path),this.isFileActivelyEdited(r)){this.scheduleContribution(r);return}let n=this.lastExternalModify.get(r.path);if(n!==void 0&&Date.now()-n<Ue.EXTERNAL_QUIESCENCE_MS){this.scheduleContribution(r);return}this.maybeRecordContribution(r)},i);this.contribTimers.set(r.path,s)}isFileActivelyEdited(r){try{let e=this.host.app.workspace.activeLeaf;if(!e)return!1;let t=e.view;if(!t||t.getViewType?.()!=="markdown"||t.file?.path!==r.path)return!1;let i=t.containerEl??null;return!!i&&i.contains(document.activeElement)}catch{return!1}}async maybeRecordContribution(r){let e="";try{e=await this.host.app.vault.cachedRead(r)}catch{return}let t=this.host.stripFrontmatter(e),i=this.knownBodies.get(r.path);if(this.knownBodies.set(r.path,t),i===void 0||i===t||this.host.plugin.rebootstrapInProgress)return;let s=this.currentAuthorLink();if(!s)return;this.ensureAuthorFile(s);let n=new Date().toISOString();this.recentSelfWrites.set(r.path,Date.now());try{await this.host.app.fileManager.processFrontMatter(r,o=>{o.modified=n;let a=typeof o.author=="string"?o.author:"",l=Array.isArray(o.contributors)?o.contributors.filter(g=>typeof g=="string"):[],c=`-${s.id}`,d=a.includes(c),h=l.some(g=>g.includes(c));!d&&!h&&l.push(s.link),o.contributors=l})}catch(o){console.warn("[Stashpad] maybeRecordContribution failed",o)}}};Ue.CONTRIB_DEBOUNCE_MS=4e3,Ue.CONTRIB_ACTIVE_EDITOR_BONUS_MS=2e3,Ue.EXTERNAL_WRITE_GRACE_MS=1500,Ue.EXTERNAL_QUIESCENCE_MS=5e3;var Xs=Ue;var Zs=class{constructor(r){this.host=r;this.dragSourceIds=null;this.dragPlaceholder=null;this.dragRowHeight=0}attachRowDnD(r,e,t){r.addEventListener("dragstart",i=>{let s=this.host.selection.has(e.id)&&this.host.selection.size>1?[...this.host.selection]:[e.id];if(this.dragSourceIds=s,this.dragRowHeight=r.offsetHeight,r.addClass("is-dragging"),this.host.listEl&&(this.dragPlaceholder=this.host.listEl.createDiv({cls:"stashpad-drop-placeholder"}),this.dragPlaceholder.setCssStyles({height:"0px"}),this.dragPlaceholder.addEventListener("dragover",n=>{this.dragSourceIds&&(n.preventDefault(),n.dataTransfer&&(n.dataTransfer.dropEffect="move"))}),this.dragPlaceholder.addEventListener("drop",n=>{if(!this.dragSourceIds||!this.dragPlaceholder)return;n.preventDefault(),n.stopPropagation();let o=this.dragSourceIds.slice();this.dragSourceIds=null;let a=this.dragPlaceholder.nextElementSibling,l=this.dragPlaceholder.previousElementSibling;this.removeDragPlaceholder();let c,d="before";a&&a.classList.contains("stashpad-note")?(c=a.dataset.id,d="before"):l&&l.classList.contains("stashpad-note")&&(c=l.dataset.id,d="after"),c&&this.host.reorderToTarget(o,c,d)}),this.dragPlaceholder.remove()),i.dataTransfer?.setData("text/plain",s.join(",")),i.dataTransfer){i.dataTransfer.effectAllowed="move";try{i.dataTransfer.setDragImage(r,12,12)}catch{}}}),r.addEventListener("dragend",()=>{r.removeClass("is-dragging"),this.clearDropIndicators(),this.removeDragPlaceholder(),this.dragSourceIds=null}),r.addEventListener("dragover",i=>{if(!this.dragSourceIds)return;i.preventDefault(),i.dataTransfer&&(i.dataTransfer.dropEffect="move");let s=this.dropZone(i,r);this.clearDropIndicators(),s==="drop-into"?(this.removeDragPlaceholder(),r.addClass("drop-into")):(r.removeClass("drop-into"),this.placePlaceholder(r,s==="drop-above"?"before":"after"))}),r.addEventListener("dragleave",i=>{let s=r.getBoundingClientRect();(i.clientX<s.left||i.clientX>s.right||i.clientY<s.top||i.clientY>s.bottom)&&r.removeClass("drop-into")}),r.addEventListener("drop",i=>{if(!this.dragSourceIds)return;i.preventDefault(),i.stopPropagation();let s=this.dragSourceIds.slice();this.dragSourceIds=null;let n=this.dropZone(i,r);this.clearDropIndicators(),this.removeDragPlaceholder(),r.removeClass("is-dragging"),n==="drop-into"?this.host.reorderToTarget(s,e.id,"into"):this.host.reorderToTarget(s,e.id,n==="drop-above"?"before":"after")})}attachListDnD(r){r.addEventListener("dragover",e=>{if(!this.dragSourceIds)return;let t=e.target;if(t&&t.closest&&t.closest(".stashpad-note"))return;e.preventDefault(),e.dataTransfer&&(e.dataTransfer.dropEffect="move");let i=Array.from(r.querySelectorAll(".stashpad-note"));if(i.length!==0){for(let s of i){let n=s.getBoundingClientRect();if(e.clientY<n.top+n.height/2){this.placePlaceholder(s,"before");return}}this.placePlaceholder(i[i.length-1],"after")}}),r.addEventListener("drop",e=>{if(!this.dragSourceIds)return;e.preventDefault();let t=this.dragSourceIds.slice();if(this.dragSourceIds=null,!this.dragPlaceholder)return;let i=this.dragPlaceholder.nextElementSibling,s=this.dragPlaceholder.previousElementSibling;if(this.removeDragPlaceholder(),i&&i.classList.contains("stashpad-note")){let n=i.dataset.id;n&&this.host.reorderToTarget(t,n,"before")}else if(s&&s.classList.contains("stashpad-note")){let n=s.dataset.id;n&&this.host.reorderToTarget(t,n,"after")}})}placePlaceholder(r,e){if(!this.dragPlaceholder||!this.host.listEl)return;let t=e==="before"?r:r.nextSibling;if(e==="before"&&this.dragPlaceholder.nextSibling===r||e==="after"&&this.dragPlaceholder.previousSibling===r)return;let i=!!this.dragPlaceholder.parentElement;this.host.listEl.insertBefore(this.dragPlaceholder,t),this.dragPlaceholder.setCssStyles({opacity:"1"}),i?this.dragPlaceholder.setCssStyles({height:`${this.dragRowHeight}px`}):(this.dragPlaceholder.setCssStyles({height:"0px"}),this.dragPlaceholder.offsetHeight,this.dragPlaceholder.setCssStyles({height:`${this.dragRowHeight}px`}))}removeDragPlaceholder(){if(!this.dragPlaceholder?.parentElement)return;let r=this.dragPlaceholder;r.setCssStyles({height:"0px",opacity:"0"}),setTimeout(()=>{r.parentElement&&r.remove()},150)}dropZone(r,e){let t=e.getBoundingClientRect(),i=r.clientY-t.top;return i<t.height*.3?"drop-above":i>t.height*.7?"drop-below":"drop-into"}clearDropIndicators(){if(this.host.listEl)for(let r of Array.from(this.host.listEl.querySelectorAll(".drop-into")))r.removeClass("drop-into")}};var rr=require("obsidian");var js=class{constructor(r,e,t){this.host=r;this.component=e;this.bodyObserver=null;this.lazyBodies=new WeakMap;this.renderCache=t??new Map}async getOrComputeRender(r){let e=this.renderCache.get(r.path);if(e&&e.mtime===r.stat.mtime)return ke.record("render.row.cacheHit",0),e;let t=await ke.timeAsync("render.row.read",()=>this.host.app.vault.cachedRead(r)),i=this.host.stripFrontmatter(t),{text:s,attachments:n}=this.splitAttachments(i),o=createDiv({cls:"stashpad-note-text"});await ke.timeAsync("render.row.markdown",()=>rr.MarkdownRenderer.render(this.host.app,s,o,r.path,this.component));let a=o.innerHTML,l={mtime:r.stat.mtime,text:s,attachments:n,html:a};return this.renderCache.set(r.path,l),l}arm(){this.bodyObserver?.disconnect(),this.lazyBodies=new WeakMap,this.bodyObserver=new IntersectionObserver(r=>{for(let e of r){if(!e.isIntersecting)continue;let t=e.target,i=this.lazyBodies.get(t);this.bodyObserver?.unobserve(t),this.lazyBodies.delete(t),i&&i()}},{root:this.host.contentEl,rootMargin:"1400px 0px"})}dispose(){this.bodyObserver?.disconnect(),this.bodyObserver=null}isArmed(){return!!this.bodyObserver}hasFreshRenderCache(r){let e=this.renderCache.get(r.path);return!!e&&e.mtime===r.stat.mtime}evict(r){let e=this.renderCache;e.evict?e.evict(r.path):e.delete&&e.delete(r.path)}defer(r,e){this.lazyBodies.set(r,e),this.bodyObserver?.observe(r)}splitAttachments(r){let e=[];return{text:r.replace(/!\[\[([^\]\|]+)(?:\|[^\]]+)?\]\]/g,(i,s)=>(e.push(s),"")).replace(/\n{3,}/g,` +`)}renderSuggestion(e,t){if(t.addClass("stashpad-suggest-item"),e.kind==="create"){t.createDiv({cls:"stashpad-suggest-create",text:e.label});return}if(e.kind==="folder-open"){t.addClass("is-folder-open"),t.createDiv({cls:"stashpad-suggest-title",text:e.label}),e.folder?t.createDiv({cls:"stashpad-suggest-preview",text:e.folder}):t.createDiv({cls:"stashpad-suggest-preview",text:"Click to choose a folder\u2026"});return}if(e.kind==="search-excluded"){t.addClass("is-search-excluded");let f=t.createDiv({cls:"stashpad-suggest-title stashpad-search-excluded-row"}),p=f.createSpan({cls:"stashpad-search-excluded-icon"});(0,ue.setIcon)(p,"folder-search"),f.createSpan({text:e.label}),t.createDiv({cls:"stashpad-suggest-preview",text:"Include folders you've excluded from search (e.g. to move a note there)."});return}if(e.locked){t.addClass("stashpad-suggest-row","is-locked");let f=t.createDiv({cls:"stashpad-suggest-body"}),p=f.createDiv({cls:"stashpad-suggest-title stashpad-suggest-locked"});(0,ue.setIcon)(p.createSpan({cls:"stashpad-suggest-locked-icon"}),"lock");let u=Go(this.inputEl?.value??"").text;this.highlightInto(p.createSpan(),e.label,u),f.createDiv({cls:"stashpad-suggest-preview",text:e.locked.count>1?`${e.locked.count} notes \xB7 locked`:"Encrypted \u2014 locked"});return}e.crossFolder&&t.addClass("is-cross-folder"),t.addClass("stashpad-suggest-row");let i=t.createDiv({cls:"stashpad-suggest-body"}),s=this.notes.find(f=>e.crossFolder?f.cross?.folder===e.crossFolder&&f.cross?.id===e.crossId:f.node?.id===e.id),n=Go(this.inputEl?.value??"").text,o=this.firstLineOfBody(s?.body??"")||e.label.trim(),a=i.createDiv({cls:"stashpad-suggest-title"});this.highlightInto(a,o,n);let l=e.crossFolder??this.opts.localFolder??null;if(e.bodyPreview&&e.matchLine!=null&&e.matchLine>=0){let f=i.createDiv({cls:"stashpad-suggest-preview stashpad-suggest-snippet"});this.highlightInto(f,e.bodyPreview,n)}let c="";if(e.crossFolder?c=s?.cross?.parentBlurb??"":c=this.parentBlurbFor(e.node),c){let f=i.createDiv({cls:"stashpad-suggest-preview"});f.appendText("Parent: "),this.highlightInto(f,c,n)}let d=e.crossFolder?this.buildCrossBreadcrumb(e):this.buildLocalBreadcrumb(e.node),h=e.crossFolder??this.opts.localFolder??null;if(h||d){let f=i.createDiv({cls:"stashpad-suggest-breadcrumb"}),u=[h?h.split("/").pop()||h:"",d].filter(I=>I&&I.length>0).join(" \u203A ");u&&f.setText(u)}let g=s?this.createdMsFor(s):null;if(g!=null||l){let f=t.createDiv({cls:"stashpad-suggest-rail"});if(g!=null){let p=f.createDiv({cls:"stashpad-suggest-time"});p.setText(this.formatRelativeTime(g)),p.title=we(g).format("YYYY-MM-DD HH:mm")}l&&f.createDiv({cls:"stashpad-suggest-folder-badge",text:l.split("/").pop()||l})}}formatRelativeTime(e){let t=we(e),i=we().startOf("day").diff(we(e).startOf("day"),"days");return i===0?t.format("h:mm a"):i===1?"yesterday":i>1&&i<7?`${i}d ago`:i>=7&&i<30?`${Math.floor(i/7)}w ago`:i>=30&&i<365?t.format("MMM D"):t.format("MMM D, YYYY")}highlightInto(e,t,i){if(!t)return;let s=i.filter(c=>c&&c.length>0);if(!s.length){e.appendText(t);return}let n=t.toLowerCase(),o=[];for(let c of s){let d=0;for(;(d=n.indexOf(c,d))!==-1;)o.push([d,d+c.length]),d+=c.length}if(!o.length){e.appendText(t);return}o.sort((c,d)=>c[0]-d[0]);let a=[];for(let c of o){let d=a[a.length-1];d&&c[0]<=d[1]?d[1]=Math.max(d[1],c[1]):a.push([c[0],c[1]])}let l=0;for(let[c,d]of a)c>l&&e.appendText(t.slice(l,c)),e.createSpan({cls:"stashpad-suggest-match",text:t.slice(c,d)}),l=d;l<t.length&&e.appendText(t.slice(l))}buildLocalBreadcrumb(e){if(!e||!e.parent||e.parent===F)return"";let t=[],i=new Set([e.id]),s=this.tree.get(e.parent);for(;s&&s.id!==F&&!i.has(s.id);)i.add(s.id),t.unshift(this.titleFn(s)),s=s.parent&&s.parent!==F?this.tree.get(s.parent):void 0;return t.join(" \u203A ")}buildCrossBreadcrumb(e){if(!e.crossFolder)return"";if(!this.crossFolderIndex){this.crossFolderIndex=new Map;for(let l of this.notes){if(!l.cross)continue;let c=this.crossFolderIndex.get(l.cross.folder);c||(c=new Map,this.crossFolderIndex.set(l.cross.folder,c)),c.set(l.cross.id,l.cross)}}let t=this.crossFolderIndex.get(e.crossFolder);if(!t)return"";let i=e.crossId??e.id.replace(/^cross:/,""),s=t.get(i);if(!s)return"";let n=[],o=new Set([s.id]),a=s.parentId?t.get(s.parentId):null;for(;a&&!o.has(a.id);)o.add(a.id),n.unshift(a.title),a=a.parentId?t.get(a.parentId):null;return n.join(" \u203A ")}firstLineOfBody(e){if(!e)return"";let t=e.split(/\r?\n/);for(let i of t){let s=i.trim();if(s)return s}return""}parentBlurbFor(e){if(!e||!e.parent||e.parent===F)return"";let t=this.tree.get(e.parent);if(!t||!t.file)return"";let i=this.notes.find(s=>s.node?.id===t.id);return i?this.firstLineOfBody(i.body):""}onOpen(){if(super.onOpen(),this.opts.showFilterChips&&!ue.Platform.isMobile){let e=this.inputEl,t=this.resultContainerEl;if(!e||!t)return;this.mountFilterChipRow(e,t);return}if(ue.Platform.isMobile){let e=this.inputEl;e&&e.focus()}}mountFilterChipRow(e,t){e.addEventListener("keydown",c=>{if(c.key==="["){c.preventDefault();let d=e.selectionStart??e.value.length,h=e.selectionEnd??d,g=e.value.slice(0,d),f=e.value.slice(d,h),p=e.value.slice(h);e.value=`${g}[${f}]${p}`;let u=d+1+f.length;e.setSelectionRange(u,u),e.dispatchEvent(new Event("input",{bubbles:!0}));return}if(c.key==="]"){let d=e.selectionStart??-1;d>=0&&e.value[d]==="]"&&(c.preventDefault(),e.setSelectionRange(d+1,d+1));return}if(c.key==="Backspace"&&!c.metaKey&&!c.ctrlKey&&!c.altKey&&e.selectionStart===e.selectionEnd){let d=e.selectionStart??-1;if(d>0&&e.value[d-1]==="["&&e.value[d]==="]"){c.preventDefault();let h=e.value.slice(0,d-1),g=e.value.slice(d+1);e.value=`${h}${g}`,e.setSelectionRange(d-1,d-1),e.dispatchEvent(new Event("input",{bubbles:!0}))}}});let i=document.createElement("div");i.className="stashpad-search-filter-row";let s=document.createElement("span");s.className="stashpad-search-filter-label",s.textContent="Filters:",i.appendChild(s);let n=document.createElement("button");n.className="stashpad-search-filter-chip",n.type="button",n.textContent="in:[parent note]",n.title='Insert "in:" \u2014 filter by parent note.',n.addEventListener("mousedown",c=>{c.preventDefault()}),n.addEventListener("click",c=>{c.preventDefault(),this.appendFilterToInput(e,"in","")}),n.addEventListener("keydown",c=>{(c.key==="Enter"||c.key===" ")&&(c.preventDefault(),c.stopPropagation(),n.click())}),i.appendChild(n);let o=document.createElement("button");o.className="stashpad-search-filter-chip stashpad-search-filter-when",o.type="button",o.textContent="when:[date / day / timeframe]",o.title="Open the time-filter builder (Before / On / After / Between).",o.addEventListener("mousedown",c=>{c.preventDefault()}),o.addEventListener("click",c=>{c.preventDefault(),this.toggleWhenBuilder(e,i)}),o.addEventListener("keydown",c=>{(c.key==="Enter"||c.key===" ")&&(c.preventDefault(),c.stopPropagation(),o.click())}),i.appendChild(o),t.parentElement?.insertBefore(i,t);let a=new ue.Scope(this.scope),l=c=>{let d=document.activeElement;return d?.classList?.contains("stashpad-search-filter-chip")?(c.preventDefault(),d.click(),!1):c.key==="Enter"&&!c.isComposing?(c.preventDefault(),this.selectActiveSuggestion?.(c),!1):!0};a.register([],"Enter",l),a.register([]," ",l),a.register(["Mod"],"f",c=>{c.preventDefault();let d=this.inputEl;return d&&(d.focus(),d.select()),!1}),a.register([],"Tab",c=>{let d=this.modalEl,h=this.inputEl;if(!d)return!0;let g=Array.from(d.querySelectorAll('input, button, select, textarea, a[href], [tabindex]:not([tabindex="-1"])')).filter(u=>!u.hasAttribute("disabled")&&u.tabIndex!==-1&&u.offsetParent!==null);if(g.length===0)return!0;let f=document.activeElement,p=f?g.indexOf(f):-1;if(c.shiftKey){if(p<=0)return c.preventDefault(),g[g.length-1].focus(),!1}else if(p===g.length-1)return c.preventDefault(),(h??g[0]).focus(),!1;return!0}),this.app.keymap?.pushScope(a),this.pendingCleanups.push(()=>{try{this.app.keymap?.popScope(a)}catch{}})}appendFilterToInput(e,t,i){let s=e.value,o=`${s.length>0&&!/\s$/.test(s)?" ":""}${t}: [`,a="]",l=i;e.value=`${s}${o}${l}${a}`;let c=s.length+o.length+l.length;if(e.setSelectionRange(c,c+0),!l){let d=s.length+o.length;e.setSelectionRange(d,d)}e.focus(),e.dispatchEvent(new Event("input",{bubbles:!0}))}toggleWhenBuilder(e,t){let i=this.modalEl;if(this.whenBuilderEl){this.whenBuilderEl.remove(),this.whenBuilderEl=null,i?.removeClass("is-when-builder-open");return}i?.addClass("is-when-builder-open");let s=document.createElement("div");s.className="stashpad-when-builder";let n=document.createElement("div");n.className="stashpad-when-tabs";let o=[{id:"before",label:"Before"},{id:"on",label:"On"},{id:"after",label:"After"},{id:"between",label:"Between"}],a={};for(let u of o){let I=document.createElement("button");I.type="button",I.className="stashpad-when-tab",I.textContent=u.label,this.whenMode===u.id&&I.addClass("is-active"),I.addEventListener("mousedown",m=>m.preventDefault()),I.addEventListener("click",m=>{if(m.preventDefault(),this.whenMode===u.id)return;let C=this.whenMode;C!=="between"&&u.id==="between"?this.whenBetweenEndText="":C==="between"&&u.id!=="between"&&(!this.whenSingleText&&this.whenBetweenEndText&&(this.whenSingleText=this.whenBetweenEndText),this.whenBetweenEndText=""),this.whenMode=u.id;for(let B in a)a[B].toggleClass("is-active",B===u.id);p()}),a[u.id]=I,n.appendChild(I)}s.appendChild(n);let l=document.createElement("div");l.className="stashpad-when-body",s.appendChild(l);let c=(u,I,m="single")=>{let C=we().format("YYYY-MM-DD"),B=!I||I===C;if(m==="betweenStart")return"00:00";if(m==="betweenEnd")return"23:59";switch(u){case"after":return"00:00";case"on":return B?we().format("HH:mm"):"23:59";case"before":return B?we().format("HH:mm"):"23:59";default:return"23:59"}},d=(u,I)=>{u.addEventListener("keydown",m=>{m.key==="Enter"?(m.preventDefault(),m.stopPropagation(),I()):m.key==="Escape"&&(m.preventDefault(),m.stopPropagation(),this.closeWhenBuilder())})},h=(u,I)=>{let m=u.selectionStart??u.value.length,C=u.selectionEnd??u.value.length,w=`${m>0&&!/\s$/.test(u.value.slice(0,m))?" ":""}${I}`,y=u.value.slice(0,m)+w+u.value.slice(C);u.value=y;let v=m+w.length;u.setSelectionRange(v,v),u.focus(),u.dispatchEvent(new Event("input",{bubbles:!0}))},g=(u,I)=>{let C=u.slice(0,I).match(/(\d{4}-\d{2}-\d{2})(?!.*\d{4}-\d{2}-\d{2})/);return C?C[1]:null},f=(u,I,m,C)=>{let B=l.createDiv({cls:"stashpad-when-input-wrap has-icons"}),w=B.createDiv({cls:"stashpad-when-icon-slot"}),y=w.createEl("button",{cls:"stashpad-when-icon stashpad-when-icon-cal"});y.type="button",y.title="Pick date",(0,ue.setIcon)(y,"calendar-days");let v=w.createEl("input",{cls:"stashpad-when-native",attr:{type:"date"}});v.tabIndex=-1;let E=B.createDiv({cls:"stashpad-when-icon-slot"}),Q=E.createEl("button",{cls:"stashpad-when-icon stashpad-when-icon-clock"});Q.type="button",Q.title="Pick time",(0,ue.setIcon)(Q,"clock");let S=B.createDiv({cls:"stashpad-when-icon-slot"}),x=S.createEl("button",{cls:"stashpad-when-icon stashpad-when-icon-day"});x.type="button",x.title="Pick day",(0,ue.setIcon)(x,"calendar");let H=B.createDiv({cls:"stashpad-when-icon-slot"}),G=H.createEl("button",{cls:"stashpad-when-icon stashpad-when-icon-tf"});G.type="button",G.title="Pick timeframe",(0,ue.setIcon)(G,"history");let N=B.createEl("input",{cls:"stashpad-when-input",attr:{type:"text",placeholder:u}});N.value=I(),N.addEventListener("input",()=>m(N.value));let R=B.createEl("button",{cls:"stashpad-when-clear"});R.type="button",R.textContent="\xD7",R.title="Clear";for(let O of[y,Q,x,G,R])O.addEventListener("mousedown",L=>L.preventDefault());let U=null,D=(O,L)=>{U&&U();let q=O.createDiv({cls:"stashpad-when-popover"}),V=O.ownerDocument??document,Y=new ue.Scope(this.scope),$=null,_=te=>{$=te},ce=!1,re=te=>{!q.contains(te.target)&&te.target!==O&&!O.contains(te.target)&&se()},se=()=>{if(!ce){ce=!0,q.remove(),V.removeEventListener("mousedown",re,!0);try{this.app.keymap?.popScope(Y)}catch{}U===se&&(U=null)}};U=se,Y.register([],"Escape",te=>(te.preventDefault(),se(),!1)),Y.register([],"Enter",te=>(te.preventDefault(),$&&$(),!1));try{L(q,se,_)}catch(te){console.error("[stashpad] popover fill threw:",te),q.remove(),U=null;return}this.app.keymap?.pushScope(Y),this.pendingCleanups.push(se),setTimeout(()=>V.addEventListener("mousedown",re,!0),0)};G.addEventListener("click",O=>{O.preventDefault(),D(H,(L,q,V)=>{let Y=this.whenMode==="on",$=(ne,ve)=>{if(!Y)return`${ne}${ve}`;let Tt={d:"days",w:"weeks",m:"months",y:"years"}[ve];return we().subtract(ne,Tt).format("YYYY-MM-DD")},_=L.createDiv({cls:"stashpad-when-pop-row"}),ce=[{label:"Last 7d",n:7,unit:"d"},{label:"Last 30d",n:30,unit:"d"},{label:"This week",n:1,unit:"w"},{label:"This month",n:1,unit:"m"}];for(let ne of ce){let ve=_.createEl("button",{cls:"stashpad-when-pop-chip",text:ne.label});ve.type="button",ve.addEventListener("mousedown",Tt=>Tt.preventDefault()),ve.addEventListener("click",Tt=>{Tt.preventDefault(),h(N,$(ne.n,ne.unit)),q()})}let re=L.createDiv({cls:"stashpad-when-pop-row stashpad-when-pop-builder"});re.createSpan({text:"Last",cls:"stashpad-when-pop-label"});let se=re.createEl("input",{cls:"stashpad-when-pop-number",attr:{type:"number",min:"1",max:"999",value:"7"}}),te=re.createEl("select",{cls:"stashpad-when-pop-unit"});for(let ne of[{v:"d",label:"days"},{v:"w",label:"weeks"},{v:"m",label:"months"},{v:"y",label:"years"}]){let ve=te.createEl("option",{text:ne.label});ve.value=ne.v}let pe=re.createEl("button",{cls:"stashpad-when-pop-chip stashpad-when-pop-go",text:"Insert"});pe.type="button";let He=()=>{let ne=parseInt(se.value,10);!Number.isFinite(ne)||ne<1||(h(N,$(ne,te.value)),q())};pe.addEventListener("mousedown",ne=>ne.preventDefault()),pe.addEventListener("click",ne=>{ne.preventDefault(),He()}),V(He),se.focus(),se.select()})});let b=(O,L,q,V,Y)=>{D(L,($,_,ce)=>{Qs($,{seedH:q,seedM:V,seedPeriod:Y,close:_,setOnEnter:ce,onFinalize:re=>h(O,Pr(re))})})};x.addEventListener("click",O=>{O.preventDefault(),D(S,(L,q,V)=>{let Y=L.createDiv({cls:"stashpad-when-pop-row"});for(let se of["Today","Yesterday"]){let te=Y.createEl("button",{cls:"stashpad-when-pop-chip",text:se});te.type="button",te.addEventListener("mousedown",pe=>pe.preventDefault()),te.addEventListener("click",pe=>{pe.preventDefault(),h(N,se),q()})}let $=L.createDiv({cls:"stashpad-when-pop-row stashpad-when-pop-days"}),_=["Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"],re=(we().day()+6)%7;for(let se=0;se<_.length;se++){let te=_[se],pe=$.createEl("button",{cls:"stashpad-when-pop-chip",text:te.slice(0,3)});pe.type="button",pe.title=te,se===re&&pe.addClass("is-today"),pe.addEventListener("mousedown",He=>He.preventDefault()),pe.addEventListener("click",He=>{if(He.preventDefault(),C==="betweenEnd"){let ne=se,ve=we().startOf("day"),Tt=(ve.day()+6)%7,zn=(ne-Tt+7)%7;zn===0&&(zn=7);let Tl=ve.clone().add(zn,"days").format("YYYY-MM-DD");h(N,Tl)}else h(N,te);q()})}})});let P=(O,L)=>{O.value||(O.value=L);let q=O;if(typeof q.showPicker=="function")try{q.showPicker();return}catch{}O.focus(),O.click()};return y.addEventListener("click",O=>{O.preventDefault(),P(v,we().format("YYYY-MM-DD"))}),v.addEventListener("change",()=>{v.value&&h(N,v.value)}),Q.addEventListener("click",O=>{O.preventDefault();let L=N.selectionStart??N.value.length,q=g(N.value,L),V=c(this.whenMode,q,C),[Y,$]=V.split(":"),_=parseInt(Y,10),ce=parseInt($,10),re=_%12;re===0&&(re=12);let se=_>=12?"pm":"am";b(N,E,re,ce,se)}),R.addEventListener("click",O=>{O.preventDefault(),N.value="",m(""),N.focus()}),N},p=()=>{if(l.empty(),this.whenMode==="between"){let u=f("start (date / day / timeframe)",()=>this.whenSingleText,y=>{this.whenSingleText=y},"betweenStart"),I=f("end (date / day / timeframe)",()=>this.whenBetweenEndText,y=>{this.whenBetweenEndText=y},"betweenEnd"),m=l.createDiv({cls:"stashpad-when-actions"}),C=m.createEl("button",{cls:"stashpad-when-insert",text:"Insert"});C.type="button",C.addEventListener("mousedown",y=>y.preventDefault());let B=()=>{let y=u.value.trim(),v=I.value.trim();y&&this.appendFilterToInput(e,"after",y),v&&this.appendFilterToInput(e,"before",v),this.closeWhenBuilder()};C.addEventListener("click",y=>{y.preventDefault(),B()});let w=m.createEl("button",{cls:"stashpad-when-cancel",text:"Cancel"});w.type="button",w.addEventListener("mousedown",y=>y.preventDefault()),w.addEventListener("click",y=>{y.preventDefault(),this.closeWhenBuilder()}),d(u,B),d(I,B),u.focus(),u.setSelectionRange(u.value.length,u.value.length)}else{let u=f("date / day / timeframe",()=>this.whenSingleText,w=>{this.whenSingleText=w},"single"),I=l.createDiv({cls:"stashpad-when-actions"}),m=I.createEl("button",{cls:"stashpad-when-insert",text:"Insert"});m.type="button",m.addEventListener("mousedown",w=>w.preventDefault());let C=()=>{let w=u.value.trim();w&&this.appendFilterToInput(e,this.whenMode,w),this.closeWhenBuilder()};m.addEventListener("click",w=>{w.preventDefault(),C()});let B=I.createEl("button",{cls:"stashpad-when-cancel",text:"Cancel"});B.type="button",B.addEventListener("mousedown",w=>w.preventDefault()),B.addEventListener("click",w=>{w.preventDefault(),this.closeWhenBuilder()}),d(u,C),u.focus(),u.setSelectionRange(u.value.length,u.value.length)}};p(),t.parentElement?.insertBefore(s,t.nextSibling),this.whenBuilderEl=s}closeWhenBuilder(){if(!this.whenBuilderEl)return;this.whenBuilderEl.remove(),this.whenBuilderEl=null,this.modalEl?.removeClass("is-when-builder-open")}selectSuggestion(e,t){if(e&&e.kind==="search-excluded"){this.loadExcludedNotes();return}super.selectSuggestion(e,t)}onChooseSuggestion(e){if(e.kind==="create"&&this.opts.onCreate){this.opts.onCreate(this.inputEl?.value??"");return}if(e.kind==="folder-open"&&e.id==="__folder_picker__"){let t=this.opts.folderResults?.()??[];if(!t.length)return;let i=this.inputEl?.value??"",s=!1,n=g=>{s=!0;let f=g.split("/").pop()??g;this.opts.onPick({id:`folder:${g}`,label:`Open folder \u201C${f}\u201D in a new tab`,node:null,kind:"folder-open",folder:g})},o=new Lo(this.app,t,n),a=o.onClose.bind(o),l=this.tree,c=this.titleFn,d=this.opts,h=this.app;o.onClose=()=>{if(a(),!s){let g=new A(h,l,c,d);g.open(),setTimeout(()=>{let f=g.inputEl;f&&(i&&(f.value=i,f.dispatchEvent(new Event("input",{bubbles:!0}))),f.focus(),f.setSelectionRange(f.value.length,f.value.length))},0)}},o.open();return}this.opts.onPick(e)}},Lo=class extends ue.FuzzySuggestModal{constructor(e,t,i){super(e);this.folders=t;this.onChosen=i;this.setPlaceholder("Open which folder?")}getItems(){return this.folders}getItemText(e){let t=e.split("/").pop()??e;return t===e?e:`${t} \u2014 ${e}`}onChooseItem(e){this.onChosen(e)}};var va=require("obsidian");le();var dn=class extends va.SuggestModal{constructor(r){super(r),this.setPlaceholder("Run a Stashpad command\u2026");let e=this.app.commands?.commands??{};this.commands=Object.keys(e).filter(t=>t.startsWith("stashpad:")).filter(t=>t!=="stashpad:stashpad-command-palette").map(t=>({id:t,name:Gc(e[t]?.name??t)})).sort((t,i)=>t.name.localeCompare(i.name))}getSuggestions(r){return this.commands.filter(e=>ws(r,e.name))}renderSuggestion(r,e){e.createDiv({text:r.name,cls:"stashpad-cmd-palette-name"})}onChooseSuggestion(r){this.app.commands?.executeCommandById?.(r.id)}};function Gc(A){return A.replace(/^(?:\s*Stashpad:\s*)+/i,"").trim()||A}ye();var Oo=require("obsidian");function Lc(A,r){return!r||A.metaKey||A.ctrlKey||A.altKey?!1:A.key.toLowerCase()===r.toLowerCase()}function mi(A,r){return r?r.includes("+")?Oc(A,r):Lc(A,r):!1}function K(A,r){if(!r)return!1;let{primary:e,secondary:t,preferRight:i,useBoth:s}=r;return e&&t?s?mi(A,e)||mi(A,t):i?mi(A,t):mi(A,e):mi(A,e)||mi(A,t)}function rs(A){if(!A)return"";let r=Oo.Platform.isMacOS;return A.split("+").map(e=>{let t=e.trim();return t?t.toLowerCase()==="mod"?r?"Cmd":"Ctrl":t.toLowerCase()==="alt"?r?"Opt":"Alt":t.length===1?t.toUpperCase():t:""}).filter(Boolean).join("+")}function Oc(A,r){if(!r)return!1;let e=r.split("+").map(h=>h.trim()).filter(Boolean);if(e.length<2)return!1;let t=e[e.length-1].toLowerCase(),i=new Set(e.slice(0,-1).map(h=>h.toLowerCase())),s=i.has("mod"),n=i.has("ctrl")||i.has("control"),o=i.has("cmd")||i.has("meta")||i.has("command"),a=i.has("alt")||i.has("option"),l=i.has("shift"),d=Oo.Platform.isMacOS?A.metaKey:A.ctrlKey;return s&&!d||n&&!A.ctrlKey||o&&!A.metaKey||a!==A.altKey||l!==A.shiftKey||!s&&(!n&&A.ctrlKey||!o&&A.metaKey)?!1:A.key.toLowerCase()===t}var zt=require("obsidian");ye();var Re=class Re{constructor(r){this.host=r;this.knownBodies=new Map;this.contribTimers=new Map;this.recentSelfWrites=new Map;this.lastExternalModify=new Map}dispose(){for(let r of this.contribTimers.values())clearTimeout(r);this.contribTimers.clear(),this.knownBodies.clear(),this.recentSelfWrites.clear(),this.lastExternalModify.clear()}currentAuthorLink(){let r=(this.host.plugin.settings.authorName??"").trim(),e=(this.host.plugin.settings.authorId??"").trim();if(!r||!e)return null;let t=r.replace(/[^\w\- ]+/g,"").trim().replace(/\s+/g,"-")||"author",i=`${this.host.noteFolder}/_authors/${t}-${e}.md`,s=r.replace(/[\[\]|]/g,"").trim()||t;return{link:`[[${i}|${s}]]`,path:i,name:r,id:e}}claimSelectedAsAuthor(){this.claimAuthorship({scope:"selection",contributorMode:!1})}claimFolderAsAuthor(){this.claimAuthorship({scope:"folder",contributorMode:!1})}claimSelectedWithContributor(){this.claimAuthorship({scope:"selection",contributorMode:!0})}claimFolderWithContributor(){this.claimAuthorship({scope:"folder",contributorMode:!0})}fileBackedNotesInFolder(){let r=this.host.noteFolder.replace(/\/+$/,"");return this.host.app.vault.getMarkdownFiles().filter(e=>{let t=e.parent?.path?.replace(/\/+$/,"")??"";if(t!==r&&!t.startsWith(r+"/")||e.path.includes("/_authors/"))return!1;let i=this.host.app.metadataCache.getFileCache(e)?.frontmatter;return typeof i?.id=="string"&&!!i.id})}async pacedFrontmatter(r,e){for(let s=0;s<r.length;s++){let n=this.host.app.vault.getAbstractFileByPath(r[s]);if(n instanceof zt.TFile){this.recentSelfWrites.set(n.path,Date.now());try{await this.host.app.fileManager.processFrontMatter(n,o=>e(o,r[s]))}catch(o){console.warn("[Stashpad] claim: frontmatter write failed",r[s],o)}}(s+1)%25===0&&await new Promise(o=>setTimeout(o,30))}}async claimAuthorship(r){let e=this.currentAuthorLink();if(!e){new zt.Notice("Set your author name in Stashpad settings first.");return}let t=`-${e.id}`,i=r.scope==="selection"?this.host.getActionTargets().map(g=>g.file).filter(g=>!!g):this.fileBackedNotesInFolder();if(i.length===0){new zt.Notice(r.scope==="selection"?"No notes selected.":"No notes in this folder.");return}let s=[],n=[];for(let g of i){let f=this.host.app.metadataCache.getFileCache(g)?.frontmatter,p=typeof f?.author=="string"?f.author:"";if(!p.trim()){s.push(g.path);continue}if(p.includes(t)||!r.contributorMode)continue;(Array.isArray(f?.contributors)?f.contributors.filter(I=>typeof I=="string"):[]).some(I=>I.includes(t))||n.push(g.path)}let o=s.length+n.length;if(o===0){new zt.Notice("Nothing to claim \u2014 those notes are already authored by you.");return}if(r.scope==="folder"){let g=[`Stamp yourself as author on ${s.length} unauthored note(s)`];if(n.length&&g.push(`and as a contributor on ${n.length} already-authored note(s)`),!await new Promise(p=>{new ie(this.host.app,"Claim authorship",`${g.join(" ")}? +Existing authors are never overwritten. This can be undone.`,"Claim",p).open()}))return}let a=this.host.noteFolder,l=e.link,c=new Set,d=async()=>{await this.pacedFrontmatter(s,(g,f)=>{typeof g.author=="string"&&g.author.trim()||(g.author=l),Array.isArray(g.contributors)&&g.contributors.some(p=>typeof p=="string"&&p.includes(t))&&(g.contributors=g.contributors.filter(p=>!(typeof p=="string"&&p.includes(t))),c.add(f))}),await this.pacedFrontmatter(n,g=>{let f=Array.isArray(g.contributors)?g.contributors.filter(p=>typeof p=="string"):[];f.some(p=>p.includes(t))||f.push(l),g.contributors=f})};this.ensureAuthorFile(e),await d(),this.host.plugin.getUndoStack(a).push({label:`Claim authorship (${o} note${o===1?"":"s"})`,undo:async()=>{await this.pacedFrontmatter(s,(g,f)=>{if(typeof g.author=="string"&&g.author.includes(t)&&delete g.author,c.has(f)){let p=Array.isArray(g.contributors)?g.contributors.filter(u=>typeof u=="string"):[];p.some(u=>u.includes(t))||p.push(l),g.contributors=p}}),await this.pacedFrontmatter(n,g=>{Array.isArray(g.contributors)&&(g.contributors=g.contributors.filter(f=>!(typeof f=="string"&&f.includes(t))))}),this.host.debouncedRender()},redo:async()=>{c.clear(),await d(),this.host.debouncedRender()}});let h=[];s.length&&h.push(`authored ${s.length}`),n.length&&h.push(`contributing to ${n.length}`),new zt.Notice(`Claimed authorship: ${h.join(", ")}. Undo available.`),this.host.debouncedRender()}async ensureAuthorFile(r){try{r.id&&this.host.plugin.authorRegistry.record({id:r.id,name:r.name});let e=`${this.host.noteFolder}/_authors`;if(await this.host.ensureFolder(e),await this.host.app.vault.adapter.exists(r.path))return;let t=r.id===(this.host.plugin.settings.authorId??"").trim(),i=this.host.plugin.buildAuthorStub({id:r.id,name:r.name,role:t?this.host.plugin.settings.authorRole:void 0,department:t?this.host.plugin.settings.authorDepartment:void 0},new Date().toISOString());await this.host.app.vault.create(r.path,i)}catch(e){console.warn("[Stashpad] ensureAuthorFile failed",e)}}collectAuthorIds(r){let e=new Set,t=i=>{if(typeof i!="string")return null;let s=i.match(/-([a-z0-9]{4,12})(?:\.md)?(?:\||\]\])/i);return s?s[1]:null};for(let i of r){if(!i.file)continue;let s=this.host.app.metadataCache.getFileCache(i.file)?.frontmatter;if(!s)continue;let n=t(s.author);if(n&&e.add(n),Array.isArray(s.contributors))for(let o of s.contributors){let a=t(o);a&&e.add(a)}}return Array.from(e)}pruneContribMaps(){let r=Date.now(),e=Re.EXTERNAL_WRITE_GRACE_MS*20,t=Re.EXTERNAL_QUIESCENCE_MS*12;for(let[i,s]of this.recentSelfWrites)r-s>e&&this.recentSelfWrites.delete(i);for(let[i,s]of this.lastExternalModify)r-s>t&&this.lastExternalModify.delete(i);if(this.knownBodies.size>64)for(let i of this.knownBodies.keys())this.host.tree.idForPath(i)||this.knownBodies.delete(i)}noteModify(r){let e=Date.now(),t=this.recentSelfWrites.get(r.path);t!==void 0&&e-t<Re.EXTERNAL_WRITE_GRACE_MS||this.lastExternalModify.set(r.path,e),this.scheduleContribution(r)}scheduleContribution(r){let e=this.contribTimers.get(r.path);e&&clearTimeout(e);let t=this.isFileActivelyEdited(r),i=Re.CONTRIB_DEBOUNCE_MS+(t?Re.CONTRIB_ACTIVE_EDITOR_BONUS_MS:0),s=setTimeout(()=>{if(this.contribTimers.delete(r.path),this.isFileActivelyEdited(r)){this.scheduleContribution(r);return}let n=this.lastExternalModify.get(r.path);if(n!==void 0&&Date.now()-n<Re.EXTERNAL_QUIESCENCE_MS){this.scheduleContribution(r);return}this.maybeRecordContribution(r)},i);this.contribTimers.set(r.path,s)}isFileActivelyEdited(r){try{let e=this.host.app.workspace.activeLeaf;if(!e)return!1;let t=e.view;if(!t||t.getViewType?.()!=="markdown"||t.file?.path!==r.path)return!1;let i=t.containerEl??null;return!!i&&i.contains(document.activeElement)}catch{return!1}}async maybeRecordContribution(r){let e="";try{e=await this.host.app.vault.cachedRead(r)}catch{return}let t=this.host.stripFrontmatter(e),i=this.knownBodies.get(r.path);if(this.knownBodies.set(r.path,t),i===void 0||i===t||this.host.plugin.rebootstrapInProgress)return;let s=this.currentAuthorLink();if(!s)return;this.ensureAuthorFile(s);let n=new Date().toISOString();this.recentSelfWrites.set(r.path,Date.now());try{await this.host.app.fileManager.processFrontMatter(r,o=>{o.modified=n;let a=typeof o.author=="string"?o.author:"",l=Array.isArray(o.contributors)?o.contributors.filter(g=>typeof g=="string"):[],c=`-${s.id}`,d=a.includes(c),h=l.some(g=>g.includes(c));!d&&!h&&l.push(s.link),o.contributors=l})}catch(o){console.warn("[Stashpad] maybeRecordContribution failed",o)}}};Re.CONTRIB_DEBOUNCE_MS=4e3,Re.CONTRIB_ACTIVE_EDITOR_BONUS_MS=2e3,Re.EXTERNAL_WRITE_GRACE_MS=1500,Re.EXTERNAL_QUIESCENCE_MS=5e3;var An=Re;var hn=class{constructor(r){this.host=r;this.dragSourceIds=null;this.dragPlaceholder=null;this.dragRowHeight=0}attachRowDnD(r,e,t){r.addEventListener("dragstart",i=>{let s=this.host.selection.has(e.id)&&this.host.selection.size>1?[...this.host.selection]:[e.id];if(this.dragSourceIds=s,this.dragRowHeight=r.offsetHeight,r.addClass("is-dragging"),this.host.listEl&&(this.dragPlaceholder=this.host.listEl.createDiv({cls:"stashpad-drop-placeholder"}),this.dragPlaceholder.setCssStyles({height:"0px"}),this.dragPlaceholder.addEventListener("dragover",n=>{this.dragSourceIds&&(n.preventDefault(),n.dataTransfer&&(n.dataTransfer.dropEffect="move"))}),this.dragPlaceholder.addEventListener("drop",n=>{if(!this.dragSourceIds||!this.dragPlaceholder)return;n.preventDefault(),n.stopPropagation();let o=this.dragSourceIds.slice();this.dragSourceIds=null;let a=this.dragPlaceholder.nextElementSibling,l=this.dragPlaceholder.previousElementSibling;this.removeDragPlaceholder();let c,d="before";a&&a.classList.contains("stashpad-note")?(c=a.dataset.id,d="before"):l&&l.classList.contains("stashpad-note")&&(c=l.dataset.id,d="after"),c&&this.host.reorderToTarget(o,c,d)}),this.dragPlaceholder.remove()),i.dataTransfer?.setData("text/plain",s.join(",")),i.dataTransfer){i.dataTransfer.effectAllowed="move";try{i.dataTransfer.setDragImage(r,12,12)}catch{}}}),r.addEventListener("dragend",()=>{r.removeClass("is-dragging"),this.clearDropIndicators(),this.removeDragPlaceholder(),this.dragSourceIds=null}),r.addEventListener("dragover",i=>{if(!this.dragSourceIds)return;i.preventDefault(),i.dataTransfer&&(i.dataTransfer.dropEffect="move");let s=this.dropZone(i,r);this.clearDropIndicators(),s==="drop-into"?(this.removeDragPlaceholder(),r.addClass("drop-into")):(r.removeClass("drop-into"),this.placePlaceholder(r,s==="drop-above"?"before":"after"))}),r.addEventListener("dragleave",i=>{let s=r.getBoundingClientRect();(i.clientX<s.left||i.clientX>s.right||i.clientY<s.top||i.clientY>s.bottom)&&r.removeClass("drop-into")}),r.addEventListener("drop",i=>{if(!this.dragSourceIds)return;i.preventDefault(),i.stopPropagation();let s=this.dragSourceIds.slice();this.dragSourceIds=null;let n=this.dropZone(i,r);this.clearDropIndicators(),this.removeDragPlaceholder(),r.removeClass("is-dragging"),n==="drop-into"?this.host.reorderToTarget(s,e.id,"into"):this.host.reorderToTarget(s,e.id,n==="drop-above"?"before":"after")})}attachListDnD(r){r.addEventListener("dragover",e=>{if(!this.dragSourceIds)return;let t=e.target;if(t&&t.closest&&t.closest(".stashpad-note"))return;e.preventDefault(),e.dataTransfer&&(e.dataTransfer.dropEffect="move");let i=Array.from(r.querySelectorAll(".stashpad-note"));if(i.length!==0){for(let s of i){let n=s.getBoundingClientRect();if(e.clientY<n.top+n.height/2){this.placePlaceholder(s,"before");return}}this.placePlaceholder(i[i.length-1],"after")}}),r.addEventListener("drop",e=>{if(!this.dragSourceIds)return;e.preventDefault();let t=this.dragSourceIds.slice();if(this.dragSourceIds=null,!this.dragPlaceholder)return;let i=this.dragPlaceholder.nextElementSibling,s=this.dragPlaceholder.previousElementSibling;if(this.removeDragPlaceholder(),i&&i.classList.contains("stashpad-note")){let n=i.dataset.id;n&&this.host.reorderToTarget(t,n,"before")}else if(s&&s.classList.contains("stashpad-note")){let n=s.dataset.id;n&&this.host.reorderToTarget(t,n,"after")}})}placePlaceholder(r,e){if(!this.dragPlaceholder||!this.host.listEl)return;let t=e==="before"?r:r.nextSibling;if(e==="before"&&this.dragPlaceholder.nextSibling===r||e==="after"&&this.dragPlaceholder.previousSibling===r)return;let i=!!this.dragPlaceholder.parentElement;this.host.listEl.insertBefore(this.dragPlaceholder,t),this.dragPlaceholder.setCssStyles({opacity:"1"}),i?this.dragPlaceholder.setCssStyles({height:`${this.dragRowHeight}px`}):(this.dragPlaceholder.setCssStyles({height:"0px"}),this.dragPlaceholder.offsetHeight,this.dragPlaceholder.setCssStyles({height:`${this.dragRowHeight}px`}))}removeDragPlaceholder(){if(!this.dragPlaceholder?.parentElement)return;let r=this.dragPlaceholder;r.setCssStyles({height:"0px",opacity:"0"}),setTimeout(()=>{r.parentElement&&r.remove()},150)}dropZone(r,e){let t=e.getBoundingClientRect(),i=r.clientY-t.top;return i<t.height*.3?"drop-above":i>t.height*.7?"drop-below":"drop-into"}clearDropIndicators(){if(this.host.listEl)for(let r of Array.from(this.host.listEl.querySelectorAll(".drop-into")))r.removeClass("drop-into")}};var Fa=require("obsidian");var pn=class{constructor(r,e,t){this.host=r;this.component=e;this.bodyObserver=null;this.lazyBodies=new WeakMap;this.renderCache=t??new Map}async getOrComputeRender(r){let e=this.renderCache.get(r.path);if(e&&e.mtime===r.stat.mtime)return Se.record("render.row.cacheHit",0),e;let t=await Se.timeAsync("render.row.read",()=>this.host.app.vault.cachedRead(r)),i=this.host.stripFrontmatter(t),{text:s,attachments:n}=this.splitAttachments(i),o=createDiv({cls:"stashpad-note-text"});await Se.timeAsync("render.row.markdown",()=>Fa.MarkdownRenderer.render(this.host.app,s,o,r.path,this.component));let a=o.innerHTML,l={mtime:r.stat.mtime,text:s,attachments:n,html:a};return this.renderCache.set(r.path,l),l}arm(){this.bodyObserver?.disconnect(),this.lazyBodies=new WeakMap,this.bodyObserver=new IntersectionObserver(r=>{for(let e of r){if(!e.isIntersecting)continue;let t=e.target,i=this.lazyBodies.get(t);this.bodyObserver?.unobserve(t),this.lazyBodies.delete(t),i&&i()}},{root:this.host.contentEl,rootMargin:"1400px 0px"})}dispose(){this.bodyObserver?.disconnect(),this.bodyObserver=null}isArmed(){return!!this.bodyObserver}hasFreshRenderCache(r){let e=this.renderCache.get(r.path);return!!e&&e.mtime===r.stat.mtime}evict(r){let e=this.renderCache;e.evict?e.evict(r.path):e.delete&&e.delete(r.path)}defer(r,e){this.lazyBodies.set(r,e),this.bodyObserver?.observe(r)}splitAttachments(r){let e=[];return{text:r.replace(/!\[\[([^\]\|]+)(?:\|[^\]]+)?\]\]/g,(i,s)=>(e.push(s),"")).replace(/\n{3,}/g,` -`).trim(),attachments:e}}};function pt(A,r,e){if(!e||e===r)return;let t=s=>{let n=!1;return A.iterateAllLeaves(o=>{o===s&&(n=!0)}),n},i=A.on("active-leaf-change",()=>{t(r)||(A.offref(i),t(e)&&(A.setActiveLeaf(e,{focus:!0}),A.revealLeaf(e)))})}function cr(A,r,e){return A.tree.getChildren(r).slice().sort((i,s)=>yc(A,i,s,e)).map(i=>i.id)}function yc(A,r,e,t){switch(t){case"created-asc":return(r.created||"").localeCompare(e.created||"");case"created-desc":return(e.created||"").localeCompare(r.created||"");case"modified-asc":case"modified-desc":{let i=lr(A,r)||r.created||"",s=lr(A,e)||e.created||"";return t==="modified-asc"?i.localeCompare(s):s.localeCompare(i)}case"title-az":case"title-za":{let i=A.titleForNode(r),s=A.titleForNode(e),n={numeric:!0,sensitivity:"base"};return t==="title-az"?i.localeCompare(s,void 0,n):s.localeCompare(i,void 0,n)}default:return 0}}function lr(A,r){if(!r.file)return"";let e=A.app.metadataCache.getFileCache(r.file)?.frontmatter;return(typeof e?.modified=="string"?e.modified:"")||""}var fi=require("obsidian");Ni();async function So(A){let r=A.getActionTargets();if(!r.length)return;let e=X().prefixTimestampsOnCopy,t=[];for(let i of r){if(!i.file)continue;let s=await A.app.vault.cachedRead(i.file),n=A.stripFrontmatter(s).trim();t.push(e?`${A.formatTimeInline(i.created)} ${n}`:n)}await navigator.clipboard.writeText(t.join(` +`).trim(),attachments:e}}};function gt(A,r,e,t){if(!e||e===r)return;let i=o=>{let a=!1;return A.iterateAllLeaves(l=>{l===o&&(a=!0)}),a},s=r,n=A.on("active-leaf-change",()=>{if(i(r)){s=A.activeLeaf??s;return}A.offref(n),s===r&&i(e)&&(A.setActiveLeaf(e,{focus:!0}),A.revealLeaf(e))});t?.(n)}function Sa(A,r,e){return A.tree.getChildren(r).slice().sort((i,s)=>qc(A,i,s,e)).map(i=>i.id)}function qc(A,r,e,t){switch(t){case"created-asc":return(r.created||"").localeCompare(e.created||"");case"created-desc":return(e.created||"").localeCompare(r.created||"");case"modified-asc":case"modified-desc":{let i=ka(A,r)||r.created||"",s=ka(A,e)||e.created||"";return t==="modified-asc"?i.localeCompare(s):s.localeCompare(i)}case"title-az":case"title-za":{let i=A.titleForNode(r),s=A.titleForNode(e),n={numeric:!0,sensitivity:"base"};return t==="title-az"?i.localeCompare(s,void 0,n):s.localeCompare(i,void 0,n)}default:return 0}}function ka(A,r){if(!r.file)return"";let e=A.app.metadataCache.getFileCache(r.file)?.frontmatter;return(typeof e?.modified=="string"?e.modified:"")||""}var Ci=require("obsidian");Hi();async function qo(A){let r=A.getActionTargets();if(!r.length)return;let e=X().prefixTimestampsOnCopy,t=[];for(let i of r){if(!i.file)continue;let s=await A.app.vault.cachedRead(i.file),n=A.stripFrontmatter(s).trim();t.push(e?`${A.formatTimeInline(i.created)} ${n}`:n)}await navigator.clipboard.writeText(t.join(` -`)),A.plugin.notifications.show({message:`Copied ${A.titleList(r)} to clipboard`,kind:"success",category:"system",affectedIds:r.map(i=>i.id),folder:A.noteFolder})}async function dr(A){let r=A.getActionTargets();if(!r.length||!r[0].file){new fi.Notice("Nothing to copy from.");return}let e=r[0],t=await A.app.vault.cachedRead(e.file),i=A.stripFrontmatter(t),s=fa(i);if(s.length===0){A.plugin.notifications.show({message:`No codeblock found in "${A.titleForNode(e)}".`,kind:"info",category:"system",affectedIds:[e.id],folder:A.noteFolder});return}if(s.length===1){await navigator.clipboard.writeText(s[0].code),A.plugin.notifications.show({message:`Copied codeblock${s[0].lang?` (${s[0].lang})`:""} from "${A.titleForNode(e)}".`,kind:"success",category:"system",affectedIds:[e.id],folder:A.noteFolder});return}let n=s.map((a,l)=>({kind:"one",idx:l,label:`${l+1}. ${a.lang||"(no language)"} \u2014 ${a.code.split(` +`)),A.plugin.notifications.show({message:`Copied ${A.titleList(r)} to clipboard`,kind:"success",category:"system",affectedIds:r.map(i=>i.id),folder:A.noteFolder})}async function ba(A){let r=A.getActionTargets();if(!r.length||!r[0].file){new Ci.Notice("Nothing to copy from.");return}let e=r[0],t=await A.app.vault.cachedRead(e.file),i=A.stripFrontmatter(t),s=xr(i);if(s.length===0){A.plugin.notifications.show({message:`No codeblock found in "${A.titleForNode(e)}".`,kind:"info",category:"system",affectedIds:[e.id],folder:A.noteFolder});return}if(s.length===1){await navigator.clipboard.writeText(s[0].code),A.plugin.notifications.show({message:`Copied codeblock${s[0].lang?` (${s[0].lang})`:""} from "${A.titleForNode(e)}".`,kind:"success",category:"system",affectedIds:[e.id],folder:A.noteFolder});return}let n=s.map((a,l)=>({kind:"one",idx:l,label:`${l+1}. ${a.lang||"(no language)"} \u2014 ${a.code.split(` `)[0].slice(0,60)}${a.code.includes(` -`)?"\u2026":""}`}));n.push({kind:"all",idx:-1,label:`Copy all ${s.length} blocks (joined with blank lines)`});let o=new class extends fi.SuggestModal{getSuggestions(a){let l=a.trim().toLowerCase();if(!l)return n;let c=l.split(/\s+/).filter(Boolean);return n.filter(d=>{let h=d.label.toLowerCase();return c.every(g=>h.includes(g))})}renderSuggestion(a,l){l.createDiv({cls:"stashpad-suggest-title",text:a.label})}async onChooseSuggestion(a){let l=a.kind==="all"?s.map(c=>c.code).join(` +`)?"\u2026":""}`}));n.push({kind:"all",idx:-1,label:`Copy all ${s.length} blocks (joined with blank lines)`});let o=new class extends Ci.SuggestModal{getSuggestions(a){let l=a.trim().toLowerCase();if(!l)return n;let c=l.split(/\s+/).filter(Boolean);return n.filter(d=>{let h=d.label.toLowerCase();return c.every(g=>h.includes(g))})}renderSuggestion(a,l){l.createDiv({cls:"stashpad-suggest-title",text:a.label})}async onChooseSuggestion(a){let l=a.kind==="all"?s.map(c=>c.code).join(` -`):s[a.idx].code;await navigator.clipboard.writeText(l),A.plugin.notifications.show({message:a.kind==="all"?`Copied all ${s.length} codeblocks from "${A.titleForNode(e)}".`:`Copied codeblock${s[a.idx].lang?` (${s[a.idx].lang})`:""} from "${A.titleForNode(e)}".`,kind:"success",category:"system",affectedIds:[e.id],folder:A.noteFolder})}}(A.app);o.setPlaceholder(`${s.length} codeblocks in "${A.titleForNode(e)}" \u2014 pick one to copy.`),o.open()}async function Ar(A){let r=A.getActionTargets();if(r.length===0){let n=A.tree.get(A.focusId);n?.file&&(r=[n])}if(r.length===0){new fi.Notice("Nothing to copy.");return}let e=X().prefixTimestampsOnCopy,t=[],i=async(n,o)=>{if(n.file){let a=await A.app.vault.cachedRead(n.file),l=A.stripFrontmatter(a).trim().split(/\r?\n/).join(" "),c=e?`${A.formatTimeInline(n.created)} `:"";t.push(`${" ".repeat(o)}- ${c}${l}`)}for(let a of A.tree.getChildren(n.id))await i(a,o+1)};for(let n of r)await i(n,0);let s=t.join(` -`);await navigator.clipboard.writeText(s),A.plugin.clearNoteClipboard(),A.plugin.noteClipboard={mode:"copy",folder:A.noteFolder,ids:r.map(n=>n.id),text:s},A.render(),A.plugin.notifications.show({message:`Copied tree of ${A.titleList(r)} (${t.length} entries) \u2014 paste in the list to clone, in a note to drop the outline in`,kind:"success",category:"system",affectedIds:r.map(n=>n.id),folder:A.noteFolder})}async function hr(A){let r=A.getActionTargets();if(r.length===0){let i=A.tree.get(A.focusId);i?.file&&(r=[i])}if(r.length===0){new fi.Notice("Nothing to copy.");return}let e=[],t=(i,s)=>{if(!i.file)return;let n=" ".repeat(s);e.push(`${n}- ![[${i.file.basename}]]`);for(let o of A.tree.getChildren(i.id))t(o,s+1)};for(let i of r)t(i,0);await navigator.clipboard.writeText(e.join(` -`)),A.plugin.notifications.show({message:`Copied outline of ${A.titleList(r)} (${e.length} entr${e.length===1?"y":"ies"})`,kind:"success",category:"system",affectedIds:r.map(i=>i.id),folder:A.noteFolder})}var Qe=require("obsidian");le();var on=require("obsidian");nn();qe();le();var nt="stash",Nr=1;function Dr(A){let e=(A.split(/[\\/]/).pop()??"").trim();return!e||e==="."||e===".."||e.includes("/")||e.includes("\\")||e.includes("..")?"":e}var Ko=/!\[\[([^\]\|]+)(?:\|[^\]]+)?\]\]/g;async function kt(A,r){let e=[],t=Oc([...r.rootNotes,...r.allDescendants]),i=new Map,s=[];for(let o of t){let a=await A.vault.read(o.file),l=a,c=Mr(a);for(let d of c){let h=A.metadataCache.getFirstLinkpathDest(d,o.file.path);if(!h){s.push(`Missing attachment "${d}" in ${o.file.path}`);continue}let g=h.name;i.has(g)||i.set(g,await A.vault.readBinary(h)),l=Yc(l,d,g)}l=Vc(l,A,o.file.path),e.push({name:`notes/${o.file.name}`,data:l})}for(let[o,a]of i)e.push({name:`attachments/${o}`,data:a});let n={stashSchema:Nr,exportedAt:new Date().toISOString(),sourceFolder:r.sourceFolder,noteCount:t.length,rootIds:r.rootNotes.map(o=>o.id)};return e.push({name:"manifest.json",data:JSON.stringify(n,null,2)}),s.length&&e.push({name:"warnings.txt",data:s.join(` -`)}),Ii(e,6)}async function ot(A,r,e,t,i={}){let s=await mi(r),n=s["manifest.json"];if(!n)throw new Error("Not a valid .stash package: missing manifest.json");let o=JSON.parse(sn(n));if(typeof o.stashSchema!="number"||o.stashSchema>Nr)throw new Error(`Unsupported .stash schema: v${o.stashSchema}`);await xr(A,e);let a=Object.entries(s).filter(([E])=>E.startsWith("notes/")&&E.endsWith(".md")),l=[];for(let[E,v]of a){let Q=sn(v),{fm:S,body:x}=he(Q),H=Dr(E.slice(6))||`${S.id||"imported-"+Z(4)}.md`;l.push({originalName:H,fm:S,body:x})}let c=new Map,d=0;for(let E of l){let v=E.fm.id;v&&(i.forceNewIds?c.set(v,Z(6)):t.has(v)||c.has(v)?(c.set(v,`${v}-${Z(4)}-Imported`),d++):c.set(v,v))}let h=new Date().toISOString(),g=[],u=`${e}/_attachments`,p=0,f=Object.entries(s).filter(([E])=>E.startsWith("attachments/")),I=new Map,m=null;if(i.dedupeExisting){m=new Map;for(let E of A.vault.getFiles())m.has(E.name)||m.set(E.name,E.path)}let C=(E,v)=>{if(E.length!==v.length)return!1;for(let Q=0;Q<E.length;Q++)if(E[Q]!==v[Q])return!1;return!0},B=!1;for(let[E,v]of f){let Q=Dr(E.slice(12));if(!Q)continue;let S=v,x=[],H=m?.get(Q);H&&x.push(H);let G=`${u}/${Q}`;await A.vault.adapter.exists(G)&&x.push(G);let N=null;for(let R of x)try{if(C(new Uint8Array(await A.vault.adapter.readBinary(R)),S)){N=R;break}}catch{}if(N){I.set(Q,N);continue}let U=G;for(let R=1;await A.vault.adapter.exists(U);R++)U=`${u}/${$c(Q,R)}`;I.set(Q,U),B||(await xr(A,u),B=!0),await A.vault.createBinary(U,S.buffer),p++}let w=0;for(let E of l){let v=E.fm.id;if(!v){g.push(`Skipped ${E.originalName} \u2014 no id in frontmatter`);continue}let Q=c.get(v),S=E.fm.parent??null,x=S;!S||S===k?x=i.reparentRootsTo??S??k:c.has(S)?x=c.get(S):x=t.has(S)?S:i.reparentRootsTo??k;let H=zc(E.body,I,u),G={...E.fm,id:Q,parent:x,import_date:h};Array.isArray(G.attachments)&&(G.attachments=G.attachments.map(D=>{let b=Tr(Nt(D));return We(I.get(b)??`${u}/${b}`)}));let N=St(G,H),U=Q===v?E.originalName:Xc(E.originalName,v,Q),R=`${e}/${U}`;await A.vault.adapter.exists(R)&&(U=`${U.replace(/\.md$/,"")}-${Z(4)}.md`,R=`${e}/${U}`),await A.vault.create(R,N),w++}let y;if(o.colorAliases&&typeof o.colorAliases=="object"){let E={};for(let[v,Q]of Object.entries(o.colorAliases)){let S=String(v).trim().toLowerCase(),x=String(Q??"").trim();/^#([0-9a-f]{6})$/.test(S)&&x&&(E[S]=x.slice(0,60))}Object.keys(E).length&&(y=E)}return{notesWritten:w,attachmentsWritten:p,collisionsRenamed:d,warnings:g,colorAliases:y,idRemap:Object.fromEntries(c)}}function Oc(A){let r=new Set,e=[];for(let t of A)r.has(t.id)||(r.add(t.id),e.push(t));return e}function Mr(A){let r=new Set,e;for(Ko.lastIndex=0;(e=Ko.exec(A))!==null;)r.add(e[1]);return[...r]}async function an(A,r){let e=await A.vault.read(r),t=[],i=new Set;for(let s of Mr(e)){let n=A.metadataCache.getFirstLinkpathDest(s,r.path);n&&!i.has(n.path)&&(i.add(n.path),t.push(n))}return t}function Yc(A,r,e){return A.replace(new RegExp(`!\\[\\[${Wc(r)}(\\|[^\\]]+)?\\]\\]`,"g"),(t,i)=>`![[${e}${i??""}]]`)}function $c(A,r){let e=A.lastIndexOf(".");return e>0?`${A.slice(0,e)}-${r}${A.slice(e)}`:`${A}-${r}`}function zc(A,r,e){return A.replace(Ko,(t,i,s)=>{if(i.includes("/"))return t;let n=r.get(i)??`${e}/${i}`;return t.replace(i,n)})}function Vc(A,r,e){let t=he(A);if(!t.fm.attachments||!Array.isArray(t.fm.attachments))return A;let i=t.fm.attachments.map(n=>{let o=r.metadataCache.getFirstLinkpathDest(n,e);return o?o.name:Tr(n)}),s={...t.fm,attachments:i};return St(s,t.body)}function Wc(A){return A.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function Tr(A){let r=A.lastIndexOf("/");return r<0?A:A.slice(r+1)}function Xc(A,r,e){return A.includes(r)?A.replace(r,e):A.replace(/\.md$/,`-${e}.md`)}function he(A){if(!A.startsWith("---"))return{fm:{},body:A};let r=A.indexOf(` ----`,3);if(r<0)return{fm:{},body:A};let e=A.slice(3,r).replace(/^\n/,""),t=A.slice(r+4),i={};try{i=(0,on.parseYaml)(e)??{}}catch{i={}}let s=t.startsWith(` -`)?t.slice(1):t;return{fm:i,body:s}}function St(A,r){return`--- -${(0,on.stringifyYaml)(A).trimEnd()} +`):s[a.idx].code;await navigator.clipboard.writeText(l),A.plugin.notifications.show({message:a.kind==="all"?`Copied all ${s.length} codeblocks from "${A.titleForNode(e)}".`:`Copied codeblock${s[a.idx].lang?` (${s[a.idx].lang})`:""} from "${A.titleForNode(e)}".`,kind:"success",category:"system",affectedIds:[e.id],folder:A.noteFolder})}}(A.app);o.setPlaceholder(`${s.length} codeblocks in "${A.titleForNode(e)}" \u2014 pick one to copy.`),o.open()}async function Da(A){let r=A.getActionTargets();if(r.length===0){let n=A.tree.get(A.focusId);n?.file&&(r=[n])}if(r.length===0){new Ci.Notice("Nothing to copy.");return}let e=X().prefixTimestampsOnCopy,t=[],i=async(n,o)=>{if(n.file){let a=await A.app.vault.cachedRead(n.file),l=A.stripFrontmatter(a).trim().split(/\r?\n/).join(" "),c=e?`${A.formatTimeInline(n.created)} `:"";t.push(`${" ".repeat(o)}- ${c}${l}`)}for(let a of A.tree.getChildren(n.id))await i(a,o+1)};for(let n of r)await i(n,0);let s=t.join(` +`);await navigator.clipboard.writeText(s),A.plugin.clearNoteClipboard(),A.plugin.noteClipboard={mode:"copy",folder:A.noteFolder,ids:r.map(n=>n.id),text:s},A.render(),A.plugin.notifications.show({message:`Copied tree of ${A.titleList(r)} (${t.length} entries) \u2014 paste in the list to clone, in a note to drop the outline in`,kind:"success",category:"system",affectedIds:r.map(n=>n.id),folder:A.noteFolder})}async function xa(A){let r=A.getActionTargets();if(r.length===0){let i=A.tree.get(A.focusId);i?.file&&(r=[i])}if(r.length===0){new Ci.Notice("Nothing to copy.");return}let e=[],t=(i,s)=>{if(!i.file)return;let n=" ".repeat(s);e.push(`${n}- ![[${i.file.basename}]]`);for(let o of A.tree.getChildren(i.id))t(o,s+1)};for(let i of r)t(i,0);await navigator.clipboard.writeText(e.join(` +`)),A.plugin.notifications.show({message:`Copied outline of ${A.titleList(r)} (${e.length} entr${e.length===1?"y":"ies"})`,kind:"success",category:"system",affectedIds:r.map(i=>i.id),folder:A.noteFolder})}var Qe=require("obsidian");le();var Cn=require("obsidian");mn();Le();le();var ot="stash",ja=1;function ir(A){let e=(A.split(/[\\/]/).pop()??"").trim();return!e||e==="."||e===".."||e.includes("/")||e.includes("\\")||e.includes("..")?"":e}var sr=/!\[\[([^\]\|]+)(?:\|[^\]]+)?\]\]/g;async function bt(A,r){let e=[],t=hd([...r.rootNotes,...r.allDescendants]),i=new Map,s=[];for(let o of t){let a=await A.vault.read(o.file),l=a,c=_a(a);for(let d of c){let h=A.metadataCache.getFirstLinkpathDest(d,o.file.path);if(!h){s.push(`Missing attachment "${d}" in ${o.file.path}`);continue}let g=h.name;i.has(g)||i.set(g,await A.vault.readBinary(h)),l=pd(l,d,g)}l=fd(l,A,o.file.path),e.push({name:`notes/${o.file.name}`,data:l})}for(let[o,a]of i)e.push({name:`attachments/${o}`,data:a});let n={stashSchema:ja,exportedAt:new Date().toISOString(),sourceFolder:r.sourceFolder,noteCount:t.length,rootIds:r.rootNotes.map(o=>o.id)};return e.push({name:"manifest.json",data:JSON.stringify(n,null,2)}),s.length&&e.push({name:"warnings.txt",data:s.join(` +`)}),Bi(e,6)}async function rt(A,r,e,t,i={}){let s=await yi(r),n=s["manifest.json"];if(!n)throw new Error("Not a valid .stash package: missing manifest.json");let o=JSON.parse(In(n));if(typeof o.stashSchema!="number"||o.stashSchema>ja)throw new Error(`Unsupported .stash schema: v${o.stashSchema}`);await Za(A,e);let a=Object.entries(s).filter(([v])=>v.startsWith("notes/")&&v.endsWith(".md")),l=[];for(let[v,E]of a){let Q=In(E),{fm:S,body:x}=he(Q),H=ir(v.slice(6))||ir(`${S.id||"imported-"+Z(4)}.md`)||`imported-${Z(4)}.md`;l.push({originalName:H,fm:S,body:x})}let c=new Map,d=0;for(let v of l){let E=v.fm.id;E&&(i.forceNewIds?c.set(E,Z(6)):t.has(E)||c.has(E)?(c.set(E,`${E}-${Z(4)}-Imported`),d++):c.set(E,E))}let h=new Date().toISOString(),g=[],f=`${e}/_attachments`,p=0,u=Object.entries(s).filter(([v])=>v.startsWith("attachments/")),I=new Map,m=null;if(i.dedupeExisting){m=new Map;for(let v of A.vault.getFiles())m.has(v.name)||m.set(v.name,v.path)}let C=(v,E)=>{if(v.length!==E.length)return!1;for(let Q=0;Q<v.length;Q++)if(v[Q]!==E[Q])return!1;return!0},B=!1;for(let[v,E]of u){let Q=ir(v.slice(12));if(!Q)continue;let S=E,x=[],H=m?.get(Q);H&&x.push(H);let G=`${f}/${Q}`;await A.vault.adapter.exists(G)&&x.push(G);let N=null;for(let U of x)try{if(C(new Uint8Array(await A.vault.adapter.readBinary(U)),S)){N=U;break}}catch{}if(N){I.set(Q,N);continue}let R=G;for(let U=1;await A.vault.adapter.exists(R);U++)R=`${f}/${gd(Q,U)}`;I.set(Q,R),B||(await Za(A,f),B=!0),await A.vault.createBinary(R,S.buffer),p++}let w=0;for(let v of l){let E=v.fm.id;if(!E){g.push(`Skipped ${v.originalName} \u2014 no id in frontmatter`);continue}let Q=c.get(E),S=v.fm.parent??null,x=S;!S||S===F?x=i.reparentRootsTo??S??F:c.has(S)?x=c.get(S):x=t.has(S)?S:i.reparentRootsTo??F;let H=ud(v.body,I,f),G=v.fm;if(i.stripReserved){let b={};for(let[P,O]of Object.entries(v.fm))Ve.includes(P)||(b[P]=O);v.fm.author!==void 0&&(b.author=v.fm.author),v.fm.contributors!==void 0&&(b.contributors=v.fm.contributors),v.fm.created!==void 0&&(b.created=v.fm.created),v.fm.modified!==void 0&&(b.modified=v.fm.modified),Array.isArray(v.fm.attachments)&&(b.attachments=v.fm.attachments),G=b}let N={...G,id:Q,parent:x,import_date:h};Array.isArray(N.attachments)&&(N.attachments=N.attachments.map(b=>{let P=el(Rt(b));return We(I.get(P)??`${f}/${P}`)}));let R=Dt(N,H),U=Q===E?v.originalName:md(v.originalName,E,Q),D=`${e}/${U}`;await A.vault.adapter.exists(D)&&(U=`${U.replace(/\.md$/,"")}-${Z(4)}.md`,D=`${e}/${U}`),await A.vault.create(D,R),w++}let y;if(o.colorAliases&&typeof o.colorAliases=="object"){let v={};for(let[E,Q]of Object.entries(o.colorAliases)){let S=String(E).trim().toLowerCase(),x=String(Q??"").trim();/^#([0-9a-f]{6})$/.test(S)&&x&&(v[S]=x.slice(0,60))}Object.keys(v).length&&(y=v)}return{notesWritten:w,attachmentsWritten:p,collisionsRenamed:d,warnings:g,colorAliases:y,idRemap:Object.fromEntries(c)}}function hd(A){let r=new Set,e=[];for(let t of A)r.has(t.id)||(r.add(t.id),e.push(t));return e}function _a(A){let r=new Set,e;for(sr.lastIndex=0;(e=sr.exec(A))!==null;)r.add(e[1]);return[...r]}async function wi(A,r){let e=await A.vault.read(r),t=[],i=new Set;for(let s of _a(e)){let n=A.metadataCache.getFirstLinkpathDest(s,r.path);n&&!i.has(n.path)&&(i.add(n.path),t.push(n))}return t}function pd(A,r,e){return A.replace(new RegExp(`!\\[\\[${Id(r)}(\\|[^\\]]+)?\\]\\]`,"g"),(t,i)=>`![[${e}${i??""}]]`)}function gd(A,r){let e=A.lastIndexOf(".");return e>0?`${A.slice(0,e)}-${r}${A.slice(e)}`:`${A}-${r}`}function ud(A,r,e){return A.replace(sr,(t,i,s)=>{if(i.includes("/"))return t;let n=r.get(i)??`${e}/${i}`;return t.replace(i,n)})}function fd(A,r,e){let t=he(A);if(!t.fm.attachments||!Array.isArray(t.fm.attachments))return A;let i=t.fm.attachments.map(n=>{let o=r.metadataCache.getFirstLinkpathDest(n,e);return o?o.name:el(n)}),s={...t.fm,attachments:i};return Dt(s,t.body)}function Id(A){return A.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function el(A){let r=A.lastIndexOf("/");return r<0?A:A.slice(r+1)}function md(A,r,e){return A.includes(r)?A.replace(r,e):A.replace(/\.md$/,`-${e}.md`)}function he(A){if(!A.startsWith("---"))return{fm:{},body:A};let r=A.indexOf(` +---`,3);if(r<0)return{fm:{},body:A};let e=A.slice(3,r).replace(/^\n/,""),t=A.slice(r+4),i={};try{i=(0,Cn.parseYaml)(e)??{}}catch{i={}}let s=t.startsWith(` +`)?t.slice(1):t;return{fm:i,body:s}}function Dt(A,r){return`--- +${(0,Cn.stringifyYaml)(A).trimEnd()} --- -${r}`}async function xr(A,r){if(!(!r||await A.vault.adapter.exists(r)))try{await A.vault.createFolder(r)}catch(t){let i=t?.message??"";if(!/already exists/i.test(i))throw t}}us();Be();nn();var xe="stashenc",Pr="stashmeta";function Le(A){return A.replace(/\.stashenc$/,`.${Pr}`)}async function cn(A,r){try{return JSON.parse(await A.vault.adapter.read(Le(r)))}catch{return null}}async function bt(A,r,e){let t=r.replace(/\/+$/,""),i=[];for(let c of A.vault.getMarkdownFiles()){if((c.parent?.path?.replace(/\/+$/,"")??"")!==t)continue;let d;try{d=he(await A.vault.read(c)).fm}catch{continue}typeof d.id=="string"&&i.push({id:d.id,file:c,parent:typeof d.parent=="string"?d.parent:null,created:typeof d.created=="string"?d.created:""})}let s=i.find(c=>c.id===e);if(!s)return null;let n=new Map;for(let c of i){if(!c.parent)continue;let d=n.get(c.parent)??[];d.push(c),n.set(c.parent,d)}let o=[],a=new Set([e]),l=[e];for(;l.length;){let c=[];for(let d of l)for(let h of n.get(d)??[])a.has(h.id)||(a.add(h.id),o.push(h),c.push(h.id));l=c}return{rootNote:s,descendants:o,parentId:s.parent}}function rn(A){if(!A)return null;let r=A.replace(/\/+$/,"");return!r||r.startsWith("/")||r.includes("\\")||/^[a-zA-Z]:/.test(r)||r.split("/").some(e=>e===".."||e===".")?null:r}async function Ci(A,r,e){await A.vault.adapter.writeBinary(r,e);let t;try{t=new Uint8Array(await A.vault.adapter.readBinary(r))}catch(s){throw new Error(`Couldn't read back the encrypted file to verify it (${s.message}). Nothing was deleted.`)}let i=t.length===e.length;if(i){for(let s=0;s<e.length;s++)if(t[s]!==e[s]){i=!1;break}}if(!i){try{await A.vault.adapter.remove(r)}catch{}throw new Error("The encrypted file on disk doesn't match what was written (bad write?). Nothing was deleted.")}}function ln(A){return A.basename.replace(/-[a-z0-9]{4,12}$/,"").replace(/-/g," ").trim()||A.basename}function Ot(A){return(A.replace(/[\\/:*?"<>|]+/g," ").replace(/\s+/g," ").trim()||"locked").slice(0,60)}async function Hr(A,r,e){let t=new Set(r.map(a=>a.file.path)),i=new Map;for(let a of r)for(let l of await an(A,a.file))i.set(l.path,l);let s=new Set,n=A.metadataCache.resolvedLinks??{};for(let a of Object.keys(n))if(!t.has(a))for(let l of Object.keys(n[a]??{}))i.has(l)&&s.add(l);let o=[];for(let a of r){let l=e?.get(a.file.path);if(l!=null)try{let c=await A.vault.adapter.stat(a.file.path);if(c&&c.mtime!==l){console.warn("[Stashpad] note changed since it was bundled \u2014 keeping plaintext",a.file.path),o.push(a.file.path);continue}}catch{}try{await A.vault.delete(a.file)}catch(c){console.warn("[Stashpad] couldn't delete plaintext note",a.file.path,c),o.push(a.file.path)}}for(let[a,l]of i)if(!s.has(a))try{await A.vault.delete(l)}catch(c){console.warn("[Stashpad] couldn't delete exclusive attachment",a,c),o.push(a)}return{unpurged:o}}async function Ur(A,r,e,t,i=null,s=!1,n){let o=await bt(A,r,e);if(!o)throw new Error("Couldn't find that note to lock.");let{rootNote:a,descendants:l,parentId:c}=o,d=[a,...l],h=new Map;for(let y of d)try{let E=await A.vault.adapter.stat(y.file.path);E&&h.set(y.file.path,E.mtime)}catch{}let g=await kt(A,{rootNotes:[{id:a.id,file:a.file}],allDescendants:l.map(y=>({id:y.id,file:y.file})),sourceFolder:r}),u=await wt(g,t),p=await ve(u,t);if(p.length!==g.length)throw new Error("Encryption self-check failed (size mismatch).");for(let y=0;y<g.length;y++)if(p[y]!==g[y])throw new Error("Encryption self-check failed (content mismatch).");let f=(n??r).replace(/\/+$/,"");n&&!await A.vault.adapter.exists(f)&&await A.vault.adapter.mkdir(f);let I=Ot(s?e:ln(a.file)),m=`${f}/${I}.${xe}`;for(let y=1;await A.vault.adapter.exists(m);y++)m=`${f}/${I} (${y}).${xe}`;await Ci(A,m,u);let C=d,B={v:1,parentId:c,title:s?"":ln(a.file),count:C.length,created:a.created,rootId:e,prevSibling:i};try{await A.vault.adapter.write(Le(m),JSON.stringify(B))}catch(y){console.warn("[Stashpad] couldn't write lock sidecar",y)}let{unpurged:w}=await Hr(A,C,h);return{blobPath:m,noteCount:C.length,rootId:e,parentId:c,title:B.title,created:a.created,unpurged:w}}async function Rr(A,r,e,t,i){let s=new Uint8Array(await A.vault.adapter.readBinary(r));if(!Ne(s))throw new Error("Not an encrypted bundle.");let n=await ve(s,e),o=rn(i)??r.replace(/\/[^/]*$/,""),a=await ot(A,n,o,t,{dedupeExisting:!0});await A.vault.adapter.remove(r);try{await A.vault.adapter.remove(Le(r))}catch{}return{notesWritten:a.notesWritten,restoredTo:o}}var Ge="_deleted";function Gr(A){let r="";for(let e=0;e<A.length;e++)r+=String.fromCharCode(A[e]);return btoa(r)}function Lr(A){let r=atob(A),e=new Uint8Array(r.length);for(let t=0;t<r.length;t++)e[t]=r.charCodeAt(t);return e}async function Jo(A,r,e,t){let i=rn(e?.originalFolder);if(!i&&e?.originalFolderEnc&&t)try{i=rn(new TextDecoder().decode(await ve(Lr(e.originalFolderEnc),t)))}catch{i=null}if(i)return await A.vault.adapter.exists(i)||await A.vault.adapter.mkdir(i),i;let s=r.replace(/\/[^/]*$/,"");if(s===Ge||s.startsWith(`${Ge}/`))throw new Error("This deleted note's origin folder is unknown (missing or tampered sidecar) \u2014 can't restore it safely. The encrypted copy was kept.");return s}async function qr(A,r,e,t,i,s=!1,n){let o=await bt(A,r,e);if(!o)throw new Error("Couldn't find that note to delete.");let{rootNote:a,descendants:l,parentId:c}=o,d=[a,...l],h=new Map;for(let E of d)try{let v=await A.vault.adapter.stat(E.file.path);v&&h.set(E.file.path,v.mtime)}catch{}let g=await kt(A,{rootNotes:[{id:a.id,file:a.file}],allDescendants:l.map(E=>({id:E.id,file:E.file})),sourceFolder:r}),u=await wt(g,t),p=await ve(u,t);if(p.length!==g.length)throw new Error("Encryption self-check failed (size).");for(let E=0;E<g.length;E++)if(p[E]!==g[E])throw new Error("Encryption self-check failed (content).");await A.vault.adapter.exists(Ge)||await A.vault.adapter.mkdir(Ge);let f=r.replace(/\/+$/,""),I=f.split("/").pop()||"vault",m=Ot(s?e:`${I} ${ln(a.file)}`),C=`${Ge}/${m}.${xe}`;for(let E=1;await A.vault.adapter.exists(C);E++)C=`${Ge}/${m} (${E}).${xe}`;await Ci(A,C,u);let B=d,w={v:1,kind:"deleted",originalFolder:s?"":f,...s?{originalFolderEnc:Gr(await wt(new TextEncoder().encode(f),t))}:{},parentId:c,title:s?"":ln(a.file),count:B.length,created:a.created,rootId:e,deletedAt:i,...n?{keyId:n}:{}};try{await A.vault.adapter.write(Le(C),JSON.stringify(w))}catch(E){console.warn("[Stashpad] couldn't write deleted sidecar",E)}let{unpurged:y}=await Hr(A,B,h);return{blobPath:C,noteCount:B.length,rootId:e,originalFolder:f,title:w.title,unpurged:y}}async function Kr(A,r,e,t){let i=new Uint8Array(await A.vault.adapter.readBinary(r));if(!Ne(i))throw new Error("Not an encrypted bundle.");let s=await ft(A,r),n=await Jo(A,r,s,e),o=await ve(i,e),a=await ot(A,o,n,t,{dedupeExisting:!0});await A.vault.adapter.remove(r);try{await A.vault.adapter.remove(Le(r))}catch{}return{notesWritten:a.notesWritten,restoredTo:n}}async function ft(A,r){try{return JSON.parse(await A.vault.adapter.read(Le(r)))}catch{return null}}async function Oo(A,r){await A.vault.adapter.remove(r);try{await A.vault.adapter.remove(Le(r))}catch{}}var Te=".trash";async function dn(A,r){let e=[],t=[r];for(;t.length;){let i=t.shift(),s;try{s=await A.vault.adapter.list(i)}catch{continue}e.push(...s.files),t.push(...s.folders)}return e}function Jr(A){let r=A.split("/").filter(Boolean);if(r.length===0)return null;for(let e of r)if(e===".."||e==="."||e.includes("\\")||/^[a-zA-Z]:/.test(e))return null;return r.join("/")}async function Or(A,r,e,t=!1){if(!await A.vault.adapter.exists(Te))return null;let i=await dn(A,Te);if(i.length===0)return null;let s=[],n=new Map;for(let u of i){let p=u.slice(Te.length+1);s.push({name:`files/${p}`,data:await A.vault.adapter.readBinary(u)});try{let f=await A.vault.adapter.stat(u);f&&n.set(u,f.mtime)}catch{}}let o=await Ii(s),a=await wt(o,r),l=await ve(a,r);if(l.length!==o.length)throw new Error("Encryption self-check failed (size).");for(let u=0;u<o.length;u++)if(l[u]!==o[u])throw new Error("Encryption self-check failed (content).");await A.vault.adapter.exists(Ge)||await A.vault.adapter.mkdir(Ge);let c=Ot(t?`trash-${e.replace(/[^0-9]/g,"").slice(0,14)}`:"Obsidian trash backfill"),d=`${Ge}/${c}.${xe}`;for(let u=1;await A.vault.adapter.exists(d);u++)d=`${Ge}/${c} (${u}).${xe}`;await Ci(A,d,a);let h={v:1,kind:"rawtrash",originalFolder:Te,parentId:null,title:t?"":"Obsidian trash (backfill)",count:i.length,created:e,rootId:"",deletedAt:e};try{await A.vault.adapter.write(Le(d),JSON.stringify(h))}catch(u){console.warn("[Stashpad] couldn't write backfill sidecar",u)}let g=[];for(let u of i){let p=n.get(u);try{if(p!=null){let f=await A.vault.adapter.stat(u);if(f&&f.mtime!==p){g.push(u);continue}}await A.vault.adapter.remove(u)}catch(f){console.warn("[Stashpad] couldn't delete trash file",u,f),g.push(u)}}return{blobPath:d,fileCount:i.length,unpurged:g}}async function Yr(A,r,e){let t=new Uint8Array(await A.vault.adapter.readBinary(r));if(!Ne(t))throw new Error("Not an encrypted bundle.");let i=await ve(t,e),s=await mi(i),n=0;for(let[o,a]of Object.entries(s)){if(!o.startsWith("files/"))continue;let l=Jr(o.slice(6));if(!l){console.warn("[Stashpad] skipped unsafe trash entry",o);continue}let c=`${Te}/${l}`.split("/").slice(0,-1).join("/"),d="";for(let g of c.split("/"))d=d?`${d}/${g}`:g,await A.vault.adapter.exists(d)||await A.vault.adapter.mkdir(d);let h=`${Te}/${l}`;for(let g=1;await A.vault.adapter.exists(h);g++)h=`${Te}/${l.replace(/(\.[^./]*)?$/,` (${g})$1`)}`;await A.vault.adapter.writeBinary(h,a.buffer),n++}await A.vault.adapter.remove(r);try{await A.vault.adapter.remove(Le(r))}catch{}return{filesWritten:n}}async function $r(A,r,e,t){let i=new Uint8Array(await A.vault.adapter.readBinary(r));if(!Ne(i))throw new Error(`Not an encrypted blob: ${r}`);let s=await ve(i,e);try{let n=await wt(s,t),o=await ve(n,t);if(o.length!==s.length)throw new Error("Rotation self-check failed (size).");for(let a=0;a<s.length;a++)if(o[a]!==s[a])throw new Error("Rotation self-check failed (content).");await Ci(A,`${r}.rot`,n)}finally{s.fill(0)}}async function zr(A,r,e,t){let i=await ft(A,r);if(!i?.originalFolderEnc)return;let s=await ve(Lr(i.originalFolderEnc),e);try{i.originalFolderEnc=Gr(await wt(s,t))}finally{s.fill(0)}await A.vault.adapter.write(Le(r),JSON.stringify(i))}async function Yo(A,r){let e=[];for(let t of await pn(A))(await ft(A,t))?.keyId===r&&e.push(t);return e}async function An(A,r){let e=`${r}.rot`;if(!await A.vault.adapter.exists(e))return;let t=new Uint8Array(await A.vault.adapter.readBinary(e));await Ci(A,r,t);try{await A.vault.adapter.remove(e)}catch{}}async function hn(A,r){let e=r.replace(/\/+$/,"");for(let t of await dn(A,e))if(t.endsWith(`.${xe}.rot`))try{await A.vault.adapter.remove(t)}catch{}}async function Vr(A,r){let e=r.replace(/\/+$/,"");return(await dn(A,e)).filter(t=>t.endsWith(`.${xe}.rot`)).map(t=>t.replace(/\.rot$/,""))}async function Wr(A,r,e,t,i){let s=r.replace(/\/+$/,""),n=(await dn(A,s)).filter(f=>!f.endsWith(`.${xe}`)&&!f.endsWith(`.${Pr}`));if(n.length===0)throw new Error("This folder has no files to encrypt.");let o=[],a=new Map;for(let f of n){o.push({name:`files/${f.slice(s.length+1)}`,data:await A.vault.adapter.readBinary(f)});try{let I=await A.vault.adapter.stat(f);I&&a.set(f,I.mtime)}catch{}}let l=await Ii(o),c=await wt(l,e),d=await ve(c,e);if(d.length!==l.length)throw new Error("Encryption self-check failed (size).");for(let f=0;f<l.length;f++)if(d[f]!==l[f])throw new Error("Encryption self-check failed (content).");let h=Ot(s.split("/").pop()||"folder"),g=`${s}/${h}.${xe}`;for(let f=1;await A.vault.adapter.exists(g);f++)g=`${s}/${h} (${f}).${xe}`;await Ci(A,g,c);let u={v:1,kind:"rawfolder",originalFolder:s,parentId:null,title:s.split("/").pop()||"",count:n.length,created:i,rootId:"",deletedAt:i,...t?{keyId:t}:{}};try{await A.vault.adapter.write(Le(g),JSON.stringify(u))}catch(f){console.warn("[Stashpad] couldn't write rawfolder sidecar",f)}let p=[];for(let f of n){let I=a.get(f);try{if(I!=null){let m=await A.vault.adapter.stat(f);if(m&&m.mtime!==I){p.push(f);continue}}await A.vault.adapter.remove(f)}catch(m){console.warn("[Stashpad] couldn't delete file",f,m),p.push(f)}}if(p.length===0){let f=[...new Set(n.map(I=>I.slice(0,I.lastIndexOf("/"))))].filter(I=>I.length>s.length).sort((I,m)=>m.length-I.length);for(let I of f)try{let m=await A.vault.adapter.list(I);!m.files.length&&!m.folders.length&&await A.vault.adapter.rmdir(I,!0)}catch{}}return{blobPath:g,fileCount:n.length,unpurged:p}}async function Xr(A,r,e){let t=new Uint8Array(await A.vault.adapter.readBinary(r));if(!Ne(t))throw new Error("Not an encrypted bundle.");let i=await ft(A,r),s=rn(i?.originalFolder)??r.replace(/\/[^/]*$/,""),n=await ve(t,e),o=await mi(n),a=0;for(let[l,c]of Object.entries(o)){if(!l.startsWith("files/"))continue;let d=Jr(l.slice(6));if(!d){console.warn("[Stashpad] skipped unsafe folder entry",l);continue}let h=`${s}/${d}`.split("/").slice(0,-1).join("/"),g="";for(let p of h.split("/"))g=g?`${g}/${p}`:p,g&&!await A.vault.adapter.exists(g)&&await A.vault.adapter.mkdir(g);let u=`${s}/${d}`;for(let p=1;await A.vault.adapter.exists(u);p++)u=`${s}/${d.replace(/(\.[^./]*)?$/,` (${p})$1`)}`;await A.vault.adapter.writeBinary(u,c.buffer),a++}await A.vault.adapter.remove(r);try{await A.vault.adapter.remove(Le(r))}catch{}return{filesWritten:a,folder:s}}async function $o(A,r){let e=r.replace(/\/+$/,"");try{let t=await A.vault.adapter.list(e);for(let i of t.files){if(!i.endsWith(`.${xe}`))continue;if((await ft(A,i))?.kind==="rawfolder")return i}}catch{}return null}async function Zr(A){let r=[];for(let e of A.vault.getFiles())if(e.extension===xe)try{(await ft(A,e.path))?.kind==="rawfolder"&&r.push({folder:e.parent?.path?.replace(/\/+$/,"")??"",blobPath:e.path})}catch{}return r}async function pn(A){if(!await A.vault.adapter.exists(Ge))return[];try{return(await A.vault.adapter.list(Ge)).files.filter(e=>e.endsWith(`.${xe}`))}catch{return[]}}function jr(A){return A.replace(/[^\w.\-]+/g,"_").replace(/^\.+/,"").slice(0,60)||"stash-export"}async function _r(A,r){let e=il(A,r);if(e.length===0){new Qe.Notice("Nothing to export.");return}let t=sl(A,e);if(t.length===0){new Qe.Notice("No exportable notes (no files attached).");return}let i=(A.noteFolder.split("/").pop()||A.noteFolder).trim(),s=e.length===1?A.titleForNode(e[0]):`${i}-${e.length}notes`;new ti(A.app,s,t.length,(n,o,a)=>{jc(A,e,t,n,o,a)},Xi).open()}async function el(A,r){let e=r.replace(/\/[^/]*$/,"").replace(/\/+$/,""),t=await A.ensureFolderUnlocked(e);if(!t)return;let i;try{let l=new Uint8Array(await A.app.vault.adapter.readBinary(r));i=await ve(l,t)}catch(l){new Qe.Notice(`Couldn't decrypt this note to export it: ${l.message}`);return}finally{t.fill(0)}let s=await cn(A.app,r),n=(A.settings.lockedSubtrees??[]).find(l=>l.blob===r),o=n?.title||s?.title||"locked-note",a=n?.count??s?.count??1;new ti(A.app,o,a,(l,c,d)=>{Zc(A,e,l,i,c,d)},Xi).open()}async function Zc(A,r,e,t,i,s=!1){try{let n=t,o="";if(i){let u=await _e(n,i);n=u.data;let p=go[u.kdf];o=p.strong?` (encrypted \xB7 ${p.label})`:` (encrypted \xB7 ${p.label} \u2014 Argon2id unavailable here, weaker)`}let a=(0,Qe.moment)().format("YYYYMMDD-HHmmss"),c=(A.settings.exportFolder||"_exports").trim().replace(/^\/+|\/+$/g,"").split("/").filter(u=>u&&u!=="."&&u!==".."&&!/[\\:]/.test(u)).join("/")||"_exports",d=`${r}/${c}`;await A.app.vault.adapter.exists(d)||await A.app.vault.adapter.mkdir(d);let h=`${jr(e)}-${a}`,g=`${d}/${h}.${nt}`;if(await A.app.vault.createBinary(g,n.buffer.slice(n.byteOffset,n.byteOffset+n.byteLength)),s&&i)try{A.app.secretStorage?.setSecret(jt(h),i)}catch(u){console.warn("[Stashpad] couldn't save export passphrase",u)}new Qe.Notice(`Exported encrypted note${o} \u2192 ${g}`,0)}catch(n){new Qe.Notice(`Export failed: ${n.message}`),console.error(n)}finally{try{t.fill(0)}catch{}}}async function tl(A,r){let e=il(A,r);if(e.length===0){new Qe.Notice("Nothing to export.");return}let t=sl(A,e);if(t.length===0){new Qe.Notice("No exportable notes.");return}let i=(A.noteFolder.split("/").pop()||A.noteFolder).trim(),s=e.length===1?A.titleForNode(e[0]):`${i}-okf`;new Li(A.app,s,t.length,(n,o)=>{(async()=>{try{let a=await A.plugin.exportOkf(A.noteFolder,e.map(l=>l.id),n,o);if(!a.length){new Qe.Notice("Nothing exported.");return}A.plugin.notifications.show({message:`Exported OKF \u2014 ${a.length} file${a.length===1?"":"s"} \u2192 \`${A.noteFolder}/${A.plugin.settings.exportFolder||"_exports"}\``,kind:"success",category:"export",affectedPaths:a,folder:A.noteFolder,duration:0}),await A.log.append({type:"stash_export",id:e[0].id,payload:{okf:!0,paths:a,noteCount:t.length,rootIds:e.map(l=>l.id)}})}catch(a){new Qe.Notice(`OKF export failed: ${a.message}`),console.error(a)}})()}).open()}async function jc(A,r,e,t,i,s=!1){try{let n=await kt(A.app,{rootNotes:r.filter(u=>!!u.file).map(u=>({id:u.id,file:u.file})),allDescendants:e.filter(u=>!r.some(p=>p.id===u.id)).filter(u=>!!u.file).map(u=>({id:u.id,file:u.file})),sourceFolder:A.noteFolder}),o="";if(i){let u=await _e(n,i);n=u.data;let p=go[u.kdf];o=p.strong?` (encrypted \xB7 ${p.label})`:` (encrypted \xB7 ${p.label} \u2014 Argon2id unavailable on this device, this export is weaker)`}let a=(0,Qe.moment)().format("YYYYMMDD-HHmmss"),l=jr(t),c=(A.plugin.settings.exportFolder||"_exports").trim().replace(/^\/+|\/+$/g,""),d=`${A.noteFolder}/${c}`;await A.ensureFolder(d);let h=`${l}-${a}`,g=`${d}/${h}.${nt}`;if(await A.app.vault.createBinary(g,n.buffer.slice(n.byteOffset,n.byteOffset+n.byteLength)),s&&i){let u=A.app.secretStorage;try{u?.setSecret(jt(h),i)}catch(p){console.warn("[Stashpad] couldn't save export passphrase to secret storage",p)}}await A.log.append({type:"stash_export",id:r[0].id,payload:{path:g,noteCount:e.length,rootIds:r.map(u=>u.id)}}),A.plugin.notifications.show({message:`Exported ${e.length} note${e.length===1?"":"s"}${o} \u2192 \`${g}\``,kind:i&&o.includes("weaker")?"warning":"success",category:"export",affectedPaths:[g],folder:A.noteFolder,actions:A.actionsForFile(g),duration:0})}catch(n){A.plugin.notifications.show({message:`Stashpad: export failed +${r}`}async function Za(A,r){if(!(!r||await A.vault.adapter.exists(r)))try{await A.vault.createFolder(r)}catch(t){let i=t?.message??"";if(!/already exists/i.test(i))throw t}}vs();ye();mn();var Ne="stashenc",tl="stashmeta";function Ge(A){return A.replace(/\.stashenc$/,`.${tl}`)}async function wn(A,r){try{return JSON.parse(await A.vault.adapter.read(Ge(r)))}catch{return null}}async function Nt(A,r,e){let t=r.replace(/\/+$/,""),i=[];for(let c of A.vault.getMarkdownFiles()){if((c.parent?.path?.replace(/\/+$/,"")??"")!==t)continue;let d;try{d=he(await A.vault.read(c)).fm}catch{continue}typeof d.id=="string"&&i.push({id:d.id,file:c,parent:typeof d.parent=="string"?d.parent:null,created:typeof d.created=="string"?d.created:""})}let s=i.find(c=>c.id===e);if(!s)return null;let n=new Map;for(let c of i){if(!c.parent)continue;let d=n.get(c.parent)??[];d.push(c),n.set(c.parent,d)}let o=[],a=new Set([e]),l=[e];for(;l.length;){let c=[];for(let d of l)for(let h of n.get(d)??[])a.has(h.id)||(a.add(h.id),o.push(h),c.push(h.id));l=c}return{rootNote:s,descendants:o,parentId:s.parent}}function Bn(A){if(!A)return null;let r=A.replace(/\/+$/,"");return!r||r.startsWith("/")||r.includes("\\")||/^[a-zA-Z]:/.test(r)||r.split("/").some(e=>e===".."||e===".")?null:r}async function Ei(A,r,e){await A.vault.adapter.writeBinary(r,e);let t;try{t=new Uint8Array(await A.vault.adapter.readBinary(r))}catch(s){throw new Error(`Couldn't read back the encrypted file to verify it (${s.message}). Nothing was deleted.`)}let i=t.length===e.length;if(i){for(let s=0;s<e.length;s++)if(t[s]!==e[s]){i=!1;break}}if(!i){try{await A.vault.adapter.remove(r)}catch{}throw new Error("The encrypted file on disk doesn't match what was written (bad write?). Nothing was deleted.")}}function yn(A){return A.basename.replace(/-[a-z0-9]{4,12}$/,"").replace(/-/g," ").trim()||A.basename}function Vt(A){return(A.replace(/[\\/:*?"<>|]+/g," ").replace(/\s+/g," ").trim()||"locked").slice(0,60)}async function il(A,r,e){let t=new Set(r.map(a=>a.file.path)),i=new Map;for(let a of r)for(let l of await wi(A,a.file))i.set(l.path,l);let s=new Set,n=A.metadataCache.resolvedLinks??{};for(let a of Object.keys(n))if(!t.has(a))for(let l of Object.keys(n[a]??{}))i.has(l)&&s.add(l);let o=[];for(let a of r){let l=e?.get(a.file.path);if(l!=null)try{let c=await A.vault.adapter.stat(a.file.path);if(c&&c.mtime!==l){console.warn("[Stashpad] note changed since it was bundled \u2014 keeping plaintext",a.file.path),o.push(a.file.path);continue}}catch{}try{await A.vault.delete(a.file)}catch(c){console.warn("[Stashpad] couldn't delete plaintext note",a.file.path,c),o.push(a.file.path)}}for(let[a,l]of i){if(s.has(a))continue;let c=e?.get(a);if(c!=null)try{let d=await A.vault.adapter.stat(a);if(d&&d.mtime!==c){console.warn("[Stashpad] attachment changed since it was bundled \u2014 keeping plaintext",a),o.push(a);continue}}catch{}try{await A.vault.delete(l)}catch(d){console.warn("[Stashpad] couldn't delete exclusive attachment",a,d),o.push(a)}}return{unpurged:o}}async function sl(A,r,e,t,i=null,s=!1,n){let o=await Nt(A,r,e);if(!o)throw new Error("Couldn't find that note to lock.");let{rootNote:a,descendants:l,parentId:c}=o,d=[a,...l],h=new Map;for(let y of d){try{let v=await A.vault.adapter.stat(y.file.path);v&&h.set(y.file.path,v.mtime)}catch{}for(let v of await wi(A,y.file))try{let E=await A.vault.adapter.stat(v.path);E&&h.set(v.path,E.mtime)}catch{}}let g=await bt(A,{rootNotes:[{id:a.id,file:a.file}],allDescendants:l.map(y=>({id:y.id,file:y.file})),sourceFolder:r}),f=await Et(g,t),p=await Fe(f,t);if(p.length!==g.length)throw new Error("Encryption self-check failed (size mismatch).");for(let y=0;y<g.length;y++)if(p[y]!==g[y])throw new Error("Encryption self-check failed (content mismatch).");let u=(n??r).replace(/\/+$/,"");n&&!await A.vault.adapter.exists(u)&&await A.vault.adapter.mkdir(u);let I=Vt(s?e:yn(a.file)),m=`${u}/${I}.${Ne}`;for(let y=1;await A.vault.adapter.exists(m);y++)m=`${u}/${I} (${y}).${Ne}`;await Ei(A,m,f);let C=d,B={v:1,parentId:c,title:s?"":yn(a.file),count:C.length,created:a.created,rootId:e,prevSibling:i};try{await A.vault.adapter.write(Ge(m),JSON.stringify(B))}catch(y){console.warn("[Stashpad] couldn't write lock sidecar",y)}let{unpurged:w}=await il(A,C,h);return{blobPath:m,noteCount:C.length,rootId:e,parentId:c,title:B.title,created:a.created,unpurged:w}}async function nl(A,r,e,t,i){let s=new Uint8Array(await A.vault.adapter.readBinary(r));if(!Me(s))throw new Error("Not an encrypted bundle.");let n=await Fe(s,e),o=Bn(i)??r.replace(/\/[^/]*$/,""),a=await rt(A,n,o,t,{dedupeExisting:!0});await A.vault.adapter.remove(r);try{await A.vault.adapter.remove(Ge(r))}catch{}return{notesWritten:a.notesWritten,restoredTo:o}}var xt="_deleted";function ol(A){let r="";for(let e=0;e<A.length;e++)r+=String.fromCharCode(A[e]);return btoa(r)}function rl(A){let r=atob(A),e=new Uint8Array(r.length);for(let t=0;t<r.length;t++)e[t]=r.charCodeAt(t);return e}async function nr(A,r,e,t){let i=Bn(e?.originalFolder);if(!i&&e?.originalFolderEnc&&t)try{i=Bn(new TextDecoder().decode(await Fe(rl(e.originalFolderEnc),t)))}catch{i=null}let s=r.replace(/\/[^/]*$/,""),n=/(^|\/)trash$/.test(s)?s.replace(/\/?trash$/,""):"";if(i&&n&&i!==n&&!await A.vault.adapter.exists(i)&&(i=n||null),i)return await A.vault.adapter.exists(i)||await A.vault.adapter.mkdir(i),i;if(s===xt||s.startsWith(`${xt}/`))throw new Error("This deleted note's origin folder is unknown (missing or tampered sidecar) \u2014 can't restore it safely. The encrypted copy was kept.");if(/(^|\/)trash$/.test(s)){let o=s.replace(/\/?trash$/,"");if(o)return await A.vault.adapter.exists(o)||await A.vault.adapter.mkdir(o),o;throw new Error("This deleted note's origin folder is unknown (missing or tampered sidecar) \u2014 can't restore it safely. The encrypted copy was kept.")}return s}async function al(A,r,e,t,i,s=!1,n,o=xt){let a=await Nt(A,r,e);if(!a)throw new Error("Couldn't find that note to delete.");let{rootNote:l,descendants:c,parentId:d}=a,h=[l,...c],g=new Map;for(let E of h){try{let Q=await A.vault.adapter.stat(E.file.path);Q&&g.set(E.file.path,Q.mtime)}catch{}for(let Q of await wi(A,E.file))try{let S=await A.vault.adapter.stat(Q.path);S&&g.set(Q.path,S.mtime)}catch{}}let f=await bt(A,{rootNotes:[{id:l.id,file:l.file}],allDescendants:c.map(E=>({id:E.id,file:E.file})),sourceFolder:r}),p=await Et(f,t),u=await Fe(p,t);if(u.length!==f.length)throw new Error("Encryption self-check failed (size).");for(let E=0;E<f.length;E++)if(u[E]!==f[E])throw new Error("Encryption self-check failed (content).");{let E=o.replace(/\/+$/,"").split("/").filter(Boolean),Q="";for(let S of E){Q=Q?`${Q}/${S}`:S;try{await A.vault.adapter.exists(Q)||await A.vault.adapter.mkdir(Q)}catch{}}}let I=r.replace(/\/+$/,""),m=I.split("/").pop()||"vault",C=Vt(s?e:`${m} ${yn(l.file)}`),B=`${o}/${C}.${Ne}`;for(let E=1;await A.vault.adapter.exists(B);E++)B=`${o}/${C} (${E}).${Ne}`;await Ei(A,B,p);let w=h,y={v:1,kind:"deleted",originalFolder:s?"":I,...s?{originalFolderEnc:ol(await Et(new TextEncoder().encode(I),t))}:{},parentId:d,title:s?"":yn(l.file),count:w.length,created:l.created,rootId:e,deletedAt:i,...n?{keyId:n}:{}};try{await A.vault.adapter.write(Ge(B),JSON.stringify(y))}catch(E){console.warn("[Stashpad] couldn't write deleted sidecar \u2014 aborting delete, keeping plaintext",E);try{await A.vault.adapter.remove(B)}catch{}throw new Error("Couldn't write trash metadata \u2014 the note was NOT deleted (kept intact).")}let{unpurged:v}=await il(A,w,g);return{blobPath:B,noteCount:w.length,rootId:e,originalFolder:I,title:y.title,unpurged:v}}async function ll(A,r,e,t){let i=new Uint8Array(await A.vault.adapter.readBinary(r));if(!Me(i))throw new Error("Not an encrypted bundle.");let s=await at(A,r),n=await nr(A,r,s,e),o=await Fe(i,e),a=await rt(A,o,n,t,{dedupeExisting:!0});await A.vault.adapter.remove(r);try{await A.vault.adapter.remove(Ge(r))}catch{}return{notesWritten:a.notesWritten,restoredTo:n}}async function at(A,r){try{return JSON.parse(await A.vault.adapter.read(Ge(r)))}catch{return null}}async function or(A,r){await A.vault.adapter.remove(r);try{await A.vault.adapter.remove(Ge(r))}catch{}}var Pe=".trash";async function En(A,r){let e=[],t=[r];for(;t.length;){let i=t.shift(),s;try{s=await A.vault.adapter.list(i)}catch{continue}e.push(...s.files),t.push(...s.folders)}return e}function cl(A){let r=A.split("/").filter(Boolean);if(r.length===0)return null;for(let e of r)if(e===".."||e==="."||e.includes("\\")||/^[a-zA-Z]:/.test(e))return null;return r.join("/")}async function dl(A,r,e,t=!1){if(!await A.vault.adapter.exists(Pe))return null;let i=await En(A,Pe);if(i.length===0)return null;let s=[],n=new Map;for(let f of i){let p=f.slice(Pe.length+1);s.push({name:`files/${p}`,data:await A.vault.adapter.readBinary(f)});try{let u=await A.vault.adapter.stat(f);u&&n.set(f,u.mtime)}catch{}}let o=await Bi(s),a=await Et(o,r),l=await Fe(a,r);if(l.length!==o.length)throw new Error("Encryption self-check failed (size).");for(let f=0;f<o.length;f++)if(l[f]!==o[f])throw new Error("Encryption self-check failed (content).");await A.vault.adapter.exists(xt)||await A.vault.adapter.mkdir(xt);let c=Vt(t?`trash-${e.replace(/[^0-9]/g,"").slice(0,14)}`:"Obsidian trash backfill"),d=`${xt}/${c}.${Ne}`;for(let f=1;await A.vault.adapter.exists(d);f++)d=`${xt}/${c} (${f}).${Ne}`;await Ei(A,d,a);let h={v:1,kind:"rawtrash",originalFolder:Pe,parentId:null,title:t?"":"Obsidian trash (backfill)",count:i.length,created:e,rootId:"",deletedAt:e};try{await A.vault.adapter.write(Ge(d),JSON.stringify(h))}catch(f){console.warn("[Stashpad] couldn't write backfill sidecar",f)}let g=[];for(let f of i){let p=n.get(f);try{if(p!=null){let u=await A.vault.adapter.stat(f);if(u&&u.mtime!==p){g.push(f);continue}}await A.vault.adapter.remove(f)}catch(u){console.warn("[Stashpad] couldn't delete trash file",f,u),g.push(f)}}return{blobPath:d,fileCount:i.length,unpurged:g}}async function Al(A,r,e){let t=new Uint8Array(await A.vault.adapter.readBinary(r));if(!Me(t))throw new Error("Not an encrypted bundle.");let i=await Fe(t,e),s=await yi(i),n=0;for(let[o,a]of Object.entries(s)){if(!o.startsWith("files/"))continue;let l=cl(o.slice(6));if(!l){console.warn("[Stashpad] skipped unsafe trash entry",o);continue}let c=`${Pe}/${l}`.split("/").slice(0,-1).join("/"),d="";for(let g of c.split("/"))d=d?`${d}/${g}`:g,await A.vault.adapter.exists(d)||await A.vault.adapter.mkdir(d);let h=`${Pe}/${l}`;for(let g=1;await A.vault.adapter.exists(h);g++)h=`${Pe}/${l.replace(/(\.[^./]*)?$/,` (${g})$1`)}`;await A.vault.adapter.writeBinary(h,a.buffer),n++}await A.vault.adapter.remove(r);try{await A.vault.adapter.remove(Ge(r))}catch{}return{filesWritten:n}}async function hl(A,r,e,t){let i=new Uint8Array(await A.vault.adapter.readBinary(r));if(!Me(i))throw new Error(`Not an encrypted blob: ${r}`);let s=await Fe(i,e);try{let n=await Et(s,t),o=await Fe(n,t);if(o.length!==s.length)throw new Error("Rotation self-check failed (size).");for(let a=0;a<s.length;a++)if(o[a]!==s[a])throw new Error("Rotation self-check failed (content).");await Ei(A,`${r}.rot`,n)}finally{s.fill(0)}}async function pl(A,r,e,t){let i=await at(A,r);if(!i?.originalFolderEnc)return;let s=await Fe(rl(i.originalFolderEnc),e);try{i.originalFolderEnc=ol(await Et(s,t))}finally{s.fill(0)}await A.vault.adapter.write(Ge(r),JSON.stringify(i))}async function rr(A,r,e=[]){let t=[];for(let i of await As(A,e))(await at(A,i))?.keyId===r&&t.push(i);return t}async function Qn(A,r){let e=`${r}.rot`;if(!await A.vault.adapter.exists(e))return;let t=new Uint8Array(await A.vault.adapter.readBinary(e));await Ei(A,r,t);try{await A.vault.adapter.remove(e)}catch{}}async function vn(A,r){let e=r.replace(/\/+$/,"");for(let t of await En(A,e))if(t.endsWith(`.${Ne}.rot`))try{await A.vault.adapter.remove(t)}catch{}}async function gl(A,r){let e=r.replace(/\/+$/,"");return(await En(A,e)).filter(t=>t.endsWith(`.${Ne}.rot`)).map(t=>t.replace(/\.rot$/,""))}async function ul(A,r,e,t,i){let s=r.replace(/\/+$/,""),n=(await En(A,s)).filter(u=>!u.endsWith(`.${Ne}`)&&!u.endsWith(`.${tl}`));if(n.length===0)throw new Error("This folder has no files to encrypt.");let o=[],a=new Map;for(let u of n){o.push({name:`files/${u.slice(s.length+1)}`,data:await A.vault.adapter.readBinary(u)});try{let I=await A.vault.adapter.stat(u);I&&a.set(u,I.mtime)}catch{}}let l=await Bi(o),c=await Et(l,e),d=await Fe(c,e);if(d.length!==l.length)throw new Error("Encryption self-check failed (size).");for(let u=0;u<l.length;u++)if(d[u]!==l[u])throw new Error("Encryption self-check failed (content).");let h=Vt(s.split("/").pop()||"folder"),g=`${s}/${h}.${Ne}`;for(let u=1;await A.vault.adapter.exists(g);u++)g=`${s}/${h} (${u}).${Ne}`;await Ei(A,g,c);let f={v:1,kind:"rawfolder",originalFolder:s,parentId:null,title:s.split("/").pop()||"",count:n.length,created:i,rootId:"",deletedAt:i,...t?{keyId:t}:{}};try{await A.vault.adapter.write(Ge(g),JSON.stringify(f))}catch(u){console.warn("[Stashpad] couldn't write rawfolder sidecar",u)}let p=[];for(let u of n){let I=a.get(u);try{if(I!=null){let m=await A.vault.adapter.stat(u);if(m&&m.mtime!==I){p.push(u);continue}}await A.vault.adapter.remove(u)}catch(m){console.warn("[Stashpad] couldn't delete file",u,m),p.push(u)}}if(p.length===0){let u=[...new Set(n.map(I=>I.slice(0,I.lastIndexOf("/"))))].filter(I=>I.length>s.length).sort((I,m)=>m.length-I.length);for(let I of u)try{let m=await A.vault.adapter.list(I);!m.files.length&&!m.folders.length&&await A.vault.adapter.rmdir(I,!0)}catch{}}return{blobPath:g,fileCount:n.length,unpurged:p}}async function fl(A,r,e){let t=new Uint8Array(await A.vault.adapter.readBinary(r));if(!Me(t))throw new Error("Not an encrypted bundle.");let i=await at(A,r),s=Bn(i?.originalFolder)??r.replace(/\/[^/]*$/,""),n=await Fe(t,e),o=await yi(n),a=0;for(let[l,c]of Object.entries(o)){if(!l.startsWith("files/"))continue;let d=cl(l.slice(6));if(!d){console.warn("[Stashpad] skipped unsafe folder entry",l);continue}let h=`${s}/${d}`.split("/").slice(0,-1).join("/"),g="";for(let p of h.split("/"))g=g?`${g}/${p}`:p,g&&!await A.vault.adapter.exists(g)&&await A.vault.adapter.mkdir(g);let f=`${s}/${d}`;for(let p=1;await A.vault.adapter.exists(f);p++)f=`${s}/${d.replace(/(\.[^./]*)?$/,` (${p})$1`)}`;await A.vault.adapter.writeBinary(f,c.buffer),a++}await A.vault.adapter.remove(r);try{await A.vault.adapter.remove(Ge(r))}catch{}return{filesWritten:a,folder:s}}async function ar(A,r){let e=r.replace(/\/+$/,"");try{let t=await A.vault.adapter.list(e);for(let i of t.files){if(!i.endsWith(`.${Ne}`))continue;if((await at(A,i))?.kind==="rawfolder")return i}}catch{}return null}async function Il(A){let r=[];for(let e of A.vault.getFiles())if(e.extension===Ne)try{(await at(A,e.path))?.kind==="rawfolder"&&r.push({folder:e.parent?.path?.replace(/\/+$/,"")??"",blobPath:e.path})}catch{}return r}function Fn(A){return`${(A||"").replace(/\/+$/,"")}/trash`}async function As(A,r=[]){let e=[];for(let t of[xt,...r])try{if(!await A.vault.adapter.exists(t))continue;let i=await A.vault.adapter.list(t);e.push(...i.files.filter(s=>s.endsWith(`.${Ne}`)))}catch{}return e}function ml(A){return A.replace(/[^\w.\-]+/g,"_").replace(/^\.+/,"").slice(0,60)||"stash-export"}async function Cl(A,r){let e=wl(A,r);if(e.length===0){new Qe.Notice("Nothing to export.");return}let t=El(A,e);if(t.length===0){new Qe.Notice("No exportable notes (no files attached).");return}let i=(A.noteFolder.split("/").pop()||A.noteFolder).trim(),s=e.length===1?A.titleForNode(e[0]):`${i}-${e.length}notes`;new ni(A.app,s,t.length,(n,o,a)=>{Bd(A,e,t,n,o,a)},ts).open()}async function Bl(A,r){let e=r.replace(/\/[^/]*$/,"").replace(/\/+$/,""),t=await A.ensureFolderUnlocked(e);if(!t)return;let i;try{let l=new Uint8Array(await A.app.vault.adapter.readBinary(r));i=await Fe(l,t)}catch(l){new Qe.Notice(`Couldn't decrypt this note to export it: ${l.message}`);return}finally{t.fill(0)}let s=await wn(A.app,r),n=(A.settings.lockedSubtrees??[]).find(l=>l.blob===r),o=n?.title||s?.title||"locked-note",a=n?.count??s?.count??1;new ni(A.app,o,a,(l,c,d)=>{Cd(A,e,l,i,c,d)},ts).open()}async function Cd(A,r,e,t,i,s=!1){try{let n=t,o="";if(i){let f=await et(n,i);n=f.data;let p=So[f.kdf];o=p.strong?` (encrypted \xB7 ${p.label})`:` (encrypted \xB7 ${p.label} \u2014 Argon2id unavailable here, weaker)`}let a=(0,Qe.moment)().format("YYYYMMDD-HHmmss"),c=(A.settings.exportFolder||"_exports").trim().replace(/^\/+|\/+$/g,"").split("/").filter(f=>f&&f!=="."&&f!==".."&&!/[\\:]/.test(f)).join("/")||"_exports",d=`${r}/${c}`;await A.app.vault.adapter.exists(d)||await A.app.vault.adapter.mkdir(d);let h=`${ml(e)}-${a}`,g=`${d}/${h}.${ot}`;if(await A.app.vault.createBinary(g,n.buffer.slice(n.byteOffset,n.byteOffset+n.byteLength)),s&&i)try{A.app.secretStorage?.setSecret(ti(h),i)}catch(f){console.warn("[Stashpad] couldn't save export passphrase",f)}new Qe.Notice(`Exported encrypted note${o} \u2192 ${g}`,0)}catch(n){new Qe.Notice(`Export failed: ${n.message}`),console.error(n)}finally{try{t.fill(0)}catch{}}}async function yl(A,r){let e=wl(A,r);if(e.length===0){new Qe.Notice("Nothing to export.");return}let t=El(A,e);if(t.length===0){new Qe.Notice("No exportable notes.");return}let i=(A.noteFolder.split("/").pop()||A.noteFolder).trim(),s=e.length===1?A.titleForNode(e[0]):`${i}-okf`;new Ji(A.app,s,t.length,(n,o)=>{(async()=>{try{let a=await A.plugin.exportOkf(A.noteFolder,e.map(l=>l.id),n,o);if(!a.length){new Qe.Notice("Nothing exported.");return}A.plugin.notifications.show({message:`Exported OKF \u2014 ${a.length} file${a.length===1?"":"s"} \u2192 \`${A.noteFolder}/${A.plugin.settings.exportFolder||"_exports"}\``,kind:"success",category:"export",affectedPaths:a,folder:A.noteFolder,duration:0}),await A.log.append({type:"stash_export",id:e[0].id,payload:{okf:!0,paths:a,noteCount:t.length,rootIds:e.map(l=>l.id)}})}catch(a){new Qe.Notice(`OKF export failed: ${a.message}`),console.error(a)}})()}).open()}async function Bd(A,r,e,t,i,s=!1){try{let n=await bt(A.app,{rootNotes:r.filter(f=>!!f.file).map(f=>({id:f.id,file:f.file})),allDescendants:e.filter(f=>!r.some(p=>p.id===f.id)).filter(f=>!!f.file).map(f=>({id:f.id,file:f.file})),sourceFolder:A.noteFolder}),o="";if(i){let f=await et(n,i);n=f.data;let p=So[f.kdf];o=p.strong?` (encrypted \xB7 ${p.label})`:` (encrypted \xB7 ${p.label} \u2014 Argon2id unavailable on this device, this export is weaker)`}let a=(0,Qe.moment)().format("YYYYMMDD-HHmmss"),l=ml(t),c=(A.plugin.settings.exportFolder||"_exports").trim().replace(/^\/+|\/+$/g,""),d=`${A.noteFolder}/${c}`;await A.ensureFolder(d);let h=`${l}-${a}`,g=`${d}/${h}.${ot}`;if(await A.app.vault.createBinary(g,n.buffer.slice(n.byteOffset,n.byteOffset+n.byteLength)),s&&i){let f=A.app.secretStorage;try{f?.setSecret(ti(h),i)}catch(p){console.warn("[Stashpad] couldn't save export passphrase to secret storage",p)}}await A.log.append({type:"stash_export",id:r[0].id,payload:{path:g,noteCount:e.length,rootIds:r.map(f=>f.id)}}),A.plugin.notifications.show({message:`Exported ${e.length} note${e.length===1?"":"s"}${o} \u2192 \`${g}\``,kind:i&&o.includes("weaker")?"warning":"success",category:"export",affectedPaths:[g],folder:A.noteFolder,actions:A.actionsForFile(g),duration:0})}catch(n){A.plugin.notifications.show({message:`Stashpad: export failed Error: ${n.message} -Check disk space + write permissions on the export folder.`,kind:"error",category:"export"}),console.error(n)}}function il(A,r){if(r?.file)return[r];if(A.selection.size>0)return[...A.selection].map(t=>A.tree.get(t)).filter(t=>!!t?.file);if(A.cursorIdx>=0&&A.currentChildren[A.cursorIdx])return[A.currentChildren[A.cursorIdx]];let e=A.tree.get(A.focusId);return e?.file?[e]:[]}function sl(A,r){let e=new Set,t=[],i=s=>{if(!e.has(s.id)){e.add(s.id),s.file&&t.push(s);for(let n of A.tree.getChildren(s.id))i(n)}};for(let s of r)i(s);return t}async function nl(A){let r=A.app.vault.getFiles().filter(t=>t.extension===nt);if(r.length===0){new Qe.Notice("No .stash files found in this vault.");return}let e=new class extends Qe.FuzzySuggestModal{getItems(){return r}getItemText(t){return t.path}onChooseItem(t){zo(A,t)}}(A.app);e.setPlaceholder("Pick a .stash file to import\u2026"),e.open()}async function zo(A,r){try{let e=new Uint8Array(await A.app.vault.readBinary(r)),t=await li(A.app,e,{secretId:jt(r.basename)});if(!t)return;let i=await ot(A.app,t,A.noteFolder,_c(A));A.tree.rebuild(A.noteFolder),A.render(),await A.log.append({type:"stash_import",id:k,payload:{from:r.path,into:A.noteFolder,noteCount:i.notesWritten,attachmentsWritten:i.attachmentsWritten,collisionsRenamed:i.collisionsRenamed}});try{await A.app.fileManager.trashFile(r)}catch{}A.plugin.importService.clearPendingEncrypted(r.path);let s=[`Imported ${i.notesWritten} note${i.notesWritten===1?"":"s"}`];i.attachmentsWritten&&s.push(`+ ${i.attachmentsWritten} attachment${i.attachmentsWritten===1?"":"s"}`),i.collisionsRenamed&&s.push(`(${i.collisionsRenamed} id collision${i.collisionsRenamed===1?"":"s"} renamed)`),A.plugin.notifications.show({message:s.join(" "),kind:"success",category:"import",folder:A.noteFolder})}catch(e){A.plugin.notifications.show({message:`Stashpad: import failed +Check disk space + write permissions on the export folder.`,kind:"error",category:"export"}),console.error(n)}}function wl(A,r){if(r?.file)return[r];if(A.selection.size>0)return[...A.selection].map(t=>A.tree.get(t)).filter(t=>!!t?.file);if(A.cursorIdx>=0&&A.currentChildren[A.cursorIdx])return[A.currentChildren[A.cursorIdx]];let e=A.tree.get(A.focusId);return e?.file?[e]:[]}function El(A,r){let e=new Set,t=[],i=s=>{if(!e.has(s.id)){e.add(s.id),s.file&&t.push(s);for(let n of A.tree.getChildren(s.id))i(n)}};for(let s of r)i(s);return t}async function Ql(A){let r=A.app.vault.getFiles().filter(t=>t.extension===ot);if(r.length===0){new Qe.Notice("No .stash files found in this vault.");return}let e=new class extends Qe.FuzzySuggestModal{getItems(){return r}getItemText(t){return t.path}onChooseItem(t){lr(A,t)}}(A.app);e.setPlaceholder("Pick a .stash file to import\u2026"),e.open()}async function lr(A,r){try{let e=new Uint8Array(await A.app.vault.readBinary(r)),t=await Ai(A.app,e,{secretId:ti(r.basename)});if(!t)return;let i=await rt(A.app,t,A.noteFolder,yd(A),{stripReserved:!0});A.tree.rebuild(A.noteFolder),A.render(),await A.log.append({type:"stash_import",id:F,payload:{from:r.path,into:A.noteFolder,noteCount:i.notesWritten,attachmentsWritten:i.attachmentsWritten,collisionsRenamed:i.collisionsRenamed}});try{await A.app.fileManager.trashFile(r)}catch{}A.plugin.importService.clearPendingEncrypted(r.path);let s=[`Imported ${i.notesWritten} note${i.notesWritten===1?"":"s"}`];i.attachmentsWritten&&s.push(`+ ${i.attachmentsWritten} attachment${i.attachmentsWritten===1?"":"s"}`),i.collisionsRenamed&&s.push(`(${i.collisionsRenamed} id collision${i.collisionsRenamed===1?"":"s"} renamed)`),A.plugin.notifications.show({message:s.join(" "),kind:"success",category:"import",folder:A.noteFolder})}catch(e){A.plugin.notifications.show({message:`Stashpad: import failed File: \`${r.name}\` Error: ${e.message} -Inspect with the buttons below \u2014 rename to .zip to crack it open in an archive tool.`,kind:"error",category:"import",affectedPaths:[r.path],actions:A.actionsForFile(r.path)}),console.error(e)}}function _c(A){let r=new Set,e=t=>{if(r.add(t),!!A.tree.get(t))for(let s of A.tree.getChildren(t))e(s.id)};return e(k),r}Ni();Ni();var td=new Set(["png","jpg","jpeg","gif","webp","svg","bmp","avif"]),os={nested:"Nested",flat:"Flat",everything:"Everything"},as=[{key:"day",calShort:"Today",rollShort:"24h",calLong:"Since midnight today",rollLong:"Last 24 hours"},{key:"week",calShort:"Week",rollShort:"7d",calLong:"Since Monday this week",rollLong:"Last 7 days"},{key:"month",calShort:"Month",rollShort:"30d",calLong:"Since the 1st of this month",rollLong:"Last 30 days"},{key:"year",calShort:"Year",rollShort:"365d",calLong:"Since January 1 this year",rollLong:"Last 365 days"},{key:"all",calShort:"All",rollShort:"ad infinitum",calLong:"All time",rollLong:"All time"}],rs=class rs extends F.ItemView{constructor(e,t){super(e);this.focusId=k;this.timeFilter="all";this.timeFilterCalendar=!1;this.tagFilter=null;this.colorFilter=null;this.importedOnly=!1;this.authorFilter=null;this.noteFolder="Stashpad";this.folderOverride=null;this.tinyMode=!1;this.tinyAlwaysOnTop=!1;this.tinyOpacity=1;this.compactMode=!1;this.detachTreeHook=null;this.detachSettings=null;this.slugDebouncers=new Map;this.attachmentDebouncers=new Map;this.bulkRenderDepth=0;this.bulkSettleTimer=null;this.bootstrappedFolders=new Set;this.selection=new Set;this.lastSelected=null;this.cursorIdx=-1;this.pendingCursorId=null;this.currentChildren=[];this.modeSplit=null;this.modeEnterSubmits=!0;this.nextDestination=null;this.nextDestinationFolder=null;this.nextDestinationLabel=null;this.inListPicker=null;this.pickerEscapeAt=0;this.composerExitAt=0;this.listEl=null;this.composerInputEl=null;this.composerDraft="";this.draftsLoadedFor=null;this.autoSelectNewest=!1;this.scrollToBottomOnNextRender=!1;this.scrollListenerSaveTimer=null;this.suppressScrollSave=!1;this.selectionGuardKey=0;this.pendingRenderPolicy=null;this.stickToListBottom=!1;this.keyboardTransitionUntil=0;this.tapSettleUntil=0;this.aimedTapTargetId=null;this.aimedTapAt=0;this.stickyRowObserver=null;this.listResizeObserver=null;this.composerNarrowObserver=null;this.barOverflowRO=null;this.lastCursorByFocus=new Map;this.lastSelectionByFocus=new Map;this.expandedNotes=new Set;this.cursorExpandOverride=new Set;this.cursorHasMoved=!1;this.activeVersionByGroup=new Map;this.focusComposerOnNextRender=!1;this.suppressComposerFocusUntil=0;this.composerAutocomplete=null;this.firstSelectedId=null;this.mobileSelectMode=!1;this.focusedMiniObserver=null;this.pendingComposerCaret=null;this.navBackStack=[];this.navForwardSnapshots=[];this.treeReconcileTimer=null;this.syncBurstTimes=[];this.autoSyncDeferActive=!1;this.autoSyncSettleTimer=null;this.autoSyncNotice=null;this.stampLastCursorTimer=null;this.stampSelectionTimer=null;this._renderT0=null;this.tinyOpacityPopover=null;this.lastListWidth=0;this.onDocKeyDown=e=>{if(!this.viewRoot.isConnected||this.app.workspace.activeLeaf!==this.leaf||ua(e.target))return;let t=X().bindings;if(J(e,t.toggleSplit)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.toggleSplit();return}if(J(e,t.pickDestination)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.openDestinationPicker();return}if(J(e,t.search)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.openSearchModal();return}if(J(e,t.commandPalette)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.openStashpadCommandPalette();return}if(J(e,t.lockSelection)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdLockSelection();return}if(J(e,t.unlockAll)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdUnlockAll();return}if(J(e,t.moveToArchive)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdMoveToArchive();return}if(J(e,t.encryptDelete)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdEncryptDelete();return}if(J(e,t.searchInParent)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.openSearchInParentModal();return}if(J(e,t.exportStash)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdExportStash();return}if(J(e,t.importStash)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdImportStash();return}if(J(e,t.pickFolder)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdOpenFolderPicker();return}if(J(e,t.cloneStashpadTab)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdCloneStashpadTab();return}let i=e.target,s=!!i&&(i.tagName==="INPUT"||i.tagName==="TEXTAREA");if(e.key===" "&&!e.metaKey&&!e.ctrlKey&&!e.altKey&&!e.shiftKey&&!s){let l=this.composerInputEl;if(l){e.preventDefault(),e.stopPropagation(),l.focus();let c=l.value.length;l.setSelectionRange(c,c);return}}let n=!!i&&(i.tagName==="INPUT"||i.tagName==="TEXTAREA"||i.tagName==="BUTTON"||i.tagName==="SELECT");if(e.key==="Escape"&&i instanceof HTMLElement&&(i.tagName==="BUTTON"||i.tagName==="SELECT")&&this.viewRoot.contains(i)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.viewRoot.focus();return}if(this.inListPicker&&e.key==="Escape"){e.preventDefault(),e.stopPropagation(),this.inListPicker=null,this.pickerEscapeAt=Date.now();let l=this.listEl,c=!!l&&l.scrollTop+l.clientHeight>=l.scrollHeight-2,d=l?.scrollTop??0;if(this.render(),l){let h=c?l.scrollHeight:d;l.scrollTop=h,requestAnimationFrame(()=>{l.scrollTop=c?l.scrollHeight:d}),setTimeout(()=>{l.scrollTop=c?l.scrollHeight:d},60),this.revealCursorRow()}return}if(this.inListPicker&&!n){if(e.key==="ArrowDown"){e.preventDefault(),this.inListPicker.activeIdx=this.nextPickableIdx(this.inListPicker.activeIdx,1),this.repaintSelectionClasses(),this.revealRowAt(this.inListPicker.activeIdx);return}if(e.key==="ArrowUp"){e.preventDefault(),this.inListPicker.activeIdx=this.nextPickableIdx(this.inListPicker.activeIdx,-1),this.repaintSelectionClasses(),this.revealRowAt(this.inListPicker.activeIdx);return}if(e.key==="Enter"){e.preventDefault(),this.commitInListPicker();return}if(J(e,X().bindings.move)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.inListPicker=null,this.repaintSelectionClasses(),this.cmdMovePicker();return}return}if(n)return;if(J(e,t.delete)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdDelete();return}if(J(e,t.toggleComplete)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdToggleComplete();return}if(J(e,t.moveToTop)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdMoveToTop();return}if(J(e,t.moveToBottom)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdMoveToBottom();return}if(J(e,t.moveUp)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdMoveUp();return}if(J(e,t.moveDown)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdMoveDown();return}if(J(e,t.outdent)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdOutdent();return}if(J(e,t.setColor)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdSetColor();return}if(J(e,t.selectAll)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdSelectAll();return}if(J(e,t.swapWithParent)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdSwapWithParent();return}if(J(e,t.undo)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdUndo();return}if(J(e,t.redo)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdRedo();return}if(e.key===" "){e.preventDefault();let l=this.composerInputEl;if(l){l.focus();let c=l.value.length;l.setSelectionRange(c,c)}return}if(e.key==="ArrowDown"){e.preventDefault(),this.cursorIdx>=this.currentChildren.length-1?this.cursorIdx=0:this.cursorIdx++,this.selectCursor(e.shiftKey);return}if(e.key==="ArrowUp"){e.preventDefault(),this.cursorIdx<=0?this.cursorIdx=this.currentChildren.length-1:this.cursorIdx--,this.selectCursor(e.shiftKey);return}if(e.altKey&&e.key==="ArrowLeft"){e.preventDefault(),this.navigateBack();return}if(e.altKey&&e.key==="ArrowRight"){e.preventDefault(),this.navigateForward();return}if(e.key==="ArrowRight"){let l=this.currentChildren[this.cursorIdx];l&&(e.preventDefault(),this.navigateTo(l.id));return}if(e.key==="ArrowLeft"||e.key==="Backspace"){e.preventDefault(),this.navigateUp();return}if(e.key==="Escape"){if(e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),Date.now()-this.pickerEscapeAt<350||Date.now()-this.composerExitAt<400)return;if(this.plugin.noteClipboard&&this.plugin.noteClipboard.folder===this.noteFolder){this.plugin.clearNoteClipboard(),this.render();return}let l=this.firstSelectedId??(this.selection.size>0?this.selection.values().next().value:null);if(this.selection.clear(),this.firstSelectedId=null,l){let c=this.currentChildren.findIndex(d=>d.id===l);this.selection.add(l),this.lastSelected=l,c>=0&&(this.cursorIdx=c)}this.repaintSelectionClasses(),this.revealCursorRow();return}let o=X().bindings;if(J(e,o.pasteNotes)&&this.plugin.noteClipboard){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdPasteNotes();return}if(this.selection.size>0||this.cursorIdx>=0&&this.currentChildren[this.cursorIdx]){if(J(e,o.move)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdMovePicker();return}if(J(e,o.pickMove)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdInListPicker();return}if(J(e,o.merge)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdMerge();return}if(J(e,o.copy)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdCopy();return}if(J(e,o.copyNotes)&&!window.getSelection()?.toString()){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdCopyNotes();return}if(J(e,o.cutNotes)&&!window.getSelection()?.toString()){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdCutNotes();return}if(J(e,o.copyTree)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdCopyTree();return}if(J(e,o.copyOutline)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdCopyOutline();return}if(J(e,o.copyCodeBlock)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdCopyCodeBlock();return}if(J(e,o.openEditor)){if(e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),e.shiftKey){let l=this.tree.get(this.focusId);l?.file&&this.cmdOpenInEditor(l)}else this.cmdOpenInEditor();return}if(J(e,o.openTab)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdOpenInNewStashpadTab();return}if(J(e,o.split)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdSplit();return}if(J(e,o.clone)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdClone();return}if(J(e,o.forkNote)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdForkNote();return}if(J(e,o.insertTemplate)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdInsertTemplate();return}if(J(e,o.toggleExpand)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdToggleExpand();return}if(J(e,o.expandAll)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdExpandAll();return}if(J(e,o.collapseAll)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdCollapseAll();return}if(J(e,o.togglePin)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdTogglePin();return}if(J(e,o.listPin)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdToggleListPin();return}if(J(e,o.toggleTask)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdToggleTask();return}if(J(e,o.setDue)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdSetDue();return}}if(this.currentChildren.length>0){if(J(e,o.jumpToTop)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.jumpToTop();return}if(J(e,o.jumpToBottom)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.jumpToBottom();return}}let a=this.tree.get(this.focusId);if(a?.file){if(J(e,o.openEditor)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdOpenInEditor(a);return}if(J(e,o.openTab)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdOpenInNewStashpadTab(a);return}}};this.openSearchInstance=null;this.fmSyncUnsubscribe=null;this.completedState=new Map;this.taskTaggedState=new Map;this.listPinnedState=new Map;this.pendingFocusIds=null;this.onFileModify=e=>{!(e instanceof F.TFile)||e.extension!=="md"||e.path.startsWith(this.noteFolder+"/")&&(this.bodyRenderer.evict(e),this.scheduleSlugRename(e),this.scheduleAttachmentSync(e),this.authorship.noteModify(e),!this.deferDuringSyncBurst()&&this.debouncedRender())};this.onFileCreate=e=>{!(e instanceof F.TFile)||e.extension!=="md"||e.path.startsWith(this.noteFolder+"/")&&(this.deferDuringSyncBurst()||this.debouncedRender())};this.plugin=t,this.tree=new Js(this.app),this.log=t.newLog(),this.integrity=new Vs(this.tree,this.log),this.order=new Ai(this.app),this.sortStore=new Os(this.app),this.fmSync=new $s(this.app,()=>this.tree),this.tree.setOrderProvider(i=>{let s=this.noteFolder,n=this.sortStore.getMode(s,i),o=n==="manual"?this.order.getOrder(s,i):cr(this,i,n);return this.hoistListPinned(i,o)}),this.debouncedRender=(0,F.debounce)(()=>{this.renderSuppressed()||this.render()},80),this.authorship=new Xs(this),this.dnd=new Zs(this),this.bodyRenderer=new js(this,this,this.plugin.renderCacheStore)}getViewType(){return z}getDisplayText(){let e=(this.noteFolder||"").trim(),t=e.split("/").pop()||e||"Stashpad";if(this.focusId&&this.focusId!==k){let i=this.tree.get(this.focusId);if(i){let s=this.titleForNode(i).trim(),n=s.length>40?s.slice(0,40)+"\u2026":s;if(n)return`${t} \u2014 ${n} \xB7 ${this.focusId}`}}return t}refreshHeaderTitle(){let e=this.getDisplayText();try{this.leaf.updateHeader?.()}catch{}let i=(this.headerEl??this.containerEl?.querySelector?.(".view-header"))?.querySelector?.(".view-header-title")??this.titleEl;i&&i.textContent!==e&&i.setText(e)}getIcon(){return this.plugin.getFolderIcon(this.noteFolder)??"list-tree"}async onOpen(){let e=this.contentEl;e.empty(),e.addClass("stashpad-scroll-host"),this.viewRoot=e.createDiv({cls:"stashpad-view"}),this.viewRoot.setAttribute("tabindex","0"),this.viewRoot.addEventListener("focusin",()=>Us(this)),this.viewRoot.addEventListener("click",()=>Us(this)),this.viewRoot.addEventListener("mouseup",l=>{l.button===3?(l.preventDefault(),this.navigateBack()):l.button===4&&(l.preventDefault(),this.navigateForward())}),this.viewRoot.addEventListener("auxclick",l=>{l.button===3?(l.preventDefault(),this.navigateBack()):l.button===4&&(l.preventDefault(),this.navigateForward())}),Us(this),this.registerInterval(window.setInterval(()=>this.authorship.pruneContribMaps(),6e4));let t=null,i=()=>{t||(t=new F.Scope(this.app.scope),t.register([],"Escape",()=>{if(this.inListPicker)return this.inListPicker=null,this.pickerEscapeAt=Date.now(),this.repaintSelectionClasses(),!1;if(Date.now()-this.pickerEscapeAt<350||Date.now()-this.composerExitAt<400)return!1;if(this.selection.size>1){let l=this.firstSelectedId??this.selection.values().next().value??null;if(this.selection.clear(),this.firstSelectedId=null,l){let c=this.currentChildren.findIndex(d=>d.id===l);this.selection.add(l),this.lastSelected=l,c>=0&&(this.cursorIdx=c)}this.render(),this.revealCursorRow()}return!1}),this.app.keymap?.pushScope(t))},s=()=>{if(t){try{this.app.keymap?.popScope(t)}catch{}t=null}};this.viewRoot.addEventListener("focusin",i),this.viewRoot.addEventListener("focusout",l=>{let c=l.relatedTarget;c&&this.viewRoot&&this.viewRoot.contains(c)||s()}),this.register(()=>s()),this.detachTreeHook=this.tree.hookMetadataCache(()=>this.debouncedRender()),this.registerEvent(this.app.metadataCache.on("resolved",()=>this.scheduleTreeReconcile())),this.registerEvent(this.app.metadataCache.on("changed",l=>{if(l.extension!=="md")return;let c=this.app.metadataCache.getFileCache(l)?.frontmatter;this.completedState.set(l.path,!!c?.completed),this.taskTaggedState.set(l.path,this.taggedFromFm(c))})),this.registerEvent(this.app.vault.on("rename",(l,c)=>{for(let d of[this.completedState,this.taskTaggedState])d.has(c)&&(d.set(l.path,d.get(c)),d.delete(c))})),this.registerEvent(this.app.vault.on("delete",l=>{this.completedState.delete(l.path),this.taskTaggedState.delete(l.path);let c=l.path.lastIndexOf("/"),d=(c>=0?l.path.slice(0,c):"").replace(/\/+$/,"");l.path.endsWith(".md")&&d===this.noteFolder.replace(/\/+$/,"")&&(this.tree.rebuild(this.noteFolder),this.debouncedRender())})),this.detachSettings=ir(()=>{this.loadConfig();let l=this.plugin.settings.drafts?.[this.noteFolder]??"",c=this.composerInputEl?.value??"";l===""&&this.composerDraft!==""&&c===""?(this.composerDraft="",this.composerInputEl&&(this.composerInputEl.value="")):l===""&&c!==""&&(this.composerDraft=c),!!this.composerInputEl&&document.activeElement===this.composerInputEl&&(this.focusComposerOnNextRender=!0),this.debouncedRender()}),this.app.vault.on("modify",this.onFileModify),this.app.vault.on("create",this.onFileCreate),window.addEventListener("keydown",this.onDocKeyDown,!0),this.loadConfig();try{await this.bootstrapFolder()}catch(l){let c=l?.message??"";/already exists/i.test(c)||console.warn("[Stashpad] bootstrapFolder failed:",l)}this.tree.rebuild(this.noteFolder),this.installFmSyncActivityNotice(),this.backfillFrontmatterSync(),this.plugin.maybeSweepFolder(this.noteFolder),this.defaultCursorToLast(),this.refreshHeaderTitle(),await this.loadDraftsForFolder();try{let l=this.plugin.loadLastCursor(this.noteFolder);for(let[c,d]of l)this.lastCursorByFocus.set(c,d);this.lastSelectionByFocus=this.plugin.loadLastSelection(this.noteFolder),this.activeVersionByGroup=this.plugin.loadActiveVersions(this.noteFolder)}catch{}let n=this.lastCursorByFocus.get(this.focusId),o;n&&this.tree.get(n)?(o={kind:"scroll-to-id",id:n,align:"start"},this.pendingFocusIds=[n]):(this.scrollToBottomOnNextRender=!0,o={kind:"pin-bottom",until:"next-user-input"});let a=this.foldRestoredSelection(n);a&&(this.pendingFocusIds=a),this.render(o),this.scheduleSelectionRestore(),this.tinyMode&&(setTimeout(()=>this.applyTinyWindow(),1e3),setTimeout(()=>this.applyTinyWindow(),1500)),this.registerDomEvent(window,"beforeunload",()=>{this.flushDrafts(),this.stampSelectedCursor(!0)}),this.registerDomEvent(window,"blur",()=>{this.flushDrafts(),this.stampSelectedCursor(!0)}),X().focusComposerOnOpen?this.focusComposer():this.focusView(),this.registerEvent(this.app.workspace.on("active-leaf-change",l=>{l===this.leaf?X().focusComposerOnOpen?this.focusComposer():this.focusView():this.stickToListBottom=!1}))}renderSuppressed(){return this.bulkRenderDepth>0||this.bulkSettleTimer!=null||this.autoSyncDeferActive||this.plugin.rebootstrapInProgress||this.plugin.okfRebuildingFolders.has(this.noteFolder)}beginBulkRender(){this.bulkRenderDepth++,this.bulkSettleTimer!=null&&(window.clearTimeout(this.bulkSettleTimer),this.bulkSettleTimer=null)}endBulkRender(e=500){this.bulkRenderDepth>0&&this.bulkRenderDepth--,!(this.bulkRenderDepth>0)&&(this.bulkSettleTimer!=null&&window.clearTimeout(this.bulkSettleTimer),this.bulkSettleTimer=window.setTimeout(()=>{this.bulkSettleTimer=null,this.viewRoot?.isConnected&&(this.tree.rebuild(this.noteFolder),this.render())},e))}deferDuringSyncBurst(){if(this.plugin.rebootstrapInProgress)return!1;let e=Date.now();this.syncBurstTimes.push(e);let t=e-2e3;for(;this.syncBurstTimes.length&&this.syncBurstTimes[0]<t;)this.syncBurstTimes.shift();return!this.autoSyncDeferActive&&this.syncBurstTimes.length>=6&&(this.autoSyncDeferActive=!0,this.autoSyncNotice=new F.Notice("Stashpad: syncing \u2014 list updates paused until it settles\u2026",0)),this.autoSyncDeferActive?(this.autoSyncSettleTimer!=null&&window.clearTimeout(this.autoSyncSettleTimer),this.autoSyncSettleTimer=window.setTimeout(()=>this.endAutoSyncDefer(),1500),!0):!1}endAutoSyncDefer(){this.autoSyncSettleTimer=null,this.autoSyncDeferActive=!1,this.syncBurstTimes=[],this.autoSyncNotice?.hide(),this.autoSyncNotice=null,this.forceReconcileRender()}forceReconcileRender(){this.viewRoot?.isConnected&&(this.tree.rebuild(this.noteFolder),this.render())}scheduleTreeReconcile(){this.renderSuppressed()||this.treeReconcileTimer==null&&(this.treeReconcileTimer=window.setTimeout(()=>{if(this.treeReconcileTimer=null,!this.viewRoot?.isConnected)return;let e=this.noteFolder,t=e+"/",i=0;for(let s of this.app.vault.getMarkdownFiles()){let n=s.parent?.path?.replace(/\/+$/,"")??"";if(!(n===e||e!==""&&n.startsWith(t)))continue;let o=this.app.metadataCache.getFileCache(s)?.frontmatter?.id;typeof o=="string"&&o&&i++}i!==this.tree.fileBackedCount()&&(this.tree.rebuild(e),this.debouncedRender())},400))}focusView(){requestAnimationFrame(()=>{this.viewRoot?.isConnected&&(document.activeElement instanceof HTMLElement&&(document.activeElement.tagName==="INPUT"||document.activeElement.tagName==="TEXTAREA")||this.viewRoot.focus({preventScroll:!0}))})}focusComposer(){if(!X().focusComposerOnOpen)return;let e=()=>{if(!this.viewRoot?.isConnected||Date.now()<this.suppressComposerFocusUntil||Date.now()<this.plugin.suppressComposerAutofocusUntil)return;let t=document.activeElement;t&&(t.tagName==="INPUT"||t.tagName==="TEXTAREA")&&t!==this.composerInputEl||(t&&t.tagName==="BUTTON"&&this.viewRoot.contains(t),this.composerInputEl?.focus({preventScroll:!0}))};requestAnimationFrame(e),setTimeout(e,50),setTimeout(e,200)}async onClose(){_a(this),this.detachTreeHook?.(),this.detachSettings?.(),this.app.vault.off("modify",this.onFileModify),this.app.vault.off("create",this.onFileCreate),window.removeEventListener("keydown",this.onDocKeyDown,!0),this.listResizeObserver?.disconnect(),this.listResizeObserver=null,this.stickyRowObserver?.disconnect(),this.stickyRowObserver=null,this.bodyRenderer.dispose(),this.barOverflowRO?.disconnect(),this.barOverflowRO=null,this.composerNarrowObserver?.disconnect(),this.composerNarrowObserver=null,this.focusedMiniObserver?.disconnect(),this.focusedMiniObserver=null,this.treeReconcileTimer!=null&&(window.clearTimeout(this.treeReconcileTimer),this.treeReconcileTimer=null),this.autoSyncSettleTimer!=null&&(window.clearTimeout(this.autoSyncSettleTimer),this.autoSyncSettleTimer=null),this.autoSyncNotice?.hide(),this.autoSyncNotice=null,this.composerAutocomplete?.detach(),this.composerAutocomplete=null;for(let e of this.slugDebouncers.values())e.cancel();for(let e of this.attachmentDebouncers.values())e.cancel();this.authorship.dispose();try{await this.flushDrafts()}catch{}try{await this.order.flush(this.noteFolder)}catch{}try{await this.sortStore.flush(this.noteFolder)}catch{}try{await this.fmSync.flush()}catch{}this.stampSelectedCursor(!0),this.fmSyncUnsubscribe?.(),this.fmSyncUnsubscribe=null}setEphemeralState(e){let t=e;t?.focusId&&(this.focusId=t.focusId),t?.timeFilter&&(this.timeFilter=t.timeFilter)}getEphemeralState(){return{focusId:this.focusId,timeFilter:this.timeFilter}}getState(){return{...super.getState()??{},folderOverride:this.folderOverride,timeFilter:this.timeFilter,focusId:this.focusId,tagFilter:this.tagFilter,colorFilter:this.colorFilter,timeFilterCalendar:this.timeFilterCalendar,tinyMode:this.tinyMode,tinyAlwaysOnTop:this.tinyAlwaysOnTop,tinyOpacity:this.tinyOpacity,compactMode:this.compactMode,navBackStack:this.navBackStack,navForwardSnapshots:this.navForwardSnapshots}}async setState(e,t){let i=e??null;if(i){"folderOverride"in i&&(this.folderOverride=i.folderOverride??null),i.timeFilter&&(this.timeFilter=i.timeFilter),i.focusId&&(this.focusId=i.focusId),i.cursorId&&(this.pendingCursorId=i.cursorId),"tagFilter"in i&&(this.tagFilter=i.tagFilter??null),"colorFilter"in i&&(this.colorFilter=i.colorFilter??null),"timeFilterCalendar"in i&&(this.timeFilterCalendar=!!i.timeFilterCalendar),"tinyMode"in i&&(this.tinyMode=!!i.tinyMode),"tinyAlwaysOnTop"in i&&(this.tinyAlwaysOnTop=!!i.tinyAlwaysOnTop),typeof i.tinyOpacity=="number"&&Number.isFinite(i.tinyOpacity)&&(this.tinyOpacity=Math.min(1,Math.max(.3,i.tinyOpacity))),"compactMode"in i&&(this.compactMode=!!i.compactMode);let o=a=>a&&typeof a.folder=="string"&&typeof a.focusId=="string";Array.isArray(i.navBackStack)&&(this.navBackStack=i.navBackStack.filter(o)),Array.isArray(i.navForwardSnapshots)&&(this.navForwardSnapshots=i.navForwardSnapshots.filter(o))}let s=(this.plugin?.settings?.folder??"Stashpad").trim().replace(/^\/+|\/+$/g,""),n=this.folderOverride?.trim().replace(/^\/+|\/+$/g,"")||null;if(this.noteFolder=n||s||"Stashpad",await super.setState(e,t),this.refreshHeaderTitle(),this.viewRoot){this.loadConfig(),await this.bootstrapFolder(),this.tree.rebuild(this.noteFolder),this.backfillFrontmatterSync(),this.defaultCursorToLast(),this.draftsLoadedFor=null,this.composerDraft="",await this.loadDraftsForFolder(),this.lastCursorByFocus.clear(),this.lastSelectionByFocus.clear();try{let c=this.plugin.loadLastCursor(this.noteFolder);for(let[d,h]of c)this.lastCursorByFocus.set(d,h);this.lastSelectionByFocus=this.plugin.loadLastSelection(this.noteFolder),this.activeVersionByGroup=this.plugin.loadActiveVersions(this.noteFolder)}catch{}let o=this.lastCursorByFocus.get(this.focusId),a;o&&this.tree.get(o)?(this.pendingFocusIds=[o],a={kind:"scroll-to-id",id:o,align:"start"}):a={kind:"pin-bottom",until:"next-user-input"};let l=this.foldRestoredSelection(o);l&&(this.pendingFocusIds=l),this.render(a),this.scheduleSelectionRestore()}}focus(){this.viewRoot?.focus({preventScroll:!0})}foldRestoredSelection(e){let t=this.lastSelectionByFocus.get(this.focusId)??null;if(!t||!t.length)return null;let i=t.filter(s=>!!this.tree.get(s));return i.length?e&&i.includes(e)?[e,...i.filter(s=>s!==e)]:i:null}stampSelection(e=!1){let t=[...this.selection],i=()=>{try{this.plugin.saveLastSelection(this.noteFolder,this.focusId,t)}catch{}};if(this.stampSelectionTimer!=null&&(window.clearTimeout(this.stampSelectionTimer),this.stampSelectionTimer=null),e){i();return}this.stampSelectionTimer=window.setTimeout(()=>{this.stampSelectionTimer=null,i()},400)}scheduleSelectionRestore(){let e=this.lastSelectionByFocus.get(this.focusId);if(!e||!e.length)return;let t=()=>{let i=e.filter(a=>this.currentChildren.some(l=>l.id===a));if(!i.length)return;let s=[...this.selection],n=new Set(i);if(s.length<i.length&&s.every(a=>n.has(a))){this.selection.clear();for(let a of i)this.selection.add(a);this.firstSelectedId=i[0],this.lastSelected=i[i.length-1],this.repaintSelectionClasses()}};for(let i of[120,400,900,1600])window.setTimeout(t,i)}loadConfig(){let e=(this.plugin?.settings?.folder??"Stashpad").trim().replace(/^\/+|\/+$/g,""),i=this.folderOverride?.trim().replace(/^\/+|\/+$/g,"")||null||e||"Stashpad";i!==this.noteFolder?(this.noteFolder=i,this.tree.rebuild(this.noteFolder)):this.noteFolder=i}captureNavSnapshot(){return{folder:this.noteFolder,focusId:this.focusId}}recordNavState(e={}){let t=this.captureNavSnapshot(),i=this.navBackStack[this.navBackStack.length-1];i&&i.folder===t.folder&&i.focusId===t.focusId||(this.navBackStack.push(t),e.keepForward||(this.navForwardSnapshots=[]))}async setFolderOverride(e,t={}){let i=e?.trim().replace(/^\/+|\/+$/g,"")||null;if(i&&this.isReservedFolder(i)){new F.Notice(`"${i}" is a reserved Stashpad subfolder (imports/exports/attachments). Pick a different folder.`);return}if((i||null)!==(this.folderOverride||null)){t.skipHistory||this.recordNavState(),this.folderOverride=i,this.focusId=k,this.lastCursorByFocus.clear(),this.selection.clear(),this.cursorIdx=-1,this.lastSelected=null,this.composerDraft="",await this.flushDrafts(),this.draftsLoadedFor=null,this.loadConfig();try{await this.bootstrapFolder()}catch(s){let n=s?.message??"";/already exists/i.test(n)||console.warn("[Stashpad] bootstrapFolder failed:",s)}this.tree.rebuild(this.noteFolder),this.backfillFrontmatterSync(),this.plugin.maybeSweepFolder(this.noteFolder),this.defaultCursorToLast(),await this.loadDraftsForFolder();try{let s=this.app.workspace;typeof s.saveLayout=="function"?await s.saveLayout():this.app.workspace.requestSaveLayout()}catch{this.app.workspace.requestSaveLayout()}this.refreshHeaderTitle(),this.render()}}cmdOpenFolderPicker(){this.openFolderPicker()}openFolderPicker(){this.plugin.openFolderPicker()}listVaultFolders(){let e=[];for(let t of this.app.vault.getAllLoadedFiles())if(t instanceof F.TFolder){if(t.path==="/"||t.path===""||t.path.startsWith(".")||this.isReservedFolder(t.path))continue;e.push(t.path)}return e.sort((t,i)=>t.localeCompare(i))}isReservedFolder(e){let t=e.split("/").filter(Boolean).pop()??"";return t?new Set([this.plugin.settings.importDropFolder,this.plugin.settings.exportFolder,"_attachments","_processed","_authors"].map(s=>(s??"").trim().replace(/^\/+|\/+$/g,"")).filter(Boolean)).has(t):!1}persistFocus(){try{this.app.workspace.requestSaveLayout()}catch{}}cmdUndo(){let e=this.plugin.getUndoStack(this.noteFolder);if(!e.canUndo()){new F.Notice("Nothing to undo.");return}let t=e.peekUndoLabel(),i=this.plugin.notifications.recent()[0]?.category??"system";e.undo().then(()=>this.plugin.notifications.show({message:`Undid: ${t}`,kind:"info",category:i,folder:this.noteFolder})).catch(s=>this.plugin.notifications.show({message:`Undo failed: ${s.message}`,kind:"error",category:"system",folder:this.noteFolder}))}cmdRedo(){let e=this.plugin.getUndoStack(this.noteFolder);if(!e.canRedo()){new F.Notice("Nothing to redo.");return}let t=e.peekRedoLabel(),i=this.plugin.notifications.recent()[0]?.category??"system";e.redo().then(()=>this.plugin.notifications.show({message:`Redid: ${t}`,kind:"info",category:i,folder:this.noteFolder})).catch(s=>this.plugin.notifications.show({message:`Redo failed: ${s.message}`,kind:"error",category:"system",folder:this.noteFolder}))}async snapshotNotes(e,t){let i=[],s=new Set;for(let l of e)!l.file||s.has(l.file.path)||(s.add(l.file.path),i.push(l.file));let n=await Promise.all(i.map(l=>this.app.vault.read(l))),o=i.map((l,c)=>({path:l.path,content:n[c]})),a=[];if(t){let l=new Set,c=[];for(let h of n)for(let g of this.extractAttachments(this.stripFrontmatter(h))){let u=this.app.metadataCache.getFirstLinkpathDest(g,"");u&&!l.has(u.path)&&(l.add(u.path),c.push(u))}let d=await Promise.all(c.map(h=>this.app.vault.readBinary(h)));for(let h=0;h<c.length;h++)a.push({path:c[h].path,data:d[h]})}return{notes:o,attachments:a}}async restoreSnapshots(e,t){for(let s of e.attachments)try{await this.app.vault.adapter.exists(s.path)||await this.app.vault.createBinary(s.path,s.data)}catch{}for(let s of e.notes)try{await this.app.vault.adapter.exists(s.path)||await this.app.vault.create(s.path,s.content)}catch{}let i=()=>{t&&t.some(n=>this.tree.get(n))&&(this.pendingFocusIds=t.slice())};i(),this.tree.rebuild(this.noteFolder),this.render({kind:"follow-cursor"}),setTimeout(()=>{i(),this.tree.rebuild(this.noteFolder),this.render({kind:"follow-cursor"})},100),setTimeout(()=>{i(),this.tree.rebuild(this.noteFolder),this.render({kind:"follow-cursor"})},400),setTimeout(()=>{for(let s of e.notes){let n=this.tree.idForPath(s.path);n&&this.fmSync.schedule(n)}},500)}async trashNotesAndAttachments(e){let t=new Set;for(let i of e.notes){let s=this.tree.idForPath(i.path);if(!s)continue;let n=this.tree.get(s);n?.parent&&t.add(n.parent)}for(let i of e.notes){let s=this.app.vault.getAbstractFileByPath(i.path);if(s)try{await this.app.fileManager.trashFile(s)}catch{}}for(let i of e.attachments){let s=this.app.vault.getAbstractFileByPath(i.path);if(s)try{await this.app.fileManager.trashFile(s)}catch{}}this.tree.rebuild(this.noteFolder),this.render();for(let i of t)this.fmSync.scheduleParentOfDeleted(i)}async loadDraftsForFolder(){if(this.draftsLoadedFor===this.noteFolder)return;this.draftsLoadedFor=this.noteFolder;let e=this.plugin.settings.drafts??{};this.composerDraft=e[this.noteFolder]??"",console.debug("[Stashpad] loadDrafts",{folder:this.noteFolder,has:!!e[this.noteFolder],available:Object.keys(e)})}async saveDraft(e){try{let t=this.noteFolder;if((this.plugin.settings.drafts?.[t]??"")===e)return;let s={...this.plugin.settings.drafts??{}};e.length===0?delete s[t]:s[t]=e,this.plugin.settings.drafts=s,e.length===0?await this.plugin.saveSettings():await this.plugin.persistSettingsQuiet()}catch(t){console.warn("Stashpad: drafts save failed",t)}}async recordLastSubmitted(e){try{let t={...this.plugin.settings.lastSubmitted??{}};t[this.noteFolder]=e,this.plugin.settings.lastSubmitted=t,await this.plugin.persistSettingsQuiet()}catch{}}hasRestorableDraft(){let e=this.plugin.settings.drafts?.[this.noteFolder];if(!e||!e.trim())return!1;let t=this.plugin.settings.lastSubmitted?.[this.noteFolder];return!(t&&t===e)}syncComposerDraftForFocus(){}async flushDrafts(){this.composerInputEl?await this.saveDraft(this.composerInputEl.value):await this.saveDraft(this.composerDraft)}timeFilterCutoff(){if(this.timeFilter==="all")return null;if(this.timeFilterCalendar){let t=(0,F.moment)();switch(this.timeFilter){case"day":return t.startOf("day").valueOf();case"week":return t.startOf("isoWeek").valueOf();case"month":return t.startOf("month").valueOf();case"year":return t.startOf("year").valueOf()}}let e=Date.now();switch(this.timeFilter){case"day":return e-864e5;case"week":return e-7*864e5;case"month":return e-30*864e5;case"year":return e-365*864e5}return null}allowedByBases(){return null}currentViewMode(){return this.plugin.settings.viewModes?.[this.noteFolder]??"nested"}currentIncludeAttachments(){return!!this.plugin.settings.includeAttachmentsInEverything?.[this.noteFolder]}async setIncludeAttachments(e){let t={...this.plugin.settings.includeAttachmentsInEverything??{}};e?t[this.noteFolder]=!0:delete t[this.noteFolder],this.plugin.settings.includeAttachmentsInEverything=t,await this.plugin.saveSettings()}currentEncryptionFilter(){return this.plugin.settings.encryptionFilter?.[this.noteFolder]??"all"}async setEncryptionFilter(e){let t={...this.plugin.settings.encryptionFilter??{}};e==="all"?delete t[this.noteFolder]:t[this.noteFolder]=e,this.plugin.settings.encryptionFilter=t,await this.plugin.saveSettings()}currentHideChildless(){return!!this.plugin.settings.hideChildlessNotes?.[this.noteFolder]}async setHideChildless(e){let t={...this.plugin.settings.hideChildlessNotes??{}};e?t[this.noteFolder]=!0:delete t[this.noteFolder],this.plugin.settings.hideChildlessNotes=t,await this.plugin.saveSettings()}currentHideCompleted(){return!!this.plugin.settings.hideCompletedNotes?.[this.noteFolder]}async setHideCompleted(e){let t={...this.plugin.settings.hideCompletedNotes??{}};e?t[this.noteFolder]=!0:delete t[this.noteFolder],this.plugin.settings.hideCompletedNotes=t,await this.plugin.saveSettings()}currentAttachmentsOnly(){return!!this.plugin.settings.attachmentsOnlyNotes?.[this.noteFolder]}async setAttachmentsOnly(e){let t={...this.plugin.settings.attachmentsOnlyNotes??{}};e?t[this.noteFolder]=!0:delete t[this.noteFolder],this.plugin.settings.attachmentsOnlyNotes=t,await this.plugin.saveSettings()}nodeHasAttachment(e){if(!e.file)return!1;let t=this.app.metadataCache.getFileCache(e.file)?.frontmatter?.attachments;return Array.isArray(t)&&t.length>0}hasAttachmentInSubtree(e){if(this.nodeHasAttachment(e))return!0;for(let t of this.tree.getChildren(e.id))if(this.hasAttachmentInSubtree(t))return!0;return!1}hasIncompleteDescendant(e){for(let t of e.children){let i=this.tree.get(t);if(i&&(!this.isCompleted(i)||this.hasIncompleteDescendant(i)))return!0}return!1}collectEmbeddedAttachmentPaths(){let e=new Set,t=this.noteFolder,i=this.app.vault.getAbstractFileByPath(t);if(!(i instanceof F.TFolder))return e;let s=[i];for(;s.length;){let n=s.pop();for(let o of n.children){if(o instanceof F.TFolder){s.push(o);continue}if(!(o instanceof F.TFile)||o.extension!=="md")continue;let a=this.app.metadataCache.getFileCache(o)?.frontmatter;if(!(!a||!Array.isArray(a.attachments)))for(let l of a.attachments){if(typeof l!="string")continue;let c=Nt(l),d=this.app.metadataCache.getFirstLinkpathDest(c,o.path);d?e.add(d.path):e.add(c)}}}return e}collectFileItems(e){let t=this.noteFolder,i=this.app.vault.getAbstractFileByPath(t);if(!(i instanceof F.TFolder))return[];let s=this.currentIncludeAttachments(),n=s?new Set:this.collectEmbeddedAttachmentPaths(),o=new Set(["_authors","_imports","_exports","_processed","_attachments","_archive",".archive"]),a=[],l=[i];for(;l.length;){let c=l.pop();for(let d of c.children){if(d instanceof F.TFolder){let h=d.name;if(c===i&&o.has(h)||c===i&&h==="_attachments"&&!s)continue;l.push(d);continue}d instanceof F.TFile&&d.extension!=="md"&&(d.name===".stashpad-order.json"||d.name===".stashpad-sort.json"||!s&&n.has(d.path)||a.push(d))}}return a}populateListBody(e,t){if(this.lastListWidth=e.clientWidth||this.lastListWidth,this.bodyRenderer.arm(),this.pendingCursorId){let d=this.currentChildren.findIndex(h=>h.id===this.pendingCursorId);d>=0&&(this.cursorIdx=d,this.selection.clear(),this.selection.add(this.pendingCursorId),this.lastSelected=this.pendingCursorId,this.pendingCursorId=null)}t.file&&F.Platform.isMobile&&!this.compactMode&&(this.renderFocusedHeaderMini(e,t),this.renderFocusedHeader(e,t));let i=this.currentViewMode(),s=i==="everything"?this.collectFileItems(t.id):[],n=d=>d.created&&Number.isFinite(Date.parse(d.created))?Date.parse(d.created):Number.POSITIVE_INFINITY,o=this.currentEncryptionFilter(),a;if(o==="unlocked")a=[];else if(i==="nested")a=this.plugin.lockedSubtreesFor(this.noteFolder,t.id);else{let d=new Set([t.id,...this.currentChildren.map(g=>g.id)]),h=new Set;a=[];for(let g of d)for(let u of this.plugin.lockedSubtreesFor(this.noteFolder,g))h.has(u.blob)||(h.add(u.blob),a.push(u))}let l=a.map(d=>({lk:d,ts:n(d)})).sort((d,h)=>d.ts-h.ts),c=i==="nested"&&this.sortStore.getMode(this.noteFolder,t.id)==="manual";if(this.currentChildren.length===0&&s.length===0&&l.length===0)e.createDiv({cls:"stashpad-empty",text:"No notes here yet. Type below to add one."});else if(c&&l.length>0){let d=new Map(this.currentChildren.map((f,I)=>[f.id,I])),h=new Map;for(let{lk:f}of l)f.rootId&&h.set(f.rootId,f);let g=this.currentChildren.map(f=>f.id),u=l.map(({lk:f})=>f).filter(f=>f.rootId).sort((f,I)=>n(I)-n(f)),p=!0;for(;u.length&&p;){p=!1;for(let f=0;f<u.length;f++){let I=u[f],m=I.prevSibling??null,C;if(m==null)C=0;else{let B=g.indexOf(m);if(B<0)continue;C=B+1}g.splice(C,0,I.rootId),u.splice(f,1),f--,p=!0}}for(let f of u)g.push(f.rootId);for(let f of g){let I=d.get(f);if(I!==void 0)this.renderNote(e,this.currentChildren[I],I);else{let m=h.get(f);m&&this.renderLockedPlaceholder(e,m)}}for(let f of s.slice().sort((I,m)=>I.stat.ctime-m.stat.ctime))this.renderFileRow(e,f)}else if(s.length===0){let d=0;for(let h=0;h<this.currentChildren.length;h++){let g=Number.isFinite(Date.parse(this.currentChildren[h].created))?Date.parse(this.currentChildren[h].created):0;for(;d<l.length&&l[d].ts<=g;)this.renderLockedPlaceholder(e,l[d].lk),d++;this.renderNote(e,this.currentChildren[h],h)}for(;d<l.length;)this.renderLockedPlaceholder(e,l[d].lk),d++}else{let d=[...this.currentChildren.map((h,g)=>({kind:"note",ts:Number.isFinite(Date.parse(h.created))?Date.parse(h.created):0,idx:g})),...s.map(h=>({kind:"file",ts:h.stat.ctime,file:h})),...l.map(h=>({kind:"lock",ts:h.ts,lk:h.lk}))];d.sort((h,g)=>h.ts-g.ts);for(let h of d)h.kind==="note"?this.renderNote(e,this.currentChildren[h.idx],h.idx):h.kind==="file"?this.renderFileRow(e,h.file):this.renderLockedPlaceholder(e,h.lk)}t.file&&F.Platform.isMobile&&!this.compactMode&&this.installFocusedMiniObserver(e)}renderLockedPlaceholder(e,t){let i=e.createDiv({cls:"stashpad-locked-row"});t.created&&i.createSpan({cls:"stashpad-note-time stashpad-locked-time",text:this.formatTime(t.created)});let s=i.createSpan({cls:"stashpad-locked-icon"});(0,F.setIcon)(s,"lock");let n=(this.plugin.settings.hideLockedTitles??!1)||!t.title;i.createSpan({cls:"stashpad-locked-title",text:n?"Locked note":t.title}),i.createSpan({cls:"stashpad-locked-count",text:t.count>1?`${t.count} notes \xB7 locked`:"locked"});let o=i.createEl("button",{cls:"stashpad-locked-unlock",text:"Unlock"});(0,F.setIcon)(o.createSpan({cls:"stashpad-btn-icon"}),"unlock");let a=async c=>{c.preventDefault(),c.stopPropagation();let d=(this.plugin.settings.lockedSubtrees??[]).find(f=>f.blob===t.blob),h=t.rootId??d?.rootId??null,g=t.parentId??d?.parentId??k,u=t.prevSibling??d?.prevSibling??null;if(await this.plugin.unlockBundleAt(t.blob)){if(this.selection.clear(),this.lastSelected=null,h){let f=this.order.getOrder(this.noteFolder,g),I=this.sortStore.getMode(this.noteFolder,g)==="manual";if(f.length===0&&I&&(f=this.tree.getChildren(g).map(m=>m.id)),f.length>0){let m=f.filter(B=>B!==h),C=u&&m.includes(u)?m.indexOf(u)+1:0;m.splice(Math.max(0,C),0,h),this.order.setOrder(this.noteFolder,g,m),this.order.flush(this.noteFolder)}this.pendingCursorId=h}this.render()}};o.onclick=a;let l=c=>{c.preventDefault(),c.stopPropagation();let d=new F.Menu;if(d.addItem(h=>h.setTitle("Decrypt (unlock)").setIcon("unlock").onClick(()=>void a(c))),!F.Platform.isMobile){let h=F.Platform.isMacOS?"Finder":F.Platform.isWin?"File Explorer":"file manager";d.addItem(g=>g.setTitle(`Show in ${h}`).setIcon("folder-search").onClick(()=>{try{let u=window.require?.("electron")?.shell,p=this.app.vault.adapter?.getFullPath?.(t.blob);p&&u?.showItemInFolder&&u.showItemInFolder(p)}catch(u){console.warn("[Stashpad] showItemInFolder failed",u)}}))}d.addItem(h=>h.setTitle("Export to .stash\u2026").setIcon("package").onClick(()=>{this.plugin.exportLockedSubtree(t.blob)})),d.addItem(h=>h.setTitle("Copy encrypted file path").setIcon("copy").onClick(()=>{let g=t.blob;if(!F.Platform.isMobile)try{g=this.app.vault.adapter?.getFullPath?.(t.blob)||t.blob}catch{}navigator.clipboard.writeText(g),new F.Notice("Path copied.")})),d.showAtMouseEvent(c)};if(i.oncontextmenu=l,F.Platform.isMobile){let c=i.createEl("button",{cls:"stashpad-locked-menu"});(0,F.setIcon)(c,"more-vertical"),c.setAttr("aria-label","Locked note menu"),c.onclick=d=>l(d)}i.setAttr("aria-label",`${n?"Locked note":`Locked: ${t.title}`}. Use the Unlock button to decrypt.`)}refreshList(){if(!this.listEl){this.render();return}let e=this.tree.get(this.focusId)??this.tree.getRoot();this.currentChildren=this.filterChildren(this.collectViewItems(e.id)),this.cursorIdx>=this.currentChildren.length&&(this.cursorIdx=this.currentChildren.length-1);let t=this.listEl.scrollTop+this.listEl.clientHeight>=this.listEl.scrollHeight-2,i=this.listEl.scrollTop;this.listEl.empty(),this.populateListBody(this.listEl,e),t?this.listEl.scrollTop=this.listEl.scrollHeight:this.listEl.scrollTop=i}renderFileRow(e,t){if(this.currentEncryptionFilter()==="locked")return;let i=e.createDiv({cls:"stashpad-file-row"});i.dataset.path=t.path,i.createDiv({cls:"stashpad-file-meta"}).createSpan({cls:"stashpad-file-time",text:this.formatTime(new Date(t.stat.ctime).toISOString())});let n=i.createDiv({cls:"stashpad-file-body"});n.createSpan({cls:"stashpad-file-name",text:t.name}),n.createSpan({cls:"stashpad-file-ext",text:t.extension.toUpperCase()}),i.title=`${t.path} \u2014 click to open`,i.onclick=o=>{o.preventDefault(),this.app.workspace.openLinkText(t.path,"",!1)}}async setViewMode(e){let t={...this.plugin.settings.viewModes??{}};e==="nested"?delete t[this.noteFolder]:t[this.noteFolder]=e,this.plugin.settings.viewModes=t,await this.plugin.saveSettings()}collectViewItems(e){let t=this.currentViewMode(),i=this.currentHideChildless(),s=this.tree.getChildren(e),n=i?s.filter(l=>l.children.length>0):s;if(t==="nested")return n;let o=[],a=l=>{o.push(l);for(let c of this.tree.getChildren(l.id))a(c)};for(let l of n)a(l);return o}filterChildren(e){e=this.collapseVersions(e);let t=this.timeFilterCutoff(),i=this.tagFilter?.toLowerCase(),s=this.colorFilter?.toLowerCase()??null,n=this.currentHideCompleted(),o=this.currentAttachmentsOnly(),a=this.importedOnly,l=this.authorFilter;return!t&&!i&&!s&&!n&&!o&&!a&&!l?e:e.filter(c=>{if(a&&(!c.file||this.app.metadataCache.getFileCache(c.file)?.frontmatter?.imported!==!0))return!1;if(l){if(!c.file)return!1;let d=rt(this.app.metadataCache.getFileCache(c.file)?.frontmatter?.author);if(!d||d.id!==l)return!1}if(t&&c.created){let d=Date.parse(c.created);if(!Number.isNaN(d)&&d<t)return!1}if(i){if(!c.file)return!1;if(this.tagFilter===Tt){if(!this.nodeHasAnyTag(c))return!1}else if(this.tagFilter===Pt){if(this.nodeHasAnyTag(c))return!1}else if(!this.nodeHasTag(c,i))return!1}return!(s&&(this.colorForNode(c)?.toLowerCase()??null)!==s||n&&this.isCompleted(c)&&!this.hasIncompleteDescendant(c)||o&&!this.hasAttachmentInSubtree(c))})}collapseVersions(e){if(!this.plugin.settings.enableSheetVersions)return e;let t=new Map,i=!1;for(let n of e){let o=Ce(this.app,n);if(!Fi(o))continue;i=!0;let a=Ve(o),l=t.get(a);l?l.push(n):t.set(a,[n])}if(!i)return e;let s=new Set;for(let[n,o]of t){let a=this.activeVersionNode(n,o);a&&s.add(a.id)}return e.filter(n=>Fi(Ce(this.app,n))?s.has(n.id):!0)}activeVersionNode(e,t){let i=this.activeVersionByGroup.get(e);if(i){let s=t.find(n=>n.id===i);if(s)return s}return la(this.app,t)}setActiveVersion(e,t){this.activeVersionByGroup.set(e,t),this.plugin.saveActiveVersion(this.noteFolder,e,t),this.render()}nodeHasAnyTag(e){if(!e.file)return!1;let t=this.app.metadataCache.getFileCache(e.file);if(!t)return!1;if(t.tags&&t.tags.length>0)return!0;let i=t.frontmatter?.tags;return!!(i&&(Array.isArray(i)?i:[i]).some(n=>typeof n=="string"&&n.trim().length>0))}nodeHasTag(e,t){if(!e.file)return!1;let i=this.app.metadataCache.getFileCache(e.file);if(!i)return!1;if(i.tags){for(let n of i.tags)if((n.tag||"").replace(/^#/,"").toLowerCase()===t)return!0}let s=i.frontmatter?.tags;if(s){let n=Array.isArray(s)?s:[s];for(let o of n)if(typeof o=="string"&&o.replace(/^#/,"").toLowerCase()===t)return!0}return!1}collectFolderTags(){let e=new Map,t=this.tree.getChildren(this.focusId);for(let s of t){if(!s.file)continue;let n=this.app.metadataCache.getFileCache(s.file);if(n?.tags)for(let a of n.tags){let l=(a.tag||"").replace(/^#/,"");l&&e.set(l,(e.get(l)??0)+1)}let o=n?.frontmatter?.tags;if(o){let a=Array.isArray(o)?o:[o];for(let l of a){if(typeof l!="string")continue;let c=l.replace(/^#/,"");c&&e.set(c,(e.get(c)??0)+1)}}}let i=[...e.entries()].map(([s,n])=>({raw:s,count:n,label:this.formatTagLabel(s)}));return i.sort((s,n)=>n.count-s.count||s.label.localeCompare(n.label)),i}formatTagLabel(e){return e&&e.split("/").map(t=>t.replace(/([a-z0-9])([A-Z])/g,"$1 $2").split(/[-_\s]+/).filter(Boolean).map(n=>n.charAt(0).toUpperCase()+n.slice(1)).join(" ")).join(" / ")}collectFolderColors(){let e=new Map,t=this.tree.getChildren(this.focusId);for(let s of t){let n=this.colorForNode(s);if(!n)continue;let o=n.toLowerCase();e.set(o,(e.get(o)??0)+1)}let i=[...e.entries()].map(([s,n])=>({hex:s,count:n}));return i.sort((s,n)=>n.count-s.count||s.hex.localeCompare(n.hex)),i}defaultCursorToLast(){let e=this.tree.get(this.focusId)??this.tree.getRoot(),t=this.filterChildren(this.collectViewItems(e.id));this.cursorIdx=t.length-1,this.selection.clear(),t.length>0&&(this.selection.add(t[t.length-1].id),this.lastSelected=t[t.length-1].id)}stampSelectedCursor(e=!1){this.stampSelection(e);let i=this.currentChildren[this.cursorIdx]?.id??this.lastSelected;if(!i)return;this.lastCursorByFocus.set(this.focusId,i);let s=()=>{let n=this.lastCursorByFocus.get(this.focusId);n&&this.plugin.saveLastCursor(this.noteFolder,this.focusId,n)};if(e){this.stampLastCursorTimer!=null&&window.clearTimeout(this.stampLastCursorTimer),this.stampLastCursorTimer=null,s();return}this.stampLastCursorTimer!=null&&window.clearTimeout(this.stampLastCursorTimer),this.stampLastCursorTimer=window.setTimeout(()=>{this.stampLastCursorTimer=null,s()},400)}captureScrollAnchor(){let e=this.listEl;if(!e)return null;let t=e.getBoundingClientRect().top,i=Array.from(e.querySelectorAll(".stashpad-note"));if(i.length===0)return null;let s=null;for(let n of i){let o=n.dataset.id;if(!o)continue;let a=n.getBoundingClientRect().top-t;if(a>=0){s={id:o,offsetFromListTop:a};break}s={id:o,offsetFromListTop:a}}return s}restoreScrollAnchor(e,t){let i=this.listEl;if(i){if(e){let s=i.querySelector(`[data-id="${e.id}"]`);if(s){let n=i.getBoundingClientRect().top,o=s.getBoundingClientRect().top-n;i.scrollTop+=o-e.offsetFromListTop;return}}t>0&&(i.scrollTop=t)}}render(e){ke.enabled&&(this._renderT0=performance.now()),this.pendingRenderPolicy=e??{kind:"preserve"},this.loadConfig();let t=this.viewRoot,i=this.listEl?.scrollTop??0,n=(e??{kind:"preserve"}).kind==="preserve"?this.captureScrollAnchor():null;!!this.composerInputEl&&document.activeElement===this.composerInputEl&&(this.focusComposerOnNextRender=!0,this.pendingComposerCaret=this.composerInputEl?.selectionStart??null);let a=!!this.listEl&&(this.stickToListBottom||this.listEl.scrollTop+this.listEl.clientHeight>=this.listEl.scrollHeight-2);t.empty(),t.toggleClass("is-mobile",F.Platform.isMobile),t.toggleClass("is-tiny",this.tinyMode),t.toggleClass("is-compact",this.compactMode);let l=this.containerEl.closest(".workspace-leaf");l&&(l.classList.toggle("stashpad-is-tiny",this.tinyMode),l.classList.toggle("stashpad-is-compact",this.compactMode));let c=this.containerEl.closest(".workspace-tabs");if(c&&c.classList.toggle("stashpad-has-tiny",this.tinyMode),this.tinyMode)this.renderTinyHeader(t);else{let p=this.compactMode&&F.Platform.isMobile;(!this.compactMode||F.Platform.isMobile)&&this.renderTimeFilterBar(t),p||this.renderBreadcrumb(t)}let d=this.tree.get(this.focusId)??this.tree.getRoot();d.file&&!F.Platform.isMobile&&!this.tinyMode&&!this.compactMode&&this.renderFocusedHeader(t,d),this.currentChildren=this.filterChildren(this.collectViewItems(d.id));let h=!1;if(this.autoSelectNewest&&this.currentChildren.length>0){let p=this.currentChildren[this.currentChildren.length-1];this.cursorIdx=this.currentChildren.length-1,this.selection.clear(),this.selection.add(p.id),this.lastSelected=p.id,this.autoSelectNewest=!1,h=!0}else if(this.pendingFocusIds){let p=this.pendingFocusIds;this.pendingFocusIds=null,this.selection.clear();let f=-1;for(let I of p){let m=this.currentChildren.findIndex(C=>C.id===I);m>=0&&(this.selection.add(I),f<0&&(f=m))}this.cursorIdx=f,f>=0&&(this.lastSelected=p.find(I=>this.currentChildren.some(m=>m.id===I))??null)}else this.cursorIdx>=this.currentChildren.length&&(this.cursorIdx=this.currentChildren.length-1);let g=t.createDiv({cls:"stashpad-list"});if(this.listEl=g,this.dnd.attachListDnD(g),this.populateListBody(g,d),this.renderComposer(t),F.Platform.isMobile&&this.renderMobileNav(t),this._renderT0!=null&&(ke.record("render.total",performance.now()-this._renderT0),this._renderT0=null),this.plugin.notifyStashpadContentChanged(),h&&this.plugin.notifyStashpadSelectionChanged(),this.focusComposerOnNextRender){this.focusComposerOnNextRender=!1;let p=this.pendingComposerCaret;this.pendingComposerCaret=null;let f=this.composerInputEl;if(f&&f.isConnected){if(f.focus({preventScroll:!0}),p!=null){let I=Math.min(p,f.value.length);try{f.setSelectionRange(I,I)}catch{}}}else requestAnimationFrame(()=>{let I=this.composerInputEl;if(I&&(I.focus({preventScroll:!0}),p!=null)){let m=Math.min(p,I.value.length);try{I.setSelectionRange(m,m)}catch{}}})}let u=this.pendingRenderPolicy;if(this.pendingRenderPolicy=null,u&&this.listEl){let p=this.scrollToBottomOnNextRender;switch(this.scrollToBottomOnNextRender=!1,u.kind){case"preserve":p?this.scrollListToBottom():a?this.scrollListToBottom():this.restoreScrollAnchor(n,i);break;case"pin-bottom":this.scrollListToBottom();break;case"restore":{let f=u.scrollTop,I=this.listEl,m=()=>{this.suppressScrollSave=!0;let C=Math.max(0,I.scrollHeight-I.clientHeight);I.scrollTop=Math.min(f,C),Promise.resolve().then(()=>{this.suppressScrollSave=!1})};m(),requestAnimationFrame(m),setTimeout(m,60),setTimeout(m,200),setTimeout(m,600);break}case"follow-cursor":i>0&&(this.listEl.scrollTop=i),this.revealCursorRow();break;case"scroll-to-id":{let f=u.id,I=u.align,m=this.listEl,C=()=>{this.suppressScrollSave=!0;let B=m.querySelector(`[data-id="${f}"]`);B&&B.scrollIntoView({block:I,behavior:"auto"}),Promise.resolve().then(()=>{this.suppressScrollSave=!1})};C(),requestAnimationFrame(C),setTimeout(C,60),setTimeout(C,200),setTimeout(C,600),setTimeout(()=>{this.suppressScrollSave=!1},700);break}}}else this.scrollToBottomOnNextRender?(this.scrollToBottomOnNextRender=!1,this.scrollListToBottom()):this.listEl&&a?this.scrollListToBottom():this.listEl&&i>0&&(this.listEl.scrollTop=i);if(this.stampSelectedCursor(),this.listEl){this.listResizeObserver?.disconnect();let p=this.listEl,f=p.scrollTop,I=new ResizeObserver(()=>{if(Date.now()<this.keyboardTransitionUntil)return;if(this.stickToListBottom){p.scrollTop=p.scrollHeight,f=p.scrollTop;return}let C=Math.max(0,p.scrollHeight-p.clientHeight);p.scrollTop<f&&f<=C?p.scrollTop=f:f=p.scrollTop});I.observe(p),this.listResizeObserver=I,p.addEventListener("wheel",C=>{C.deltaY<0&&(this.stickToListBottom=!1)},{passive:!0});let m=0;p.addEventListener("touchstart",C=>{m=C.touches[0]?.clientY??0},{passive:!0}),p.addEventListener("touchmove",C=>{let B=C.touches[0]?.clientY??m;B>m&&(this.stickToListBottom=!1),m=B},{passive:!0}),p.addEventListener("mousedown",()=>{this.stickToListBottom=!1}),p.addEventListener("keydown",()=>{this.stickToListBottom=!1})}}refreshFolderSwitcherIcon(){let e=this.containerEl.querySelector(".stashpad-folder-btn .stashpad-btn-icon");e&&(e.empty(),(0,F.setIcon)(e,this.plugin.getFolderIcon(this.noteFolder)??"folder"))}renderTimeFilterBar(e){let t=e.createDiv({cls:"stashpad-time-filter-bar"});if(!F.Platform.isMobile){let c=t.createEl("button",{cls:"stashpad-folder-btn"}),d=!!this.folderOverride,h=this.noteFolder.split("/").pop()||this.noteFolder||"Stashpad";(0,F.setIcon)(c.createSpan({cls:"stashpad-btn-icon"}),this.plugin.getFolderIcon(this.noteFolder)??"folder"),c.createSpan({text:h,cls:"stashpad-btn-text"}),c.title=d?`Folder (override): ${this.noteFolder} +Inspect with the buttons below \u2014 rename to .zip to crack it open in an archive tool.`,kind:"error",category:"import",affectedPaths:[r.path],actions:A.actionsForFile(r.path)}),console.error(e)}}function yd(A){let r=new Set,e=t=>{if(r.add(t),!!A.tree.get(t))for(let s of A.tree.getChildren(t))e(s.id)};return e(F),r}Hi();Hi();var Ed=new Set(["png","jpg","jpeg","gif","webp","svg","bmp","avif"]),hs={nested:"Nested",flat:"Flat",everything:"Everything"},ps=[{key:"day",calShort:"Today",rollShort:"24h",calLong:"Since midnight today",rollLong:"Last 24 hours"},{key:"week",calShort:"Week",rollShort:"7d",calLong:"Since Monday this week",rollLong:"Last 7 days"},{key:"month",calShort:"Month",rollShort:"30d",calLong:"Since the 1st of this month",rollLong:"Last 30 days"},{key:"year",calShort:"Year",rollShort:"365d",calLong:"Since January 1 this year",rollLong:"Last 365 days"},{key:"all",calShort:"All",rollShort:"ad infinitum",calLong:"All time",rollLong:"All time"}],gs=class gs extends k.ItemView{constructor(e,t){super(e);this.focusId=F;this.timeFilter="all";this.timeFilterCalendar=!1;this.tagFilter=null;this.colorFilter=null;this.importedOnly=!1;this.authorFilter=null;this.noteFolder="Stashpad";this.folderOverride=null;this.tinyMode=!1;this.tinyAlwaysOnTop=!1;this.tinyOpacity=1;this.compactMode=!1;this.detachTreeHook=null;this.detachSettings=null;this.slugDebouncers=new Map;this.attachmentDebouncers=new Map;this.bulkRenderDepth=0;this.bulkSettleTimer=null;this.bootstrappedFolders=new Set;this.selection=new Set;this.lastSelected=null;this.cursorIdx=-1;this.pendingCursorId=null;this.currentChildren=[];this.modeSplit=null;this.modeEnterSubmits=!0;this.nextDestination=null;this.nextDestinationFolder=null;this.nextDestinationLabel=null;this.inListPicker=null;this.pickerEscapeAt=0;this.composerExitAt=0;this.listEl=null;this.composerInputEl=null;this.composerDraft="";this.draftsLoadedFor=null;this.autoSelectNewest=!1;this.scrollToBottomOnNextRender=!1;this.scrollListenerSaveTimer=null;this.suppressScrollSave=!1;this.selectionGuardKey=0;this.pendingRenderPolicy=null;this.stickToListBottom=!1;this.keyboardTransitionUntil=0;this.tapSettleUntil=0;this.aimedTapTargetId=null;this.aimedTapAt=0;this.stickyRowObserver=null;this.listResizeObserver=null;this.composerNarrowObserver=null;this.barOverflowRO=null;this.lastCursorByFocus=new Map;this.lastSelectionByFocus=new Map;this.expandedNotes=new Set;this.cursorExpandOverride=new Set;this.cursorHasMoved=!1;this.activeVersionByGroup=new Map;this.focusComposerOnNextRender=!1;this.suppressComposerFocusUntil=0;this.composerAutocomplete=null;this.firstSelectedId=null;this.mobileSelectMode=!1;this.focusedMiniObserver=null;this.pendingComposerCaret=null;this.navBackStack=[];this.navForwardSnapshots=[];this.treeReconcileTimer=null;this.syncBurstTimes=[];this.autoSyncDeferActive=!1;this.autoSyncSettleTimer=null;this.autoSyncNotice=null;this.stampLastCursorTimer=null;this.stampSelectionTimer=null;this._renderT0=null;this.tinyOpacityPopover=null;this.lastListWidth=0;this.onDocKeyDown=e=>{if(!this.viewRoot.isConnected||this.app.workspace.activeLeaf!==this.leaf||Dr(e.target))return;let t=X().bindings;if(K(e,t.toggleSplit)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.toggleSplit();return}if(K(e,t.pickDestination)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.openDestinationPicker();return}if(K(e,t.search)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.openSearchModal();return}if(K(e,t.commandPalette)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.openStashpadCommandPalette();return}if(K(e,t.lockSelection)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdLockSelection();return}if(K(e,t.unlockAll)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdUnlockAll();return}if(K(e,t.moveToArchive)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdMoveToArchive();return}if(K(e,t.encryptDelete)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdEncryptDelete();return}if(K(e,t.searchInParent)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.openSearchInParentModal();return}if(K(e,t.exportStash)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdExportStash();return}if(K(e,t.importStash)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdImportStash();return}if(K(e,t.pickFolder)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdOpenFolderPicker();return}if(K(e,t.cloneStashpadTab)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdCloneStashpadTab();return}let i=e.target,s=!!i&&(i.tagName==="INPUT"||i.tagName==="TEXTAREA");if(e.key===" "&&!e.metaKey&&!e.ctrlKey&&!e.altKey&&!e.shiftKey&&!s){let l=this.composerInputEl;if(l){e.preventDefault(),e.stopPropagation(),l.focus();let c=l.value.length;l.setSelectionRange(c,c);return}}let n=!!i&&(i.tagName==="INPUT"||i.tagName==="TEXTAREA"||i.tagName==="BUTTON"||i.tagName==="SELECT");if(e.key==="Escape"&&i instanceof HTMLElement&&(i.tagName==="BUTTON"||i.tagName==="SELECT")&&this.viewRoot.contains(i)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.viewRoot.focus();return}if(this.inListPicker&&e.key==="Escape"){e.preventDefault(),e.stopPropagation(),this.inListPicker=null,this.pickerEscapeAt=Date.now();let l=this.listEl,c=!!l&&l.scrollTop+l.clientHeight>=l.scrollHeight-2,d=l?.scrollTop??0;if(this.render(),l){let h=c?l.scrollHeight:d;l.scrollTop=h,requestAnimationFrame(()=>{l.scrollTop=c?l.scrollHeight:d}),setTimeout(()=>{l.scrollTop=c?l.scrollHeight:d},60),this.revealCursorRow()}return}if(this.inListPicker&&!n){if(e.key==="ArrowDown"){e.preventDefault(),this.inListPicker.activeIdx=this.nextPickableIdx(this.inListPicker.activeIdx,1),this.repaintSelectionClasses(),this.revealRowAt(this.inListPicker.activeIdx);return}if(e.key==="ArrowUp"){e.preventDefault(),this.inListPicker.activeIdx=this.nextPickableIdx(this.inListPicker.activeIdx,-1),this.repaintSelectionClasses(),this.revealRowAt(this.inListPicker.activeIdx);return}if(e.key==="Enter"){e.preventDefault(),this.commitInListPicker();return}if(K(e,X().bindings.move)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.inListPicker=null,this.repaintSelectionClasses(),this.cmdMovePicker();return}return}if(n)return;if(K(e,t.delete)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdDelete();return}if(K(e,t.toggleComplete)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdToggleComplete();return}if(K(e,t.moveToTop)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdMoveToTop();return}if(K(e,t.moveToBottom)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdMoveToBottom();return}if(K(e,t.moveUp)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdMoveUp();return}if(K(e,t.moveDown)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdMoveDown();return}if(K(e,t.outdent)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdOutdent();return}if(K(e,t.setColor)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdSetColor();return}if(K(e,t.selectAll)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdSelectAll();return}if(K(e,t.swapWithParent)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdSwapWithParent();return}if(K(e,t.undo)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdUndo();return}if(K(e,t.redo)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdRedo();return}if(e.key===" "){e.preventDefault();let l=this.composerInputEl;if(l){l.focus();let c=l.value.length;l.setSelectionRange(c,c)}return}if(e.key==="ArrowDown"){e.preventDefault(),this.cursorIdx>=this.currentChildren.length-1?this.cursorIdx=0:this.cursorIdx++,this.selectCursor(e.shiftKey);return}if(e.key==="ArrowUp"){e.preventDefault(),this.cursorIdx<=0?this.cursorIdx=this.currentChildren.length-1:this.cursorIdx--,this.selectCursor(e.shiftKey);return}if(e.altKey&&e.key==="ArrowLeft"){e.preventDefault(),this.navigateBack();return}if(e.altKey&&e.key==="ArrowRight"){e.preventDefault(),this.navigateForward();return}if(e.key==="ArrowRight"){let l=this.currentChildren[this.cursorIdx];l&&(e.preventDefault(),this.navigateTo(l.id));return}if(e.key==="ArrowLeft"||e.key==="Backspace"){e.preventDefault(),this.navigateUp();return}if(e.key==="Escape"){if(e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),Date.now()-this.pickerEscapeAt<350||Date.now()-this.composerExitAt<400)return;if(this.plugin.noteClipboard&&this.plugin.noteClipboard.folder===this.noteFolder){this.plugin.clearNoteClipboard(),this.render();return}let l=this.firstSelectedId??(this.selection.size>0?this.selection.values().next().value:null);if(this.selection.clear(),this.firstSelectedId=null,l){let c=this.currentChildren.findIndex(d=>d.id===l);this.selection.add(l),this.lastSelected=l,c>=0&&(this.cursorIdx=c)}this.repaintSelectionClasses(),this.revealCursorRow();return}let o=X().bindings;if(K(e,o.pasteNotes)&&this.plugin.noteClipboard){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdPasteNotes();return}if(this.selection.size>0||this.cursorIdx>=0&&this.currentChildren[this.cursorIdx]){if(K(e,o.move)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdMovePicker();return}if(K(e,o.pickMove)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdInListPicker();return}if(K(e,o.merge)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdMerge();return}if(K(e,o.copy)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdCopy();return}if(K(e,o.copyNotes)&&!window.getSelection()?.toString()){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdCopyNotes();return}if(K(e,o.cutNotes)&&!window.getSelection()?.toString()){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdCutNotes();return}if(K(e,o.copyTree)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdCopyTree();return}if(K(e,o.copyOutline)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdCopyOutline();return}if(K(e,o.copyCodeBlock)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdCopyCodeBlock();return}if(K(e,o.openEditor)){if(e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),e.shiftKey){let l=this.tree.get(this.focusId);l?.file&&this.cmdOpenInEditor(l)}else this.cmdOpenInEditor();return}if(K(e,o.openTab)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdOpenInNewStashpadTab();return}if(K(e,o.split)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdSplit();return}if(K(e,o.clone)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdClone();return}if(K(e,o.forkNote)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdForkNote();return}if(K(e,o.insertTemplate)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdInsertTemplate();return}if(K(e,o.toggleExpand)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdToggleExpand();return}if(K(e,o.expandAll)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdExpandAll();return}if(K(e,o.collapseAll)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdCollapseAll();return}if(K(e,o.togglePin)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdTogglePin();return}if(K(e,o.listPin)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdToggleListPin();return}if(K(e,o.toggleTask)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdToggleTask();return}if(K(e,o.setDue)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdSetDue();return}}if(this.currentChildren.length>0){if(K(e,o.jumpToTop)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.jumpToTop();return}if(K(e,o.jumpToBottom)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.jumpToBottom();return}}let a=this.tree.get(this.focusId);if(a?.file){if(K(e,o.openEditor)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdOpenInEditor(a);return}if(K(e,o.openTab)){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),this.cmdOpenInNewStashpadTab(a);return}}};this.openSearchInstance=null;this.fmSyncUnsubscribe=null;this.completedState=new Map;this.taskTaggedState=new Map;this.listPinnedState=new Map;this.pendingFocusIds=null;this.onFileModify=e=>{!(e instanceof k.TFile)||e.extension!=="md"||e.path.startsWith(this.noteFolder+"/")&&(this.bodyRenderer.evict(e),this.scheduleSlugRename(e),this.scheduleAttachmentSync(e),this.authorship.noteModify(e),!this.deferDuringSyncBurst()&&this.debouncedRender())};this.onFileCreate=e=>{!(e instanceof k.TFile)||e.extension!=="md"||e.path.startsWith(this.noteFolder+"/")&&(this.deferDuringSyncBurst()||this.debouncedRender())};this.plugin=t,this.tree=new tn(this.app),this.log=t.newLog(),this.integrity=new cn(this.tree,this.log),this.order=new gi(this.app),this.sortStore=new on(this.app),this.fmSync=new an(this.app,()=>this.tree),this.tree.setOrderProvider(i=>{let s=this.noteFolder,n=this.sortStore.getMode(s,i),o=n==="manual"?this.order.getOrder(s,i):Sa(this,i,n);return this.hoistListPinned(i,o)}),this.debouncedRender=(0,k.debounce)(()=>{this.renderSuppressed()||this.render()},80),this.authorship=new An(this),this.dnd=new hn(this),this.bodyRenderer=new pn(this,this,this.plugin.renderCacheStore)}getViewType(){return z}getDisplayText(){let e=(this.noteFolder||"").trim(),t=e.split("/").pop()||e||"Stashpad";if(this.focusId&&this.focusId!==F){let i=this.tree.get(this.focusId);if(i){let s=this.titleForNode(i).trim(),n=s.length>40?s.slice(0,40)+"\u2026":s;if(n)return`${t} \u2014 ${n} \xB7 ${this.focusId}`}}return t}refreshHeaderTitle(){let e=this.getDisplayText();try{this.leaf.updateHeader?.()}catch{}let i=(this.headerEl??this.containerEl?.querySelector?.(".view-header"))?.querySelector?.(".view-header-title")??this.titleEl;i&&i.textContent!==e&&i.setText(e)}getIcon(){return this.plugin.getFolderIcon(this.noteFolder)??"list-tree"}async onOpen(){let e=this.contentEl;e.empty(),e.addClass("stashpad-scroll-host"),this.viewRoot=e.createDiv({cls:"stashpad-view"}),this.viewRoot.setAttribute("tabindex","0"),this.viewRoot.addEventListener("focusin",()=>Vs(this)),this.viewRoot.addEventListener("click",()=>Vs(this)),this.viewRoot.addEventListener("mouseup",l=>{l.button===3?(l.preventDefault(),this.navigateBack()):l.button===4&&(l.preventDefault(),this.navigateForward())}),this.viewRoot.addEventListener("auxclick",l=>{l.button===3?(l.preventDefault(),this.navigateBack()):l.button===4&&(l.preventDefault(),this.navigateForward())}),Vs(this),this.registerInterval(window.setInterval(()=>this.authorship.pruneContribMaps(),6e4));let t=null,i=()=>{t||(t=new k.Scope(this.app.scope),t.register([],"Escape",()=>{if(this.inListPicker)return this.inListPicker=null,this.pickerEscapeAt=Date.now(),this.repaintSelectionClasses(),!1;if(Date.now()-this.pickerEscapeAt<350||Date.now()-this.composerExitAt<400)return!1;if(this.selection.size>1){let l=this.firstSelectedId??this.selection.values().next().value??null;if(this.selection.clear(),this.firstSelectedId=null,l){let c=this.currentChildren.findIndex(d=>d.id===l);this.selection.add(l),this.lastSelected=l,c>=0&&(this.cursorIdx=c)}this.render(),this.revealCursorRow()}return!1}),this.app.keymap?.pushScope(t))},s=()=>{if(t){try{this.app.keymap?.popScope(t)}catch{}t=null}};this.viewRoot.addEventListener("focusin",i),this.viewRoot.addEventListener("focusout",l=>{let c=l.relatedTarget;c&&this.viewRoot&&this.viewRoot.contains(c)||s()}),this.register(()=>s()),this.detachTreeHook=this.tree.hookMetadataCache(()=>this.debouncedRender()),this.registerEvent(this.app.metadataCache.on("resolved",()=>this.scheduleTreeReconcile())),this.registerEvent(this.app.metadataCache.on("changed",l=>{if(l.extension!=="md")return;let c=this.app.metadataCache.getFileCache(l)?.frontmatter;this.completedState.set(l.path,!!c?.completed),this.taskTaggedState.set(l.path,this.taggedFromFm(c))})),this.registerEvent(this.app.vault.on("rename",(l,c)=>{for(let d of[this.completedState,this.taskTaggedState])d.has(c)&&(d.set(l.path,d.get(c)),d.delete(c))})),this.registerEvent(this.app.vault.on("delete",l=>{this.completedState.delete(l.path),this.taskTaggedState.delete(l.path);let c=l.path.lastIndexOf("/"),d=(c>=0?l.path.slice(0,c):"").replace(/\/+$/,"");l.path.endsWith(".md")&&d===this.noteFolder.replace(/\/+$/,"")&&(this.tree.rebuild(this.noteFolder),this.debouncedRender())})),this.detachSettings=fa(()=>{this.loadConfig();let l=this.plugin.settings.drafts?.[this.noteFolder]??"",c=this.composerInputEl?.value??"";l===""&&this.composerDraft!==""&&c===""?(this.composerDraft="",this.composerInputEl&&(this.composerInputEl.value="")):l===""&&c!==""&&(this.composerDraft=c),!!this.composerInputEl&&document.activeElement===this.composerInputEl&&(this.focusComposerOnNextRender=!0),this.debouncedRender()}),this.app.vault.on("modify",this.onFileModify),this.app.vault.on("create",this.onFileCreate),window.addEventListener("keydown",this.onDocKeyDown,!0),this.loadConfig();try{await this.bootstrapFolder()}catch(l){let c=l?.message??"";/already exists/i.test(c)||console.warn("[Stashpad] bootstrapFolder failed:",l)}this.tree.rebuild(this.noteFolder),this.installFmSyncActivityNotice(),this.backfillFrontmatterSync(),this.plugin.maybeSweepFolder(this.noteFolder),this.defaultCursorToLast(),this.refreshHeaderTitle(),await this.loadDraftsForFolder();try{let l=this.plugin.loadLastCursor(this.noteFolder);for(let[c,d]of l)this.lastCursorByFocus.set(c,d);this.lastSelectionByFocus=this.plugin.loadLastSelection(this.noteFolder),this.activeVersionByGroup=this.plugin.loadActiveVersions(this.noteFolder)}catch{}let n=this.lastCursorByFocus.get(this.focusId),o;n&&this.tree.get(n)?(o={kind:"scroll-to-id",id:n,align:"start"},this.pendingFocusIds=[n]):(this.scrollToBottomOnNextRender=!0,o={kind:"pin-bottom",until:"next-user-input"});let a=this.foldRestoredSelection(n);a&&(this.pendingFocusIds=a),this.render(o),this.scheduleSelectionRestore(),this.tinyMode&&(setTimeout(()=>this.applyTinyWindow(),1e3),setTimeout(()=>this.applyTinyWindow(),1500)),this.registerDomEvent(window,"beforeunload",()=>{this.flushDrafts(),this.stampSelectedCursor(!0)}),this.registerDomEvent(window,"blur",()=>{this.flushDrafts(),this.stampSelectedCursor(!0)}),X().focusComposerOnOpen?this.focusComposer():this.focusView(),this.registerEvent(this.app.workspace.on("active-leaf-change",l=>{l===this.leaf?X().focusComposerOnOpen?this.focusComposer():this.focusView():this.stickToListBottom=!1}))}renderSuppressed(){return this.bulkRenderDepth>0||this.bulkSettleTimer!=null||this.autoSyncDeferActive||this.plugin.rebootstrapInProgress||this.plugin.okfRebuildingFolders.has(this.noteFolder)}beginBulkRender(){this.bulkRenderDepth++,this.bulkSettleTimer!=null&&(window.clearTimeout(this.bulkSettleTimer),this.bulkSettleTimer=null)}endBulkRender(e=500){this.bulkRenderDepth>0&&this.bulkRenderDepth--,!(this.bulkRenderDepth>0)&&(this.bulkSettleTimer!=null&&window.clearTimeout(this.bulkSettleTimer),this.bulkSettleTimer=window.setTimeout(()=>{this.bulkSettleTimer=null,this.viewRoot?.isConnected&&(this.tree.rebuild(this.noteFolder),this.render())},e))}deferDuringSyncBurst(){if(this.plugin.rebootstrapInProgress)return!1;let e=Date.now();this.syncBurstTimes.push(e);let t=e-2e3;for(;this.syncBurstTimes.length&&this.syncBurstTimes[0]<t;)this.syncBurstTimes.shift();return!this.autoSyncDeferActive&&this.syncBurstTimes.length>=6&&(this.autoSyncDeferActive=!0,this.autoSyncNotice=new k.Notice("Stashpad: syncing \u2014 list updates paused until it settles\u2026",0)),this.autoSyncDeferActive?(this.autoSyncSettleTimer!=null&&window.clearTimeout(this.autoSyncSettleTimer),this.autoSyncSettleTimer=window.setTimeout(()=>this.endAutoSyncDefer(),1500),!0):!1}endAutoSyncDefer(){this.autoSyncSettleTimer=null,this.autoSyncDeferActive=!1,this.syncBurstTimes=[],this.autoSyncNotice?.hide(),this.autoSyncNotice=null,this.forceReconcileRender()}forceReconcileRender(){this.viewRoot?.isConnected&&(this.tree.rebuild(this.noteFolder),this.render())}scheduleTreeReconcile(){this.renderSuppressed()||this.treeReconcileTimer==null&&(this.treeReconcileTimer=window.setTimeout(()=>{if(this.treeReconcileTimer=null,!this.viewRoot?.isConnected)return;let e=this.noteFolder,t=e+"/",i=0;for(let s of this.app.vault.getMarkdownFiles()){let n=s.parent?.path?.replace(/\/+$/,"")??"";if(!(n===e||e!==""&&n.startsWith(t)))continue;let o=this.app.metadataCache.getFileCache(s)?.frontmatter?.id;typeof o=="string"&&o&&i++}i!==this.tree.fileBackedCount()&&(this.tree.rebuild(e),this.debouncedRender())},400))}focusView(){requestAnimationFrame(()=>{this.viewRoot?.isConnected&&(document.activeElement instanceof HTMLElement&&(document.activeElement.tagName==="INPUT"||document.activeElement.tagName==="TEXTAREA")||this.viewRoot.focus({preventScroll:!0}))})}focusComposer(){if(!X().focusComposerOnOpen)return;let e=()=>{if(!this.viewRoot?.isConnected||Date.now()<this.suppressComposerFocusUntil||Date.now()<this.plugin.suppressComposerAutofocusUntil)return;let t=document.activeElement;t&&(t.tagName==="INPUT"||t.tagName==="TEXTAREA")&&t!==this.composerInputEl||(t&&t.tagName==="BUTTON"&&this.viewRoot.contains(t),this.composerInputEl?.focus({preventScroll:!0}))};requestAnimationFrame(e),setTimeout(e,50),setTimeout(e,200)}async onClose(){pa(this),this.detachTreeHook?.(),this.detachSettings?.(),this.app.vault.off("modify",this.onFileModify),this.app.vault.off("create",this.onFileCreate),window.removeEventListener("keydown",this.onDocKeyDown,!0),this.listResizeObserver?.disconnect(),this.listResizeObserver=null,this.stickyRowObserver?.disconnect(),this.stickyRowObserver=null,this.bodyRenderer.dispose(),this.barOverflowRO?.disconnect(),this.barOverflowRO=null,this.composerNarrowObserver?.disconnect(),this.composerNarrowObserver=null,this.focusedMiniObserver?.disconnect(),this.focusedMiniObserver=null,this.treeReconcileTimer!=null&&(window.clearTimeout(this.treeReconcileTimer),this.treeReconcileTimer=null),this.autoSyncSettleTimer!=null&&(window.clearTimeout(this.autoSyncSettleTimer),this.autoSyncSettleTimer=null),this.autoSyncNotice?.hide(),this.autoSyncNotice=null,this.composerAutocomplete?.detach(),this.composerAutocomplete=null;for(let e of this.slugDebouncers.values())e.cancel();for(let e of this.attachmentDebouncers.values())e.cancel();this.authorship.dispose();try{await this.flushDrafts()}catch{}try{await this.order.flush(this.noteFolder)}catch{}try{await this.sortStore.flush(this.noteFolder)}catch{}try{await this.fmSync.flush()}catch{}this.stampSelectedCursor(!0),this.fmSyncUnsubscribe?.(),this.fmSyncUnsubscribe=null}setEphemeralState(e){let t=e;t?.focusId&&(this.focusId=t.focusId),t?.timeFilter&&(this.timeFilter=t.timeFilter)}getEphemeralState(){return{focusId:this.focusId,timeFilter:this.timeFilter}}getState(){return{...super.getState()??{},folderOverride:this.folderOverride,timeFilter:this.timeFilter,focusId:this.focusId,tagFilter:this.tagFilter,colorFilter:this.colorFilter,timeFilterCalendar:this.timeFilterCalendar,tinyMode:this.tinyMode,tinyAlwaysOnTop:this.tinyAlwaysOnTop,tinyOpacity:this.tinyOpacity,compactMode:this.compactMode,navBackStack:this.navBackStack,navForwardSnapshots:this.navForwardSnapshots}}async setState(e,t){let i=e??null;if(i){"folderOverride"in i&&(this.folderOverride=i.folderOverride??null),i.timeFilter&&(this.timeFilter=i.timeFilter),i.focusId&&(this.focusId=i.focusId),i.cursorId&&(this.pendingCursorId=i.cursorId),"tagFilter"in i&&(this.tagFilter=i.tagFilter??null),"colorFilter"in i&&(this.colorFilter=i.colorFilter??null),"timeFilterCalendar"in i&&(this.timeFilterCalendar=!!i.timeFilterCalendar),"tinyMode"in i&&(this.tinyMode=!!i.tinyMode),"tinyAlwaysOnTop"in i&&(this.tinyAlwaysOnTop=!!i.tinyAlwaysOnTop),typeof i.tinyOpacity=="number"&&Number.isFinite(i.tinyOpacity)&&(this.tinyOpacity=Math.min(1,Math.max(.3,i.tinyOpacity))),"compactMode"in i&&(this.compactMode=!!i.compactMode);let o=a=>a&&typeof a.folder=="string"&&typeof a.focusId=="string";Array.isArray(i.navBackStack)&&(this.navBackStack=i.navBackStack.filter(o)),Array.isArray(i.navForwardSnapshots)&&(this.navForwardSnapshots=i.navForwardSnapshots.filter(o))}let s=(this.plugin?.settings?.folder??"Stashpad").trim().replace(/^\/+|\/+$/g,""),n=this.folderOverride?.trim().replace(/^\/+|\/+$/g,"")||null;if(this.noteFolder=n||s||"Stashpad",await super.setState(e,t),this.refreshHeaderTitle(),this.viewRoot){this.loadConfig(),await this.bootstrapFolder(),this.tree.rebuild(this.noteFolder),this.backfillFrontmatterSync(),this.defaultCursorToLast(),this.draftsLoadedFor=null,this.composerDraft="",await this.loadDraftsForFolder(),this.lastCursorByFocus.clear(),this.lastSelectionByFocus.clear();try{let c=this.plugin.loadLastCursor(this.noteFolder);for(let[d,h]of c)this.lastCursorByFocus.set(d,h);this.lastSelectionByFocus=this.plugin.loadLastSelection(this.noteFolder),this.activeVersionByGroup=this.plugin.loadActiveVersions(this.noteFolder)}catch{}let o=this.lastCursorByFocus.get(this.focusId),a;o&&this.tree.get(o)?(this.pendingFocusIds=[o],a={kind:"scroll-to-id",id:o,align:"start"}):a={kind:"pin-bottom",until:"next-user-input"};let l=this.foldRestoredSelection(o);l&&(this.pendingFocusIds=l),this.render(a),this.scheduleSelectionRestore()}}focus(){this.viewRoot?.focus({preventScroll:!0})}foldRestoredSelection(e){let t=this.lastSelectionByFocus.get(this.focusId)??null;if(!t||!t.length)return null;let i=t.filter(s=>!!this.tree.get(s));return i.length?e&&i.includes(e)?[e,...i.filter(s=>s!==e)]:i:null}stampSelection(e=!1){let t=[...this.selection],i=()=>{try{this.plugin.saveLastSelection(this.noteFolder,this.focusId,t)}catch{}};if(this.stampSelectionTimer!=null&&(window.clearTimeout(this.stampSelectionTimer),this.stampSelectionTimer=null),e){i();return}this.stampSelectionTimer=window.setTimeout(()=>{this.stampSelectionTimer=null,i()},400)}scheduleSelectionRestore(){let e=this.lastSelectionByFocus.get(this.focusId);if(!e||!e.length)return;let t=()=>{let i=e.filter(a=>this.currentChildren.some(l=>l.id===a));if(!i.length)return;let s=[...this.selection],n=new Set(i);if(s.length<i.length&&s.every(a=>n.has(a))){this.selection.clear();for(let a of i)this.selection.add(a);this.firstSelectedId=i[0],this.lastSelected=i[i.length-1],this.repaintSelectionClasses()}};for(let i of[120,400,900,1600])window.setTimeout(t,i)}loadConfig(){let e=(this.plugin?.settings?.folder??"Stashpad").trim().replace(/^\/+|\/+$/g,""),i=this.folderOverride?.trim().replace(/^\/+|\/+$/g,"")||null||e||"Stashpad";i!==this.noteFolder?(this.noteFolder=i,this.tree.rebuild(this.noteFolder)):this.noteFolder=i}captureNavSnapshot(){return{folder:this.noteFolder,focusId:this.focusId}}recordNavState(e={}){let t=this.captureNavSnapshot(),i=this.navBackStack[this.navBackStack.length-1];i&&i.folder===t.folder&&i.focusId===t.focusId||(this.navBackStack.push(t),e.keepForward||(this.navForwardSnapshots=[]))}async setFolderOverride(e,t={}){let i=e?.trim().replace(/^\/+|\/+$/g,"")||null;if(i&&this.isReservedFolder(i)){new k.Notice(`"${i}" is a reserved Stashpad subfolder (imports/exports/attachments). Pick a different folder.`);return}if((i||null)!==(this.folderOverride||null)){t.skipHistory||this.recordNavState(),this.folderOverride=i,this.focusId=F,this.lastCursorByFocus.clear(),this.selection.clear(),this.cursorIdx=-1,this.lastSelected=null,this.composerDraft="",await this.flushDrafts(),this.draftsLoadedFor=null,this.loadConfig();try{await this.bootstrapFolder()}catch(s){let n=s?.message??"";/already exists/i.test(n)||console.warn("[Stashpad] bootstrapFolder failed:",s)}this.tree.rebuild(this.noteFolder),this.backfillFrontmatterSync(),this.plugin.maybeSweepFolder(this.noteFolder),this.defaultCursorToLast(),await this.loadDraftsForFolder();try{let s=this.app.workspace;typeof s.saveLayout=="function"?await s.saveLayout():this.app.workspace.requestSaveLayout()}catch{this.app.workspace.requestSaveLayout()}this.refreshHeaderTitle(),this.render()}}cmdOpenFolderPicker(){this.openFolderPicker()}openFolderPicker(){this.plugin.openFolderPicker()}listVaultFolders(){let e=[];for(let t of this.app.vault.getAllLoadedFiles())if(t instanceof k.TFolder){if(t.path==="/"||t.path===""||t.path.startsWith(".")||this.isReservedFolder(t.path))continue;e.push(t.path)}return e.sort((t,i)=>t.localeCompare(i))}isReservedFolder(e){let t=e.split("/").filter(Boolean).pop()??"";return t?new Set([this.plugin.settings.importDropFolder,this.plugin.settings.exportFolder,"_attachments","_processed","_authors"].map(s=>(s??"").trim().replace(/^\/+|\/+$/g,"")).filter(Boolean)).has(t):!1}persistFocus(){try{this.app.workspace.requestSaveLayout()}catch{}}cmdUndo(){let e=this.plugin.getUndoStack(this.noteFolder);if(!e.canUndo()){new k.Notice("Nothing to undo.");return}let t=e.peekUndoLabel(),i=this.plugin.notifications.recent()[0]?.category??"system";e.undo().then(()=>this.plugin.notifications.show({message:`Undid: ${t}`,kind:"info",category:i,folder:this.noteFolder})).catch(s=>this.plugin.notifications.show({message:`Undo failed: ${s.message}`,kind:"error",category:"system",folder:this.noteFolder}))}cmdRedo(){let e=this.plugin.getUndoStack(this.noteFolder);if(!e.canRedo()){new k.Notice("Nothing to redo.");return}let t=e.peekRedoLabel(),i=this.plugin.notifications.recent()[0]?.category??"system";e.redo().then(()=>this.plugin.notifications.show({message:`Redid: ${t}`,kind:"info",category:i,folder:this.noteFolder})).catch(s=>this.plugin.notifications.show({message:`Redo failed: ${s.message}`,kind:"error",category:"system",folder:this.noteFolder}))}async snapshotNotes(e,t){let i=[],s=new Set;for(let l of e)!l.file||s.has(l.file.path)||(s.add(l.file.path),i.push(l.file));let n=await Promise.all(i.map(l=>this.app.vault.read(l))),o=i.map((l,c)=>({path:l.path,content:n[c]})),a=[];if(t){let l=new Set,c=[];for(let h of n)for(let g of this.extractAttachments(this.stripFrontmatter(h))){let f=this.app.metadataCache.getFirstLinkpathDest(g,"");f&&!l.has(f.path)&&(l.add(f.path),c.push(f))}let d=await Promise.all(c.map(h=>this.app.vault.readBinary(h)));for(let h=0;h<c.length;h++)a.push({path:c[h].path,data:d[h]})}return{notes:o,attachments:a}}async restoreSnapshots(e,t){for(let s of e.attachments)try{await this.app.vault.adapter.exists(s.path)||await this.app.vault.createBinary(s.path,s.data)}catch{}for(let s of e.notes)try{await this.app.vault.adapter.exists(s.path)||await this.app.vault.create(s.path,s.content)}catch{}let i=()=>{t&&t.some(n=>this.tree.get(n))&&(this.pendingFocusIds=t.slice())};i(),this.tree.rebuild(this.noteFolder),this.render({kind:"follow-cursor"}),setTimeout(()=>{i(),this.tree.rebuild(this.noteFolder),this.render({kind:"follow-cursor"})},100),setTimeout(()=>{i(),this.tree.rebuild(this.noteFolder),this.render({kind:"follow-cursor"})},400),setTimeout(()=>{for(let s of e.notes){let n=this.tree.idForPath(s.path);n&&this.fmSync.schedule(n)}},500)}async trashNotesAndAttachments(e){let t=new Set;for(let i of e.notes){let s=this.tree.idForPath(i.path);if(!s)continue;let n=this.tree.get(s);n?.parent&&t.add(n.parent)}for(let i of e.notes){let s=this.app.vault.getAbstractFileByPath(i.path);if(s)try{await this.app.fileManager.trashFile(s)}catch{}}for(let i of e.attachments){let s=this.app.vault.getAbstractFileByPath(i.path);if(s)try{await this.app.fileManager.trashFile(s)}catch{}}this.tree.rebuild(this.noteFolder),this.render();for(let i of t)this.fmSync.scheduleParentOfDeleted(i)}async loadDraftsForFolder(){if(this.draftsLoadedFor===this.noteFolder)return;this.draftsLoadedFor=this.noteFolder;let e=this.plugin.settings.drafts??{};this.composerDraft=e[this.noteFolder]??"",console.debug("[Stashpad] loadDrafts",{folder:this.noteFolder,has:!!e[this.noteFolder],available:Object.keys(e)})}async saveDraft(e){try{let t=this.noteFolder;if((this.plugin.settings.drafts?.[t]??"")===e)return;let s={...this.plugin.settings.drafts??{}};e.length===0?delete s[t]:s[t]=e,this.plugin.settings.drafts=s,e.length===0?await this.plugin.saveSettings():await this.plugin.persistSettingsQuiet()}catch(t){console.warn("Stashpad: drafts save failed",t)}}async recordLastSubmitted(e){try{let t={...this.plugin.settings.lastSubmitted??{}};t[this.noteFolder]=e,this.plugin.settings.lastSubmitted=t,await this.plugin.persistSettingsQuiet()}catch{}}hasRestorableDraft(){let e=this.plugin.settings.drafts?.[this.noteFolder];if(!e||!e.trim())return!1;let t=this.plugin.settings.lastSubmitted?.[this.noteFolder];return!(t&&t===e)}syncComposerDraftForFocus(){}async flushDrafts(){this.composerInputEl?await this.saveDraft(this.composerInputEl.value):await this.saveDraft(this.composerDraft)}timeFilterCutoff(){if(this.timeFilter==="all")return null;if(this.timeFilterCalendar){let t=(0,k.moment)();switch(this.timeFilter){case"day":return t.startOf("day").valueOf();case"week":return t.startOf("isoWeek").valueOf();case"month":return t.startOf("month").valueOf();case"year":return t.startOf("year").valueOf()}}let e=Date.now();switch(this.timeFilter){case"day":return e-864e5;case"week":return e-7*864e5;case"month":return e-30*864e5;case"year":return e-365*864e5}return null}allowedByBases(){return null}currentViewMode(){return this.plugin.settings.viewModes?.[this.noteFolder]??"nested"}currentIncludeAttachments(){return!!this.plugin.settings.includeAttachmentsInEverything?.[this.noteFolder]}async setIncludeAttachments(e){let t={...this.plugin.settings.includeAttachmentsInEverything??{}};e?t[this.noteFolder]=!0:delete t[this.noteFolder],this.plugin.settings.includeAttachmentsInEverything=t,await this.plugin.saveSettings()}currentEncryptionFilter(){return this.plugin.settings.encryptionFilter?.[this.noteFolder]??"all"}async setEncryptionFilter(e){let t={...this.plugin.settings.encryptionFilter??{}};e==="all"?delete t[this.noteFolder]:t[this.noteFolder]=e,this.plugin.settings.encryptionFilter=t,await this.plugin.saveSettings()}currentHideChildless(){return!!this.plugin.settings.hideChildlessNotes?.[this.noteFolder]}async setHideChildless(e){let t={...this.plugin.settings.hideChildlessNotes??{}};e?t[this.noteFolder]=!0:delete t[this.noteFolder],this.plugin.settings.hideChildlessNotes=t,await this.plugin.saveSettings()}currentHideCompleted(){return!!this.plugin.settings.hideCompletedNotes?.[this.noteFolder]}async setHideCompleted(e){let t={...this.plugin.settings.hideCompletedNotes??{}};e?t[this.noteFolder]=!0:delete t[this.noteFolder],this.plugin.settings.hideCompletedNotes=t,await this.plugin.saveSettings()}currentAttachmentsOnly(){return!!this.plugin.settings.attachmentsOnlyNotes?.[this.noteFolder]}async setAttachmentsOnly(e){let t={...this.plugin.settings.attachmentsOnlyNotes??{}};e?t[this.noteFolder]=!0:delete t[this.noteFolder],this.plugin.settings.attachmentsOnlyNotes=t,await this.plugin.saveSettings()}nodeHasAttachment(e){if(!e.file)return!1;let t=this.app.metadataCache.getFileCache(e.file)?.frontmatter?.attachments;return Array.isArray(t)&&t.length>0}hasAttachmentInSubtree(e){if(this.nodeHasAttachment(e))return!0;for(let t of this.tree.getChildren(e.id))if(this.hasAttachmentInSubtree(t))return!0;return!1}hasIncompleteDescendant(e){for(let t of e.children){let i=this.tree.get(t);if(i&&(!this.isCompleted(i)||this.hasIncompleteDescendant(i)))return!0}return!1}collectEmbeddedAttachmentPaths(){let e=new Set,t=this.noteFolder,i=this.app.vault.getAbstractFileByPath(t);if(!(i instanceof k.TFolder))return e;let s=[i];for(;s.length;){let n=s.pop();for(let o of n.children){if(o instanceof k.TFolder){s.push(o);continue}if(!(o instanceof k.TFile)||o.extension!=="md")continue;let a=this.app.metadataCache.getFileCache(o)?.frontmatter;if(!(!a||!Array.isArray(a.attachments)))for(let l of a.attachments){if(typeof l!="string")continue;let c=Rt(l),d=this.app.metadataCache.getFirstLinkpathDest(c,o.path);d?e.add(d.path):e.add(c)}}}return e}collectFileItems(e){let t=this.noteFolder,i=this.app.vault.getAbstractFileByPath(t);if(!(i instanceof k.TFolder))return[];let s=this.currentIncludeAttachments(),n=s?new Set:this.collectEmbeddedAttachmentPaths(),o=new Set(["_authors","_imports","_exports","_processed","_attachments","_archive",".archive"]),a=[],l=[i];for(;l.length;){let c=l.pop();for(let d of c.children){if(d instanceof k.TFolder){let h=d.name;if(c===i&&o.has(h)||c===i&&h==="_attachments"&&!s)continue;l.push(d);continue}d instanceof k.TFile&&d.extension!=="md"&&(d.name===".stashpad-order.json"||d.name===".stashpad-sort.json"||!s&&n.has(d.path)||a.push(d))}}return a}populateListBody(e,t){if(this.lastListWidth=e.clientWidth||this.lastListWidth,this.bodyRenderer.arm(),this.pendingCursorId){let d=this.currentChildren.findIndex(h=>h.id===this.pendingCursorId);d>=0&&(this.cursorIdx=d,this.selection.clear(),this.selection.add(this.pendingCursorId),this.lastSelected=this.pendingCursorId,this.pendingCursorId=null)}t.file&&k.Platform.isMobile&&!this.compactMode&&(this.renderFocusedHeaderMini(e,t),this.renderFocusedHeader(e,t));let i=this.currentViewMode(),s=i==="everything"?this.collectFileItems(t.id):[],n=d=>d.created&&Number.isFinite(Date.parse(d.created))?Date.parse(d.created):Number.POSITIVE_INFINITY,o=this.currentEncryptionFilter(),a;if(o==="unlocked")a=[];else if(i==="nested")a=this.plugin.lockedSubtreesFor(this.noteFolder,t.id);else{let d=new Set([t.id,...this.currentChildren.map(g=>g.id)]),h=new Set;a=[];for(let g of d)for(let f of this.plugin.lockedSubtreesFor(this.noteFolder,g))h.has(f.blob)||(h.add(f.blob),a.push(f))}let l=a.map(d=>({lk:d,ts:n(d)})).sort((d,h)=>d.ts-h.ts),c=i==="nested"&&this.sortStore.getMode(this.noteFolder,t.id)==="manual";if(this.currentChildren.length===0&&s.length===0&&l.length===0)e.createDiv({cls:"stashpad-empty",text:"No notes here yet. Type below to add one."});else if(c&&l.length>0){let d=new Map(this.currentChildren.map((u,I)=>[u.id,I])),h=new Map;for(let{lk:u}of l)u.rootId&&h.set(u.rootId,u);let g=this.currentChildren.map(u=>u.id),f=l.map(({lk:u})=>u).filter(u=>u.rootId).sort((u,I)=>n(I)-n(u)),p=!0;for(;f.length&&p;){p=!1;for(let u=0;u<f.length;u++){let I=f[u],m=I.prevSibling??null,C;if(m==null)C=0;else{let B=g.indexOf(m);if(B<0)continue;C=B+1}g.splice(C,0,I.rootId),f.splice(u,1),u--,p=!0}}for(let u of f)g.push(u.rootId);for(let u of g){let I=d.get(u);if(I!==void 0)this.renderNote(e,this.currentChildren[I],I);else{let m=h.get(u);m&&this.renderLockedPlaceholder(e,m)}}for(let u of s.slice().sort((I,m)=>I.stat.ctime-m.stat.ctime))this.renderFileRow(e,u)}else if(s.length===0){let d=0;for(let h=0;h<this.currentChildren.length;h++){let g=Number.isFinite(Date.parse(this.currentChildren[h].created))?Date.parse(this.currentChildren[h].created):0;for(;d<l.length&&l[d].ts<=g;)this.renderLockedPlaceholder(e,l[d].lk),d++;this.renderNote(e,this.currentChildren[h],h)}for(;d<l.length;)this.renderLockedPlaceholder(e,l[d].lk),d++}else{let d=[...this.currentChildren.map((h,g)=>({kind:"note",ts:Number.isFinite(Date.parse(h.created))?Date.parse(h.created):0,idx:g})),...s.map(h=>({kind:"file",ts:h.stat.ctime,file:h})),...l.map(h=>({kind:"lock",ts:h.ts,lk:h.lk}))];d.sort((h,g)=>h.ts-g.ts);for(let h of d)h.kind==="note"?this.renderNote(e,this.currentChildren[h.idx],h.idx):h.kind==="file"?this.renderFileRow(e,h.file):this.renderLockedPlaceholder(e,h.lk)}t.file&&k.Platform.isMobile&&!this.compactMode&&this.installFocusedMiniObserver(e)}renderLockedPlaceholder(e,t){let i=e.createDiv({cls:"stashpad-locked-row"});t.created&&i.createSpan({cls:"stashpad-note-time stashpad-locked-time",text:this.formatTime(t.created)});let s=i.createSpan({cls:"stashpad-locked-icon"});(0,k.setIcon)(s,"lock");let n=!t.title;i.createSpan({cls:"stashpad-locked-title",text:n?"Locked note":t.title}),i.createSpan({cls:"stashpad-locked-count",text:t.count>1?`${t.count} notes \xB7 locked`:"locked"});let o=i.createEl("button",{cls:"stashpad-locked-unlock",text:"Unlock"});(0,k.setIcon)(o.createSpan({cls:"stashpad-btn-icon"}),"unlock");let a=async c=>{c.preventDefault(),c.stopPropagation();let d=(this.plugin.settings.lockedSubtrees??[]).find(u=>u.blob===t.blob),h=t.rootId??d?.rootId??null,g=t.parentId??d?.parentId??F,f=t.prevSibling??d?.prevSibling??null;if(await this.plugin.unlockBundleAt(t.blob)){if(this.selection.clear(),this.lastSelected=null,h){let u=this.order.getOrder(this.noteFolder,g),I=this.sortStore.getMode(this.noteFolder,g)==="manual";if(u.length===0&&I&&(u=this.tree.getChildren(g).map(m=>m.id)),u.length>0){let m=u.filter(B=>B!==h),C=f&&m.includes(f)?m.indexOf(f)+1:0;m.splice(Math.max(0,C),0,h),this.order.setOrder(this.noteFolder,g,m),this.order.flush(this.noteFolder)}this.pendingCursorId=h}this.render()}};o.onclick=a;let l=c=>{c.preventDefault(),c.stopPropagation();let d=new k.Menu;if(d.addItem(h=>h.setTitle("Decrypt (unlock)").setIcon("unlock").onClick(()=>void a(c))),!k.Platform.isMobile){let h=k.Platform.isMacOS?"Finder":k.Platform.isWin?"File Explorer":"file manager";d.addItem(g=>g.setTitle(`Show in ${h}`).setIcon("folder-search").onClick(()=>{try{let f=window.require?.("electron")?.shell,p=this.app.vault.adapter?.getFullPath?.(t.blob);p&&f?.showItemInFolder&&f.showItemInFolder(p)}catch(f){console.warn("[Stashpad] showItemInFolder failed",f)}}))}d.addItem(h=>h.setTitle("Export to .stash\u2026").setIcon("package").onClick(()=>{this.plugin.exportLockedSubtree(t.blob)})),d.addItem(h=>h.setTitle("Copy encrypted file path").setIcon("copy").onClick(()=>{let g=t.blob;if(!k.Platform.isMobile)try{g=this.app.vault.adapter?.getFullPath?.(t.blob)||t.blob}catch{}navigator.clipboard.writeText(g),new k.Notice("Path copied.")})),d.showAtMouseEvent(c)};if(i.oncontextmenu=l,k.Platform.isMobile){let c=i.createEl("button",{cls:"stashpad-locked-menu"});(0,k.setIcon)(c,"more-vertical"),c.setAttr("aria-label","Locked note menu"),c.onclick=d=>l(d)}i.setAttr("aria-label",`${n?"Locked note":`Locked: ${t.title}`}. Use the Unlock button to decrypt.`)}refreshList(){if(!this.listEl){this.render();return}let e=this.tree.get(this.focusId)??this.tree.getRoot();this.currentChildren=this.filterChildren(this.collectViewItems(e.id)),this.cursorIdx>=this.currentChildren.length&&(this.cursorIdx=this.currentChildren.length-1);let t=this.listEl.scrollTop+this.listEl.clientHeight>=this.listEl.scrollHeight-2,i=this.listEl.scrollTop;this.listEl.empty(),this.populateListBody(this.listEl,e),t?this.listEl.scrollTop=this.listEl.scrollHeight:this.listEl.scrollTop=i}renderFileRow(e,t){if(this.currentEncryptionFilter()==="locked")return;let i=e.createDiv({cls:"stashpad-file-row"});i.dataset.path=t.path,i.createDiv({cls:"stashpad-file-meta"}).createSpan({cls:"stashpad-file-time",text:this.formatTime(new Date(t.stat.ctime).toISOString())});let n=i.createDiv({cls:"stashpad-file-body"});n.createSpan({cls:"stashpad-file-name",text:t.name}),n.createSpan({cls:"stashpad-file-ext",text:t.extension.toUpperCase()}),i.title=`${t.path} \u2014 click to open`,i.onclick=o=>{o.preventDefault(),this.app.workspace.openLinkText(t.path,"",!1)}}async setViewMode(e){let t={...this.plugin.settings.viewModes??{}};e==="nested"?delete t[this.noteFolder]:t[this.noteFolder]=e,this.plugin.settings.viewModes=t,await this.plugin.saveSettings()}collectViewItems(e){let t=this.currentViewMode(),i=this.currentHideChildless(),s=this.tree.getChildren(e),n=i?s.filter(l=>l.children.length>0):s;if(t==="nested")return n;let o=[],a=l=>{o.push(l);for(let c of this.tree.getChildren(l.id))a(c)};for(let l of n)a(l);return o}filterChildren(e){e=this.collapseVersions(e);let t=this.timeFilterCutoff(),i=this.tagFilter?.toLowerCase(),s=this.colorFilter?.toLowerCase()??null,n=this.currentHideCompleted(),o=this.currentAttachmentsOnly(),a=this.importedOnly,l=this.authorFilter;return!t&&!i&&!s&&!n&&!o&&!a&&!l?e:e.filter(c=>{if(a&&(!c.file||this.app.metadataCache.getFileCache(c.file)?.frontmatter?.imported!==!0))return!1;if(l){if(!c.file)return!1;let d=ct(this.app.metadataCache.getFileCache(c.file)?.frontmatter?.author);if(!d||d.id!==l)return!1}if(t&&c.created){let d=Date.parse(c.created);if(!Number.isNaN(d)&&d<t)return!1}if(i){if(!c.file)return!1;if(this.tagFilter===Gt){if(!this.nodeHasAnyTag(c))return!1}else if(this.tagFilter===Lt){if(this.nodeHasAnyTag(c))return!1}else if(!this.nodeHasTag(c,i))return!1}return!(s&&(this.colorForNode(c)?.toLowerCase()??null)!==s||n&&this.isCompleted(c)&&!this.hasIncompleteDescendant(c)||o&&!this.hasAttachmentInSubtree(c))})}collapseVersions(e){if(!this.plugin.settings.enableSheetVersions)return e;let t=new Map,i=!1;for(let n of e){let o=Be(this.app,n);if(!xi(o))continue;i=!0;let a=ze(o),l=t.get(a);l?l.push(n):t.set(a,[n])}if(!i)return e;let s=new Set;for(let[n,o]of t){let a=this.activeVersionNode(n,o);a&&s.add(a.id)}return e.filter(n=>xi(Be(this.app,n))?s.has(n.id):!0)}activeVersionNode(e,t){let i=this.activeVersionByGroup.get(e);if(i){let s=t.find(n=>n.id===i);if(s)return s}return yr(this.app,t)}setActiveVersion(e,t){this.activeVersionByGroup.set(e,t),this.plugin.saveActiveVersion(this.noteFolder,e,t),this.render()}nodeHasAnyTag(e){if(!e.file)return!1;let t=this.app.metadataCache.getFileCache(e.file);if(!t)return!1;if(t.tags&&t.tags.length>0)return!0;let i=t.frontmatter?.tags;return!!(i&&(Array.isArray(i)?i:[i]).some(n=>typeof n=="string"&&n.trim().length>0))}nodeHasTag(e,t){if(!e.file)return!1;let i=this.app.metadataCache.getFileCache(e.file);if(!i)return!1;if(i.tags){for(let n of i.tags)if((n.tag||"").replace(/^#/,"").toLowerCase()===t)return!0}let s=i.frontmatter?.tags;if(s){let n=Array.isArray(s)?s:[s];for(let o of n)if(typeof o=="string"&&o.replace(/^#/,"").toLowerCase()===t)return!0}return!1}collectFolderTags(){let e=new Map,t=this.tree.getChildren(this.focusId);for(let s of t){if(!s.file)continue;let n=this.app.metadataCache.getFileCache(s.file);if(n?.tags)for(let a of n.tags){let l=(a.tag||"").replace(/^#/,"");l&&e.set(l,(e.get(l)??0)+1)}let o=n?.frontmatter?.tags;if(o){let a=Array.isArray(o)?o:[o];for(let l of a){if(typeof l!="string")continue;let c=l.replace(/^#/,"");c&&e.set(c,(e.get(c)??0)+1)}}}let i=[...e.entries()].map(([s,n])=>({raw:s,count:n,label:this.formatTagLabel(s)}));return i.sort((s,n)=>n.count-s.count||s.label.localeCompare(n.label)),i}formatTagLabel(e){return e&&e.split("/").map(t=>t.replace(/([a-z0-9])([A-Z])/g,"$1 $2").split(/[-_\s]+/).filter(Boolean).map(n=>n.charAt(0).toUpperCase()+n.slice(1)).join(" ")).join(" / ")}collectFolderColors(){let e=new Map,t=this.tree.getChildren(this.focusId);for(let s of t){let n=this.colorForNode(s);if(!n)continue;let o=n.toLowerCase();e.set(o,(e.get(o)??0)+1)}let i=[...e.entries()].map(([s,n])=>({hex:s,count:n}));return i.sort((s,n)=>n.count-s.count||s.hex.localeCompare(n.hex)),i}defaultCursorToLast(){let e=this.tree.get(this.focusId)??this.tree.getRoot(),t=this.filterChildren(this.collectViewItems(e.id));this.cursorIdx=t.length-1,this.selection.clear(),t.length>0&&(this.selection.add(t[t.length-1].id),this.lastSelected=t[t.length-1].id)}stampSelectedCursor(e=!1){this.stampSelection(e);let i=this.currentChildren[this.cursorIdx]?.id??this.lastSelected;if(!i)return;this.lastCursorByFocus.set(this.focusId,i);let s=()=>{let n=this.lastCursorByFocus.get(this.focusId);n&&this.plugin.saveLastCursor(this.noteFolder,this.focusId,n)};if(e){this.stampLastCursorTimer!=null&&window.clearTimeout(this.stampLastCursorTimer),this.stampLastCursorTimer=null,s();return}this.stampLastCursorTimer!=null&&window.clearTimeout(this.stampLastCursorTimer),this.stampLastCursorTimer=window.setTimeout(()=>{this.stampLastCursorTimer=null,s()},400)}captureScrollAnchor(){let e=this.listEl;if(!e)return null;let t=e.getBoundingClientRect().top,i=Array.from(e.querySelectorAll(".stashpad-note"));if(i.length===0)return null;let s=null;for(let n of i){let o=n.dataset.id;if(!o)continue;let a=n.getBoundingClientRect().top-t;if(a>=0){s={id:o,offsetFromListTop:a};break}s={id:o,offsetFromListTop:a}}return s}restoreScrollAnchor(e,t){let i=this.listEl;if(i){if(e){let s=i.querySelector(`[data-id="${e.id}"]`);if(s){let n=i.getBoundingClientRect().top,o=s.getBoundingClientRect().top-n;i.scrollTop+=o-e.offsetFromListTop;return}}t>0&&(i.scrollTop=t)}}render(e){Se.enabled&&(this._renderT0=performance.now()),this.pendingRenderPolicy=e??{kind:"preserve"},this.loadConfig();let t=this.viewRoot,i=this.listEl?.scrollTop??0,n=(e??{kind:"preserve"}).kind==="preserve"?this.captureScrollAnchor():null;!!this.composerInputEl&&document.activeElement===this.composerInputEl&&(this.focusComposerOnNextRender=!0,this.pendingComposerCaret=this.composerInputEl?.selectionStart??null);let a=!!this.listEl&&(this.stickToListBottom||this.listEl.scrollTop+this.listEl.clientHeight>=this.listEl.scrollHeight-2);t.empty(),t.toggleClass("is-mobile",k.Platform.isMobile),t.toggleClass("is-tiny",this.tinyMode),t.toggleClass("is-compact",this.compactMode);let l=this.containerEl.closest(".workspace-leaf");l&&(l.classList.toggle("stashpad-is-tiny",this.tinyMode),l.classList.toggle("stashpad-is-compact",this.compactMode));let c=this.containerEl.closest(".workspace-tabs");if(c&&c.classList.toggle("stashpad-has-tiny",this.tinyMode),this.tinyMode)this.renderTinyHeader(t);else{let p=this.compactMode&&k.Platform.isMobile;(!this.compactMode||k.Platform.isMobile)&&this.renderTimeFilterBar(t),p||this.renderBreadcrumb(t)}let d=this.tree.get(this.focusId)??this.tree.getRoot();d.file&&!k.Platform.isMobile&&!this.tinyMode&&!this.compactMode&&this.renderFocusedHeader(t,d),this.currentChildren=this.filterChildren(this.collectViewItems(d.id));let h=!1;if(this.autoSelectNewest&&this.currentChildren.length>0){let p=this.currentChildren[this.currentChildren.length-1];this.cursorIdx=this.currentChildren.length-1,this.selection.clear(),this.selection.add(p.id),this.lastSelected=p.id,this.autoSelectNewest=!1,h=!0}else if(this.pendingFocusIds){let p=this.pendingFocusIds;this.pendingFocusIds=null,this.selection.clear();let u=-1;for(let I of p){let m=this.currentChildren.findIndex(C=>C.id===I);m>=0&&(this.selection.add(I),u<0&&(u=m))}this.cursorIdx=u,u>=0&&(this.lastSelected=p.find(I=>this.currentChildren.some(m=>m.id===I))??null)}else this.cursorIdx>=this.currentChildren.length&&(this.cursorIdx=this.currentChildren.length-1);let g=t.createDiv({cls:"stashpad-list"});if(this.listEl=g,this.dnd.attachListDnD(g),this.populateListBody(g,d),this.renderComposer(t),k.Platform.isMobile&&this.renderMobileNav(t),this._renderT0!=null&&(Se.record("render.total",performance.now()-this._renderT0),this._renderT0=null),this.plugin.notifyStashpadContentChanged(),h&&this.plugin.notifyStashpadSelectionChanged(),this.focusComposerOnNextRender){this.focusComposerOnNextRender=!1;let p=this.pendingComposerCaret;this.pendingComposerCaret=null;let u=this.composerInputEl;if(u&&u.isConnected){if(u.focus({preventScroll:!0}),p!=null){let I=Math.min(p,u.value.length);try{u.setSelectionRange(I,I)}catch{}}}else requestAnimationFrame(()=>{let I=this.composerInputEl;if(I&&(I.focus({preventScroll:!0}),p!=null)){let m=Math.min(p,I.value.length);try{I.setSelectionRange(m,m)}catch{}}})}let f=this.pendingRenderPolicy;if(this.pendingRenderPolicy=null,f&&this.listEl){let p=this.scrollToBottomOnNextRender;switch(this.scrollToBottomOnNextRender=!1,f.kind){case"preserve":p?this.scrollListToBottom():a?this.scrollListToBottom():this.restoreScrollAnchor(n,i);break;case"pin-bottom":this.scrollListToBottom();break;case"restore":{let u=f.scrollTop,I=this.listEl,m=()=>{this.suppressScrollSave=!0;let C=Math.max(0,I.scrollHeight-I.clientHeight);I.scrollTop=Math.min(u,C),Promise.resolve().then(()=>{this.suppressScrollSave=!1})};m(),requestAnimationFrame(m),setTimeout(m,60),setTimeout(m,200),setTimeout(m,600);break}case"follow-cursor":i>0&&(this.listEl.scrollTop=i),this.revealCursorRow();break;case"scroll-to-id":{let u=f.id,I=f.align,m=this.listEl,C=()=>{this.suppressScrollSave=!0;let B=m.querySelector(`[data-id="${u}"]`);B&&B.scrollIntoView({block:I,behavior:"auto"}),Promise.resolve().then(()=>{this.suppressScrollSave=!1})};C(),requestAnimationFrame(C),setTimeout(C,60),setTimeout(C,200),setTimeout(C,600),setTimeout(()=>{this.suppressScrollSave=!1},700);break}}}else this.scrollToBottomOnNextRender?(this.scrollToBottomOnNextRender=!1,this.scrollListToBottom()):this.listEl&&a?this.scrollListToBottom():this.listEl&&i>0&&(this.listEl.scrollTop=i);if(this.stampSelectedCursor(),this.listEl){this.listResizeObserver?.disconnect();let p=this.listEl,u=p.scrollTop,I=new ResizeObserver(()=>{if(Date.now()<this.keyboardTransitionUntil)return;if(this.stickToListBottom){p.scrollTop=p.scrollHeight,u=p.scrollTop;return}let C=Math.max(0,p.scrollHeight-p.clientHeight);p.scrollTop<u&&u<=C?p.scrollTop=u:u=p.scrollTop});I.observe(p),this.listResizeObserver=I,p.addEventListener("wheel",C=>{C.deltaY<0&&(this.stickToListBottom=!1)},{passive:!0});let m=0;p.addEventListener("touchstart",C=>{m=C.touches[0]?.clientY??0},{passive:!0}),p.addEventListener("touchmove",C=>{let B=C.touches[0]?.clientY??m;B>m&&(this.stickToListBottom=!1),m=B},{passive:!0}),p.addEventListener("mousedown",()=>{this.stickToListBottom=!1}),p.addEventListener("keydown",()=>{this.stickToListBottom=!1})}}refreshFolderSwitcherIcon(){let e=this.containerEl.querySelector(".stashpad-folder-btn .stashpad-btn-icon");e&&(e.empty(),(0,k.setIcon)(e,this.plugin.getFolderIcon(this.noteFolder)??"folder"))}renderTimeFilterBar(e){let t=e.createDiv({cls:"stashpad-time-filter-bar"});if(!k.Platform.isMobile){let c=t.createEl("button",{cls:"stashpad-folder-btn"}),d=!!this.folderOverride,h=this.noteFolder.split("/").pop()||this.noteFolder||"Stashpad";(0,k.setIcon)(c.createSpan({cls:"stashpad-btn-icon"}),this.plugin.getFolderIcon(this.noteFolder)??"folder"),c.createSpan({text:h,cls:"stashpad-btn-text"}),c.title=d?`Folder (override): ${this.noteFolder} Click to change or revert to default.`:`Folder: ${this.noteFolder} -Click to override for this tab.`,d&&c.addClass("is-override"),c.onclick=u=>{u.preventDefault(),this.openFolderPicker()};let g=t.createEl("button",{cls:"stashpad-search-btn"});Se(g,"search","\u{1F50D}"),g.title="Search notes (Mod+F)",g.onclick=u=>{u.preventDefault(),this.openSearchModal()}}F.Platform.isMobile?(this.renderActionsCluster(t),this.renderMobileFiltersButton(t)):(this.renderTagFilterDropdown(t),this.renderColorFilterDropdown(t),this.renderSortDropdown(t),this.renderViewDropdown(t));let i=t.createDiv({cls:"stashpad-time-filter-btns"}),s=i.createEl("button",{cls:"stashpad-time-filter-btn stashpad-time-filter-cal"});(0,F.setIcon)(s,this.timeFilterCalendar?"calendar":"history"),s.title=this.timeFilterCalendar?"Calendar mode: filters use start-of-day/week/month/year. Click for rolling windows.":"Rolling mode: filters look back N days from now. Click for calendar boundaries.",this.timeFilterCalendar&&s.addClass("is-active"),s.onclick=c=>{c.preventDefault(),this.timeFilterCalendar=!this.timeFilterCalendar,this.persistFocus(),this.render()};for(let c of as){let d=this.timeFilterCalendar?c.calShort:c.rollShort,h=this.timeFilterCalendar?c.calLong:c.rollLong,g=i.createEl("button",{cls:"stashpad-time-filter-btn",text:d});g.title=h,this.timeFilter===c.key&&g.addClass("is-active"),g.onclick=u=>{u.preventDefault(),this.setTimeFilter(c.key)}}let n=t.createEl("select",{cls:"stashpad-time-filter-select"});for(let c of as){let d=this.timeFilterCalendar?c.calLong:c.rollLong,h=n.createEl("option",{text:d});h.value=c.key,this.timeFilter===c.key&&(h.selected=!0)}n.onchange=()=>this.setTimeFilter(n.value);let o=t.createDiv({cls:"stashpad-view-mode-btns"});if(!F.Platform.isMobile){let c=o.createEl("button",{cls:"stashpad-view-mode-btn"});(0,F.setIcon)(c,"minimize-2"),c.title="Tiny mode \u2014 open this tab in a small always-on-top-capable popout window.",c.onclick=d=>{d.preventDefault(),this.plugin.openTinyWindow()}}let a=o.createEl("button",{cls:"stashpad-view-mode-btn"});if((0,F.setIcon)(a,this.compactMode?"panel-top":"rows-2"),a.title=this.compactMode?"Compact mode is ON \u2014 click to restore full chrome.":"Compact mode \u2014 hide the filter row + focused header; keep breadcrumb + list + composer.",this.compactMode&&a.addClass("is-active"),a.onclick=c=>{c.preventDefault(),this.toggleCompactMode()},F.Platform.isMobile)return;let l=o.createEl("button",{cls:"stashpad-view-mode-btn"});(0,F.setIcon)(l,"external-link"),l.title=X().popoutDuplicates?"Duplicate this Stashpad tab into a new Obsidian window. (Toggle in Settings \u2192 Open in new window \u2014 duplicate tab.)":"Move this Stashpad tab to a new Obsidian window. (Toggle in Settings \u2192 Open in new window \u2014 duplicate tab.)",l.onclick=c=>{c.preventDefault();let d=X().popoutDuplicates;try{let h=this.app.workspace;if(d){let g=this.leaf.getViewState(),u=h.openPopoutLeaf?.();u?u.setViewState({...g,active:!0}):new F.Notice("Stashpad: this Obsidian build doesn't expose openPopoutLeaf.")}else h.moveLeafToPopout?.(this.leaf)}catch(h){new F.Notice(`Stashpad: open-in-new-window failed (${h.message})`)}},this.setupBarOverflow(t)}setupBarOverflow(e){let t=[{key:"nav",icon:"folder-search",title:"Folder & search",memberSel:[".stashpad-folder-btn",".stashpad-search-btn"],memberKeys:["folder","search"],collapsePrio:5,displayIdx:0},{key:"filter",icon:"filter",title:"Tag & color",memberSel:[".stashpad-tag-filter-btn",".stashpad-color-filter-btn"],memberKeys:["tag","color"],collapsePrio:3,displayIdx:1},{key:"arrange",icon:"sliders-horizontal",title:"Sort & view",memberSel:[".stashpad-sort-btn",".stashpad-view-btn"],memberKeys:["sort","view"],collapsePrio:4,displayIdx:2},{key:"time",icon:"clock",title:"Time",memberSel:[".stashpad-time-filter-btns",".stashpad-time-filter-select"],memberKeys:["time"],collapsePrio:2,displayIdx:3},{key:"window",icon:"app-window",title:"Window & layout",memberSel:[".stashpad-view-mode-btns"],memberKeys:["mode"],collapsePrio:1,displayIdx:4}];e.addClass("stashpad-overflow-managed");let i=[];for(let u of[...t].sort((p,f)=>p.displayIdx-f.displayIdx)){let p=u.memberSel.map(B=>e.querySelector(B)).filter(B=>!!B);if(p.length===0)continue;let f=createDiv({cls:"stashpad-bar-group"});e.insertBefore(f,p[0]);let I=f.createDiv({cls:"stashpad-bar-group-expanded"});for(let B of p)I.appendChild(B);let m=f.createDiv({cls:"stashpad-bar-overflow-btn stashpad-bar-group-icon stashpad-overflow-hidden"});m.setAttribute("role","button"),m.setAttribute("tabindex","0"),(0,F.setIcon)(m.createSpan({cls:"stashpad-bar-group-icon-glyph"}),u.icon),m.title=`${u.title} \u2014 combined menu`;let C=B=>{B.preventDefault(),this.openBarOverflowMenu(m,u.memberKeys)};m.onclick=C,m.onkeydown=B=>{(B.key==="Enter"||B.key===" ")&&C(B)},i.push({...u,wrapper:f,expanded:I,iconBtn:m})}let s=e.createDiv({cls:"stashpad-bar-overflow-btn stashpad-bar-kebab stashpad-overflow-hidden"});s.setAttribute("role","button"),s.setAttribute("tabindex","0"),(0,F.setIcon)(s.createSpan({cls:"stashpad-bar-overflow-icon"}),"more-horizontal"),s.title="More filters / view options";let n=[...i].sort((u,p)=>u.collapsePrio-p.collapsePrio),o=[...i].sort((u,p)=>p.displayIdx-u.displayIdx),a=[],l=()=>e.scrollWidth<=e.clientWidth+1,c=()=>{if(!e.isConnected||e.clientWidth===0)return;for(let p of i)p.wrapper.removeClass("stashpad-overflow-hidden"),p.expanded.removeClass("stashpad-overflow-hidden"),p.iconBtn.addClass("stashpad-overflow-hidden"),p.iconBtn.toggleClass("is-active",p.memberKeys.some(f=>this.barUnitActive(f)));if(s.addClass("stashpad-overflow-hidden"),a=[],l())return;for(let p of n){if(l())break;p.expanded.addClass("stashpad-overflow-hidden"),p.iconBtn.removeClass("stashpad-overflow-hidden")}if(l())return;s.removeClass("stashpad-overflow-hidden");let u=[];for(let p of o){if(l())break;p.wrapper.addClass("stashpad-overflow-hidden"),u.push(p)}a=[...u].sort((p,f)=>p.displayIdx-f.displayIdx).flatMap(p=>p.memberKeys),s.toggleClass("is-active",a.some(p=>this.barUnitActive(p)))},d=u=>{u.preventDefault(),this.openBarOverflowMenu(s,a)};s.onclick=d,s.onkeydown=u=>{(u.key==="Enter"||u.key===" ")&&d(u)};try{this.barOverflowRO?.disconnect()}catch{}let h=(e.ownerDocument?.defaultView??window).ResizeObserver,g=-1;this.barOverflowRO=new h(u=>{let p=Math.round(u[u.length-1].contentRect.width);p!==g&&(g=p,c())}),this.barOverflowRO.observe(e),requestAnimationFrame(()=>c())}barUnitActive(e){switch(e){case"folder":return!!this.folderOverride;case"tag":return!!this.tagFilter;case"color":return!!this.colorFilter;case"time":return this.timeFilter!=="all";case"sort":return this.currentViewMode()==="nested"&&this.sortStore.getMode(this.noteFolder,this.focusId)!=="manual";case"view":return this.currentViewMode()!=="nested"||this.currentEncryptionFilter()!=="all"||this.currentHideChildless()||this.currentHideCompleted()||this.currentAttachmentsOnly()||this.currentIncludeAttachments();case"mode":return this.compactMode;default:return!1}}openBarOverflowMenu(e,t){let i=e.ownerDocument??document;i.querySelectorAll(".stashpad-mobile-filters-popover").forEach(p=>p.remove());let s=i.body.createDiv({cls:"stashpad-mobile-filters-popover"}),n=e.getBoundingClientRect(),o=i.defaultView??window;s.setCssStyles({right:`${Math.max(8,o.innerWidth-n.right)}px`,left:"auto",top:`${n.bottom+4}px`,maxWidth:"min(360px, calc(100vw - 16px))",width:"max-content",minWidth:"260px"});let a=new F.Scope(this.app.scope),l=()=>{s.remove(),i.removeEventListener("mousedown",c,!0);try{this.app.keymap?.popScope(a)}catch{}},c=p=>{!s.contains(p.target)&&p.target!==e&&!e.contains(p.target)&&l()},h=[{key:"folder",title:"Folder",summary:()=>this.noteFolder.split("/").pop()||this.noteFolder||"Stashpad",populate:p=>this.populateFolderMenuBody(p,l)},{key:"search",title:"Search",summary:()=>"Search notes (Mod+F)",populate:p=>this.populateSearchMenuBody(p,l)},{key:"tag",title:"Tag filter",summary:()=>this.tagFilter?`#${this.tagFilter}`:"All tags",populate:p=>this.populateTagMenuBody(p,l)},{key:"color",title:"Color filter",summary:()=>this.colorFilter?this.plugin.getColorAlias(this.noteFolder,this.colorFilter)??this.colorFilter:"All colors",populate:p=>this.populateColorMenuBody(p,this.collectFolderColors(),l)},{key:"sort",title:"Sort",summary:()=>this.currentViewMode()!=="nested"?"\u2014 (Nested only)":hi[this.sortStore.getMode(this.noteFolder,this.focusId)],populate:p=>{if(this.currentViewMode()!=="nested"){p.createDiv({cls:"stashpad-mobile-filters-note",text:"Sort applies only in Nested view."});return}this.populateSortMenuBody(p,l)}},{key:"view",title:"View",summary:()=>os[this.currentViewMode()],populate:p=>this.populateViewMenuBody(p,l)},{key:"time",title:"Time filter",summary:()=>{let p=as.find(f=>f.key===this.timeFilter);return p?this.timeFilterCalendar?p.calShort:p.rollShort:"All"},populate:p=>this.populateTimeMenuBody(p,l)},{key:"mode",title:"View mode",summary:()=>this.compactMode?"Compact on":"Tiny \xB7 Compact \xB7 Window",populate:p=>this.populateModeMenuBody(p,l)}].filter(p=>t.includes(p.key)),g="",u=()=>{s.empty();for(let p of h){let f=s.createDiv({cls:"stashpad-mobile-filters-section"}),I=f.createDiv({cls:"stashpad-mobile-filters-header"}),m=I.createSpan({cls:"stashpad-mobile-filters-chev"});(0,F.setIcon)(m,g===p.key?"chevron-down":"chevron-right"),I.createSpan({cls:"stashpad-mobile-filters-title",text:p.title}),I.createSpan({cls:"stashpad-mobile-filters-summary",text:p.summary()}),I.onclick=C=>{C.preventDefault(),C.stopPropagation(),g=g===p.key?"":p.key,u()},g===p.key&&p.populate(f.createDiv({cls:"stashpad-mobile-filters-body"}))}};u(),a.register([],"Escape",p=>(p.preventDefault(),l(),!1)),this.app.keymap?.pushScope(a),setTimeout(()=>{i.addEventListener("mousedown",c,!0)},0)}populateFolderMenuBody(e,t){let i=e.createDiv({cls:"stashpad-sort-popover-row"});i.createSpan({cls:"stashpad-sort-popover-label",text:this.folderOverride?"Change folder (override active)\u2026":"Change folder for this tab\u2026"}),i.onclick=s=>{s.preventDefault(),s.stopPropagation(),t(),this.openFolderPicker()}}populateSearchMenuBody(e,t){let i=e.createDiv({cls:"stashpad-sort-popover-row"});i.createSpan({cls:"stashpad-sort-popover-label",text:"Search notes\u2026"}),i.onclick=s=>{s.preventDefault(),s.stopPropagation(),t(),this.openSearchModal()}}populateModeMenuBody(e,t){let i=(n,o)=>{let a=e.createDiv({cls:"stashpad-sort-popover-row"});a.createSpan({cls:"stashpad-sort-popover-label",text:n}),a.onclick=l=>{l.preventDefault(),l.stopPropagation(),t(),o()}};i("Tiny mode (popout window)",()=>void this.plugin.openTinyWindow());let s=e.createDiv({cls:"stashpad-sort-popover-row"});this.compactMode&&s.addClass("is-active"),s.createSpan({cls:"stashpad-sort-popover-label",text:this.compactMode?"Compact mode (on)":"Compact mode"}),s.onclick=n=>{n.preventDefault(),n.stopPropagation(),t(),this.toggleCompactMode()},i(X().popoutDuplicates?"Duplicate to new window":"Move to new window",()=>{try{let n=this.app.workspace;if(X().popoutDuplicates){let o=n.openPopoutLeaf?.();o?o.setViewState({...this.leaf.getViewState(),active:!0}):new F.Notice("Stashpad: this Obsidian build doesn't expose openPopoutLeaf.")}else n.moveLeafToPopout?.(this.leaf)}catch(n){new F.Notice(`Stashpad: open-in-new-window failed (${n.message})`)}})}toggleCompactMode(){this.compactMode=!this.compactMode,this.render();try{this.app.workspace.requestSaveLayout?.()}catch{}}renderActionsCluster(e){let t=e.createDiv({cls:"stashpad-mobile-actions"}),i=t.createEl("button",{cls:"stashpad-mobile-action-btn"});Se(i,"arrow-left","\u2039");let s=this.navBackStack.length>0||this.focusId!==k;i.title=this.navBackStack.length>0?"Back":this.focusId!==k?"Back (up to parent)":"No back history",s||i.addClass("is-disabled"),i.onclick=d=>{d.preventDefault(),this.navigateBack()};let n=t.createEl("button",{cls:"stashpad-mobile-action-btn"});Se(n,"arrow-right","\u203A");let o=this.navForwardSnapshots.length>0;if(n.title=o?"Forward":"No forward history",o||n.addClass("is-disabled"),n.onclick=d=>{d.preventDefault(),this.navigateForward()},F.Platform.isMobile){let d=t.createEl("button",{cls:"stashpad-mobile-action-btn"});Se(d,"route","\u22D4"),d.title="Jump to a level in the path",d.onclick=h=>{h.preventDefault(),this.openBreadcrumbLevelsModal()}}let a=t.createEl("button",{cls:"stashpad-mobile-action-btn"}),l=this.mobileSelectMode;Se(a,l?"check-square":"square",l?"\u2611":"\u2610"),a.title=l?`${this.selection.size} selected \u2014 tap to exit (keeps the first selection)`:"Enter select mode (tap notes to add)",l&&a.addClass("is-active"),a.onclick=d=>{if(d.preventDefault(),this.mobileSelectMode){let h=this.firstSelectedId??this.selection.values().next().value;if(this.selection.clear(),h){let g=this.currentChildren.findIndex(u=>u.id===h);this.selection.add(h),this.lastSelected=h,g>=0&&(this.cursorIdx=g)}this.firstSelectedId=null,this.mobileSelectMode=!1,this.render()}else{let h=this.currentChildren[Math.max(0,this.cursorIdx)];this.mobileSelectMode=!0,this.selection.clear(),h&&(this.selection.add(h.id),this.lastSelected=h.id,this.firstSelectedId=h.id),this.render(),new F.Notice("Select mode: tap notes to add, press \u26A1 for actions")}};let c=t.createEl("button",{cls:"stashpad-mobile-action-btn"});Se(c,"zap","\u26A1"),c.title="Actions (move, delete, undo, \u2026)",c.onclick=d=>{d.preventDefault(),this.openMobileActionsMenu(c)}}openMobileActionsMenu(e){let t=new F.Menu,i=this.selection.size>0||this.cursorIdx>=0&&!!this.currentChildren[this.cursorIdx],s=this.selection.size<=1;t.addItem(o=>o.setTitle("Undo").setIcon("undo").onClick(()=>this.cmdUndo())),t.addItem(o=>o.setTitle("Redo").setIcon("redo").onClick(()=>this.cmdRedo())),t.addSeparator(),t.addItem(o=>o.setTitle("Notification history\u2026").setIcon("bell").onClick(()=>{this.app.commands?.executeCommandById?.("stashpad:stashpad-open-notification-history")})),t.addItem(o=>o.setTitle("Reload without saving").setIcon("rotate-ccw").onClick(()=>this.plugin.reloadAppForUpdate())),t.addSeparator(),t.addItem(o=>o.setTitle("Expand all").setIcon("unfold-vertical").onClick(()=>this.cmdExpandAll())),t.addItem(o=>o.setTitle("Collapse all").setIcon("fold-vertical").onClick(()=>this.cmdCollapseAll())),t.addSeparator(),t.addItem(o=>o.setTitle("Open in new Stashpad tab").setIcon("list-tree").setDisabled(!i).onClick(()=>this.cmdOpenInNewStashpadTab())),t.addItem(o=>o.setTitle("Open in editor").setIcon("pencil").setDisabled(!i).onClick(()=>this.cmdOpenInEditor())),t.addSeparator(),t.addItem(o=>o.setTitle("Move\u2026").setIcon("arrow-right-circle").setDisabled(!i).onClick(()=>this.cmdMovePicker())),t.addItem(o=>o.setTitle("Nest under\u2026 (in-list)").setIcon("indent").setDisabled(!i).onClick(()=>this.cmdInListPicker())),t.addItem(o=>o.setTitle("Outdent").setIcon("outdent").setDisabled(!i).onClick(()=>void this.cmdOutdent())),t.addItem(o=>o.setTitle("Set color\u2026").setIcon("palette").setDisabled(!i).onClick(()=>this.cmdSetColor())),t.addItem(o=>o.setTitle("Toggle complete").setIcon("check-circle").setDisabled(!i).onClick(()=>void this.cmdToggleComplete())),t.addItem(o=>o.setTitle("Toggle task (todo)").setIcon("check-square").setDisabled(!i).onClick(()=>void this.cmdToggleTask())),t.addItem(o=>o.setTitle("Set due date\u2026").setIcon("calendar-clock").setDisabled(!i).onClick(()=>this.cmdSetDue())),t.addItem(o=>o.setTitle("Assign to\u2026").setIcon("user-plus").setDisabled(!i).onClick(()=>this.cmdAssign())),t.addSeparator(),t.addItem(o=>o.setTitle("Copy").setIcon("copy").setDisabled(!i).onClick(()=>void this.cmdCopy())),t.addItem(o=>o.setTitle("Copy tree").setIcon("copy-plus").setDisabled(!i).onClick(()=>void this.cmdCopyTree())),t.addItem(o=>o.setTitle("Clone (duplicate / copy)").setIcon("files").setDisabled(!i).onClick(()=>void this.cmdClone())),this.plugin.settings.enableSheetVersions&&(t.addItem(o=>o.setTitle("Fork as a version (draft)").setIcon("git-fork").setDisabled(!i||!s).onClick(()=>void this.cmdForkVersion())),t.addItem(o=>o.setTitle("Mark version as final").setIcon("star").setDisabled(!i||!s).onClick(()=>void this.cmdMarkVersionFinal()))),t.addItem(o=>o.setTitle("Insert template\u2026").setIcon("file-plus-2").onClick(()=>this.cmdInsertTemplate())),t.addItem(o=>o.setTitle("Merge").setIcon("merge").setDisabled(this.selection.size<2).onClick(()=>void this.cmdMerge())),t.addItem(o=>o.setTitle("Split note\u2026").setIcon("scissors").setDisabled(!i||!s).onClick(()=>void this.cmdSplit())),t.addSeparator(),t.addItem(o=>o.setTitle("Delete").setIcon("trash-2").setDisabled(!i).onClick(()=>void this.cmdDelete())),t.addSeparator(),t.addItem(o=>o.setTitle("More commands\u2026").setIcon("terminal").onClick(()=>this.openCommandPalette()));let n=e.getBoundingClientRect();t.showAtPosition({x:n.left,y:n.bottom+4})}openCommandPalette(){this.app.commands?.executeCommandById?.("command-palette:open")}openStashpadCommandPalette(){new Ws(this.app).open()}tagFilterLabel(){return this.tagFilter===Tt?"Tagged":this.tagFilter===Pt?"Untagged":this.tagFilter?`#${this.formatTagLabel(this.tagFilter)}`:this.collectFolderTags().length===0?"No tags":"All tags"}renderTagFilterDropdown(e){let t=this.collectFolderTags(),i=e.createDiv({cls:"stashpad-tag-filter-btn"});i.setAttribute("role","button"),i.setAttribute("tabindex","0");let s=i.createSpan({cls:"stashpad-tag-filter-btn-icon"});Se(s,"tag","#"),i.createSpan({cls:"stashpad-tag-filter-label",text:this.tagFilterLabel()}),t.length===0&&!this.tagFilter&&i.addClass("is-disabled");let n=o=>{o.preventDefault(),!(t.length===0&&!this.tagFilter)&&this.openTagFilterMenu(i)};i.onclick=n,i.onkeydown=o=>{(o.key==="Enter"||o.key===" ")&&n(o)}}openTagFilterMenu(e){let t=e.ownerDocument??document;t.querySelectorAll(".stashpad-tag-filter-popover").forEach(w=>w.remove());let i=t.body.createDiv({cls:"stashpad-tag-filter-popover"}),s=e.getBoundingClientRect();i.setCssStyles({left:`${Math.max(8,s.left)}px`,top:`${s.bottom+4}px`,minWidth:`${Math.max(s.width,200)}px`,maxWidth:"min(320px, calc(100vw - 16px))",width:"max-content"});let n=new F.Scope(this.app.scope),o=()=>{i.remove(),t.removeEventListener("mousedown",a,!0);try{this.app.keymap?.popScope(n)}catch{}},a=w=>{!i.contains(w.target)&&w.target!==e&&!e.contains(w.target)&&o()},l=i.createDiv({cls:"stashpad-tag-filter-search"}),c=l.createSpan({cls:"stashpad-tag-filter-search-icon"});Se(c,"search","\u2315");let d=l.createEl("input",{type:"text",cls:"stashpad-tag-filter-input",attr:{placeholder:"Filter tags\u2026"}}),h=l.createSpan({cls:"stashpad-tag-filter-clear",attr:{"aria-label":"Clear"}});Se(h,"x","\xD7"),h.setCssStyles({display:"none"});let g=i.createDiv({cls:"stashpad-tag-filter-list"}),u=this.collectFolderTags(),p=[],f=0,I=w=>{this.setTagFilter(w),o()},m=w=>{if(p.length===0){f=0;return}f=(w+p.length)%p.length,p.forEach((y,E)=>y.el.toggleClass("is-highlighted",E===f)),p[f].el.scrollIntoView({block:"nearest"})},C=(w,y,E,v)=>{let Q=g.createDiv({cls:"stashpad-tag-filter-row"});v&&Q.addClass("is-active"),Q.createSpan({cls:"stashpad-tag-filter-row-label",text:w}),typeof E=="number"&&Q.createSpan({cls:"stashpad-tag-filter-row-count",text:String(E)});let S=p.length;p.push({value:y,el:Q}),Q.onmousedown=x=>{x.preventDefault(),I(y)},Q.onmouseenter=()=>m(S)},B=()=>{let w=d.value;h.setCssStyles({display:w?"":"none"}),g.empty(),p=[];let y=this.tagFilter?.toLowerCase();if(w.trim()){let E=ma(w,u);if(E.length===0)g.createDiv({cls:"stashpad-tag-filter-empty",text:"No matching tags"});else for(let v of E)C(`#${v.label}`,v.raw,v.count,y===v.raw.toLowerCase())}else{C("All tags",null,void 0,!this.tagFilter),C("Tagged",Tt,void 0,this.tagFilter===Tt),C("Untagged",Pt,void 0,this.tagFilter===Pt);for(let E of u)C(`#${E.label}`,E.raw,E.count,y===E.raw.toLowerCase())}m(0)};h.onclick=()=>{d.value="",B(),d.focus()},d.addEventListener("input",B),d.addEventListener("keydown",w=>{w.key==="ArrowDown"?(w.preventDefault(),m(f+1)):w.key==="ArrowUp"?(w.preventDefault(),m(f-1)):w.key==="Enter"&&(w.preventDefault(),p[f]&&I(p[f].value))}),n.register([],"Escape",w=>(w.preventDefault(),o(),!1)),this.app.keymap?.pushScope(n),B(),setTimeout(()=>{t.addEventListener("mousedown",a,!0),d.focus()},0)}renderColorFilterDropdown(e){let t=this.collectFolderColors(),i=e.createDiv({cls:"stashpad-color-filter-btn"});i.setAttribute("role","button"),i.setAttribute("tabindex","0"),(()=>{i.empty();let o=i.createSpan({cls:"stashpad-color-filter-swatch"}),a=i.createSpan({cls:"stashpad-color-filter-label"});if(this.colorFilter){let l=this.colorFilter.toLowerCase();o.setCssStyles({background:l});let c=this.plugin.getColorAlias(this.noteFolder,l);a.setText(c??l)}else t.length===0?(o.addClass("is-empty"),a.setText("No colors"),i.addClass("is-disabled")):(o.addClass("is-empty"),a.setText("All colors"))})();let n=o=>{o.preventDefault(),!(t.length===0&&!this.colorFilter)&&this.openColorFilterMenu(i,t)};i.onclick=n,i.onkeydown=o=>{(o.key==="Enter"||o.key===" ")&&n(o)}}openColorFilterMenu(e,t){let i=e.ownerDocument??document;i.querySelectorAll(".stashpad-color-filter-popover").forEach(c=>c.remove());let s=i.body.createDiv({cls:"stashpad-color-filter-popover"}),n=e.getBoundingClientRect();s.setCssStyles({left:`${Math.max(8,n.left)}px`,top:`${n.bottom+4}px`,minWidth:`${n.width}px`,maxWidth:"min(280px, calc(100vw - 16px))",width:"max-content"});let o=new F.Scope(this.app.scope),a=()=>{s.remove(),i.removeEventListener("mousedown",l,!0);try{this.app.keymap?.popScope(o)}catch{}},l=c=>{!s.contains(c.target)&&c.target!==e&&!e.contains(c.target)&&a()};o.register([],"Escape",c=>(c.preventDefault(),a(),!1)),this.app.keymap?.pushScope(o),this.populateColorMenuBody(s,t,a),setTimeout(()=>{i.addEventListener("mousedown",l,!0)},0)}renderSortDropdown(e){let t=this.noteFolder,i=this.focusId,s=this.sortStore.getMode(t,i),n=this.currentViewMode(),o=n!=="nested",a=e.createDiv({cls:"stashpad-sort-btn"});a.setAttribute("role","button"),a.setAttribute("tabindex",o?"-1":"0"),o&&a.addClass("is-disabled");let l=a.createSpan({cls:"stashpad-sort-icon"});(0,F.setIcon)(l,"arrow-up-down");let c=a.createSpan({cls:"stashpad-sort-label"});o?(c.setText("Sort: \u2014"),a.title=`Sort is per-parent and applies only to Nested view. The current view (${os[n]}) shows a synthesized flat list sorted by created time \u2014 switch back to Nested to change sort.`):(c.setText(hi[s]),s!=="manual"&&a.addClass("is-active"),a.title=s==="manual"?"Sort children of this view. Click to change. Drag-reorder always reverts the affected parent to Manual.":`Currently: ${hi[s]}. Drag-reorder will revert this parent to Manual.`);let d=h=>{o||(h.preventDefault(),this.openSortMenu(a))};a.onclick=d,a.onkeydown=h=>{o||(h.key==="Enter"||h.key===" ")&&d(h)}}openSortMenu(e){let t=e.ownerDocument??document;t.querySelectorAll(".stashpad-sort-popover").forEach(l=>l.remove());let i=t.body.createDiv({cls:"stashpad-sort-popover"}),s=e.getBoundingClientRect();i.setCssStyles({left:`${Math.max(8,s.left)}px`,top:`${s.bottom+4}px`,minWidth:`${s.width}px`,maxWidth:"min(280px, calc(100vw - 16px))",width:"max-content"});let n=()=>{i.remove(),t.removeEventListener("mousedown",o,!0);try{this.app.keymap?.popScope(a)}catch{}},o=l=>{!i.contains(l.target)&&l.target!==e&&!e.contains(l.target)&&n()};this.populateSortMenuBody(i,n);let a=new F.Scope(this.app.scope);a.register([],"Escape",l=>(l.preventDefault(),n(),!1)),this.app.keymap?.pushScope(a),setTimeout(()=>{t.addEventListener("mousedown",o,!0)},0)}renderMobileFiltersButton(e){let t=e.createDiv({cls:"stashpad-mobile-filters-btn"});t.setAttribute("role","button"),t.setAttribute("tabindex","0");let i=t.createSpan({cls:"stashpad-mobile-filters-icon"});(0,F.setIcon)(i,"sliders-horizontal"),t.title="Filters / view options";let s=!!this.tagFilter,n=!!this.colorFilter,o=this.timeFilter!=="all",a=this.sortStore.getMode(this.noteFolder,this.focusId)!=="manual",l=this.currentViewMode()!=="nested"||this.currentHideChildless()||this.currentHideCompleted()||this.currentAttachmentsOnly()||this.currentIncludeAttachments();(s||n||o||a||l)&&t.addClass("is-active");let c=d=>{d.preventDefault(),this.openMobileFiltersMenu(t)};t.onclick=c,t.onkeydown=d=>{(d.key==="Enter"||d.key===" ")&&c(d)}}openMobileFiltersMenu(e){let t=e.ownerDocument??document;t.querySelectorAll(".stashpad-mobile-filters-popover").forEach(u=>u.remove());let i=t.body.createDiv({cls:"stashpad-mobile-filters-popover"}),s=e.getBoundingClientRect(),n=t.defaultView??window,o=s.bottom+280>n.innerHeight;i.setCssStyles({right:`${Math.max(8,n.innerWidth-s.right)}px`,left:"auto",...o?{bottom:`${Math.max(8,n.innerHeight-s.top+4)}px`,top:"auto"}:{top:`${s.bottom+4}px`,bottom:"auto"},maxWidth:"min(360px, calc(100vw - 16px))",maxHeight:"min(60vh, 420px)",overflowY:"auto",width:"max-content",minWidth:"260px"});let a=()=>{i.remove(),t.removeEventListener("mousedown",l,!0);try{this.app.keymap?.popScope(g)}catch{}},l=u=>{!i.contains(u.target)&&u.target!==e&&!e.contains(u.target)&&a()},c=[{key:"tag",title:"Tag filter",summary:()=>this.tagFilter?`#${this.tagFilter}`:"All tags",populate:u=>this.populateTagMenuBody(u,a)},{key:"color",title:"Color filter",summary:()=>this.colorFilter?this.plugin.getColorAlias(this.noteFolder,this.colorFilter)??this.colorFilter:"All colors",populate:u=>this.populateColorMenuBody(u,this.collectFolderColors(),a)},{key:"time",title:"Time filter",summary:()=>{let u=as.find(p=>p.key===this.timeFilter);return u?this.timeFilterCalendar?u.calShort:u.rollShort:"All"},populate:u=>this.populateTimeMenuBody(u,a)},{key:"sort",title:"Sort",summary:()=>this.currentViewMode()!=="nested"?"\u2014 (Nested only)":hi[this.sortStore.getMode(this.noteFolder,this.focusId)],populate:u=>{if(this.currentViewMode()!=="nested"){u.createDiv({cls:"stashpad-mobile-filters-note",text:"Sort applies only in Nested view."});return}this.populateSortMenuBody(u,a)}},{key:"view",title:"View",summary:()=>os[this.currentViewMode()],populate:u=>this.populateViewMenuBody(u,a)}],d="",h=()=>{i.empty();for(let u of c){let p=i.createDiv({cls:"stashpad-mobile-filters-section"}),f=p.createDiv({cls:"stashpad-mobile-filters-header"}),I=f.createSpan({cls:"stashpad-mobile-filters-chev"});if((0,F.setIcon)(I,d===u.key?"chevron-down":"chevron-right"),f.createSpan({cls:"stashpad-mobile-filters-title",text:u.title}),f.createSpan({cls:"stashpad-mobile-filters-summary",text:u.summary()}),f.onclick=m=>{m.preventDefault(),m.stopPropagation(),d=d===u.key?"":u.key,h()},d===u.key){let m=p.createDiv({cls:"stashpad-mobile-filters-body"});u.populate(m)}}};h();let g=new F.Scope(this.app.scope);g.register([],"Escape",u=>(u.preventDefault(),a(),!1)),this.app.keymap?.pushScope(g),setTimeout(()=>{t.addEventListener("mousedown",l,!0)},0)}renderViewDropdown(e){let t=this.currentViewMode(),i=e.createDiv({cls:"stashpad-view-btn"});i.setAttribute("role","button"),i.setAttribute("tabindex","0");let s=i.createSpan({cls:"stashpad-view-icon"});(0,F.setIcon)(s,t==="flat"?"list":t==="everything"?"layout-grid":"list-tree"),i.createSpan({cls:"stashpad-view-label"}).setText(os[t]),(t!=="nested"||this.currentEncryptionFilter()!=="all")&&i.addClass("is-active"),i.title=t==="nested"?"View: Nested (the default). Click to switch to Flat or Everything.":t==="flat"?"View: Flat \u2014 all descendants of the current focus, flat by sort order. Drag-reorder is disabled in this mode. Click to change.":"View: Everything \u2014 all descendants of the current focus PLUS non-Stashpad files in the folder, flat by created/ctime. Click to change.";let o=a=>{a.preventDefault(),this.openViewMenu(i)};i.onclick=o,i.onkeydown=a=>{(a.key==="Enter"||a.key===" ")&&o(a)}}openViewMenu(e){let t=e.ownerDocument??document;t.querySelectorAll(".stashpad-view-popover").forEach(l=>l.remove());let i=t.body.createDiv({cls:"stashpad-view-popover"});F.Platform.isMobile&&i.addClass("is-mobile");let s=e.getBoundingClientRect();i.setCssStyles({left:`${Math.max(8,s.left)}px`,top:`${s.bottom+4}px`,minWidth:`${s.width}px`,maxWidth:"min(320px, calc(100vw - 16px))",width:"max-content"});let n=()=>{i.remove(),t.removeEventListener("mousedown",o,!0);try{this.app.keymap?.popScope(a)}catch{}},o=l=>{!i.contains(l.target)&&l.target!==e&&!e.contains(l.target)&&n()};this.populateViewMenuBody(i,n);let a=new F.Scope(this.app.scope);a.register([],"Escape",l=>(l.preventDefault(),n(),!1)),this.app.keymap?.pushScope(a),setTimeout(()=>{t.addEventListener("mousedown",o,!0)},0)}populateViewMenuBody(e,t){let i=this.currentViewMode(),s=(y,E,v)=>{let Q=e.createDiv({cls:"stashpad-view-popover-row"});y===i&&Q.addClass("is-active");let S=Q.createDiv({cls:"stashpad-view-popover-main"});(0,F.setIcon)(S.createSpan({cls:"stashpad-view-popover-icon"}),v),S.createSpan({cls:"stashpad-view-popover-label",text:os[y]}),Q.createDiv({cls:"stashpad-view-popover-desc",text:E}),Q.onclick=async x=>{x.preventDefault(),x.stopPropagation(),t(),y!==i&&(await this.setViewMode(y),this.render())}};if(s("nested","Tree of immediate children (default).","list-tree"),e.createDiv({cls:"stashpad-view-popover-divider"}),s("flat","All descendants of the current focus, flat by sort.","list"),e.createDiv({cls:"stashpad-view-popover-divider"}),s("everything","All descendants PLUS non-Stashpad files in the folder.","layout-grid"),e.createDiv({cls:"stashpad-view-popover-divider"}),this.plugin.encryption?.isConfigured?.()){let y=this.currentEncryptionFilter(),E=(v,Q,S)=>{let x=e.createDiv({cls:"stashpad-view-popover-row"});v===y&&x.addClass("is-active"),x.createDiv({cls:"stashpad-view-popover-main"}).createSpan({cls:"stashpad-view-popover-label",text:Q}),x.createDiv({cls:"stashpad-view-popover-desc",text:S}),x.onclick=async H=>{H.preventDefault(),H.stopPropagation(),v!==y&&(await this.setEncryptionFilter(v),this.refreshList()),t()}};E("all","Encryption: show all","Both locked \u{1F512} and decrypted notes."),E("locked","Encryption: locked only","Show only locked \u{1F512} stubs."),E("unlocked","Encryption: decrypted only","Hide locked \u{1F512} stubs."),e.createDiv({cls:"stashpad-view-popover-divider"})}let n=e.createDiv({cls:"stashpad-view-popover-row stashpad-view-popover-toggle"}),o=n.createEl("input",{type:"checkbox"});o.checked=this.currentHideChildless(),n.createDiv({cls:"stashpad-view-popover-main"}).createSpan({cls:"stashpad-view-popover-label",text:"Hide childless notes"}),n.createDiv({cls:"stashpad-view-popover-desc",text:i==="nested"?"Show only notes that have children. Applied at this level.":"Hide top-level notes without children; keep every parent's full subtree so no task is overlooked."}),n.onclick=async y=>{y.target!==o&&(y.preventDefault(),o.checked=!o.checked),await this.setHideChildless(o.checked),this.refreshList()};let a=e.createDiv({cls:"stashpad-view-popover-row stashpad-view-popover-toggle"}),l=a.createEl("input",{type:"checkbox"});l.checked=this.currentHideCompleted(),a.createDiv({cls:"stashpad-view-popover-main"}).createSpan({cls:"stashpad-view-popover-label",text:"Hide completed notes"}),a.createDiv({cls:"stashpad-view-popover-desc",text:"Hide notes marked complete. A completed parent stays visible while any descendant is still incomplete."}),a.onclick=async y=>{y.target!==l&&(y.preventDefault(),l.checked=!l.checked),await this.setHideCompleted(l.checked),this.refreshList()};let c=e.createDiv({cls:"stashpad-view-popover-row stashpad-view-popover-toggle"}),d=c.createEl("input",{type:"checkbox"});d.checked=this.currentAttachmentsOnly(),c.createDiv({cls:"stashpad-view-popover-main"}).createSpan({cls:"stashpad-view-popover-label",text:"Hide notes without attachments"}),c.createDiv({cls:"stashpad-view-popover-desc",text:"Show only notes that have an attachment. A parent stays visible while any descendant has one."}),c.onclick=async y=>{y.target!==d&&(y.preventDefault(),d.checked=!d.checked),await this.setAttachmentsOnly(d.checked),this.refreshList()},e.createDiv({cls:"stashpad-view-popover-divider"});let h=e.createDiv({cls:"stashpad-view-popover-row stashpad-view-popover-toggle"});i!=="everything"&&h.addClass("is-disabled");let g=h.createEl("input",{type:"checkbox"});g.checked=this.currentIncludeAttachments(),g.disabled=i!=="everything",h.createDiv({cls:"stashpad-view-popover-main"}).createSpan({cls:"stashpad-view-popover-label",text:"Include attachments"}),h.createDiv({cls:"stashpad-view-popover-desc",text:i==="everything"?"Show attachments referenced by notes as their own rows in the file list. Off by default \u2014 they already appear inline on the notes that embed them.":"Only applies in Everything mode."}),h.onclick=async y=>{i==="everything"&&(y.target!==g&&(y.preventDefault(),g.checked=!g.checked),await this.setIncludeAttachments(g.checked),this.refreshList())},e.createDiv({cls:"stashpad-view-popover-divider"});let u=e.createDiv({cls:"stashpad-view-popover-row stashpad-view-popover-toggle"}),p=u.createEl("input",{type:"checkbox"});p.checked=this.importedOnly,u.createDiv({cls:"stashpad-view-popover-main"}).createSpan({cls:"stashpad-view-popover-label",text:"Imported notes only"}),u.createDiv({cls:"stashpad-view-popover-desc",text:"Show only notes that came in via import."}),u.onclick=y=>{y.target!==p&&(y.preventDefault(),p.checked=!p.checked),this.importedOnly=p.checked,this.reconcileSelectionAfterFilter(),this.refreshList()};let f=new Map,I=this.noteFolder.replace(/\/+$/,"");for(let y of this.app.vault.getMarkdownFiles()){if((y.parent?.path?.replace(/\/+$/,"")??"")!==I)continue;let E=rt(this.app.metadataCache.getFileCache(y)?.frontmatter?.author);E&&f.set(E.id,E.name)}e.createDiv({cls:"stashpad-view-popover-divider"});let m=e.createDiv({cls:"stashpad-view-popover-row"}),C=m.createDiv({cls:"stashpad-view-popover-main"});(0,F.setIcon)(C.createSpan({cls:"stashpad-view-popover-icon"}),"user"),C.createSpan({cls:"stashpad-view-popover-label",text:"By author"});let B=C.createEl("select",{cls:"stashpad-view-author-select"}),w=B.createEl("option",{text:"All authors",value:""});this.authorFilter||(w.selected=!0);for(let[y,E]of[...f.entries()].sort((v,Q)=>v[1].localeCompare(Q[1]))){let v=B.createEl("option",{text:E,value:y});this.authorFilter===y&&(v.selected=!0)}B.onclick=y=>y.stopPropagation(),B.onchange=()=>{this.authorFilter=B.value||null,this.reconcileSelectionAfterFilter(),this.refreshList()},m.createDiv({cls:"stashpad-view-popover-desc",text:f.size?"Show only notes by the chosen author.":"No authored notes in this folder yet."})}setTagFilter(e){(this.tagFilter??null)!==(e??null)&&(this.tagFilter=e,this.reconcileSelectionAfterFilter(),this.persistFocus(),this.render())}populateSortMenuBody(e,t){let i=this.noteFolder,s=this.focusId,n=this.sortStore.getMode(i,s);for(let o of Qo){let a=e.createDiv({cls:"stashpad-sort-popover-row"});o===n&&a.addClass("is-active"),a.createSpan({cls:"stashpad-sort-popover-label",text:hi[o]}),a.onclick=async l=>{l.preventDefault(),l.stopPropagation(),t(),o!==n&&(this.sortStore.setMode(i,s,o),await this.sortStore.save(i),this.tree.rebuild(i),this.render())}}}populateTimeMenuBody(e,t){let i=e.createDiv({cls:"stashpad-view-popover-row stashpad-view-popover-toggle"}),s=i.createEl("input",{type:"checkbox"});s.checked=this.timeFilterCalendar,i.createDiv({cls:"stashpad-view-popover-main"}).createSpan({cls:"stashpad-view-popover-label",text:"Calendar mode"}),i.createDiv({cls:"stashpad-view-popover-desc",text:"Use calendar boundaries (start of today/week/month/year). Off = rolling windows back from now."}),i.onclick=n=>{n.target!==s&&(n.preventDefault(),s.checked=!s.checked),this.timeFilterCalendar=s.checked,this.persistFocus(),this.refreshList()};for(let n of as){let o=e.createDiv({cls:"stashpad-sort-popover-row"});this.timeFilter===n.key&&o.addClass("is-active");let a=this.timeFilterCalendar?n.calLong:n.rollLong;o.createSpan({cls:"stashpad-sort-popover-label",text:a}),o.onclick=l=>{l.preventDefault(),l.stopPropagation(),t(),this.timeFilter!==n.key&&this.setTimeFilter(n.key)}}}populateColorMenuBody(e,t,i){let s=(n,o,a)=>{let l=e.createDiv({cls:"stashpad-color-filter-popover-row"}),c=l.createSpan({cls:"stashpad-color-filter-swatch"});o?c.setCssStyles({background:o}):c.addClass("is-empty");let d=l.createSpan({cls:"stashpad-color-filter-popover-label"});d.setText(n),o&&(d.style.color=o),l.onclick=h=>{h.preventDefault(),h.stopPropagation(),a(),i()}};s("All colors",null,()=>this.setColorFilter(null));for(let n of t){let o=this.plugin.getColorAlias(this.noteFolder,n.hex),a=o?`${o} (${n.count})`:`${n.hex} (${n.count})`;s(a,n.hex,()=>this.setColorFilter(n.hex))}}populateTagMenuBody(e,t){let i=this.collectFolderTags(),s=(n,o)=>{let a=e.createDiv({cls:"stashpad-color-filter-popover-row"});a.createSpan({cls:"stashpad-color-filter-swatch is-empty"}),a.createSpan({cls:"stashpad-color-filter-popover-label"}).setText(n),(this.tagFilter??"")===(o??"")&&a.addClass("is-active"),a.onclick=d=>{d.preventDefault(),d.stopPropagation(),this.setTagFilter(o),t()}};s(i.length===0?"No tags":"All tags",null),s("Tagged",Tt),s("Untagged",Pt);for(let n of i)s(`${n.label} (${n.count})`,n.raw)}setColorFilter(e){let t=e?e.toLowerCase():null;(this.colorFilter??null)!==t&&(this.colorFilter=t,this.reconcileSelectionAfterFilter(),this.persistFocus(),this.render())}setTimeFilter(e){this.timeFilter!==e&&(this.timeFilter=e,this.reconcileSelectionAfterFilter(),this.persistFocus(),this.render())}reconcileSelectionAfterFilter(){let e=this.filterChildren(this.collectViewItems(this.focusId)),t=new Set(e.map(i=>i.id));for(let i of[...this.selection])t.has(i)||this.selection.delete(i);if(this.firstSelectedId&&!t.has(this.firstSelectedId)&&(this.firstSelectedId=null),this.lastSelected&&!t.has(this.lastSelected)&&(this.lastSelected=null),this.selection.size>0){let i=e.findIndex(s=>this.selection.has(s.id));this.cursorIdx=i>=0?i:Math.min(this.cursorIdx,e.length-1)}else this.cursorIdx>=e.length&&(this.cursorIdx=e.length-1)}renderTinyHeader(e){let t=e.createDiv({cls:"stashpad-tiny-header"}),i=t.createEl("button",{cls:"stashpad-tiny-nav-btn"});Se(i,"arrow-left","\u2039"),i.title="Back (up to parent)",this.navBackStack.length>0||this.focusId!==k||i.addClass("is-disabled"),i.title=this.navBackStack.length>0?"Back":this.focusId!==k?"Back (up to parent)":"No back history",i.onclick=()=>this.navigateBack();let n=t.createEl("button",{cls:"stashpad-tiny-nav-btn"});Se(n,"arrow-right","\u203A"),n.title=this.navForwardSnapshots.length>0?"Forward":"No forward history",this.navForwardSnapshots.length===0&&n.addClass("is-disabled"),n.onclick=()=>this.navigateForward();let o=this.tree.get(this.focusId)??this.tree.getRoot(),a=(this.noteFolder.split("/").pop()||this.noteFolder).trim(),l=this.focusId===k?a:`${a} / ${this.titleForNode(o).trim()}`,c=t.createEl("button",{cls:"stashpad-tiny-title stashpad-folder-btn"}),d=c.createSpan({cls:"stashpad-tiny-title-icon stashpad-btn-icon"});(0,F.setIcon)(d,"folder"),c.createSpan({cls:"stashpad-tiny-title-text stashpad-btn-text",text:l}),c.title=`${this.noteFolder}${this.focusId!==k?` / ${this.titleForNode(o).trim()}`:""} -Click to switch / create folder.`,c.onclick=f=>{f.preventDefault(),this.plugin.openFolderPicker()};let h=t.createDiv({cls:"stashpad-tiny-sticky"}),g=h.createEl("input",{type:"checkbox"});if(g.checked=this.tinyAlwaysOnTop,h.createSpan({text:"Sticky"}),g.onchange=()=>{this.tinyAlwaysOnTop=g.checked,this.applyTinyAlwaysOnTop()},!F.Platform.isMobile){let f=t.createEl("button",{cls:"stashpad-tiny-nav-btn stashpad-tiny-opacity-btn"});(0,F.setIcon)(f,"contrast"),f.title="Window transparency",this.tinyOpacity<1&&f.addClass("is-active"),f.onclick=I=>{I.stopPropagation(),this.toggleTinyOpacityPopover(f)}}let u=t.createEl("button",{cls:"stashpad-tiny-expand stashpad-tiny-exit-compact"});(0,F.setIcon)(u,this.compactMode?"panel-top":"rows-2"),u.title=this.compactMode?"Compact mode is ON \u2014 click to turn off.":"Compact mode \u2014 click to turn on (strips row metadata).",this.compactMode&&u.addClass("is-active"),u.onclick=()=>{this.toggleCompactMode()};let p=t.createEl("button",{cls:"stashpad-tiny-expand"});(0,F.setIcon)(p,"maximize-2"),p.title="Exit tiny mode",p.onclick=()=>{this.exitTinyMode()}}toggleTinyOpacityPopover(e){if(this.tinyOpacityPopover){this.tinyOpacityPopover.remove(),this.tinyOpacityPopover=null;return}let t=document.createElement("div");t.className="stashpad-tiny-opacity-popover",t.createSpan({cls:"stashpad-tiny-opacity-label",text:"Transparency"});let i=t.createEl("input",{type:"range"});i.min="30",i.max="100",i.step="1",i.value=String(Math.round(this.tinyOpacity*100));let s=t.createSpan({cls:"stashpad-tiny-opacity-pct",text:`${i.value}%`});i.addEventListener("input",()=>{let d=Math.min(100,Math.max(30,parseInt(i.value,10)||100));this.tinyOpacity=d/100,s.setText(`${d}%`),this.applyTinyOpacity(),e.toggleClass("is-active",this.tinyOpacity<1)}),i.addEventListener("change",()=>{this.app.workspace.requestSaveLayout()}),this.viewRoot.appendChild(t);let n=e.getBoundingClientRect(),o=this.viewRoot.getBoundingClientRect();t.setCssStyles({top:`${n.bottom-o.top+4}px`,left:`${Math.max(4,Math.min(n.left-o.left,o.width-180))}px`});let a=d=>{t.contains(d.target)||d.target===e||e.contains(d.target)||c()},l=d=>{d.key==="Escape"&&c()},c=()=>{t.remove(),this.tinyOpacityPopover=null,document.removeEventListener("mousedown",a,!0),document.removeEventListener("keydown",l,!0)};setTimeout(()=>{document.addEventListener("mousedown",a,!0),document.addEventListener("keydown",l,!0)},0),this.tinyOpacityPopover=t,i.focus()}getOwnElectronWindow(){try{let e=this.containerEl?.ownerDocument?.defaultView??window,s=((e?.require?.("electron")??window.require?.("electron"))?.remote??e?.electron?.remote??e?.["@electron/remote"])?.getCurrentWindow?.()??e?.electronWindow??null;if(!s)try{let n=window.require?.("electron"),o=n?.remote??n?.["@electron/remote"],l=(o?.BrowserWindow??n?.BrowserWindow)?.getAllWindows?.()??[];l.length===1?s=l[0]:l.length>1&&(s=o?.getFocusedWindow?.()??null??l[l.length-1])}catch(n){console.debug("[Stashpad] BrowserWindow.getAllWindows fallback failed",n)}return s||console.debug("[Stashpad] couldn't resolve own electron window"),s??null}catch(e){return console.debug("[Stashpad] resolve own electron window failed",e),null}}applyTinyAlwaysOnTop(){let e=this.getOwnElectronWindow();if(e)try{e.setAlwaysOnTop?.(!!this.tinyAlwaysOnTop)}catch(t){console.debug("[Stashpad] setAlwaysOnTop failed",t)}}applyTinyOpacity(){let e=this.getOwnElectronWindow();if(!e)return;let t=Math.min(1,Math.max(.3,this.tinyOpacity));try{e.setOpacity?.(t)}catch(i){console.debug("[Stashpad] setOpacity failed",i)}}applyTinyWindow(){let e=this.getOwnElectronWindow();if(e)try{if(this.tinyMode){e.setMinimumSize?.(220,260);let s=100,n=100;try{let o=e.getBounds?.();o&&typeof o.x=="number"&&(s=o.x,n=o.y)}catch{}try{e.setBounds?.({x:s,y:n,width:280,height:360})}catch{}try{e.setSize?.(280,360)}catch{}e.setAlwaysOnTop?.(!!this.tinyAlwaysOnTop);try{e.setOpacity?.(Math.min(1,Math.max(.3,this.tinyOpacity)))}catch{}}else e.setAlwaysOnTop?.(!1)}catch(t){console.debug("[Stashpad] tiny window apply failed",t)}}async exitTinyMode(){this.tinyMode=!1,this.tinyAlwaysOnTop=!1,this.tinyOpacity=1;try{this.getOwnElectronWindow()?.setOpacity?.(1)}catch{}this.compactMode=!1,this.applyTinyWindow();let e=this.getOwnElectronWindow();try{if(e?.setMinimumSize?.(400,300),F.Platform.isMacOS??!1){let i=this.containerEl?.ownerDocument?.defaultView?.require?.("electron")??window.require?.("electron"),n=(i?.remote?.screen??i?.screen)?.getPrimaryDisplay?.().workArea;n?e?.setBounds?.({x:n.x,y:n.y,width:n.width,height:n.height}):e?.maximize?.()}else e?.maximize?.()}catch{}this.render();try{await this.app.workspace.requestSaveLayout?.()}catch{}}enterTinyMode(){this.tinyMode=!0,this.applyTinyWindow(),this.render();try{this.app.workspace.requestSaveLayout?.()}catch{}}renderBreadcrumb(e){let t=e.createDiv({cls:"stashpad-breadcrumb"});F.Platform.isMobile||this.renderActionsCluster(t);let i=t.createSpan({cls:"stashpad-crumb stashpad-crumb-home"});if(F.Platform.isMobile?((0,F.setIcon)(i,"home"),i.title="Home"):i.setText("Home"),i.onclick=()=>this.navigateTo(k),this.focusId===k){let h=this.tree.getChildren(this.focusId).length;if(h>0&&(t.createSpan({cls:"stashpad-crumb-count",text:`\xB7 ${h}`}).title=`${h} direct child${h===1?"":"ren"}`),this.compactMode){let g=t.createEl("button",{cls:"stashpad-compact-exit-btn"});(0,F.setIcon)(g,"panel-top"),g.title="Exit compact mode",g.onclick=u=>{u.preventDefault(),this.toggleCompactMode()}}return}let s=28,n=100,o=this.tree.pathTo(this.focusId),a=o.map(h=>{let g=this.titleForNode(h),u=g.length>s?g.slice(0,s-1)+"\u2026":g;return{id:h.id,label:u}}),l=h=>h.reduce((g,u)=>g+u.label.length+3,0);if(l(a)>n&&a.length>2){let h=!1;for(;l(a)>n&&a.length>2;)a.splice(1,1),h||(a.splice(1,0,{id:"__ellipsis__",label:"\u2026",isEllipsis:!0}),h=!0)}let c=F.Platform.isMobile?"/":" / ";for(let h of a)if(t.createSpan({cls:"stashpad-crumb-sep",text:c}),h.isEllipsis)t.createSpan({cls:"stashpad-crumb stashpad-crumb-ellipsis",text:h.label}).title=o.map(g=>this.titleForNode(g)).join(" / ");else{let g=h.id,u=t.createSpan({cls:"stashpad-crumb",text:h.label});u.title=h.label,u.onclick=()=>this.navigateTo(g),u.oncontextmenu=p=>{p.preventDefault(),this.openCrumbMenu(p,g)},F.Platform.isMobile&&this.attachLongPress(u,()=>this.openCrumbMenu(null,g))}if(t.querySelector(".stashpad-crumb-home")?.addEventListener("contextmenu",h=>{h.preventDefault(),this.openCrumbMenu(h,k)}),F.Platform.isMobile){let h=t.querySelector(".stashpad-crumb-home");h&&this.attachLongPress(h,()=>this.openCrumbMenu(null,k))}let d=this.tree.getChildren(this.focusId).length;if(d>0&&(t.createSpan({cls:"stashpad-crumb-count",text:`\xB7 ${d}`}).title=`${d} direct child${d===1?"":"ren"}`),this.compactMode){let h=t.createEl("button",{cls:"stashpad-compact-exit-btn"});(0,F.setIcon)(h,"panel-top"),h.title="Exit compact mode",h.onclick=g=>{g.preventDefault(),this.toggleCompactMode()}}F.Platform.isMobile||this.renderBreadcrumbLevelsButton(t)}renderBreadcrumbLevelsButton(e){let t=e.createEl("button",{cls:"stashpad-mobile-action-btn stashpad-crumb-levels-btn"});Se(t,"route","\u22D4"),t.title="Show all levels \u2014 jump to any level in the path",t.onclick=i=>{i.preventDefault(),this.openBreadcrumbLevelsModal()}}openBreadcrumbLevelsModal(){let e=this.tree.pathTo(this.focusId),t=[{id:k,label:"Home",level:0,isCurrent:this.focusId===k,isHome:!0}];e.forEach((i,s)=>{t.push({id:i.id,label:this.titleForNode(i),level:s+1,isCurrent:i.id===this.focusId})}),new Ui(this.app,t,i=>this.navigateTo(i),{onContext:(i,s,n,o)=>this.openCrumbMenu(s,i,n,o),attachLongPress:(i,s)=>this.attachLongPress(i,s)}).open()}attachLongPress(e,t){let i=null,s=0,n=0,o=()=>{i!=null&&(window.clearTimeout(i),i=null)};e.addEventListener("touchstart",a=>{let l=a.touches[0];s=l?.clientX??0,n=l?.clientY??0,o(),i=window.setTimeout(()=>{i=null,t()},500)},{passive:!0}),e.addEventListener("touchmove",a=>{let l=a.touches[0];l&&(Math.abs(l.clientX-s)>10||Math.abs(l.clientY-n)>10)&&o()},{passive:!0}),e.addEventListener("touchend",o),e.addEventListener("touchcancel",o)}openCrumbMenu(e,t,i,s){let n=this.tree.get(t);if(!n)return;let o=new F.Menu;if(o.addItem(a=>a.setTitle("Navigate here").setIcon("arrow-right-circle").onClick(()=>{s?.(),this.navigateTo(t)})),o.addItem(a=>a.setTitle("Open in new Stashpad tab").setIcon("list-tree").onClick(()=>{s?.(),this.cmdOpenInNewStashpadTab(n)})),n.file&&o.addItem(a=>a.setTitle("Open in editor (new tab)").setIcon("pencil").onClick(()=>{s?.(),this.cmdOpenInEditor(n)})),e&&(e.clientX>0||e.clientY>0))o.showAtMouseEvent(e);else{let l=(i??e?.target??null)?.getBoundingClientRect();o.showAtPosition({x:l?.left??8,y:(l?.bottom??60)+4})}}renderFocusedHeaderMini(e,t){if(!t.file)return;let i=e.createDiv({cls:"stashpad-focused-mini"});i.dataset.id=t.id,i.createDiv({cls:"stashpad-focused-mini-text"}).setText(this.titleForNode(t).trim());let n=i.createEl("button",{cls:"stashpad-pencil stashpad-note-more stashpad-focused-mini-more"});(0,F.setIcon)(n,"more-horizontal"),n.title="Actions",n.onclick=o=>{o.stopPropagation(),this.openNoteMenu(o,t)}}installFocusedMiniObserver(e){let t=e.querySelector(".stashpad-focused"),i=e.querySelector(".stashpad-focused-mini");!t||!i||(this.focusedMiniObserver&&this.focusedMiniObserver.disconnect(),this.focusedMiniObserver=new IntersectionObserver(s=>{for(let n of s)i.toggleClass("is-visible",!n.isIntersecting)},{root:e,threshold:.05}),this.focusedMiniObserver.observe(t))}renderFocusedHeader(e,t){if(!t.file)return;let i=t.file,s=e.createDiv({cls:"stashpad-focused"});s.oncontextmenu=d=>{d.preventDefault(),this.openNoteMenu(d,t)};let o=s.createDiv({cls:"stashpad-focused-meta"}).createDiv({cls:"stashpad-focused-meta-top"});o.createSpan({cls:"stashpad-focused-time stashpad-note-time",text:this.formatTime(t.created)}),o.createDiv({cls:"stashpad-focused-grip-spacer"});let a=s.createDiv({cls:"stashpad-focused-body"});a.addEventListener("click",d=>this.handleRenderedClick(d,t));let l=s.createDiv({cls:"stashpad-focused-actions"}),c;if(F.Platform.isMobile){let d=l.createEl("button",{cls:"stashpad-pencil stashpad-note-more stashpad-focused-more"});(0,F.setIcon)(d,"more-horizontal"),d.title="Actions",d.onclick=h=>{h.stopPropagation(),this.openNoteMenu(h,t)},c=d}else{let d=l.createEl("button",{cls:"stashpad-pencil stashpad-focused-pencil"});(0,F.setIcon)(d,"pencil"),d.title="Edit in new tab",d.onclick=()=>void this.openFileAtEnd(i);let h=l.createEl("button",{cls:"stashpad-pencil stashpad-focused-dup"});(0,F.setIcon)(h,"copy"),h.title="Open this Stashpad in a new tab (clone)",h.onclick=()=>this.cmdOpenInNewStashpadTab(t),c=d}this.renderNoteBody(a,t,{clamp:F.Platform.isMobile,toggleHost:l,toggleAnchor:c})}renderRowBreadcrumb(e,t){let i=this.tree.pathTo(t.id),s=i.findIndex(a=>a.id===this.focusId),n=i.slice(s+1,i.length-1);if(n.length===0)return;let o=e.createDiv({cls:"stashpad-row-breadcrumb"});n.forEach((a,l)=>{let c=o.createSpan({cls:"stashpad-row-breadcrumb-seg",text:this.titleForNode(a)});c.title=`Focus into "${this.titleForNode(a)}"`,c.onclick=d=>{d.stopPropagation(),this.navigateTo(a.id)},l<n.length-1&&o.createSpan({cls:"stashpad-row-breadcrumb-sep",text:" / "})})}actionsForFile(e){return bs(this.app,e,F.Platform.isMobile)}bulkActionMessage(e){let t=e.nodes.map(a=>`"${this.titleForNode(a).trim()||"(untitled)"}"`),i=e.suffix?` ${e.suffix}`:"",s=e.destination?` ${e.destination}`:"";if(t.length===0)return`${e.verb}${i}${s}`;if(t.length===1)return`${e.verb} ${t[0]}${i}${s}`;let n=e.bulletMax??10,o=t.length<=n?t.map(a=>`\u2022 ${a}`).join(` +Click to override for this tab.`,d&&c.addClass("is-override"),c.onclick=f=>{f.preventDefault(),this.openFolderPicker()};let g=t.createEl("button",{cls:"stashpad-search-btn"});be(g,"search","\u{1F50D}"),g.title="Search notes (Mod+F)",g.onclick=f=>{f.preventDefault(),this.openSearchModal()}}k.Platform.isMobile?(this.renderActionsCluster(t),this.renderMobileFiltersButton(t)):(this.renderTagFilterDropdown(t),this.renderColorFilterDropdown(t),this.renderSortDropdown(t),this.renderViewDropdown(t));let i=t.createDiv({cls:"stashpad-time-filter-btns"}),s=i.createEl("button",{cls:"stashpad-time-filter-btn stashpad-time-filter-cal"});(0,k.setIcon)(s,this.timeFilterCalendar?"calendar":"history"),s.title=this.timeFilterCalendar?"Calendar mode: filters use start-of-day/week/month/year. Click for rolling windows.":"Rolling mode: filters look back N days from now. Click for calendar boundaries.",this.timeFilterCalendar&&s.addClass("is-active"),s.onclick=c=>{c.preventDefault(),this.timeFilterCalendar=!this.timeFilterCalendar,this.persistFocus(),this.render()};for(let c of ps){let d=this.timeFilterCalendar?c.calShort:c.rollShort,h=this.timeFilterCalendar?c.calLong:c.rollLong,g=i.createEl("button",{cls:"stashpad-time-filter-btn",text:d});g.title=h,this.timeFilter===c.key&&g.addClass("is-active"),g.onclick=f=>{f.preventDefault(),this.setTimeFilter(c.key)}}let n=t.createEl("select",{cls:"stashpad-time-filter-select"});for(let c of ps){let d=this.timeFilterCalendar?c.calLong:c.rollLong,h=n.createEl("option",{text:d});h.value=c.key,this.timeFilter===c.key&&(h.selected=!0)}n.onchange=()=>this.setTimeFilter(n.value);let o=t.createDiv({cls:"stashpad-view-mode-btns"});if(!k.Platform.isMobile){let c=o.createEl("button",{cls:"stashpad-view-mode-btn"});(0,k.setIcon)(c,"minimize-2"),c.title="Tiny mode \u2014 open this tab in a small always-on-top-capable popout window.",c.onclick=d=>{d.preventDefault(),this.plugin.openTinyWindow()}}let a=o.createEl("button",{cls:"stashpad-view-mode-btn"});if((0,k.setIcon)(a,this.compactMode?"panel-top":"rows-2"),a.title=this.compactMode?"Compact mode is ON \u2014 click to restore full chrome.":"Compact mode \u2014 hide the filter row + focused header; keep breadcrumb + list + composer.",this.compactMode&&a.addClass("is-active"),a.onclick=c=>{c.preventDefault(),this.toggleCompactMode()},k.Platform.isMobile)return;let l=o.createEl("button",{cls:"stashpad-view-mode-btn"});(0,k.setIcon)(l,"external-link"),l.title=X().popoutDuplicates?"Duplicate this Stashpad tab into a new Obsidian window. (Toggle in Settings \u2192 Open in new window \u2014 duplicate tab.)":"Move this Stashpad tab to a new Obsidian window. (Toggle in Settings \u2192 Open in new window \u2014 duplicate tab.)",l.onclick=c=>{c.preventDefault();let d=X().popoutDuplicates;try{let h=this.app.workspace;if(d){let g=this.leaf.getViewState(),f=h.openPopoutLeaf?.();f?f.setViewState({...g,active:!0}):new k.Notice("Stashpad: this Obsidian build doesn't expose openPopoutLeaf.")}else h.moveLeafToPopout?.(this.leaf)}catch(h){new k.Notice(`Stashpad: open-in-new-window failed (${h.message})`)}},this.setupBarOverflow(t)}setupBarOverflow(e){let t=[{key:"nav",icon:"folder-search",title:"Folder & search",memberSel:[".stashpad-folder-btn",".stashpad-search-btn"],memberKeys:["folder","search"],collapsePrio:5,displayIdx:0},{key:"filter",icon:"filter",title:"Tag & color",memberSel:[".stashpad-tag-filter-btn",".stashpad-color-filter-btn"],memberKeys:["tag","color"],collapsePrio:3,displayIdx:1},{key:"arrange",icon:"sliders-horizontal",title:"Sort & view",memberSel:[".stashpad-sort-btn",".stashpad-view-btn"],memberKeys:["sort","view"],collapsePrio:4,displayIdx:2},{key:"time",icon:"clock",title:"Time",memberSel:[".stashpad-time-filter-btns",".stashpad-time-filter-select"],memberKeys:["time"],collapsePrio:2,displayIdx:3},{key:"window",icon:"app-window",title:"Window & layout",memberSel:[".stashpad-view-mode-btns"],memberKeys:["mode"],collapsePrio:1,displayIdx:4}];e.addClass("stashpad-overflow-managed");let i=[];for(let f of[...t].sort((p,u)=>p.displayIdx-u.displayIdx)){let p=f.memberSel.map(B=>e.querySelector(B)).filter(B=>!!B);if(p.length===0)continue;let u=createDiv({cls:"stashpad-bar-group"});e.insertBefore(u,p[0]);let I=u.createDiv({cls:"stashpad-bar-group-expanded"});for(let B of p)I.appendChild(B);let m=u.createDiv({cls:"stashpad-bar-overflow-btn stashpad-bar-group-icon stashpad-overflow-hidden"});m.setAttribute("role","button"),m.setAttribute("tabindex","0"),(0,k.setIcon)(m.createSpan({cls:"stashpad-bar-group-icon-glyph"}),f.icon),m.title=`${f.title} \u2014 combined menu`;let C=B=>{B.preventDefault(),this.openBarOverflowMenu(m,f.memberKeys)};m.onclick=C,m.onkeydown=B=>{(B.key==="Enter"||B.key===" ")&&C(B)},i.push({...f,wrapper:u,expanded:I,iconBtn:m})}let s=e.createDiv({cls:"stashpad-bar-overflow-btn stashpad-bar-kebab stashpad-overflow-hidden"});s.setAttribute("role","button"),s.setAttribute("tabindex","0"),(0,k.setIcon)(s.createSpan({cls:"stashpad-bar-overflow-icon"}),"more-horizontal"),s.title="More filters / view options";let n=[...i].sort((f,p)=>f.collapsePrio-p.collapsePrio),o=[...i].sort((f,p)=>p.displayIdx-f.displayIdx),a=[],l=()=>e.scrollWidth<=e.clientWidth+1,c=()=>{if(!e.isConnected||e.clientWidth===0)return;for(let p of i)p.wrapper.removeClass("stashpad-overflow-hidden"),p.expanded.removeClass("stashpad-overflow-hidden"),p.iconBtn.addClass("stashpad-overflow-hidden"),p.iconBtn.toggleClass("is-active",p.memberKeys.some(u=>this.barUnitActive(u)));if(s.addClass("stashpad-overflow-hidden"),a=[],l())return;for(let p of n){if(l())break;p.expanded.addClass("stashpad-overflow-hidden"),p.iconBtn.removeClass("stashpad-overflow-hidden")}if(l())return;s.removeClass("stashpad-overflow-hidden");let f=[];for(let p of o){if(l())break;p.wrapper.addClass("stashpad-overflow-hidden"),f.push(p)}a=[...f].sort((p,u)=>p.displayIdx-u.displayIdx).flatMap(p=>p.memberKeys),s.toggleClass("is-active",a.some(p=>this.barUnitActive(p)))},d=f=>{f.preventDefault(),this.openBarOverflowMenu(s,a)};s.onclick=d,s.onkeydown=f=>{(f.key==="Enter"||f.key===" ")&&d(f)};try{this.barOverflowRO?.disconnect()}catch{}let h=(e.ownerDocument?.defaultView??window).ResizeObserver,g=-1;this.barOverflowRO=new h(f=>{let p=Math.round(f[f.length-1].contentRect.width);p!==g&&(g=p,c())}),this.barOverflowRO.observe(e),requestAnimationFrame(()=>c())}barUnitActive(e){switch(e){case"folder":return!!this.folderOverride;case"tag":return!!this.tagFilter;case"color":return!!this.colorFilter;case"time":return this.timeFilter!=="all";case"sort":return this.currentViewMode()==="nested"&&this.sortStore.getMode(this.noteFolder,this.focusId)!=="manual";case"view":return this.currentViewMode()!=="nested"||this.currentEncryptionFilter()!=="all"||this.currentHideChildless()||this.currentHideCompleted()||this.currentAttachmentsOnly()||this.currentIncludeAttachments();case"mode":return this.compactMode;default:return!1}}openBarOverflowMenu(e,t){let i=e.ownerDocument??document;i.querySelectorAll(".stashpad-mobile-filters-popover").forEach(p=>p.remove());let s=i.body.createDiv({cls:"stashpad-mobile-filters-popover"}),n=e.getBoundingClientRect(),o=i.defaultView??window;s.setCssStyles({right:`${Math.max(8,o.innerWidth-n.right)}px`,left:"auto",top:`${n.bottom+4}px`,maxWidth:"min(360px, calc(100vw - 16px))",width:"max-content",minWidth:"260px"});let a=new k.Scope(this.app.scope),l=()=>{s.remove(),i.removeEventListener("mousedown",c,!0);try{this.app.keymap?.popScope(a)}catch{}},c=p=>{!s.contains(p.target)&&p.target!==e&&!e.contains(p.target)&&l()},h=[{key:"folder",title:"Folder",summary:()=>this.noteFolder.split("/").pop()||this.noteFolder||"Stashpad",populate:p=>this.populateFolderMenuBody(p,l)},{key:"search",title:"Search",summary:()=>"Search notes (Mod+F)",populate:p=>this.populateSearchMenuBody(p,l)},{key:"tag",title:"Tag filter",summary:()=>this.tagFilter?`#${this.tagFilter}`:"All tags",populate:p=>this.populateTagMenuBody(p,l)},{key:"color",title:"Color filter",summary:()=>this.colorFilter?this.plugin.getColorAlias(this.noteFolder,this.colorFilter)??this.colorFilter:"All colors",populate:p=>this.populateColorMenuBody(p,this.collectFolderColors(),l)},{key:"sort",title:"Sort",summary:()=>this.currentViewMode()!=="nested"?"\u2014 (Nested only)":ui[this.sortStore.getMode(this.noteFolder,this.focusId)],populate:p=>{if(this.currentViewMode()!=="nested"){p.createDiv({cls:"stashpad-mobile-filters-note",text:"Sort applies only in Nested view."});return}this.populateSortMenuBody(p,l)}},{key:"view",title:"View",summary:()=>hs[this.currentViewMode()],populate:p=>this.populateViewMenuBody(p,l)},{key:"time",title:"Time filter",summary:()=>{let p=ps.find(u=>u.key===this.timeFilter);return p?this.timeFilterCalendar?p.calShort:p.rollShort:"All"},populate:p=>this.populateTimeMenuBody(p,l)},{key:"mode",title:"View mode",summary:()=>this.compactMode?"Compact on":"Tiny \xB7 Compact \xB7 Window",populate:p=>this.populateModeMenuBody(p,l)}].filter(p=>t.includes(p.key)),g="",f=()=>{s.empty();for(let p of h){let u=s.createDiv({cls:"stashpad-mobile-filters-section"}),I=u.createDiv({cls:"stashpad-mobile-filters-header"}),m=I.createSpan({cls:"stashpad-mobile-filters-chev"});(0,k.setIcon)(m,g===p.key?"chevron-down":"chevron-right"),I.createSpan({cls:"stashpad-mobile-filters-title",text:p.title}),I.createSpan({cls:"stashpad-mobile-filters-summary",text:p.summary()}),I.onclick=C=>{C.preventDefault(),C.stopPropagation(),g=g===p.key?"":p.key,f()},g===p.key&&p.populate(u.createDiv({cls:"stashpad-mobile-filters-body"}))}};f(),a.register([],"Escape",p=>(p.preventDefault(),l(),!1)),this.app.keymap?.pushScope(a),setTimeout(()=>{i.addEventListener("mousedown",c,!0)},0)}populateFolderMenuBody(e,t){let i=e.createDiv({cls:"stashpad-sort-popover-row"});i.createSpan({cls:"stashpad-sort-popover-label",text:this.folderOverride?"Change folder (override active)\u2026":"Change folder for this tab\u2026"}),i.onclick=s=>{s.preventDefault(),s.stopPropagation(),t(),this.openFolderPicker()}}populateSearchMenuBody(e,t){let i=e.createDiv({cls:"stashpad-sort-popover-row"});i.createSpan({cls:"stashpad-sort-popover-label",text:"Search notes\u2026"}),i.onclick=s=>{s.preventDefault(),s.stopPropagation(),t(),this.openSearchModal()}}populateModeMenuBody(e,t){let i=(n,o)=>{let a=e.createDiv({cls:"stashpad-sort-popover-row"});a.createSpan({cls:"stashpad-sort-popover-label",text:n}),a.onclick=l=>{l.preventDefault(),l.stopPropagation(),t(),o()}};i("Tiny mode (popout window)",()=>void this.plugin.openTinyWindow());let s=e.createDiv({cls:"stashpad-sort-popover-row"});this.compactMode&&s.addClass("is-active"),s.createSpan({cls:"stashpad-sort-popover-label",text:this.compactMode?"Compact mode (on)":"Compact mode"}),s.onclick=n=>{n.preventDefault(),n.stopPropagation(),t(),this.toggleCompactMode()},i(X().popoutDuplicates?"Duplicate to new window":"Move to new window",()=>{try{let n=this.app.workspace;if(X().popoutDuplicates){let o=n.openPopoutLeaf?.();o?o.setViewState({...this.leaf.getViewState(),active:!0}):new k.Notice("Stashpad: this Obsidian build doesn't expose openPopoutLeaf.")}else n.moveLeafToPopout?.(this.leaf)}catch(n){new k.Notice(`Stashpad: open-in-new-window failed (${n.message})`)}})}toggleCompactMode(){this.compactMode=!this.compactMode,this.render();try{this.app.workspace.requestSaveLayout?.()}catch{}}renderActionsCluster(e){let t=e.createDiv({cls:"stashpad-mobile-actions"}),i=t.createEl("button",{cls:"stashpad-mobile-action-btn"});be(i,"arrow-left","\u2039");let s=this.navBackStack.length>0||this.focusId!==F;i.title=this.navBackStack.length>0?"Back":this.focusId!==F?"Back (up to parent)":"No back history",s||i.addClass("is-disabled"),i.onclick=d=>{d.preventDefault(),this.navigateBack()};let n=t.createEl("button",{cls:"stashpad-mobile-action-btn"});be(n,"arrow-right","\u203A");let o=this.navForwardSnapshots.length>0;if(n.title=o?"Forward":"No forward history",o||n.addClass("is-disabled"),n.onclick=d=>{d.preventDefault(),this.navigateForward()},k.Platform.isMobile){let d=t.createEl("button",{cls:"stashpad-mobile-action-btn"});be(d,"route","\u22D4"),d.title="Jump to a level in the path",d.onclick=h=>{h.preventDefault(),this.openBreadcrumbLevelsModal()}}let a=t.createEl("button",{cls:"stashpad-mobile-action-btn"}),l=this.mobileSelectMode;be(a,l?"check-square":"square",l?"\u2611":"\u2610"),a.title=l?`${this.selection.size} selected \u2014 tap to exit (keeps the first selection)`:"Enter select mode (tap notes to add)",l&&a.addClass("is-active"),a.onclick=d=>{if(d.preventDefault(),this.mobileSelectMode){let h=this.firstSelectedId??this.selection.values().next().value;if(this.selection.clear(),h){let g=this.currentChildren.findIndex(f=>f.id===h);this.selection.add(h),this.lastSelected=h,g>=0&&(this.cursorIdx=g)}this.firstSelectedId=null,this.mobileSelectMode=!1,this.render()}else{let h=this.currentChildren[Math.max(0,this.cursorIdx)];this.mobileSelectMode=!0,this.selection.clear(),h&&(this.selection.add(h.id),this.lastSelected=h.id,this.firstSelectedId=h.id),this.render(),new k.Notice("Select mode: tap notes to add, press \u26A1 for actions")}};let c=t.createEl("button",{cls:"stashpad-mobile-action-btn"});be(c,"zap","\u26A1"),c.title="Actions (move, delete, undo, \u2026)",c.onclick=d=>{d.preventDefault(),this.openMobileActionsMenu(c)}}openMobileActionsMenu(e){let t=new k.Menu,i=this.selection.size>0||this.cursorIdx>=0&&!!this.currentChildren[this.cursorIdx],s=this.selection.size<=1;t.addItem(o=>o.setTitle("Undo").setIcon("undo").onClick(()=>this.cmdUndo())),t.addItem(o=>o.setTitle("Redo").setIcon("redo").onClick(()=>this.cmdRedo())),t.addSeparator(),t.addItem(o=>o.setTitle("Notification history\u2026").setIcon("bell").onClick(()=>{this.app.commands?.executeCommandById?.("stashpad:stashpad-open-notification-history")})),t.addItem(o=>o.setTitle("Reload without saving").setIcon("rotate-ccw").onClick(()=>this.plugin.reloadAppForUpdate())),t.addSeparator(),t.addItem(o=>o.setTitle("Expand all").setIcon("unfold-vertical").onClick(()=>this.cmdExpandAll())),t.addItem(o=>o.setTitle("Collapse all").setIcon("fold-vertical").onClick(()=>this.cmdCollapseAll())),t.addSeparator(),t.addItem(o=>o.setTitle("Open in new Stashpad tab").setIcon("list-tree").setDisabled(!i).onClick(()=>this.cmdOpenInNewStashpadTab())),t.addItem(o=>o.setTitle("Open in editor").setIcon("pencil").setDisabled(!i).onClick(()=>this.cmdOpenInEditor())),t.addSeparator(),t.addItem(o=>o.setTitle("Move\u2026").setIcon("arrow-right-circle").setDisabled(!i).onClick(()=>this.cmdMovePicker())),t.addItem(o=>o.setTitle("Nest under\u2026 (in-list)").setIcon("indent").setDisabled(!i).onClick(()=>this.cmdInListPicker())),t.addItem(o=>o.setTitle("Outdent").setIcon("outdent").setDisabled(!i).onClick(()=>void this.cmdOutdent())),t.addItem(o=>o.setTitle("Set color\u2026").setIcon("palette").setDisabled(!i).onClick(()=>this.cmdSetColor())),t.addItem(o=>o.setTitle("Toggle complete").setIcon("check-circle").setDisabled(!i).onClick(()=>void this.cmdToggleComplete())),t.addItem(o=>o.setTitle("Toggle task (todo)").setIcon("check-square").setDisabled(!i).onClick(()=>void this.cmdToggleTask())),t.addItem(o=>o.setTitle("Set due date\u2026").setIcon("calendar-clock").setDisabled(!i).onClick(()=>this.cmdSetDue())),t.addItem(o=>o.setTitle("Assign to\u2026").setIcon("user-plus").setDisabled(!i).onClick(()=>this.cmdAssign())),t.addSeparator(),t.addItem(o=>o.setTitle("Copy").setIcon("copy").setDisabled(!i).onClick(()=>void this.cmdCopy())),t.addItem(o=>o.setTitle("Copy tree").setIcon("copy-plus").setDisabled(!i).onClick(()=>void this.cmdCopyTree())),t.addItem(o=>o.setTitle("Clone (duplicate / copy)").setIcon("files").setDisabled(!i).onClick(()=>void this.cmdClone())),this.plugin.settings.enableSheetVersions&&(t.addItem(o=>o.setTitle("Fork as a version (draft)").setIcon("git-fork").setDisabled(!i||!s).onClick(()=>void this.cmdForkVersion())),t.addItem(o=>o.setTitle("Mark version as final").setIcon("star").setDisabled(!i||!s).onClick(()=>void this.cmdMarkVersionFinal()))),t.addItem(o=>o.setTitle("Insert template\u2026").setIcon("file-plus-2").onClick(()=>this.cmdInsertTemplate())),t.addItem(o=>o.setTitle("Merge").setIcon("merge").setDisabled(this.selection.size<2).onClick(()=>void this.cmdMerge())),t.addItem(o=>o.setTitle("Split note\u2026").setIcon("scissors").setDisabled(!i||!s).onClick(()=>void this.cmdSplit())),t.addSeparator(),t.addItem(o=>o.setTitle("Delete").setIcon("trash-2").setDisabled(!i).onClick(()=>void this.cmdDelete())),t.addSeparator(),t.addItem(o=>o.setTitle("More commands\u2026").setIcon("terminal").onClick(()=>this.openCommandPalette()));let n=e.getBoundingClientRect();t.showAtPosition({x:n.left,y:n.bottom+4})}openCommandPalette(){this.app.commands?.executeCommandById?.("command-palette:open")}openStashpadCommandPalette(){new dn(this.app).open()}tagFilterLabel(){return this.tagFilter===Gt?"Tagged":this.tagFilter===Lt?"Untagged":this.tagFilter?`#${this.formatTagLabel(this.tagFilter)}`:this.collectFolderTags().length===0?"No tags":"All tags"}renderTagFilterDropdown(e){let t=this.collectFolderTags(),i=e.createDiv({cls:"stashpad-tag-filter-btn"});i.setAttribute("role","button"),i.setAttribute("tabindex","0");let s=i.createSpan({cls:"stashpad-tag-filter-btn-icon"});be(s,"tag","#"),i.createSpan({cls:"stashpad-tag-filter-label",text:this.tagFilterLabel()}),t.length===0&&!this.tagFilter&&i.addClass("is-disabled");let n=o=>{o.preventDefault(),!(t.length===0&&!this.tagFilter)&&this.openTagFilterMenu(i)};i.onclick=n,i.onkeydown=o=>{(o.key==="Enter"||o.key===" ")&&n(o)}}openTagFilterMenu(e){let t=e.ownerDocument??document;t.querySelectorAll(".stashpad-tag-filter-popover").forEach(w=>w.remove());let i=t.body.createDiv({cls:"stashpad-tag-filter-popover"}),s=e.getBoundingClientRect();i.setCssStyles({left:`${Math.max(8,s.left)}px`,top:`${s.bottom+4}px`,minWidth:`${Math.max(s.width,200)}px`,maxWidth:"min(320px, calc(100vw - 16px))",width:"max-content"});let n=new k.Scope(this.app.scope),o=()=>{i.remove(),t.removeEventListener("mousedown",a,!0);try{this.app.keymap?.popScope(n)}catch{}},a=w=>{!i.contains(w.target)&&w.target!==e&&!e.contains(w.target)&&o()},l=i.createDiv({cls:"stashpad-tag-filter-search"}),c=l.createSpan({cls:"stashpad-tag-filter-search-icon"});be(c,"search","\u2315");let d=l.createEl("input",{type:"text",cls:"stashpad-tag-filter-input",attr:{placeholder:"Filter tags\u2026"}}),h=l.createSpan({cls:"stashpad-tag-filter-clear",attr:{"aria-label":"Clear"}});be(h,"x","\xD7"),h.setCssStyles({display:"none"});let g=i.createDiv({cls:"stashpad-tag-filter-list"}),f=this.collectFolderTags(),p=[],u=0,I=w=>{this.setTagFilter(w),o()},m=w=>{if(p.length===0){u=0;return}u=(w+p.length)%p.length,p.forEach((y,v)=>y.el.toggleClass("is-highlighted",v===u)),p[u].el.scrollIntoView({block:"nearest"})},C=(w,y,v,E)=>{let Q=g.createDiv({cls:"stashpad-tag-filter-row"});E&&Q.addClass("is-active"),Q.createSpan({cls:"stashpad-tag-filter-row-label",text:w}),typeof v=="number"&&Q.createSpan({cls:"stashpad-tag-filter-row-count",text:String(v)});let S=p.length;p.push({value:y,el:Q}),Q.onmousedown=x=>{x.preventDefault(),I(y)},Q.onmouseenter=()=>m(S)},B=()=>{let w=d.value;h.setCssStyles({display:w?"":"none"}),g.empty(),p=[];let y=this.tagFilter?.toLowerCase();if(w.trim()){let v=Mr(w,f);if(v.length===0)g.createDiv({cls:"stashpad-tag-filter-empty",text:"No matching tags"});else for(let E of v)C(`#${E.label}`,E.raw,E.count,y===E.raw.toLowerCase())}else{C("All tags",null,void 0,!this.tagFilter),C("Tagged",Gt,void 0,this.tagFilter===Gt),C("Untagged",Lt,void 0,this.tagFilter===Lt);for(let v of f)C(`#${v.label}`,v.raw,v.count,y===v.raw.toLowerCase())}m(0)};h.onclick=()=>{d.value="",B(),d.focus()},d.addEventListener("input",B),d.addEventListener("keydown",w=>{w.key==="ArrowDown"?(w.preventDefault(),m(u+1)):w.key==="ArrowUp"?(w.preventDefault(),m(u-1)):w.key==="Enter"&&(w.preventDefault(),p[u]&&I(p[u].value))}),n.register([],"Escape",w=>(w.preventDefault(),o(),!1)),this.app.keymap?.pushScope(n),B(),setTimeout(()=>{t.addEventListener("mousedown",a,!0),d.focus()},0)}renderColorFilterDropdown(e){let t=this.collectFolderColors(),i=e.createDiv({cls:"stashpad-color-filter-btn"});i.setAttribute("role","button"),i.setAttribute("tabindex","0"),(()=>{i.empty();let o=i.createSpan({cls:"stashpad-color-filter-swatch"}),a=i.createSpan({cls:"stashpad-color-filter-label"});if(this.colorFilter){let l=this.colorFilter.toLowerCase();o.setCssStyles({background:l});let c=this.plugin.getColorAlias(this.noteFolder,l);a.setText(c??l)}else t.length===0?(o.addClass("is-empty"),a.setText("No colors"),i.addClass("is-disabled")):(o.addClass("is-empty"),a.setText("All colors"))})();let n=o=>{o.preventDefault(),!(t.length===0&&!this.colorFilter)&&this.openColorFilterMenu(i,t)};i.onclick=n,i.onkeydown=o=>{(o.key==="Enter"||o.key===" ")&&n(o)}}openColorFilterMenu(e,t){let i=e.ownerDocument??document;i.querySelectorAll(".stashpad-color-filter-popover").forEach(c=>c.remove());let s=i.body.createDiv({cls:"stashpad-color-filter-popover"}),n=e.getBoundingClientRect();s.setCssStyles({left:`${Math.max(8,n.left)}px`,top:`${n.bottom+4}px`,minWidth:`${n.width}px`,maxWidth:"min(280px, calc(100vw - 16px))",width:"max-content"});let o=new k.Scope(this.app.scope),a=()=>{s.remove(),i.removeEventListener("mousedown",l,!0);try{this.app.keymap?.popScope(o)}catch{}},l=c=>{!s.contains(c.target)&&c.target!==e&&!e.contains(c.target)&&a()};o.register([],"Escape",c=>(c.preventDefault(),a(),!1)),this.app.keymap?.pushScope(o),this.populateColorMenuBody(s,t,a),setTimeout(()=>{i.addEventListener("mousedown",l,!0)},0)}renderSortDropdown(e){let t=this.noteFolder,i=this.focusId,s=this.sortStore.getMode(t,i),n=this.currentViewMode(),o=n!=="nested",a=e.createDiv({cls:"stashpad-sort-btn"});a.setAttribute("role","button"),a.setAttribute("tabindex",o?"-1":"0"),o&&a.addClass("is-disabled");let l=a.createSpan({cls:"stashpad-sort-icon"});(0,k.setIcon)(l,"arrow-up-down");let c=a.createSpan({cls:"stashpad-sort-label"});o?(c.setText("Sort: \u2014"),a.title=`Sort is per-parent and applies only to Nested view. The current view (${hs[n]}) shows a synthesized flat list sorted by created time \u2014 switch back to Nested to change sort.`):(c.setText(ui[s]),s!=="manual"&&a.addClass("is-active"),a.title=s==="manual"?"Sort children of this view. Click to change. Drag-reorder always reverts the affected parent to Manual.":`Currently: ${ui[s]}. Drag-reorder will revert this parent to Manual.`);let d=h=>{o||(h.preventDefault(),this.openSortMenu(a))};a.onclick=d,a.onkeydown=h=>{o||(h.key==="Enter"||h.key===" ")&&d(h)}}openSortMenu(e){let t=e.ownerDocument??document;t.querySelectorAll(".stashpad-sort-popover").forEach(l=>l.remove());let i=t.body.createDiv({cls:"stashpad-sort-popover"}),s=e.getBoundingClientRect();i.setCssStyles({left:`${Math.max(8,s.left)}px`,top:`${s.bottom+4}px`,minWidth:`${s.width}px`,maxWidth:"min(280px, calc(100vw - 16px))",width:"max-content"});let n=()=>{i.remove(),t.removeEventListener("mousedown",o,!0);try{this.app.keymap?.popScope(a)}catch{}},o=l=>{!i.contains(l.target)&&l.target!==e&&!e.contains(l.target)&&n()};this.populateSortMenuBody(i,n);let a=new k.Scope(this.app.scope);a.register([],"Escape",l=>(l.preventDefault(),n(),!1)),this.app.keymap?.pushScope(a),setTimeout(()=>{t.addEventListener("mousedown",o,!0)},0)}renderMobileFiltersButton(e){let t=e.createDiv({cls:"stashpad-mobile-filters-btn"});t.setAttribute("role","button"),t.setAttribute("tabindex","0");let i=t.createSpan({cls:"stashpad-mobile-filters-icon"});(0,k.setIcon)(i,"sliders-horizontal"),t.title="Filters / view options";let s=!!this.tagFilter,n=!!this.colorFilter,o=this.timeFilter!=="all",a=this.sortStore.getMode(this.noteFolder,this.focusId)!=="manual",l=this.currentViewMode()!=="nested"||this.currentHideChildless()||this.currentHideCompleted()||this.currentAttachmentsOnly()||this.currentIncludeAttachments();(s||n||o||a||l)&&t.addClass("is-active");let c=d=>{d.preventDefault(),this.openMobileFiltersMenu(t)};t.onclick=c,t.onkeydown=d=>{(d.key==="Enter"||d.key===" ")&&c(d)}}openMobileFiltersMenu(e){let t=e.ownerDocument??document;t.querySelectorAll(".stashpad-mobile-filters-popover").forEach(f=>f.remove());let i=t.body.createDiv({cls:"stashpad-mobile-filters-popover"}),s=e.getBoundingClientRect(),n=t.defaultView??window,o=s.bottom+280>n.innerHeight;i.setCssStyles({right:`${Math.max(8,n.innerWidth-s.right)}px`,left:"auto",...o?{bottom:`${Math.max(8,n.innerHeight-s.top+4)}px`,top:"auto"}:{top:`${s.bottom+4}px`,bottom:"auto"},maxWidth:"min(360px, calc(100vw - 16px))",maxHeight:"min(60vh, 420px)",overflowY:"auto",width:"max-content",minWidth:"260px"});let a=()=>{i.remove(),t.removeEventListener("mousedown",l,!0);try{this.app.keymap?.popScope(g)}catch{}},l=f=>{!i.contains(f.target)&&f.target!==e&&!e.contains(f.target)&&a()},c=[{key:"tag",title:"Tag filter",summary:()=>this.tagFilter?`#${this.tagFilter}`:"All tags",populate:f=>this.populateTagMenuBody(f,a)},{key:"color",title:"Color filter",summary:()=>this.colorFilter?this.plugin.getColorAlias(this.noteFolder,this.colorFilter)??this.colorFilter:"All colors",populate:f=>this.populateColorMenuBody(f,this.collectFolderColors(),a)},{key:"time",title:"Time filter",summary:()=>{let f=ps.find(p=>p.key===this.timeFilter);return f?this.timeFilterCalendar?f.calShort:f.rollShort:"All"},populate:f=>this.populateTimeMenuBody(f,a)},{key:"sort",title:"Sort",summary:()=>this.currentViewMode()!=="nested"?"\u2014 (Nested only)":ui[this.sortStore.getMode(this.noteFolder,this.focusId)],populate:f=>{if(this.currentViewMode()!=="nested"){f.createDiv({cls:"stashpad-mobile-filters-note",text:"Sort applies only in Nested view."});return}this.populateSortMenuBody(f,a)}},{key:"view",title:"View",summary:()=>hs[this.currentViewMode()],populate:f=>this.populateViewMenuBody(f,a)}],d="",h=()=>{i.empty();for(let f of c){let p=i.createDiv({cls:"stashpad-mobile-filters-section"}),u=p.createDiv({cls:"stashpad-mobile-filters-header"}),I=u.createSpan({cls:"stashpad-mobile-filters-chev"});if((0,k.setIcon)(I,d===f.key?"chevron-down":"chevron-right"),u.createSpan({cls:"stashpad-mobile-filters-title",text:f.title}),u.createSpan({cls:"stashpad-mobile-filters-summary",text:f.summary()}),u.onclick=m=>{m.preventDefault(),m.stopPropagation(),d=d===f.key?"":f.key,h()},d===f.key){let m=p.createDiv({cls:"stashpad-mobile-filters-body"});f.populate(m)}}};h();let g=new k.Scope(this.app.scope);g.register([],"Escape",f=>(f.preventDefault(),a(),!1)),this.app.keymap?.pushScope(g),setTimeout(()=>{t.addEventListener("mousedown",l,!0)},0)}renderViewDropdown(e){let t=this.currentViewMode(),i=e.createDiv({cls:"stashpad-view-btn"});i.setAttribute("role","button"),i.setAttribute("tabindex","0");let s=i.createSpan({cls:"stashpad-view-icon"});(0,k.setIcon)(s,t==="flat"?"list":t==="everything"?"layout-grid":"list-tree"),i.createSpan({cls:"stashpad-view-label"}).setText(hs[t]),(t!=="nested"||this.currentEncryptionFilter()!=="all")&&i.addClass("is-active"),i.title=t==="nested"?"View: Nested (the default). Click to switch to Flat or Everything.":t==="flat"?"View: Flat \u2014 all descendants of the current focus, flat by sort order. Drag-reorder is disabled in this mode. Click to change.":"View: Everything \u2014 all descendants of the current focus PLUS non-Stashpad files in the folder, flat by created/ctime. Click to change.";let o=a=>{a.preventDefault(),this.openViewMenu(i)};i.onclick=o,i.onkeydown=a=>{(a.key==="Enter"||a.key===" ")&&o(a)}}openViewMenu(e){let t=e.ownerDocument??document;t.querySelectorAll(".stashpad-view-popover").forEach(l=>l.remove());let i=t.body.createDiv({cls:"stashpad-view-popover"});k.Platform.isMobile&&i.addClass("is-mobile");let s=e.getBoundingClientRect();i.setCssStyles({left:`${Math.max(8,s.left)}px`,top:`${s.bottom+4}px`,minWidth:`${s.width}px`,maxWidth:"min(320px, calc(100vw - 16px))",width:"max-content"});let n=()=>{i.remove(),t.removeEventListener("mousedown",o,!0);try{this.app.keymap?.popScope(a)}catch{}},o=l=>{!i.contains(l.target)&&l.target!==e&&!e.contains(l.target)&&n()};this.populateViewMenuBody(i,n);let a=new k.Scope(this.app.scope);a.register([],"Escape",l=>(l.preventDefault(),n(),!1)),this.app.keymap?.pushScope(a),setTimeout(()=>{t.addEventListener("mousedown",o,!0)},0)}populateViewMenuBody(e,t){let i=this.currentViewMode(),s=(y,v,E)=>{let Q=e.createDiv({cls:"stashpad-view-popover-row"});y===i&&Q.addClass("is-active");let S=Q.createDiv({cls:"stashpad-view-popover-main"});(0,k.setIcon)(S.createSpan({cls:"stashpad-view-popover-icon"}),E),S.createSpan({cls:"stashpad-view-popover-label",text:hs[y]}),Q.createDiv({cls:"stashpad-view-popover-desc",text:v}),Q.onclick=async x=>{x.preventDefault(),x.stopPropagation(),t(),y!==i&&(await this.setViewMode(y),this.render())}};if(s("nested","Tree of immediate children (default).","list-tree"),e.createDiv({cls:"stashpad-view-popover-divider"}),s("flat","All descendants of the current focus, flat by sort.","list"),e.createDiv({cls:"stashpad-view-popover-divider"}),s("everything","All descendants PLUS non-Stashpad files in the folder.","layout-grid"),e.createDiv({cls:"stashpad-view-popover-divider"}),this.plugin.encryption?.isConfigured?.()){let y=this.currentEncryptionFilter(),v=(E,Q,S)=>{let x=e.createDiv({cls:"stashpad-view-popover-row"});E===y&&x.addClass("is-active"),x.createDiv({cls:"stashpad-view-popover-main"}).createSpan({cls:"stashpad-view-popover-label",text:Q}),x.createDiv({cls:"stashpad-view-popover-desc",text:S}),x.onclick=async H=>{H.preventDefault(),H.stopPropagation(),E!==y&&(await this.setEncryptionFilter(E),this.refreshList()),t()}};v("all","Encryption: show all","Both locked \u{1F512} and decrypted notes."),v("locked","Encryption: locked only","Show only locked \u{1F512} stubs."),v("unlocked","Encryption: decrypted only","Hide locked \u{1F512} stubs."),e.createDiv({cls:"stashpad-view-popover-divider"})}let n=e.createDiv({cls:"stashpad-view-popover-row stashpad-view-popover-toggle"}),o=n.createEl("input",{type:"checkbox"});o.checked=this.currentHideChildless(),n.createDiv({cls:"stashpad-view-popover-main"}).createSpan({cls:"stashpad-view-popover-label",text:"Hide childless notes"}),n.createDiv({cls:"stashpad-view-popover-desc",text:i==="nested"?"Show only notes that have children. Applied at this level.":"Hide top-level notes without children; keep every parent's full subtree so no task is overlooked."}),n.onclick=async y=>{y.target!==o&&(y.preventDefault(),o.checked=!o.checked),await this.setHideChildless(o.checked),this.refreshList()};let a=e.createDiv({cls:"stashpad-view-popover-row stashpad-view-popover-toggle"}),l=a.createEl("input",{type:"checkbox"});l.checked=this.currentHideCompleted(),a.createDiv({cls:"stashpad-view-popover-main"}).createSpan({cls:"stashpad-view-popover-label",text:"Hide completed notes"}),a.createDiv({cls:"stashpad-view-popover-desc",text:"Hide notes marked complete. A completed parent stays visible while any descendant is still incomplete."}),a.onclick=async y=>{y.target!==l&&(y.preventDefault(),l.checked=!l.checked),await this.setHideCompleted(l.checked),this.refreshList()};let c=e.createDiv({cls:"stashpad-view-popover-row stashpad-view-popover-toggle"}),d=c.createEl("input",{type:"checkbox"});d.checked=this.currentAttachmentsOnly(),c.createDiv({cls:"stashpad-view-popover-main"}).createSpan({cls:"stashpad-view-popover-label",text:"Hide notes without attachments"}),c.createDiv({cls:"stashpad-view-popover-desc",text:"Show only notes that have an attachment. A parent stays visible while any descendant has one."}),c.onclick=async y=>{y.target!==d&&(y.preventDefault(),d.checked=!d.checked),await this.setAttachmentsOnly(d.checked),this.refreshList()},e.createDiv({cls:"stashpad-view-popover-divider"});let h=e.createDiv({cls:"stashpad-view-popover-row stashpad-view-popover-toggle"});i!=="everything"&&h.addClass("is-disabled");let g=h.createEl("input",{type:"checkbox"});g.checked=this.currentIncludeAttachments(),g.disabled=i!=="everything",h.createDiv({cls:"stashpad-view-popover-main"}).createSpan({cls:"stashpad-view-popover-label",text:"Include attachments"}),h.createDiv({cls:"stashpad-view-popover-desc",text:i==="everything"?"Show attachments referenced by notes as their own rows in the file list. Off by default \u2014 they already appear inline on the notes that embed them.":"Only applies in Everything mode."}),h.onclick=async y=>{i==="everything"&&(y.target!==g&&(y.preventDefault(),g.checked=!g.checked),await this.setIncludeAttachments(g.checked),this.refreshList())},e.createDiv({cls:"stashpad-view-popover-divider"});let f=e.createDiv({cls:"stashpad-view-popover-row stashpad-view-popover-toggle"}),p=f.createEl("input",{type:"checkbox"});p.checked=this.importedOnly,f.createDiv({cls:"stashpad-view-popover-main"}).createSpan({cls:"stashpad-view-popover-label",text:"Imported notes only"}),f.createDiv({cls:"stashpad-view-popover-desc",text:"Show only notes that came in via import."}),f.onclick=y=>{y.target!==p&&(y.preventDefault(),p.checked=!p.checked),this.importedOnly=p.checked,this.reconcileSelectionAfterFilter(),this.refreshList()};let u=new Map,I=this.noteFolder.replace(/\/+$/,"");for(let y of this.app.vault.getMarkdownFiles()){if((y.parent?.path?.replace(/\/+$/,"")??"")!==I)continue;let v=ct(this.app.metadataCache.getFileCache(y)?.frontmatter?.author);v&&u.set(v.id,v.name)}e.createDiv({cls:"stashpad-view-popover-divider"});let m=e.createDiv({cls:"stashpad-view-popover-row"}),C=m.createDiv({cls:"stashpad-view-popover-main"});(0,k.setIcon)(C.createSpan({cls:"stashpad-view-popover-icon"}),"user"),C.createSpan({cls:"stashpad-view-popover-label",text:"By author"});let B=C.createEl("select",{cls:"stashpad-view-author-select"}),w=B.createEl("option",{text:"All authors",value:""});this.authorFilter||(w.selected=!0);for(let[y,v]of[...u.entries()].sort((E,Q)=>E[1].localeCompare(Q[1]))){let E=B.createEl("option",{text:v,value:y});this.authorFilter===y&&(E.selected=!0)}B.onclick=y=>y.stopPropagation(),B.onchange=()=>{this.authorFilter=B.value||null,this.reconcileSelectionAfterFilter(),this.refreshList()},m.createDiv({cls:"stashpad-view-popover-desc",text:u.size?"Show only notes by the chosen author.":"No authored notes in this folder yet."})}setTagFilter(e){(this.tagFilter??null)!==(e??null)&&(this.tagFilter=e,this.reconcileSelectionAfterFilter(),this.persistFocus(),this.render())}populateSortMenuBody(e,t){let i=this.noteFolder,s=this.focusId,n=this.sortStore.getMode(i,s);for(let o of Ro){let a=e.createDiv({cls:"stashpad-sort-popover-row"});o===n&&a.addClass("is-active"),a.createSpan({cls:"stashpad-sort-popover-label",text:ui[o]}),a.onclick=async l=>{l.preventDefault(),l.stopPropagation(),t(),o!==n&&(this.sortStore.setMode(i,s,o),await this.sortStore.save(i),this.tree.rebuild(i),this.render())}}}populateTimeMenuBody(e,t){let i=e.createDiv({cls:"stashpad-view-popover-row stashpad-view-popover-toggle"}),s=i.createEl("input",{type:"checkbox"});s.checked=this.timeFilterCalendar,i.createDiv({cls:"stashpad-view-popover-main"}).createSpan({cls:"stashpad-view-popover-label",text:"Calendar mode"}),i.createDiv({cls:"stashpad-view-popover-desc",text:"Use calendar boundaries (start of today/week/month/year). Off = rolling windows back from now."}),i.onclick=n=>{n.target!==s&&(n.preventDefault(),s.checked=!s.checked),this.timeFilterCalendar=s.checked,this.persistFocus(),this.refreshList()};for(let n of ps){let o=e.createDiv({cls:"stashpad-sort-popover-row"});this.timeFilter===n.key&&o.addClass("is-active");let a=this.timeFilterCalendar?n.calLong:n.rollLong;o.createSpan({cls:"stashpad-sort-popover-label",text:a}),o.onclick=l=>{l.preventDefault(),l.stopPropagation(),t(),this.timeFilter!==n.key&&this.setTimeFilter(n.key)}}}populateColorMenuBody(e,t,i){let s=(n,o,a)=>{let l=e.createDiv({cls:"stashpad-color-filter-popover-row"}),c=l.createSpan({cls:"stashpad-color-filter-swatch"});o?c.setCssStyles({background:o}):c.addClass("is-empty");let d=l.createSpan({cls:"stashpad-color-filter-popover-label"});d.setText(n),o&&(d.style.color=o),l.onclick=h=>{h.preventDefault(),h.stopPropagation(),a(),i()}};s("All colors",null,()=>this.setColorFilter(null));for(let n of t){let o=this.plugin.getColorAlias(this.noteFolder,n.hex),a=o?`${o} (${n.count})`:`${n.hex} (${n.count})`;s(a,n.hex,()=>this.setColorFilter(n.hex))}}populateTagMenuBody(e,t){let i=this.collectFolderTags(),s=(n,o)=>{let a=e.createDiv({cls:"stashpad-color-filter-popover-row"});a.createSpan({cls:"stashpad-color-filter-swatch is-empty"}),a.createSpan({cls:"stashpad-color-filter-popover-label"}).setText(n),(this.tagFilter??"")===(o??"")&&a.addClass("is-active"),a.onclick=d=>{d.preventDefault(),d.stopPropagation(),this.setTagFilter(o),t()}};s(i.length===0?"No tags":"All tags",null),s("Tagged",Gt),s("Untagged",Lt);for(let n of i)s(`${n.label} (${n.count})`,n.raw)}setColorFilter(e){let t=e?e.toLowerCase():null;(this.colorFilter??null)!==t&&(this.colorFilter=t,this.reconcileSelectionAfterFilter(),this.persistFocus(),this.render())}setTimeFilter(e){this.timeFilter!==e&&(this.timeFilter=e,this.reconcileSelectionAfterFilter(),this.persistFocus(),this.render())}reconcileSelectionAfterFilter(){let e=this.filterChildren(this.collectViewItems(this.focusId)),t=new Set(e.map(i=>i.id));for(let i of[...this.selection])t.has(i)||this.selection.delete(i);if(this.firstSelectedId&&!t.has(this.firstSelectedId)&&(this.firstSelectedId=null),this.lastSelected&&!t.has(this.lastSelected)&&(this.lastSelected=null),this.selection.size>0){let i=e.findIndex(s=>this.selection.has(s.id));this.cursorIdx=i>=0?i:Math.min(this.cursorIdx,e.length-1)}else this.cursorIdx>=e.length&&(this.cursorIdx=e.length-1)}renderTinyHeader(e){let t=e.createDiv({cls:"stashpad-tiny-header"}),i=t.createEl("button",{cls:"stashpad-tiny-nav-btn"});be(i,"arrow-left","\u2039"),i.title="Back (up to parent)",this.navBackStack.length>0||this.focusId!==F||i.addClass("is-disabled"),i.title=this.navBackStack.length>0?"Back":this.focusId!==F?"Back (up to parent)":"No back history",i.onclick=()=>this.navigateBack();let n=t.createEl("button",{cls:"stashpad-tiny-nav-btn"});be(n,"arrow-right","\u203A"),n.title=this.navForwardSnapshots.length>0?"Forward":"No forward history",this.navForwardSnapshots.length===0&&n.addClass("is-disabled"),n.onclick=()=>this.navigateForward();let o=this.tree.get(this.focusId)??this.tree.getRoot(),a=(this.noteFolder.split("/").pop()||this.noteFolder).trim(),l=this.focusId===F?a:`${a} / ${this.titleForNode(o).trim()}`,c=t.createEl("button",{cls:"stashpad-tiny-title stashpad-folder-btn"}),d=c.createSpan({cls:"stashpad-tiny-title-icon stashpad-btn-icon"});(0,k.setIcon)(d,"folder"),c.createSpan({cls:"stashpad-tiny-title-text stashpad-btn-text",text:l}),c.title=`${this.noteFolder}${this.focusId!==F?` / ${this.titleForNode(o).trim()}`:""} +Click to switch / create folder.`,c.onclick=u=>{u.preventDefault(),this.plugin.openFolderPicker()};let h=t.createDiv({cls:"stashpad-tiny-sticky"}),g=h.createEl("input",{type:"checkbox"});if(g.checked=this.tinyAlwaysOnTop,h.createSpan({text:"Sticky"}),g.onchange=()=>{this.tinyAlwaysOnTop=g.checked,this.applyTinyAlwaysOnTop()},!k.Platform.isMobile){let u=t.createEl("button",{cls:"stashpad-tiny-nav-btn stashpad-tiny-opacity-btn"});(0,k.setIcon)(u,"contrast"),u.title="Window transparency",this.tinyOpacity<1&&u.addClass("is-active"),u.onclick=I=>{I.stopPropagation(),this.toggleTinyOpacityPopover(u)}}let f=t.createEl("button",{cls:"stashpad-tiny-expand stashpad-tiny-exit-compact"});(0,k.setIcon)(f,this.compactMode?"panel-top":"rows-2"),f.title=this.compactMode?"Compact mode is ON \u2014 click to turn off.":"Compact mode \u2014 click to turn on (strips row metadata).",this.compactMode&&f.addClass("is-active"),f.onclick=()=>{this.toggleCompactMode()};let p=t.createEl("button",{cls:"stashpad-tiny-expand"});(0,k.setIcon)(p,"maximize-2"),p.title="Exit tiny mode",p.onclick=()=>{this.exitTinyMode()}}toggleTinyOpacityPopover(e){if(this.tinyOpacityPopover){this.tinyOpacityPopover.remove(),this.tinyOpacityPopover=null;return}let t=document.createElement("div");t.className="stashpad-tiny-opacity-popover",t.createSpan({cls:"stashpad-tiny-opacity-label",text:"Transparency"});let i=t.createEl("input",{type:"range"});i.min="30",i.max="100",i.step="1",i.value=String(Math.round(this.tinyOpacity*100));let s=t.createSpan({cls:"stashpad-tiny-opacity-pct",text:`${i.value}%`});i.addEventListener("input",()=>{let d=Math.min(100,Math.max(30,parseInt(i.value,10)||100));this.tinyOpacity=d/100,s.setText(`${d}%`),this.applyTinyOpacity(),e.toggleClass("is-active",this.tinyOpacity<1)}),i.addEventListener("change",()=>{this.app.workspace.requestSaveLayout()}),this.viewRoot.appendChild(t);let n=e.getBoundingClientRect(),o=this.viewRoot.getBoundingClientRect();t.setCssStyles({top:`${n.bottom-o.top+4}px`,left:`${Math.max(4,Math.min(n.left-o.left,o.width-180))}px`});let a=d=>{t.contains(d.target)||d.target===e||e.contains(d.target)||c()},l=d=>{d.key==="Escape"&&c()},c=()=>{t.remove(),this.tinyOpacityPopover=null,document.removeEventListener("mousedown",a,!0),document.removeEventListener("keydown",l,!0)};setTimeout(()=>{document.addEventListener("mousedown",a,!0),document.addEventListener("keydown",l,!0)},0),this.tinyOpacityPopover=t,i.focus()}getOwnElectronWindow(){try{let e=this.containerEl?.ownerDocument?.defaultView??window,s=((e?.require?.("electron")??window.require?.("electron"))?.remote??e?.electron?.remote??e?.["@electron/remote"])?.getCurrentWindow?.()??e?.electronWindow??null;if(!s)try{let n=window.require?.("electron"),o=n?.remote??n?.["@electron/remote"],l=(o?.BrowserWindow??n?.BrowserWindow)?.getAllWindows?.()??[];l.length===1?s=l[0]:l.length>1&&(s=o?.getFocusedWindow?.()??null??l[l.length-1])}catch(n){console.debug("[Stashpad] BrowserWindow.getAllWindows fallback failed",n)}return s||console.debug("[Stashpad] couldn't resolve own electron window"),s??null}catch(e){return console.debug("[Stashpad] resolve own electron window failed",e),null}}applyTinyAlwaysOnTop(){let e=this.getOwnElectronWindow();if(e)try{e.setAlwaysOnTop?.(!!this.tinyAlwaysOnTop)}catch(t){console.debug("[Stashpad] setAlwaysOnTop failed",t)}}applyTinyOpacity(){let e=this.getOwnElectronWindow();if(!e)return;let t=Math.min(1,Math.max(.3,this.tinyOpacity));try{e.setOpacity?.(t)}catch(i){console.debug("[Stashpad] setOpacity failed",i)}}applyTinyWindow(){let e=this.getOwnElectronWindow();if(e)try{if(this.tinyMode){e.setMinimumSize?.(220,260);let s=100,n=100;try{let o=e.getBounds?.();o&&typeof o.x=="number"&&(s=o.x,n=o.y)}catch{}try{e.setBounds?.({x:s,y:n,width:280,height:360})}catch{}try{e.setSize?.(280,360)}catch{}e.setAlwaysOnTop?.(!!this.tinyAlwaysOnTop);try{e.setOpacity?.(Math.min(1,Math.max(.3,this.tinyOpacity)))}catch{}}else e.setAlwaysOnTop?.(!1)}catch(t){console.debug("[Stashpad] tiny window apply failed",t)}}async exitTinyMode(){this.tinyMode=!1,this.tinyAlwaysOnTop=!1,this.tinyOpacity=1;try{this.getOwnElectronWindow()?.setOpacity?.(1)}catch{}this.compactMode=!1,this.applyTinyWindow();let e=this.getOwnElectronWindow();try{if(e?.setMinimumSize?.(400,300),k.Platform.isMacOS??!1){let i=this.containerEl?.ownerDocument?.defaultView?.require?.("electron")??window.require?.("electron"),n=(i?.remote?.screen??i?.screen)?.getPrimaryDisplay?.().workArea;n?e?.setBounds?.({x:n.x,y:n.y,width:n.width,height:n.height}):e?.maximize?.()}else e?.maximize?.()}catch{}this.render();try{await this.app.workspace.requestSaveLayout?.()}catch{}}enterTinyMode(){this.tinyMode=!0,this.applyTinyWindow(),this.render();try{this.app.workspace.requestSaveLayout?.()}catch{}}renderBreadcrumb(e){let t=e.createDiv({cls:"stashpad-breadcrumb"});k.Platform.isMobile||this.renderActionsCluster(t);let i=t.createSpan({cls:"stashpad-crumb stashpad-crumb-home"});if(k.Platform.isMobile?((0,k.setIcon)(i,"home"),i.title="Home"):i.setText("Home"),i.onclick=()=>this.navigateTo(F),this.focusId===F){let h=this.tree.getChildren(this.focusId).length;if(h>0&&(t.createSpan({cls:"stashpad-crumb-count",text:`\xB7 ${h}`}).title=`${h} direct child${h===1?"":"ren"}`),this.compactMode){let g=t.createEl("button",{cls:"stashpad-compact-exit-btn"});(0,k.setIcon)(g,"panel-top"),g.title="Exit compact mode",g.onclick=f=>{f.preventDefault(),this.toggleCompactMode()}}return}let s=28,n=100,o=this.tree.pathTo(this.focusId),a=o.map(h=>{let g=this.titleForNode(h),f=g.length>s?g.slice(0,s-1)+"\u2026":g;return{id:h.id,label:f}}),l=h=>h.reduce((g,f)=>g+f.label.length+3,0);if(l(a)>n&&a.length>2){let h=!1;for(;l(a)>n&&a.length>2;)a.splice(1,1),h||(a.splice(1,0,{id:"__ellipsis__",label:"\u2026",isEllipsis:!0}),h=!0)}let c=k.Platform.isMobile?"/":" / ";for(let h of a)if(t.createSpan({cls:"stashpad-crumb-sep",text:c}),h.isEllipsis)t.createSpan({cls:"stashpad-crumb stashpad-crumb-ellipsis",text:h.label}).title=o.map(g=>this.titleForNode(g)).join(" / ");else{let g=h.id,f=t.createSpan({cls:"stashpad-crumb",text:h.label});f.title=h.label,f.onclick=()=>this.navigateTo(g),f.oncontextmenu=p=>{p.preventDefault(),this.openCrumbMenu(p,g)},k.Platform.isMobile&&this.attachLongPress(f,()=>this.openCrumbMenu(null,g))}if(t.querySelector(".stashpad-crumb-home")?.addEventListener("contextmenu",h=>{h.preventDefault(),this.openCrumbMenu(h,F)}),k.Platform.isMobile){let h=t.querySelector(".stashpad-crumb-home");h&&this.attachLongPress(h,()=>this.openCrumbMenu(null,F))}let d=this.tree.getChildren(this.focusId).length;if(d>0&&(t.createSpan({cls:"stashpad-crumb-count",text:`\xB7 ${d}`}).title=`${d} direct child${d===1?"":"ren"}`),this.compactMode){let h=t.createEl("button",{cls:"stashpad-compact-exit-btn"});(0,k.setIcon)(h,"panel-top"),h.title="Exit compact mode",h.onclick=g=>{g.preventDefault(),this.toggleCompactMode()}}k.Platform.isMobile||this.renderBreadcrumbLevelsButton(t)}renderBreadcrumbLevelsButton(e){let t=e.createEl("button",{cls:"stashpad-mobile-action-btn stashpad-crumb-levels-btn"});be(t,"route","\u22D4"),t.title="Show all levels \u2014 jump to any level in the path",t.onclick=i=>{i.preventDefault(),this.openBreadcrumbLevelsModal()}}openBreadcrumbLevelsModal(){let e=this.tree.pathTo(this.focusId),t=[{id:F,label:"Home",level:0,isCurrent:this.focusId===F,isHome:!0}];e.forEach((i,s)=>{t.push({id:i.id,label:this.titleForNode(i),level:s+1,isCurrent:i.id===this.focusId})}),new Oi(this.app,t,i=>this.navigateTo(i),{onContext:(i,s,n,o)=>this.openCrumbMenu(s,i,n,o),attachLongPress:(i,s)=>this.attachLongPress(i,s)}).open()}attachLongPress(e,t){let i=null,s=0,n=0,o=()=>{i!=null&&(window.clearTimeout(i),i=null)};e.addEventListener("touchstart",a=>{let l=a.touches[0];s=l?.clientX??0,n=l?.clientY??0,o(),i=window.setTimeout(()=>{i=null,t()},500)},{passive:!0}),e.addEventListener("touchmove",a=>{let l=a.touches[0];l&&(Math.abs(l.clientX-s)>10||Math.abs(l.clientY-n)>10)&&o()},{passive:!0}),e.addEventListener("touchend",o),e.addEventListener("touchcancel",o)}openCrumbMenu(e,t,i,s){let n=this.tree.get(t);if(!n)return;let o=new k.Menu;if(o.addItem(a=>a.setTitle("Navigate here").setIcon("arrow-right-circle").onClick(()=>{s?.(),this.navigateTo(t)})),o.addItem(a=>a.setTitle("Open in new Stashpad tab").setIcon("list-tree").onClick(()=>{s?.(),this.cmdOpenInNewStashpadTab(n)})),n.file&&o.addItem(a=>a.setTitle("Open in editor (new tab)").setIcon("pencil").onClick(()=>{s?.(),this.cmdOpenInEditor(n)})),e&&(e.clientX>0||e.clientY>0))o.showAtMouseEvent(e);else{let l=(i??e?.target??null)?.getBoundingClientRect();o.showAtPosition({x:l?.left??8,y:(l?.bottom??60)+4})}}renderFocusedHeaderMini(e,t){if(!t.file)return;let i=e.createDiv({cls:"stashpad-focused-mini"});i.dataset.id=t.id,i.createDiv({cls:"stashpad-focused-mini-text"}).setText(this.titleForNode(t).trim());let n=i.createEl("button",{cls:"stashpad-pencil stashpad-note-more stashpad-focused-mini-more"});(0,k.setIcon)(n,"more-horizontal"),n.title="Actions",n.onclick=o=>{o.stopPropagation(),this.openNoteMenu(o,t)}}installFocusedMiniObserver(e){let t=e.querySelector(".stashpad-focused"),i=e.querySelector(".stashpad-focused-mini");!t||!i||(this.focusedMiniObserver&&this.focusedMiniObserver.disconnect(),this.focusedMiniObserver=new IntersectionObserver(s=>{for(let n of s)i.toggleClass("is-visible",!n.isIntersecting)},{root:e,threshold:.05}),this.focusedMiniObserver.observe(t))}renderFocusedHeader(e,t){if(!t.file)return;let i=t.file,s=e.createDiv({cls:"stashpad-focused"});s.oncontextmenu=d=>{d.preventDefault(),this.openNoteMenu(d,t)};let o=s.createDiv({cls:"stashpad-focused-meta"}).createDiv({cls:"stashpad-focused-meta-top"});o.createSpan({cls:"stashpad-focused-time stashpad-note-time",text:this.formatTime(t.created)}),o.createDiv({cls:"stashpad-focused-grip-spacer"});let a=s.createDiv({cls:"stashpad-focused-body"});a.addEventListener("click",d=>this.handleRenderedClick(d,t));let l=s.createDiv({cls:"stashpad-focused-actions"}),c;if(k.Platform.isMobile){let d=l.createEl("button",{cls:"stashpad-pencil stashpad-note-more stashpad-focused-more"});(0,k.setIcon)(d,"more-horizontal"),d.title="Actions",d.onclick=h=>{h.stopPropagation(),this.openNoteMenu(h,t)},c=d}else{let d=l.createEl("button",{cls:"stashpad-pencil stashpad-focused-pencil"});(0,k.setIcon)(d,"pencil"),d.title="Edit in new tab",d.onclick=()=>void this.openFileAtEnd(i);let h=l.createEl("button",{cls:"stashpad-pencil stashpad-focused-dup"});(0,k.setIcon)(h,"copy"),h.title="Open this Stashpad in a new tab (clone)",h.onclick=()=>this.cmdOpenInNewStashpadTab(t),c=d}this.renderNoteBody(a,t,{clamp:k.Platform.isMobile,toggleHost:l,toggleAnchor:c})}renderRowBreadcrumb(e,t){let i=this.tree.pathTo(t.id),s=i.findIndex(a=>a.id===this.focusId),n=i.slice(s+1,i.length-1);if(n.length===0)return;let o=e.createDiv({cls:"stashpad-row-breadcrumb"});n.forEach((a,l)=>{let c=o.createSpan({cls:"stashpad-row-breadcrumb-seg",text:this.titleForNode(a)});c.title=`Focus into "${this.titleForNode(a)}"`,c.onclick=d=>{d.stopPropagation(),this.navigateTo(a.id)},l<n.length-1&&o.createSpan({cls:"stashpad-row-breadcrumb-sep",text:" / "})})}actionsForFile(e){return Gs(this.app,e,k.Platform.isMobile)}bulkActionMessage(e){let t=e.nodes.map(a=>`"${this.titleForNode(a).trim()||"(untitled)"}"`),i=e.suffix?` ${e.suffix}`:"",s=e.destination?` ${e.destination}`:"";if(t.length===0)return`${e.verb}${i}${s}`;if(t.length===1)return`${e.verb} ${t[0]}${i}${s}`;let n=e.bulletMax??10,o=t.length<=n?t.map(a=>`\u2022 ${a}`).join(` `):t.slice(0,n).map(a=>`\u2022 ${a}`).join(` `)+` \u2026+ ${t.length-n} more`;return`${e.verb} ${t.length} notes${i}${s}: -${o}`}titleList(e,t=3){if(!e.length)return"";let i=e.map(n=>this.titleForNode(n).trim()||"(untitled)");return i.length<=t?i.map(n=>`"${n}"`).join(", "):`${i.slice(0,t).map(n=>`"${n}"`).join(", ")}, +${i.length-t} more`}titleForNode(e){if(!e.file)return"Untitled";let i=this.app.metadataCache.getFileCache(e.file)?.headings?.[0]?.heading;return i||e.file.basename.replace(/-[a-z0-9]{4,12}$/,"").replace(/-/g," ")||"Untitled"}async forceManualMode(e){let t=this.noteFolder;this.sortStore.getMode(t,e)!=="manual"&&(this.sortStore.setMode(t,e,"manual"),await this.sortStore.save(t))}renderNote(e,t,i){if(!t.file||this.currentEncryptionFilter()==="locked")return;let s=t.file,n=this.tree.getChildren(t.id).length,o=this.selection.has(t.id),a=i===this.cursorIdx,l=this.inListPicker?.activeIdx===i,c=e.createDiv({cls:"stashpad-note"});o&&c.addClass("is-selected"),a&&c.addClass("is-cursor"),a&&this.cursorHasMoved&&this.plugin.settings.autoExpandCursorRow&&!this.cursorExpandOverride.has(t.id)&&c.addClass("is-cursor-expanded"),l&&c.addClass("is-pick-target"),this.isCompleted(t)&&c.addClass("is-completed"),this.isListPinned(t.id)&&c.addClass("is-list-pinned"),this.isCutPending(t.id)?c.addClass("is-cut-pending"):this.isCopyPending(t.id)&&c.addClass("is-copy-pending"),c.dataset.idx=String(i),c.dataset.id=t.id;let d=this.currentViewMode()==="nested";c.draggable=d,d&&this.dnd.attachRowDnD(c,t,i),c.addEventListener("click",E=>this.handleRowClick(E,i,t)),c.addEventListener("dblclick",E=>{if(!this.plugin.settings.doubleClickToFocus)return;let v=this.shouldAbsorbDismissTap();if(v&&this.aimedTapTargetId&&Date.now()-this.aimedTapAt<=rs.AIMED_TAP_WINDOW_MS){let S=this.aimedTapTargetId;this.aimedTapTargetId=null,this.traceTap("dblclick",E,i,!1),E.preventDefault(),window.getSelection()?.removeAllRanges(),this.navigateTo(S);return}if(this.traceTap("dblclick",E,i,v),v){E.preventDefault();return}E.target?.closest?.(".internal-link, .tag, a, .stashpad-note-task-checkbox")||(E.preventDefault(),window.getSelection()?.removeAllRanges(),this.navigateTo(t.id))});let h=this.isTask(t)||this.compactMode;h&&(c.addClass("is-task"),F.Platform.isMobile||this.addTaskCheckbox(c,t));let g=c.createDiv({cls:"stashpad-note-meta"}),u=g.createDiv({cls:"stashpad-note-meta-top"});u.createSpan({cls:"stashpad-note-time",text:this.formatTime(t.created)});let p=this.colorForNode(t),f=u.createDiv({cls:"stashpad-note-grip"});p&&f.addClass("has-color"),(0,F.setIcon)(f,"grip-vertical"),f.title=p?"Drag to reorder \xB7 right-click to change color":"Drag to reorder",f.draggable=d,d||(f.title=p?"Right-click to change color \xB7 drag disabled in this view mode":"Drag disabled in this view mode"),p&&f.style.setProperty("--stashpad-note-color",p);let I=h&&F.Platform.isMobile,m=this.isListPinned(t.id);if(n>0||I||m){let E=g.createDiv({cls:"stashpad-note-meta-bottom"});if(m){let v=E.createSpan({cls:"stashpad-note-listpin"});(0,F.setIcon)(v,"pin"),v.setAttr("aria-label","Pinned to top of list")}if(I&&this.addTaskCheckbox(E,t),n>0){let v=E.createSpan({cls:"stashpad-note-enter"});p&&(v.style.color=p),(0,F.setIcon)(v.createSpan({cls:"stashpad-btn-icon"}),"corner-down-right"),v.createSpan({text:` ${n}`}),v.onclick=Q=>{Q.stopPropagation(),this.navigateTo(t.id)}}}if(p)c.addClass("has-color"),c.style.setProperty("--stashpad-note-color",p);else{let E=this.inheritedColorForNode(t);E&&E.depth>0&&(c.addClass("has-inherited-color"),c.style.setProperty("--stashpad-inherited-color",E.hex),c.style.setProperty("--stashpad-inherited-depth",String(E.depth)))}let C=c.createDiv({cls:"stashpad-note-body"});this.currentViewMode()!=="nested"&&this.renderRowBreadcrumb(C,t);let B=C.createDiv({cls:"stashpad-note-body-content"}),w=c.createDiv({cls:"stashpad-note-actions"}),y;if(F.Platform.isMobile){let E=w.createEl("button",{cls:"stashpad-pencil stashpad-note-more"});(0,F.setIcon)(E,"ellipsis-vertical"),E.title="Actions",E.onclick=v=>{v.stopPropagation(),this.openNoteMenu(v,t)},y=E}else{let E=w.createEl("button",{cls:"stashpad-pencil"});(0,F.setIcon)(E,"pencil"),E.title="Edit in new tab",E.onclick=Q=>{Q.stopPropagation(),this.openFileAtEnd(s)};let v=w.createEl("button",{cls:"stashpad-pencil stashpad-enter-btn"});(0,F.setIcon)(v,"arrow-right"),v.title="Open in Stashpad view",v.onclick=Q=>{Q.stopPropagation(),this.navigateTo(t.id)},y=E}this.renderNoteBody(B,t,{clamp:!0,toggleHost:w,toggleAnchor:y}),this.renderVersionTabs(C,t),c.oncontextmenu=E=>{E.preventDefault(),this.openNoteMenu(E,t)}}renderVersionTabs(e,t){if(!this.plugin.settings.enableSheetVersions||!Fi(Ce(this.app,t)))return;let i=Ve(Ce(this.app,t)),s=t.parent??this.focusId,n=this.tree.getChildren(s),o=Rn(this.app,n.filter(c=>{let d=Ce(this.app,c);return Fi(d)&&Ve(d)===i}));if(o.length<2)return;let a=e.createDiv({cls:"stashpad-version-tabs"});for(let c of o){let d=c.id===t.id,h=Ce(this.app,c),g=ki(h),u=oa(h),p=a.createDiv({cls:"stashpad-version-tab"+(d?" is-active":"")+(g?" is-final":"")+(u?" is-original":"")});g&&p.createSpan({cls:"stashpad-version-star",text:"\u2605"}),p.createSpan({cls:"stashpad-version-tab-label",text:ca(this.app,c,this.titleForNode(c).trim()||"Untitled")});let f=this.titleForNode(c).trim();p.title=u?`${f} (original)`:`${f} (forked)`,d||(p.onclick=I=>{I.stopPropagation(),this.setActiveVersion(i,c.id)})}let l=a.createEl("button",{cls:"stashpad-version-add stashpad-version-fork"});(0,F.setIcon)(l,"git-fork"),l.title="Fork this version (copy)",l.onclick=c=>{c.stopPropagation(),this.cmdForkVersion(t)}}addTaskCheckbox(e,t){let i=e.createSpan({cls:"stashpad-note-task-checkbox"}),s=this.isCompleted(t);(0,F.setIcon)(i,s?"check-square":"square"),i.title=s?"Mark not done":"Mark done",i.addEventListener("mousedown",n=>n.stopPropagation()),i.addEventListener("dblclick",n=>{n.preventDefault(),n.stopPropagation()}),i.onclick=n=>{n.preventDefault(),n.stopPropagation(),this.toggleCompletedForNode(t)}}renderNoteBody(e,t,i={clamp:!0}){if(!t.file)return;if(this.bodyRenderer.hasFreshRenderCache(t.file)||!this.bodyRenderer.isArmed()){this.renderNoteBodyNow(e,t,i);return}e.empty();let s=e.createDiv({cls:"stashpad-note-text is-plain is-lazy-placeholder"});s.textContent=this.titleForNode(t),this.bodyRenderer.defer(e,()=>this.renderNoteBodyNow(e,t,i))}renderNoteBodyNow(e,t,i={clamp:!0}){if(!t.file)return;let s=t.file,n=(e.__stashpadRenderToken??0)+1;e.__stashpadRenderToken=n,this.bodyRenderer.getOrComputeRender(s).then(o=>{if(e.__stashpadRenderToken!==n)return;let{text:a,attachments:l,html:c}=o;e.empty();let d=e.createDiv({cls:"stashpad-note-text"}),h=this.isNoteExpanded(t.id);if(i.clamp&&!h&&d.addClass("is-clamped"),this.compactMode||this.tinyMode?(d.addClass("is-plain"),d.textContent=a):d.append((0,F.sanitizeHTMLToDom)(c)),l.length>0&&this.renderAttachmentRail(e,l),this.renderAuthorshipFooter(e,t),!i.clamp)return;let g=this.lastListWidth;if(o.ovW===g&&o.ovV!==void 0&&!h){o.ovV?this.attachExpandToggle(i,e,t,h):d.removeClass("is-clamped");return}requestAnimationFrame(()=>{if(h){this.attachExpandToggle(i,e,t,h);return}let u=e.closest?.(".stashpad-note.is-cursor-expanded");u&&u.removeClass("is-cursor-expanded");let p=d.scrollHeight>d.clientHeight+4;if(u&&u.addClass("is-cursor-expanded"),o.ovW=g,o.ovV=p,!p){d.removeClass("is-clamped");return}this.attachExpandToggle(i,e,t,h)})})}attachExpandToggle(e,t,i,s){let n=!!e.toggleHost,o=e.toggleHost??t;o.querySelector(".stashpad-expand-toggle")?.remove();let a=!!t.closest?.(".stashpad-note.is-cursor-expanded"),l=s||a,c=o.createEl("button",{cls:"stashpad-expand-toggle"});c.title=l?"Show less":"Show more",n||F.Platform.isMobile?((0,F.setIcon)(c,l?"chevron-up":"chevron-down"),c.addClass("is-icon"),n&&c.addClass("is-inline")):c.setText(l?"Show less":"Show more"),e.toggleAnchor&&e.toggleAnchor.parentElement===o&&o.insertBefore(c,e.toggleAnchor),c.addEventListener("dblclick",d=>{d.preventDefault(),d.stopPropagation()}),c.addEventListener("mousedown",d=>{d.stopPropagation()}),c.onclick=d=>{d.stopPropagation();let h=t.closest?.(".stashpad-note"),g=!!h?.classList.contains("is-cursor-expanded"),p=!(this.isNoteExpanded(i.id)||g);g&&!p?(h?.removeClass("is-cursor-expanded"),this.cursorExpandOverride.add(i.id)):p&&this.cursorExpandOverride.delete(i.id),this.setNoteExpanded(i.id,p),t.empty(),this.renderNoteBody(t,i,e)}}renderAttachmentRail(e,t){let i=e.createDiv({cls:"stashpad-rail"});for(let s of t){let n=this.app.metadataCache.getFirstLinkpathDest(s,""),o=(s.split(".").pop()??"").toLowerCase(),a=i.createDiv({cls:"stashpad-att"});if(a.title=s,n&&td.has(o)){let l=a.createEl("img",{cls:"stashpad-att-img"});l.src=this.app.vault.getResourcePath(n),l.alt=s}else{a.createDiv({cls:"stashpad-att-ext",text:o.toUpperCase()||"?"});let l=(s.split("/").pop()??s).replace(/\.[^.]+$/,"");a.createDiv({cls:"stashpad-att-name",text:l})}a.onclick=l=>{l.stopPropagation(),n&&this.app.workspace.getLeaf("tab").openFile(n)}}}renderComposerNavCluster(e){let t=e.createDiv({cls:"stashpad-composer-nav"}),i=t.createEl("button",{cls:"stashpad-composer-btn stashpad-composer-nav-folder"});(0,F.setIcon)(i,this.plugin.getFolderIcon(this.noteFolder)??"folder");let s=this.noteFolder.split("/").pop()||this.noteFolder||"Stashpad";i.createSpan({cls:"stashpad-btn-text",text:s}),i.title=`Folder: ${this.noteFolder} -Tap to switch / create.`,this.folderOverride&&i.addClass("is-active"),i.onmousedown=o=>o.preventDefault(),i.onclick=o=>{o.preventDefault(),this.openFolderPicker()};let n=t.createEl("button",{cls:"stashpad-composer-btn"});Se(n,"search","\u{1F50D}"),n.title="Search notes (Mod+F)",n.onmousedown=o=>o.preventDefault(),n.onclick=o=>{o.preventDefault(),this.openSearchModal()}}renderComposer(e){let t=X(),i=this.modeEnterSubmits,s=this.modeSplit??t.splitOnLines,n=e.createDiv({cls:"stashpad-composer"}),a=n.createDiv({cls:"stashpad-composer-input-wrap"}).createEl("textarea",{cls:"stashpad-composer-input",attr:{rows:"2",placeholder:this.composerPlaceholder(i,s)}});a.value=this.composerDraft,this.debouncedSaveDraft||(this.debouncedSaveDraft=(0,F.debounce)(N=>{this.saveDraft(N)},250)),a.addEventListener("input",()=>{this.composerDraft=a.value,this.debouncedSaveDraft(a.value)}),a.addEventListener("blur",()=>{this.saveDraft(a.value)});let l=null,c=()=>{l||(l=new F.Scope(this.app.scope),l.register([],"Escape",()=>(this.composerExitAt=Date.now(),a.blur(),this.viewRoot?.focus({preventScroll:!0}),!1)),l.register(["Mod"],"z",()=>!0),l.register(["Mod","Shift"],"z",()=>!0),this.app.keymap?.pushScope(l))},d=()=>{if(l){try{this.app.keymap?.popScope(l)}catch{}l=null}};if(a.addEventListener("focus",c),a.addEventListener("blur",d),document.activeElement===a&&c(),F.Platform.isMobile){let N=()=>{this.keyboardTransitionUntil=Date.now()+600};a.addEventListener("focus",()=>{document.body.classList.add("stashpad-keyboard-open"),N()}),a.addEventListener("blur",()=>{document.body.classList.remove("stashpad-keyboard-open"),N(),this.tapSettleUntil=Date.now()+300,F.Platform.isMobile&&this.cursorIdx>=0&&setTimeout(()=>this.revealCursorRow(),350)})}this.composerInputEl=a,this.composerAutocomplete&&this.composerAutocomplete.detach(),this.composerAutocomplete=new di(this.app,a),this.composerAutocomplete.attach();let h=async N=>{let U="";for(let R of N){let D=await this.importAttachment(R);if(!D)continue;let b=a.value+U,P=b&&!b.endsWith(` +${o}`}titleList(e,t=3){if(!e.length)return"";let i=e.map(n=>this.titleForNode(n).trim()||"(untitled)");return i.length<=t?i.map(n=>`"${n}"`).join(", "):`${i.slice(0,t).map(n=>`"${n}"`).join(", ")}, +${i.length-t} more`}titleForNode(e){if(!e.file)return"Untitled";let i=this.app.metadataCache.getFileCache(e.file)?.headings?.[0]?.heading;return i||e.file.basename.replace(/-[a-z0-9]{4,12}$/,"").replace(/-/g," ")||"Untitled"}async forceManualMode(e){let t=this.noteFolder;this.sortStore.getMode(t,e)!=="manual"&&(this.sortStore.setMode(t,e,"manual"),await this.sortStore.save(t))}renderNote(e,t,i){if(!t.file||this.currentEncryptionFilter()==="locked")return;let s=t.file,n=this.tree.getChildren(t.id).length,o=this.selection.has(t.id),a=i===this.cursorIdx,l=this.inListPicker?.activeIdx===i,c=e.createDiv({cls:"stashpad-note"});o&&c.addClass("is-selected"),a&&c.addClass("is-cursor"),a&&this.cursorHasMoved&&this.plugin.settings.autoExpandCursorRow&&!this.cursorExpandOverride.has(t.id)&&c.addClass("is-cursor-expanded"),l&&c.addClass("is-pick-target"),this.isCompleted(t)&&c.addClass("is-completed"),this.isListPinned(t.id)&&c.addClass("is-list-pinned"),this.isCutPending(t.id)?c.addClass("is-cut-pending"):this.isCopyPending(t.id)&&c.addClass("is-copy-pending"),c.dataset.idx=String(i),c.dataset.id=t.id;let d=this.currentViewMode()==="nested";c.draggable=d,d&&this.dnd.attachRowDnD(c,t,i),c.addEventListener("click",v=>this.handleRowClick(v,i,t)),c.addEventListener("dblclick",v=>{if(!this.plugin.settings.doubleClickToFocus)return;let E=this.shouldAbsorbDismissTap();if(E&&this.aimedTapTargetId&&Date.now()-this.aimedTapAt<=gs.AIMED_TAP_WINDOW_MS){let S=this.aimedTapTargetId;this.aimedTapTargetId=null,this.traceTap("dblclick",v,i,!1),v.preventDefault(),window.getSelection()?.removeAllRanges(),this.navigateTo(S);return}if(this.traceTap("dblclick",v,i,E),E){v.preventDefault();return}v.target?.closest?.(".internal-link, .tag, a, .stashpad-note-task-checkbox")||(v.preventDefault(),window.getSelection()?.removeAllRanges(),this.navigateTo(t.id))});let h=this.isTask(t)||this.compactMode;h&&(c.addClass("is-task"),k.Platform.isMobile||this.addTaskCheckbox(c,t));let g=c.createDiv({cls:"stashpad-note-meta"}),f=g.createDiv({cls:"stashpad-note-meta-top"});f.createSpan({cls:"stashpad-note-time",text:this.formatTime(t.created)});let p=this.colorForNode(t),u=f.createDiv({cls:"stashpad-note-grip"});p&&u.addClass("has-color"),(0,k.setIcon)(u,"grip-vertical"),u.title=p?"Drag to reorder \xB7 right-click to change color":"Drag to reorder",u.draggable=d,d||(u.title=p?"Right-click to change color \xB7 drag disabled in this view mode":"Drag disabled in this view mode"),p&&u.style.setProperty("--stashpad-note-color",p);let I=h&&k.Platform.isMobile,m=this.isListPinned(t.id);if(n>0||I||m){let v=g.createDiv({cls:"stashpad-note-meta-bottom"});if(m){let E=v.createSpan({cls:"stashpad-note-listpin"});(0,k.setIcon)(E,"pin"),E.setAttr("aria-label","Pinned to top of list")}if(I&&this.addTaskCheckbox(v,t),n>0){let E=v.createSpan({cls:"stashpad-note-enter"});p&&(E.style.color=p),(0,k.setIcon)(E.createSpan({cls:"stashpad-btn-icon"}),"corner-down-right"),E.createSpan({text:` ${n}`}),E.onclick=Q=>{Q.stopPropagation(),this.navigateTo(t.id)}}}if(p)c.addClass("has-color"),c.style.setProperty("--stashpad-note-color",p);else{let v=this.inheritedColorForNode(t);v&&v.depth>0&&(c.addClass("has-inherited-color"),c.style.setProperty("--stashpad-inherited-color",v.hex),c.style.setProperty("--stashpad-inherited-depth",String(v.depth)))}let C=c.createDiv({cls:"stashpad-note-body"});this.currentViewMode()!=="nested"&&this.renderRowBreadcrumb(C,t);let B=C.createDiv({cls:"stashpad-note-body-content"}),w=c.createDiv({cls:"stashpad-note-actions"}),y;if(k.Platform.isMobile){let v=w.createEl("button",{cls:"stashpad-pencil stashpad-note-more"});(0,k.setIcon)(v,"ellipsis-vertical"),v.title="Actions",v.onclick=E=>{E.stopPropagation(),this.openNoteMenu(E,t)},y=v}else{let v=w.createEl("button",{cls:"stashpad-pencil"});(0,k.setIcon)(v,"pencil"),v.title="Edit in new tab",v.onclick=Q=>{Q.stopPropagation(),this.openFileAtEnd(s)};let E=w.createEl("button",{cls:"stashpad-pencil stashpad-enter-btn"});(0,k.setIcon)(E,"arrow-right"),E.title="Open in Stashpad view",E.onclick=Q=>{Q.stopPropagation(),this.navigateTo(t.id)},y=v}this.renderNoteBody(B,t,{clamp:!0,toggleHost:w,toggleAnchor:y}),this.renderVersionTabs(C,t),c.oncontextmenu=v=>{v.preventDefault(),this.openNoteMenu(v,t)}}renderVersionTabs(e,t){if(!this.plugin.settings.enableSheetVersions||!xi(Be(this.app,t)))return;let i=ze(Be(this.app,t)),s=t.parent??this.focusId,n=this.tree.getChildren(s),o=jn(this.app,n.filter(c=>{let d=Be(this.app,c);return xi(d)&&ze(d)===i}));if(o.length<2)return;let a=e.createDiv({cls:"stashpad-version-tabs"});for(let c of o){let d=c.id===t.id,h=Be(this.app,c),g=Ni(h),f=mr(h),p=a.createDiv({cls:"stashpad-version-tab"+(d?" is-active":"")+(g?" is-final":"")+(f?" is-original":"")});g&&p.createSpan({cls:"stashpad-version-star",text:"\u2605"}),p.createSpan({cls:"stashpad-version-tab-label",text:wr(this.app,c,this.titleForNode(c).trim()||"Untitled")});let u=this.titleForNode(c).trim();p.title=f?`${u} (original)`:`${u} (forked)`,d||(p.onclick=I=>{I.stopPropagation(),this.setActiveVersion(i,c.id)})}let l=a.createEl("button",{cls:"stashpad-version-add stashpad-version-fork"});(0,k.setIcon)(l,"git-fork"),l.title="Fork this version (copy)",l.onclick=c=>{c.stopPropagation(),this.cmdForkVersion(t)}}addTaskCheckbox(e,t){let i=e.createSpan({cls:"stashpad-note-task-checkbox"}),s=this.isCompleted(t);(0,k.setIcon)(i,s?"check-square":"square"),i.title=s?"Mark not done":"Mark done",i.addEventListener("mousedown",n=>n.stopPropagation()),i.addEventListener("dblclick",n=>{n.preventDefault(),n.stopPropagation()}),i.onclick=n=>{n.preventDefault(),n.stopPropagation(),this.toggleCompletedForNode(t)}}renderNoteBody(e,t,i={clamp:!0}){if(!t.file)return;if(this.bodyRenderer.hasFreshRenderCache(t.file)||!this.bodyRenderer.isArmed()){this.renderNoteBodyNow(e,t,i);return}e.empty();let s=e.createDiv({cls:"stashpad-note-text is-plain is-lazy-placeholder"});s.textContent=this.titleForNode(t),this.bodyRenderer.defer(e,()=>this.renderNoteBodyNow(e,t,i))}renderNoteBodyNow(e,t,i={clamp:!0}){if(!t.file)return;let s=t.file,n=(e.__stashpadRenderToken??0)+1;e.__stashpadRenderToken=n,this.bodyRenderer.getOrComputeRender(s).then(o=>{if(e.__stashpadRenderToken!==n)return;let{text:a,attachments:l,html:c}=o;e.empty();let d=e.createDiv({cls:"stashpad-note-text"}),h=this.isNoteExpanded(t.id);if(i.clamp&&!h&&d.addClass("is-clamped"),this.compactMode||this.tinyMode?(d.addClass("is-plain"),d.textContent=a):d.append((0,k.sanitizeHTMLToDom)(c)),l.length>0&&this.renderAttachmentRail(e,l),this.renderAuthorshipFooter(e,t),!i.clamp)return;let g=this.lastListWidth;if(o.ovW===g&&o.ovV!==void 0&&!h){o.ovV?this.attachExpandToggle(i,e,t,h):d.removeClass("is-clamped");return}requestAnimationFrame(()=>{if(h){this.attachExpandToggle(i,e,t,h);return}let f=e.closest?.(".stashpad-note.is-cursor-expanded");f&&f.removeClass("is-cursor-expanded");let p=d.scrollHeight>d.clientHeight+4;if(f&&f.addClass("is-cursor-expanded"),o.ovW=g,o.ovV=p,!p){d.removeClass("is-clamped");return}this.attachExpandToggle(i,e,t,h)})})}attachExpandToggle(e,t,i,s){let n=!!e.toggleHost,o=e.toggleHost??t;o.querySelector(".stashpad-expand-toggle")?.remove();let a=!!t.closest?.(".stashpad-note.is-cursor-expanded"),l=s||a,c=o.createEl("button",{cls:"stashpad-expand-toggle"});c.title=l?"Show less":"Show more",n||k.Platform.isMobile?((0,k.setIcon)(c,l?"chevron-up":"chevron-down"),c.addClass("is-icon"),n&&c.addClass("is-inline")):c.setText(l?"Show less":"Show more"),e.toggleAnchor&&e.toggleAnchor.parentElement===o&&o.insertBefore(c,e.toggleAnchor),c.addEventListener("dblclick",d=>{d.preventDefault(),d.stopPropagation()}),c.addEventListener("mousedown",d=>{d.stopPropagation()}),c.onclick=d=>{d.stopPropagation();let h=t.closest?.(".stashpad-note"),g=!!h?.classList.contains("is-cursor-expanded"),p=!(this.isNoteExpanded(i.id)||g);g&&!p?(h?.removeClass("is-cursor-expanded"),this.cursorExpandOverride.add(i.id)):p&&this.cursorExpandOverride.delete(i.id),this.setNoteExpanded(i.id,p),t.empty(),this.renderNoteBody(t,i,e)}}renderAttachmentRail(e,t){let i=e.createDiv({cls:"stashpad-rail"});for(let s of t){let n=this.app.metadataCache.getFirstLinkpathDest(s,""),o=(s.split(".").pop()??"").toLowerCase(),a=i.createDiv({cls:"stashpad-att"});if(a.title=s,n&&Ed.has(o)){let l=a.createEl("img",{cls:"stashpad-att-img"});l.src=this.app.vault.getResourcePath(n),l.alt=s}else{a.createDiv({cls:"stashpad-att-ext",text:o.toUpperCase()||"?"});let l=(s.split("/").pop()??s).replace(/\.[^.]+$/,"");a.createDiv({cls:"stashpad-att-name",text:l})}a.onclick=l=>{l.stopPropagation(),n&&this.app.workspace.getLeaf("tab").openFile(n)}}}renderComposerNavCluster(e){let t=e.createDiv({cls:"stashpad-composer-nav"}),i=t.createEl("button",{cls:"stashpad-composer-btn stashpad-composer-nav-folder"});(0,k.setIcon)(i,this.plugin.getFolderIcon(this.noteFolder)??"folder");let s=this.noteFolder.split("/").pop()||this.noteFolder||"Stashpad";i.createSpan({cls:"stashpad-btn-text",text:s}),i.title=`Folder: ${this.noteFolder} +Tap to switch / create.`,this.folderOverride&&i.addClass("is-active"),i.onmousedown=o=>o.preventDefault(),i.onclick=o=>{o.preventDefault(),this.openFolderPicker()};let n=t.createEl("button",{cls:"stashpad-composer-btn"});be(n,"search","\u{1F50D}"),n.title="Search notes (Mod+F)",n.onmousedown=o=>o.preventDefault(),n.onclick=o=>{o.preventDefault(),this.openSearchModal()}}renderComposer(e){let t=X(),i=this.modeEnterSubmits,s=this.modeSplit??t.splitOnLines,n=e.createDiv({cls:"stashpad-composer"}),a=n.createDiv({cls:"stashpad-composer-input-wrap"}).createEl("textarea",{cls:"stashpad-composer-input",attr:{rows:"2",placeholder:this.composerPlaceholder(i,s)}});a.value=this.composerDraft,this.debouncedSaveDraft||(this.debouncedSaveDraft=(0,k.debounce)(N=>{this.saveDraft(N)},250)),a.addEventListener("input",()=>{this.composerDraft=a.value,this.debouncedSaveDraft(a.value)}),a.addEventListener("blur",()=>{this.saveDraft(a.value)});let l=null,c=()=>{l||(l=new k.Scope(this.app.scope),l.register([],"Escape",()=>(this.composerExitAt=Date.now(),a.blur(),this.viewRoot?.focus({preventScroll:!0}),!1)),l.register(["Mod"],"z",()=>!0),l.register(["Mod","Shift"],"z",()=>!0),this.app.keymap?.pushScope(l))},d=()=>{if(l){try{this.app.keymap?.popScope(l)}catch{}l=null}};if(a.addEventListener("focus",c),a.addEventListener("blur",d),document.activeElement===a&&c(),k.Platform.isMobile){let N=()=>{this.keyboardTransitionUntil=Date.now()+600};a.addEventListener("focus",()=>{document.body.classList.add("stashpad-keyboard-open"),N()}),a.addEventListener("blur",()=>{document.body.classList.remove("stashpad-keyboard-open"),N(),this.tapSettleUntil=Date.now()+300,k.Platform.isMobile&&this.cursorIdx>=0&&setTimeout(()=>this.revealCursorRow(),350)})}this.composerInputEl=a,this.composerAutocomplete&&this.composerAutocomplete.detach(),this.composerAutocomplete=new pi(this.app,a),this.composerAutocomplete.attach();let h=async N=>{let R="";for(let U of N){let D=await this.importAttachment(U);if(!D)continue;let b=a.value+R,P=b&&!b.endsWith(` `)?` -`:"";U+=`${P}${D} -`}U&&(a.value=a.value+U,this.composerDraft=a.value,this.saveDraft(a.value),a.focus(),a.setSelectionRange(a.value.length,a.value.length))};a.addEventListener("dragover",N=>{if(!(!N.dataTransfer||!Array.from(N.dataTransfer.types).includes("Files"))){N.preventDefault(),N.stopPropagation();try{N.dataTransfer.dropEffect="copy"}catch{}}}),a.addEventListener("drop",N=>{let U=Array.from(N.dataTransfer?.files??[]);U.length!==0&&(N.preventDefault(),N.stopPropagation(),h(U))}),a.addEventListener("paste",N=>{let U=this.plugin.noteClipboard;if(U?.mode==="cut"&&U.text&&N.clipboardData?.getData("text/plain")===U.text){if(this.focusedInsideCut(U.ids)){N.preventDefault(),new F.Notice("Can't paste a cut note into the note you're cutting.");return}N.preventDefault(),this.completeCutIntoComposer();return}let R=[],D=N.clipboardData;if(D){for(let b of Array.from(D.files??[]))R.push(b);if(R.length===0){for(let b of Array.from(D.items??[]))if(b.kind==="file"){let P=b.getAsFile();P&&R.push(P)}}R.length!==0&&(N.preventDefault(),N.stopPropagation(),h(R))}});let g=n.createEl("input",{cls:"stashpad-composer-file-input",type:"file",attr:{multiple:"true"}});g.setCssStyles({display:"none"});let u=n.createDiv({cls:"stashpad-composer-btn-rail"});F.Platform.isMobile&&this.renderComposerNavCluster(u);let p=u.createDiv({cls:"stashpad-composer-btn-group"}),f=p.createEl("button",{cls:"stashpad-composer-btn"});(0,F.setIcon)(f,"list-end");let I=bi[X().splitMode].toLowerCase();f.title=s?`Split: ON \u2014 ${I} (Mod+/ to toggle, right-click to change)`:`Split into notes (Mod+/) \u2014 right-click to choose: ${I}`,s&&f.addClass("is-active"),f.onmousedown=N=>N.preventDefault(),f.onclick=N=>{N.preventDefault(),this.toggleSplit()},f.oncontextmenu=N=>{N.preventDefault();let U=new F.Menu,R=X().splitMode;["lines","paragraphs","headings"].forEach(D=>{U.addItem(b=>b.setTitle(bi[D]).setChecked(D===R).onClick(async()=>{this.plugin.settings.splitMode=D,await this.plugin.saveSettings(),this.render()}))}),U.showAtMouseEvent(N)};let m=p.createEl("button",{cls:"stashpad-composer-btn"});(0,F.setIcon)(m,"map-pin"),this.nextDestination&&m.createSpan({text:` ${this.destinationLabel()}`,cls:"stashpad-btn-text"}),m.title="Set destination (Mod+D)",this.nextDestination&&m.addClass("is-active"),m.onmousedown=N=>N.preventDefault(),m.onclick=N=>{N.preventDefault();let U=document.activeElement===a;this.openDestinationPicker(U)};let C=p.createEl("button",{cls:"stashpad-composer-btn"});(0,F.setIcon)(C,i?"corner-down-left":"arrow-big-down-dash"),C.title=i?"Enter sends (click to switch to Shift+Enter)":"Shift+Enter sends (click to switch to Enter)",C.onmousedown=N=>N.preventDefault(),C.onclick=N=>{N.preventDefault(),this.modeEnterSubmits=!i,this.render(),this.composerInputEl?.focus()};let B=N=>{let U=a.value&&!a.value.endsWith(` +`:"";R+=`${P}${D} +`}R&&(a.value=a.value+R,this.composerDraft=a.value,this.saveDraft(a.value),a.focus(),a.setSelectionRange(a.value.length,a.value.length))};a.addEventListener("dragover",N=>{if(!(!N.dataTransfer||!Array.from(N.dataTransfer.types).includes("Files"))){N.preventDefault(),N.stopPropagation();try{N.dataTransfer.dropEffect="copy"}catch{}}}),a.addEventListener("drop",N=>{let R=Array.from(N.dataTransfer?.files??[]);R.length!==0&&(N.preventDefault(),N.stopPropagation(),h(R))}),a.addEventListener("paste",N=>{let R=this.plugin.noteClipboard;if(R?.mode==="cut"&&R.text&&N.clipboardData?.getData("text/plain")===R.text){if(this.focusedInsideCut(R.ids)){N.preventDefault(),new k.Notice("Can't paste a cut note into the note you're cutting.");return}N.preventDefault(),this.completeCutIntoComposer();return}let U=[],D=N.clipboardData;if(D){for(let b of Array.from(D.files??[]))U.push(b);if(U.length===0){for(let b of Array.from(D.items??[]))if(b.kind==="file"){let P=b.getAsFile();P&&U.push(P)}}U.length!==0&&(N.preventDefault(),N.stopPropagation(),h(U))}});let g=n.createEl("input",{cls:"stashpad-composer-file-input",type:"file",attr:{multiple:"true"}});g.setCssStyles({display:"none"});let f=n.createDiv({cls:"stashpad-composer-btn-rail"});k.Platform.isMobile&&this.renderComposerNavCluster(f);let p=f.createDiv({cls:"stashpad-composer-btn-group"}),u=p.createEl("button",{cls:"stashpad-composer-btn"});(0,k.setIcon)(u,"list-end");let I=Mi[X().splitMode].toLowerCase();u.title=s?`Split: ON \u2014 ${I} (Mod+/ to toggle, right-click to change)`:`Split into notes (Mod+/) \u2014 right-click to choose: ${I}`,s&&u.addClass("is-active"),u.onmousedown=N=>N.preventDefault(),u.onclick=N=>{N.preventDefault(),this.toggleSplit()},u.oncontextmenu=N=>{N.preventDefault();let R=new k.Menu,U=X().splitMode;["lines","paragraphs","headings"].forEach(D=>{R.addItem(b=>b.setTitle(Mi[D]).setChecked(D===U).onClick(async()=>{this.plugin.settings.splitMode=D,await this.plugin.saveSettings(),this.render()}))}),R.showAtMouseEvent(N)};let m=p.createEl("button",{cls:"stashpad-composer-btn"});(0,k.setIcon)(m,"map-pin"),this.nextDestination&&m.createSpan({text:` ${this.destinationLabel()}`,cls:"stashpad-btn-text"}),m.title="Set destination (Mod+D)",this.nextDestination&&m.addClass("is-active"),m.onmousedown=N=>N.preventDefault(),m.onclick=N=>{N.preventDefault();let R=document.activeElement===a;this.openDestinationPicker(R)};let C=p.createEl("button",{cls:"stashpad-composer-btn"});(0,k.setIcon)(C,i?"corner-down-left":"arrow-big-down-dash"),C.title=i?"Enter sends (click to switch to Shift+Enter)":"Shift+Enter sends (click to switch to Enter)",C.onmousedown=N=>N.preventDefault(),C.onclick=N=>{N.preventDefault(),this.modeEnterSubmits=!i,this.render(),this.composerInputEl?.focus()};let B=N=>{let R=a.value&&!a.value.endsWith(` `)?` -`:"";a.value+=`${U}${N} -`,this.composerDraft=a.value},w=p.createEl("button",{cls:"stashpad-composer-btn"});(0,F.setIcon)(w,"paperclip"),w.title="Attach files",w.onmousedown=N=>N.preventDefault(),w.onclick=N=>{N.preventDefault();let U=document.activeElement===a;if(g.click(),U){let R=()=>{a.focus()};setTimeout(R,100),setTimeout(R,500)}},g.addEventListener("change",async()=>{let N=Array.from(g.files??[]);g.value="";for(let U of N){let R=await this.importAttachment(U);R&&B(R)}a.focus()});let y=u.createEl("button",{cls:"stashpad-composer-btn stashpad-composer-rail-toggle"});(0,F.setIcon)(y,"chevron-left"),y.title="Show more composer options",u.insertBefore(y,p);let E=N=>{u.toggleClass("is-expanded",N),y.title=N?"Hide options":"Show more composer options",(0,F.setIcon)(y,N?"chevron-right":"chevron-left")};y.onmousedown=N=>N.preventDefault(),y.onclick=N=>{N.preventDefault(),E(!u.hasClass("is-expanded"))},E(!1);let v=()=>n.clientWidth<700,Q=()=>{let N=v();n.toggleClass("is-narrow",N),E(!N)};Q(),requestAnimationFrame(Q);let S=new ResizeObserver(Q);S.observe(n),this.composerNarrowObserver?.disconnect(),this.composerNarrowObserver=S;let x=u.createEl("button",{cls:"stashpad-composer-btn stashpad-composer-send"});x.title="Send (Enter)",(0,F.setIcon)(x,"arrow-up");let H=async()=>{let N=a.value.trim();if(!N)return;a.value="",this.composerDraft="";try{await this.saveDraft("")}catch{}try{await this.recordLastSubmitted(N)}catch{}let U=this.modeSplit??X().splitOnLines,R=this.nextDestination,D=this.nextDestinationFolder,b=!!D&&D!==this.noteFolder;this.nextDestination=null,this.nextDestinationFolder=null,this.nextDestinationLabel=null,this.autoSelectNewest=!b,this.scrollToBottomOnNextRender=!b;let P=b?{targetFolder:D}:void 0,q=R??this.focusId;if(U){let L=Di(N,X().splitMode);L.length===1?await this.createNoteUnder(L[0],q,P):L.length>1&&await this.createNotesBatch(L,q,P,N,b?D:this.noteFolder)}else await this.createNoteUnder(N,q,P);X().autofocusComposerAfterSend&&(this.focusComposerOnNextRender=!0,b&&this.composerInputEl?.focus())};x.onclick=()=>void H(),a.addEventListener("keydown",N=>{let U=this.modeEnterSubmits;if(N.key==="Escape"){N.preventDefault(),N.stopPropagation(),a.blur(),this.viewRoot.focus({preventScroll:!0});return}if(N.key==="ArrowUp"&&a.selectionStart===0&&a.selectionEnd===0){if(N.preventDefault(),a.blur(),this.viewRoot.focus({preventScroll:!0}),this.currentChildren.length>0){let R=this.lastCursorByFocus.get(this.focusId)??this.lastSelected,D=R?this.currentChildren.findIndex(b=>b.id===R):-1;this.cursorIdx=D>=0?D:this.currentChildren.length-1,this.selectCursor(!1)}return}N.key==="Enter"&&!N.isComposing&&(U?!N.shiftKey:N.shiftKey)&&(N.preventDefault(),N.stopPropagation(),N.stopImmediatePropagation(),H())}),e.createDiv({cls:"stashpad-composer-help"}).setText(this.composerHelperText(i,s))}composerPlaceholder(e,t){return F.Platform.isMobile?t?"New notes (split on newlines)":"New note":`Type a note. ${e?"Enter":"Shift+Enter"} = send, ${e?"Shift+Enter":"Enter"} = newline${t?" (each line \u2192 a note)":""}\u2026`}composerHelperText(e,t){let i=e?"Enter":"Shift+Enter",s=e?"Shift+Enter":"Enter",n=X().bindings,o=h=>{let g=n[h];return g.primary&&g.secondary?g.preferRight?g.secondary:g.primary:g.primary||g.secondary},a=es(o("toggleSplit")),l=es(o("pickDestination")),c=es(o("search")),d=this.nextDestination?` \u2022 destination: ${this.destinationLabel()}`:"";return`${i} sends \xB7 ${s} newline \xB7 ${a} split: ${t?"ON":"off"} \xB7 ${l} destination \xB7 ${c} search${d}`}destinationLabel(){if(!this.nextDestination)return"current";if(this.nextDestinationFolder)return this.nextDestinationLabel??this.nextDestinationFolder;if(this.nextDestination===k)return"Home";let e=this.tree.get(this.nextDestination);return e?this.titleForNode(e).trim():"?"}renderMobileNav(e){let t=e.createDiv({cls:"stashpad-mobile-nav"});t.createEl("button",{text:"Home"}).onclick=()=>this.navigateTo(k),t.createEl("button",{text:"Back"}).onclick=()=>this.navigateUp(),t.createEl("button",{text:"Bookmarks"}).onclick=()=>this.openBookmarks()}handleRenderedClick(e,t){let i=e.target,s=i?.closest?.(".tag");if(s){e.preventDefault(),e.stopPropagation();let a=(s.getAttribute("href")||s.textContent||"").replace(/^#/,"").trim();if(a){let l=this.app.internalPlugins?.plugins?.["global-search"],c=l?.instance?.openGlobalSearch?.bind(l.instance);c&&c(`tag:#${a}`)}return}let n=i?.closest?.(".internal-link");if(n){e.preventDefault(),e.stopPropagation();let o=n.getAttribute("data-href")||n.getAttribute("href");if(o){let a=t.file?.path||"";this.app.workspace.openLinkText(o,a,!0)}}}handleRowClick(e,t,i){let s=this.shouldAbsorbDismissTap();if(this.traceTap("click",e,t,s),s){(!this.aimedTapTargetId||Date.now()-this.aimedTapAt>rs.AIMED_TAP_WINDOW_MS)&&(this.aimedTapTargetId=i.id,this.aimedTapAt=Date.now()),e.stopPropagation();return}this.aimedTapTargetId=null,this.cursorHasMoved=!0;let n=e.target,o=n?.closest?.(".tag");if(o){e.preventDefault(),e.stopPropagation();let d=(o.getAttribute("href")||o.textContent||"").replace(/^#/,"").trim();if(d){let h=this.app.internalPlugins?.plugins?.["global-search"],g=h?.instance?.openGlobalSearch?.bind(h.instance);g&&g(`tag:#${d}`)}return}let a=n?.closest?.(".internal-link");if(a){e.preventDefault(),e.stopPropagation();let c=a.getAttribute("data-href")||a.getAttribute("href");if(c){let d=i.file?.path||"";this.app.workspace.openLinkText(c,d,!0)}return}if(n?.tagName==="A"&&n.href)return;if(e.stopPropagation(),this.inListPicker){this.inListPicker.activeIdx=t,this.commitInListPicker();return}let l=this.selection.size===0;if(e.shiftKey&&this.lastSelected){this.cursorIdx=t;let c=this.currentChildren.findIndex(d=>d.id===this.lastSelected);if(l&&(this.firstSelectedId=this.lastSelected),c!==-1){let[d,h]=c<t?[c,t]:[t,c];for(let g=d;g<=h;g++)this.selection.add(this.currentChildren[g].id)}else this.selection.add(i.id)}else if(e.metaKey||e.ctrlKey)if(this.selection.has(i.id))if(this.selection.delete(i.id),this.firstSelectedId===i.id&&(this.firstSelectedId=null),this.lastSelected===i.id&&(this.lastSelected=this.selection.size>0?[...this.selection][this.selection.size-1]:null),this.selection.size===0)this.cursorIdx=-1;else{let c=this.lastSelected??[...this.selection][this.selection.size-1],d=c?this.currentChildren.findIndex(h=>h.id===c):-1;d>=0&&(this.cursorIdx=d)}else this.cursorIdx=t,l&&(this.firstSelectedId=i.id),this.selection.add(i.id);else this.mobileSelectMode?(this.cursorIdx=t,this.selection.has(i.id)?(this.selection.delete(i.id),this.firstSelectedId===i.id&&(this.firstSelectedId=null)):this.selection.add(i.id)):(this.cursorIdx=t,this.selection.clear(),this.selection.add(i.id),this.firstSelectedId=i.id,this.lastSelected=i.id);this.selection.size===0&&(this.firstSelectedId=null),this.selection.has(i.id)&&(this.lastSelected=i.id),this.viewRoot.focus({preventScroll:!0}),this.repaintSelectionClasses(),this.revealCursorRow(),this.stampSelectedCursor(),this.plugin.notifyStashpadSelectionChanged()}revealCursorRow(){let e=()=>{if(this.cursorIdx<0)return;let t=this.listEl?.querySelector(`[data-idx="${this.cursorIdx}"]`);if(!t||!this.listEl)return;let i=this.listEl,s=i.getBoundingClientRect(),n=t.getBoundingClientRect(),o=4,a=s.top+o,l=s.bottom-o;if(F.Platform.isMobile&&document.body.classList.contains("stashpad-keyboard-open")){let c=this.viewRoot?.querySelector(".stashpad-composer");c&&(l=Math.min(l,c.getBoundingClientRect().top-o))}n.top<a?i.scrollTop+=n.top-a:n.bottom>l&&(i.scrollTop+=n.bottom-l)};e(),requestAnimationFrame(e),setTimeout(e,60),setTimeout(e,200)}shouldAbsorbDismissTap(){return F.Platform.isMobile&&Date.now()<this.tapSettleUntil}traceTap(e,t,i,s){if(!this.plugin.settings.debugTrace||!this.listEl)return;let n=this.listEl,o=window.visualViewport,a=n.querySelector(`.stashpad-note[data-idx="${i}"]`)?.getBoundingClientRect();this.plugin.trace(`tap:${e}`,{absorbed:s,clientY:Math.round(t.clientY),clientX:Math.round(t.clientX),idx:i,rowTop:a?Math.round(a.top):null,listTop:Math.round(n.getBoundingClientRect().top),listScrollTop:Math.round(n.scrollTop),listScrollH:Math.round(n.scrollHeight),listClientH:Math.round(n.clientHeight),innerH:window.innerHeight,vvHeight:o?Math.round(o.height):null,vvOffsetTop:o?Math.round(o.offsetTop):null,tapSettleInMs:Math.max(0,this.tapSettleUntil-Date.now()),kbOpen:document.body.classList.contains("stashpad-keyboard-open")})}jumpToTop(){this.currentChildren.length!==0&&(this.cursorIdx=0,this.selectCursor(!1))}jumpToBottom(){this.currentChildren.length!==0&&(this.cursorIdx=this.currentChildren.length-1,this.selectCursor(!1))}selectCursor(e){let t=this.currentChildren[this.cursorIdx];if(t){if(this.cursorHasMoved=!0,!e)this.selection.clear(),this.selection.add(t.id),this.firstSelectedId=t.id,this.lastSelected=t.id;else{let i=this.firstSelectedId??t.id,s=this.currentChildren.findIndex(n=>n.id===i);if(s===-1)this.selection.add(t.id),this.firstSelectedId=t.id;else{let[n,o]=s<this.cursorIdx?[s,this.cursorIdx]:[this.cursorIdx,s];this.selection.clear();for(let a=n;a<=o;a++)this.selection.add(this.currentChildren[a].id);this.firstSelectedId=i}this.lastSelected=t.id}this.repaintSelectionClasses(),this.revealCursorRow(),this.stampSelectedCursor(),this.plugin.notifyStashpadSelectionChanged()}}repaintSelectionClasses(){if(!this.listEl)return;let e=!!this.plugin.settings.autoExpandCursorRow,t=this.inListPicker?.activeIdx??-1;this.listEl.querySelectorAll(".stashpad-note").forEach(s=>{let n=Number(s.dataset.idx),o=s.dataset.id??"",a=n===this.cursorIdx;s.classList.toggle("is-cursor",a),s.classList.toggle("is-selected",this.selection.has(o)),a||this.cursorExpandOverride.delete(o),s.classList.toggle("is-cursor-expanded",e&&a&&this.cursorHasMoved&&!this.cursorExpandOverride.has(o)),s.classList.toggle("is-pick-target",n===t)})}revealRowAt(e){if(!this.listEl)return;let t=this.listEl.querySelector(`.stashpad-note[data-idx="${e}"]`);if(!t)return;let i=t.getBoundingClientRect(),s=this.listEl.getBoundingClientRect();(i.top<s.top||i.bottom>s.bottom)&&t.scrollIntoView({block:"nearest",behavior:"auto"})}getActionTargets(){if(this.selection.size>0)return[...this.selection].map(t=>this.tree.get(t)).filter(t=>!!t&&!!t.file);let e=this.currentChildren[this.cursorIdx];return e?[e]:[]}async cmdLockSelection(){if(!this.plugin.encryption?.isConfigured?.()){new F.Notice("Set up encryption first (Settings \u2192 Stashpad \u2192 Encryption).");return}let e=this.getActionTargets();if(e.length===0)return;let t=new Set(e.map(o=>o.id)),i=new Set((this.plugin.settings.lockedSubtrees??[]).map(o=>o.rootId).filter(o=>!!o)),s=e.filter(o=>{if(i.has(o.id))return!1;let a=o.parent;for(;a;){if(t.has(a))return!1;a=this.tree.get(a)?.parent??null}return!0});if(s.length===0){new F.Notice("Nothing to lock (already locked).");return}let n=0;for(let o of s){let a=this.order.getOrder(this.noteFolder,o.parent??k),l=a.indexOf(o.id),c=l>0?a[l-1]:null;await this.plugin.lockNoteSubtree(this.noteFolder,o.id,c,{silent:!0})&&n++}n>0&&(this.selection.clear(),this.lastSelected=null,this.render(),this.plugin.notifications.show({message:`Locked ${n} stash${n===1?"":"es"}.`,kind:"success",category:"system",folder:this.noteFolder}))}async cmdMoveToArchive(){let e=this.getActionTargets();if(e.length===0)return;let t=(this.noteFolder??"").replace(/\/+$/,"");if(this.plugin.isArchiveFolder(t)){new F.Notice(`These notes are already in an archive folder (\u201C${t.split("/").pop()}\u201D).`,6e3);return}let i=(this.plugin.settings.archiveFolders??[]).map(a=>a.replace(/\/+$/,"")).filter(a=>a!==t);if(i.length===0){new F.Notice("No archive folder available. Mark a folder as archive first (Settings \u2192 Stashpad \u2192 Encryption \u2192 Per-folder, or the folder panel right-click).",8e3);return}let s=(this.plugin.settings.defaultArchiveFolder??"").replace(/\/+$/,""),n=s&&i.includes(s)?s:i.length===1?i[0]:null,o=a=>{this.archiveSources(e,a)};if(n){o(n);return}new Vo(this.app,i,o).open()}async archiveSources(e,t){let i=this.noteFolder,s=t.replace(/\/+$/,"");if(!((this.plugin.settings.folderEncPrefs??{})[s]?.archiveEncryptContent??!0)){let h=new Set(e.map(C=>C.id)),g=e.filter(C=>{let B=C.parent;for(;B;){if(h.has(B))return!1;B=this.tree.get(B)?.parent??null}return!0});if(g.length===0)return;let u=[],p=new Set,f=C=>{if(!p.has(C.id)){p.add(C.id),C.file&&u.push(C.file);for(let B of this.tree.getChildren(C.id))f(B)}};for(let C of g)f(C);let I=[];for(let C of u){let B=C.path,w=C.name,y=w.lastIndexOf("."),E=y>0?w.slice(0,y):w,v=y>0?w.slice(y):"",Q=`${s}/${w}`;for(let S=1;await this.app.vault.adapter.exists(Q);S++)Q=`${s}/${E}-${S}${v}`;try{await this.app.fileManager.renameFile(C,Q),I.push({from:B,to:Q})}catch(S){console.warn("[Stashpad] plaintext archive move failed",B,S)}}this.selection.clear(),this.lastSelected=null,this.tree.rebuild(i),this.render();let m=s.split("/").pop()||s;this.plugin.notifications.show({message:`Moved ${I.length} note${I.length===1?"":"s"} \u2192 plaintext archive \u201C${m}\u201D (de-indexed, not encrypted). Undo to bring ${I.length===1?"it":"them"} back.`,kind:"success",category:"system",folder:i}),this.plugin.getUndoStack(i).push({label:`Archive (plaintext, ${g.length})`,undo:async()=>{for(let C of I){let B=this.app.vault.getAbstractFileByPath(C.to);if(B instanceof F.TFile)try{await this.app.fileManager.renameFile(B,C.from)}catch(w){console.warn("[Stashpad] plaintext archive undo failed",C.to,w)}}this.tree.rebuild(i),this.render()}});return}if(!this.plugin.encryption?.isConfigured?.()){new F.Notice(`Archiving encrypts the notes, but encryption isn't set up yet (Settings \u2192 Stashpad \u2192 Encryption). Set it up \u2014 or turn off \u201CEncrypt archived notes\u201D for \u201C${s.split("/").pop()}\u201D to make it a plaintext archive.`,9e3);return}let o=new Set(e.map(h=>h.id)),a=e.filter(h=>{let g=h.parent;for(;g;){if(o.has(g))return!1;g=this.tree.get(g)?.parent??null}return!0});if(a.length===0)return;let l=a.map(h=>h.id),c=[];for(let h of a){let g=this.order.getOrder(i,h.parent??k),u=g.indexOf(h.id),p=u>0?g[u-1]:null,f=await this.plugin.lockNoteSubtree(i,h.id,p,{silent:!0,blobFolder:t});f&&c.push(f.blobPath)}if(c.length===0)return;this.selection.clear(),this.lastSelected=null,this.tree.rebuild(i),this.render();let d=t.split("/").pop()||t;this.plugin.notifications.show({message:`Archived ${c.length} note${c.length===1?"":"s"} \u2192 \u201C${d}\u201D. Undo to bring ${c.length===1?"it":"them"} back.`,kind:"success",category:"system",folder:i}),this.plugin.getUndoStack(i).push({label:`Archive (${c.length})`,undo:async()=>{for(let h of c)try{await this.plugin.unlockBundleAt(h,{silent:!0,destFolder:i})}catch(g){console.warn("[Stashpad] archive undo failed",h,g)}c=[],this.tree.rebuild(i),this.render()},redo:async()=>{c=[];for(let h of l){let g=await this.plugin.lockNoteSubtree(i,h,null,{silent:!0,blobFolder:t});g&&c.push(g.blobPath)}this.tree.rebuild(i),this.render()}})}async cmdEncryptDelete(){if(!this.plugin.encryption?.isConfigured?.()){new F.Notice("Set up encryption first (Settings \u2192 Stashpad \u2192 Encryption).");return}let e=this.getActionTargets();if(e.length===0)return;let t=new Set(e.map(n=>n.id)),i=e.filter(n=>{let o=n.parent;for(;o;){if(t.has(o))return!1;o=this.tree.get(o)?.parent??null}return!0});if(i.length===0)return;let s=i.length;new ie(this.app,`Encrypt & delete ${s} note${s===1?"":"s"}?`,[`The selected note${s===1?"":"s"} (and any children) will be encrypted and moved to Stashpad's encrypted trash.`,"","\u2022 The readable copy is permanently removed from the folder.",`\u2022 You can restore ${s===1?"it":"them"} later from the encrypted trash \u2014 you'll need your encryption password.`,`\u2022 If you lose your password, ${s===1?"it's":"they're"} gone for good.`].join(` -`),"Encrypt & delete",async n=>{n&&await this.secureDeleteSources(i)}).open()}async secureDeleteSources(e){if(!this.plugin.encryption?.isConfigured?.()){new F.Notice("Set up encryption first (Settings \u2192 Stashpad \u2192 Encryption).");return}let t=new Set(e.map(a=>a.id)),i=e.filter(a=>{let l=a.parent;for(;l;){if(t.has(l))return!1;l=this.tree.get(l)?.parent??null}return!0});if(i.length===0)return;let s=this.noteFolder,n=i.map(a=>a.id),o=[];for(let a of n){let l=await this.plugin.encryptDeleteSubtree(s,a);l&&o.push(l)}o.length!==0&&(this.selection.clear(),this.lastSelected=null,this.tree.rebuild(s),this.render(),this.plugin.notifications.show({message:`Securely deleted ${o.length} note${o.length===1?"":"s"} \u2192 encrypted trash. Undo to bring ${o.length===1?"it":"them"} back.`,kind:"success",category:"system",folder:s}),this.plugin.getUndoStack(s).push({label:`Secure delete (${o.length})`,undo:async()=>{for(let a of o)try{await this.plugin.restoreDeletedAt(a,{silent:!0})}catch(l){console.warn("[Stashpad] secure-delete undo failed",a,l)}o=[],this.tree.rebuild(s),this.render()},redo:async()=>{o=[];for(let a of n){let l=await this.plugin.encryptDeleteSubtree(s,a);l&&o.push(l)}this.tree.rebuild(s),this.render()}}))}async cmdUnlockAll(){if(!this.plugin.encryption?.isConfigured?.()){new F.Notice("Set up encryption first (Settings \u2192 Stashpad \u2192 Encryption).");return}let e=new Set,t=new Set,i=this.getActionTargets().map(n=>n.id);for(;i.length;){let n=i.pop();if(!t.has(n)){t.add(n);for(let o of this.tree.getChildren(n))i.push(o.id)}}for(let n of t)for(let o of this.plugin.lockedSubtreesFor(this.noteFolder,n))e.add(o.blob);if(e.size===0){let n=(this.tree.get(this.focusId)??this.tree.getRoot()).id;for(let o of this.plugin.lockedSubtreesFor(this.noteFolder,n))e.add(o.blob)}if(e.size===0){new F.Notice("No locked notes to unlock here.");return}if(!await this.plugin.ensureEncryptionUnlocked())return;let s=0;for(let n of e)try{await this.plugin.unlockBundleAt(n,{silent:!0})&&s++}catch(o){console.warn("[Stashpad] batch unlock skipped",n,o)}s>0&&(this.selection.clear(),this.lastSelected=null,this.render(),this.plugin.notifications.show({message:`Unlocked ${s} stash${s===1?"":"es"}.`,kind:"success",category:"system",folder:this.noteFolder}))}toggleSplit(){let e=this.modeSplit??X().splitOnLines;this.modeSplit=!e,this.render(),this.composerInputEl?.focus()}openDestinationPicker(e=!1){let t=!1;new tt(this.app,this.tree,i=>this.titleForNode(i),{mode:"pick",placeholder:"Send next note(s) under which note?",allowCreate:!0,onPick:async i=>{if(t=!0,i.crossFolder){let s=i.crossId??i.id.replace(/^cross:/,"");this.nextDestination=s,this.nextDestinationFolder=i.crossFolder;let n=i.crossFolder.split("/").pop()||i.crossFolder,o=s===k?"Home":(i.crossFile?.basename??"note").replace(/-[a-z0-9]{4,12}$/,"").replace(/-/g," ");this.nextDestinationLabel=`${n} \u25B8 ${o}`,this.render(),this.composerInputEl?.focus();return}this.nextDestination=i.id,this.nextDestinationFolder=null,this.nextDestinationLabel=null,this.render(),this.composerInputEl?.focus()},onCreate:async i=>{t=!0;let s=await this.createNoteUnder(i,this.focusId);s&&(this.nextDestination=s,this.nextDestinationFolder=null,this.nextDestinationLabel=null,this.render(),this.composerInputEl?.focus())},crossFolderNotes:()=>this.collectCrossFolderDestinations(),excludedFolderNotes:this.excludedSearchFolders().length>0?()=>this.collectExcludedFolderNotes():void 0,onClose:()=>{e&&!t&&this.composerInputEl?.focus()}}).open()}collectCrossFolderDestinations(){let e=this.collectCrossFolderNotes(),t=this.plugin.searchableFolders(this.noteFolder).filter(n=>n!==this.noteFolder),i=new Map;for(let n of this.app.vault.getMarkdownFiles()){let o=n.parent?.path?.replace(/\/+$/,"")??"";if(!t.includes(o))continue;this.app.metadataCache.getFileCache(n)?.frontmatter?.id===k&&i.set(o,n)}return[...t.map(n=>({file:i.get(n),folder:n,id:k,title:`Home \u2014 ${n.split("/").pop()||n}`,body:""})),...e]}openSearchInParentModal(){let e=this.focusId,t=s=>{if(s===e)return!0;let n=new Set,o=this.tree.get(s);for(;o&&o.id!==k&&!n.has(o.id);){if(n.add(o.id),o.parent===e||o.id===e)return!0;if(!o.parent)return!1;o=this.tree.get(o.parent)}return e===k},i=new Proxy(this.tree,{get:(s,n)=>n==="getRoot"?()=>s.get(e)??s.getRoot():n==="getChildren"?o=>s.getChildren(o):s[n]});new tt(this.app,i,s=>this.titleForNode(s),{mode:"search",placeholder:`Search in "${this.titleForNode(this.tree.get(e)??this.tree.getRoot()).trim()}"\u2026`,allowCreate:!1,onPick:s=>{s.node&&t(s.node.id)?this.navigateTo(s.node.id):s.node&&this.navigateTo(s.node.id)}}).open()}openSearchModal(){if(this.openSearchInstance){let i=this.openSearchInstance.inputEl;i&&(i.focus(),i.select());return}let e=new tt(this.app,this.tree,i=>this.titleForNode(i),{mode:"search",placeholder:"Search Stashpad notes\u2026",allowCreate:!0,onCreate:async i=>{let s=i.trim();s&&new tt(this.app,this.tree,n=>this.titleForNode(n),{mode:"pick",placeholder:`Create "${s}" under which note?`,allowCreate:!1,crossFolderNotes:()=>this.collectCrossFolderDestinations(),excludedFolderNotes:this.excludedSearchFolders().length>0?()=>this.collectExcludedFolderNotes():void 0,folderResults:()=>this.plugin.discoverStashpadFolders().filter(n=>n!==this.noteFolder),localFolder:this.noteFolder,onPick:async n=>{let o=n.crossFolder?n.crossId??n.id.replace(/^cross:/,""):n.node?.id,a=n.crossFolder??this.noteFolder;if(!o)return;let l=(this.plugin.settings.folder||"Stashpad").trim().replace(/^\/+|\/+$/g,"")||"Stashpad",c=this.app.workspace,d=c.getLeaf("tab");await d.setViewState({type:z,active:!0,state:{focusId:o,folderOverride:a===l?null:a}}),c.revealLeaf(d);let h=d.view;if(h&&typeof h.createNoteUnder=="function"){let g=await h.createNoteUnder(s,o);g&&typeof h.navigateTo=="function"&&h.navigateTo(g)}}}).open()},onPick:i=>{if(i.kind==="folder-open"&&i.folder){this.openFolderInNewTab(i.folder);return}let s=this.plugin.settings.searchOpensInNewTab!==!1;if(i.locked){let n=i.locked.parentId??k;s?this.openNoteInNewTab(this.noteFolder,n):this.navigateTo(n);return}if(i.crossFolder&&i.crossFile){let n=i.crossId??i.id.replace(/^cross:/,"");s?this.openNoteInNewTab(i.crossFolder,n):this.switchToFolderAndFocus(i.crossFolder,n);return}if(i.node){let n=this.plugin.settings.searchOpensInContext!==!1,o=n?i.node.parent??k:i.node.id,a=n?i.node.id:void 0;s?this.openNoteInNewTab(this.noteFolder,o,a):(a&&(this.pendingCursorId=a),this.navigateTo(o))}},crossFolderNotes:()=>this.collectCrossFolderDestinations(),excludedFolderNotes:this.excludedSearchFolders().length>0?()=>this.collectExcludedFolderNotes():void 0,folderResults:()=>this.plugin.discoverStashpadFolders().filter(i=>i!==this.noteFolder),showFilterChips:!0,localFolder:this.noteFolder,lockedNotes:()=>this.plugin.lockedSubtreesInFolder(this.noteFolder),hideLockedTitles:this.plugin.settings.hideLockedTitles??!1});this.openSearchInstance=e;let t=e.onClose.bind(e);e.onClose=()=>{t(),this.openSearchInstance===e&&(this.openSearchInstance=null)},e.open()}excludedSearchFolders(){let e=new Set(this.plugin.searchableFolders(this.noteFolder));return this.plugin.discoverStashpadFolders().filter(t=>t!==this.noteFolder&&!e.has(t))}collectExcludedFolderNotes(){let e=this.excludedSearchFolders();if(!e.length)return[];let t=this.collectCrossFolderNotes(e),i=new Map;for(let n of this.app.vault.getMarkdownFiles()){let o=n.parent?.path?.replace(/\/+$/,"")??"";if(!e.includes(o))continue;this.app.metadataCache.getFileCache(n)?.frontmatter?.id===k&&i.set(o,n)}return[...e.map(n=>({file:i.get(n),folder:n,id:k,title:`Home \u2014 ${n.split("/").pop()||n}`,body:""})),...t]}collectCrossFolderNotes(e){let t=[],i=(e??this.plugin.searchableFolders(this.noteFolder)).filter(o=>o!==this.noteFolder);if(!i.length)return t;let s=new Set(i),n=new Map;for(let o of this.app.vault.getMarkdownFiles()){let a=o.parent?.path?.replace(/\/+$/,"")??"";if(!s.has(a))continue;let l=n.get(a);l||(l=[],n.set(a,l)),l.push(o)}for(let o of i){let a=n.get(o)??[],l=new Map;for(let c of a){let d=this.app.metadataCache.getFileCache(c)?.frontmatter;typeof d?.id=="string"&&l.set(d.id,c)}for(let c of a){let d=this.app.metadataCache.getFileCache(c)?.frontmatter,h=typeof d?.id=="string"?d.id:"";if(!h||h===k)continue;let g=c.basename.replace(/-[a-z0-9]{4,12}$/,"").replace(/-/g," "),u,p=d?.parent??null;if(p&&p!==k){let f=l.get(p);f&&(u=f.basename.replace(/-[a-z0-9]{4,12}$/,"").replace(/-/g," "))}t.push({file:c,folder:o,id:h,title:g,body:"",parentBlurb:u,parentId:p??null})}}return t}async switchToFolderAndFocus(e,t){await this.setFolderOverride(e),this.tree.get(t)&&this.navigateTo(t)}async cmdOutdent(){let e=this.getActionTargets();if(!e.length)return;let t=[],i=[];for(let s of e){let n=s.parent?this.tree.get(s.parent):null;if(!n||n.id===k){i.push(s.id);continue}let o=n.parent??k;await this.changeParent(s,o),t.push(s)}if(t.length===0){new F.Notice(i.length?"Already at the top level.":"Nothing to outdent.");return}if(this.render(),i.length&&this.plugin.notifications.show({message:this.bulkActionMessage({verb:"Outdented",nodes:t,suffix:i.length?`(${i.length} already at root)`:void 0}),kind:"success",category:"move",affectedIds:t.map(s=>s.id),folder:this.noteFolder}),this.plugin.settings.autoNavOnMoveOut&&t.length>0){let s=t[0].parent;s!=null&&t.every(o=>o.parent===s)&&s!==this.focusId&&this.navigateTo(s)}}cmdSetColor(){let e=this.getActionTargets();if(!e.length)return;let t=new Set(e.map(n=>this.colorForNode(n)??"")),i=t.size===1&&Array.from(t)[0]||null,s=this.plugin.settings.customPalette??[];new Rt(this.app,i,s,async(n,o)=>{let a=[];for(let h of e)if(h.file){a.push({id:h.id,path:h.file.path,was:this.colorForNode(h)??null});try{await this.app.fileManager.processFrontMatter(h.file,g=>{n?g.color=n:delete g.color})}catch(g){new F.Notice(`Couldn't set color for ${h.id}: ${g.message}`)}}if(o.addToPalette&&typeof n=="string"){let h=[...this.plugin.settings.customPalette??[]],g=n.toLowerCase();h.some(u=>u.toLowerCase()===g)||(h.push(n),this.plugin.settings.customPalette=h,await this.plugin.persistSettingsQuiet(),await this.log.append({type:"palette_color_add",id:k,payload:{color:n}}))}this.render();let l=this.noteFolder,c=n,d=async h=>{for(let g of h){let u=this.app.vault.getAbstractFileByPath(g.path);if(u)try{await this.app.fileManager.processFrontMatter(u,p=>{g.col?p.color=g.col:delete p.color})}catch{}}this.tree.rebuild(l),this.render()};this.plugin.getUndoStack(l).push({label:a.length===1?"Color change":`Color change (${a.length})`,undo:()=>d(a.map(h=>({path:h.path,col:h.was}))),redo:()=>d(a.map(h=>({path:h.path,col:c})))})},async n=>{let o=(this.plugin.settings.customPalette??[]).filter(a=>a.toLowerCase()!==n.toLowerCase());return this.plugin.settings.customPalette=o,await this.plugin.persistSettingsQuiet(),await this.log.append({type:"palette_color_remove",id:k,payload:{color:n}}),o}).open()}cmdMovePicker(){let e=this.getActionTargets();e.length&&new tt(this.app,this.tree,t=>this.titleForNode(t),{mode:"pick",placeholder:"Move to which note?",allowCreate:!0,onPick:async t=>{if(t.crossFolder){let o=t.crossId??t.id.replace(/^cross:/,"");await this.moveAcrossFolders(e,t.crossFolder,o),this.selection.clear(),this.render();return}let i=t.id,s=new Map(e.map(o=>[o.id,this.tree.getChildren(o.id).length])),n=[];for(let o of e)await this.changeParent(o,i,{silentSuccess:!0})&&n.push(o);this.notifyBatchMove(n,i,s),this.selection.clear(),this.render(),this.plugin.settings.autoNavOnMoveOut&&i!==this.focusId&&this.navigateTo(i)},onCreate:async t=>{let i=await this.createNoteUnder(t,this.focusId);if(!i)return;let s=new Map(e.map(o=>[o.id,this.tree.getChildren(o.id).length])),n=[];for(let o of e)await this.changeParent(o,i,{silentSuccess:!0})&&n.push(o);this.notifyBatchMove(n,i,s),this.selection.clear(),this.render(),this.plugin.settings.autoNavOnMoveOut&&this.navigateTo(i)},crossFolderNotes:()=>this.collectCrossFolderDestinations(),excludedFolderNotes:this.excludedSearchFolders().length>0?()=>this.collectExcludedFolderNotes():void 0,showFilterChips:!0}).open()}async moveAcrossFolders(e,t,i){if(!e.length)return;let s=(t||"").replace(/\/+$/,"");if(!s){new F.Notice("Target folder is empty");return}let n=[],o=new Set;for(let g of this.app.vault.getMarkdownFiles())(g.parent?.path?.replace(/\/+$/,"")??"")===s&&o.add(g.path);let a=(g,u)=>{if(!g.file)return;let p=g.file,f=`${s}/${p.name}`;if(o.has(f)){let I=p.basename.match(/^(.*)(-[a-z0-9]{4,12})$/),m=I?I[1]:p.basename,C=I?I[2]:"";for(let B=1;B<1e3;B++){let w=`${m}-${B}${C}.md`,y=`${s}/${w}`;if(!o.has(y)){f=y;break}}}o.add(f),n.push({id:g.id,file:p,oldPath:p.path,newPath:f,oldParent:g.parent,isRoot:u});for(let I of this.tree.getChildren(g.id))a(I,!1)};for(let g of e)a(g,!0);if(!n.length)return;await this.ensureFolder(s);for(let g of n)try{await this.app.fileManager.renameFile(g.file,g.newPath),g.isRoot&&await this.app.fileManager.processFrontMatter(g.file,u=>{u.parent=i}),await this.log.append({type:"parent_change",id:g.id,payload:{from:g.oldParent,to:g.isRoot?i:g.oldParent,crossFolder:{from:this.noteFolder,to:s}}})}catch(u){new F.Notice(`Move failed for ${g.id}: ${u.message}`)}if(X().writeRecoveryLinks){let g=this.noteFolder;window.setTimeout(()=>{gi(this.app,g),gi(this.app,s)},350)}let l=this.titleList(e);this.tree.rebuild(this.noteFolder);let c=i===k?"Open home":"Open parent",d=Math.max(0,n.length-e.length),h=e.length===1?`Moved ${l}${d>0?` and its ${d} nested note${d===1?"":"s"}`:""} \u2192 \`${s}\``:`Moved ${l}${d>0?` (${d} nested)`:""} \u2192 \`${s}\``;this.plugin.notifications.show({message:h,kind:"success",category:"move",duration:0,affectedIds:e.map(g=>g.id),folder:this.noteFolder,actions:[{label:c,onClick:()=>{this.switchToFolderAndFocus(s,i)}}]}),this.plugin.getUndoStack(this.noteFolder).push({label:`Cross-Stashpad move (${n.length})`,undo:async()=>{for(let g of n){let u=this.app.vault.getAbstractFileByPath(g.newPath);if(u)try{await this.app.fileManager.renameFile(u,g.oldPath),g.isRoot&&await this.app.fileManager.processFrontMatter(u,p=>{p.parent=g.oldParent})}catch{}}this.tree.rebuild(this.noteFolder),this.render()},redo:async()=>{for(let g of n){let u=this.app.vault.getAbstractFileByPath(g.oldPath);if(u)try{await this.app.fileManager.renameFile(u,g.newPath),g.isRoot&&await this.app.fileManager.processFrontMatter(u,p=>{p.parent=i})}catch{}}this.tree.rebuild(this.noteFolder),this.render()}})}nextPickableIdx(e,t){for(let i=e+t;i>=0&&i<this.currentChildren.length;i+=t){let s=this.currentChildren[i];if(s&&!this.selection.has(s.id))return i}return e}cmdInListPicker(){if(this.currentChildren.length===0)return;let e=this.cursorIdx>0?this.cursorIdx-1:0;if(this.currentChildren[e]&&this.selection.has(this.currentChildren[e].id)){let n=this.nextPickableIdx(e,-1);e=n!==e?n:this.nextPickableIdx(e,1)}this.inListPicker={activeIdx:e};let t=X().bindings.move,i=es(t.primary||t.secondary||"M");new F.Notice(`Arrows to pick parent, Enter confirms, ${i} for the full picker, Esc cancels.`);let s=this.listEl?.scrollTop??0;if(this.render(),this.listEl){let n=this.listEl;n.scrollTop=s,requestAnimationFrame(()=>{n.scrollTop=s}),setTimeout(()=>{n.scrollTop=s},60)}}async commitInListPicker(){if(!this.inListPicker)return;let e=this.currentChildren[this.inListPicker.activeIdx];if(this.inListPicker=null,!e){this.render();return}let t=this.getActionTargets().filter(a=>a.id!==e.id),i=new Map(t.map(a=>[a.id,this.tree.getChildren(a.id).length])),s=[];for(let a of t)await this.changeParent(a,e.id,{silentSuccess:!0})&&s.push(a);if(this.notifyBatchMove(s,e.id,i),this.plugin.settings.autoNavOnMoveIn){this.navigateTo(e.id);return}this.selection.clear(),this.cursorIdx=-1,this.pendingFocusIds=[e.id],this.render({kind:"follow-cursor"});let n=this.selectionGuardKey,o=()=>{if(this.selectionGuardKey!==n)return;let a=this.currentChildren.findIndex(l=>l.id===e.id);a<0||this.selection.size===1&&this.selection.has(e.id)&&this.cursorIdx===a||(this.selection.clear(),this.selection.add(e.id),this.cursorIdx=a,this.render({kind:"follow-cursor"}))};setTimeout(o,120),setTimeout(o,400)}async cmdMerge(){let e=this.getActionTargets();if(e.length<2){new F.Notice("Select 2+ notes to merge.");return}e.sort((f,I)=>(f.created||"").localeCompare(I.created||""));let t=e[0];if(!t.file)return;let i=t.file.path,s=await this.app.vault.read(t.file),n=await this.snapshotNotes(e.slice(1),!1),o=[],a=[];for(let f of e){if(!f.file)continue;let I=await this.app.vault.cachedRead(f.file);a.push(this.stripFrontmatter(I).trim())}let l=a.map(f=>f.trim()).filter(Boolean).join(` +`:"";a.value+=`${R}${N} +`,this.composerDraft=a.value},w=p.createEl("button",{cls:"stashpad-composer-btn"});(0,k.setIcon)(w,"paperclip"),w.title="Attach files",w.onmousedown=N=>N.preventDefault(),w.onclick=N=>{N.preventDefault();let R=document.activeElement===a;if(g.click(),R){let U=()=>{a.focus()};setTimeout(U,100),setTimeout(U,500)}},g.addEventListener("change",async()=>{let N=Array.from(g.files??[]);g.value="";for(let R of N){let U=await this.importAttachment(R);U&&B(U)}a.focus()});let y=f.createEl("button",{cls:"stashpad-composer-btn stashpad-composer-rail-toggle"});(0,k.setIcon)(y,"chevron-left"),y.title="Show more composer options",f.insertBefore(y,p);let v=N=>{f.toggleClass("is-expanded",N),y.title=N?"Hide options":"Show more composer options",(0,k.setIcon)(y,N?"chevron-right":"chevron-left")};y.onmousedown=N=>N.preventDefault(),y.onclick=N=>{N.preventDefault(),v(!f.hasClass("is-expanded"))},v(!1);let E=()=>n.clientWidth<700,Q=()=>{let N=E();n.toggleClass("is-narrow",N),v(!N)};Q(),requestAnimationFrame(Q);let S=new ResizeObserver(Q);S.observe(n),this.composerNarrowObserver?.disconnect(),this.composerNarrowObserver=S;let x=f.createEl("button",{cls:"stashpad-composer-btn stashpad-composer-send"});x.title="Send (Enter)",(0,k.setIcon)(x,"arrow-up");let H=async()=>{let N=a.value.trim();if(!N)return;a.value="",this.composerDraft="";try{await this.saveDraft("")}catch{}try{await this.recordLastSubmitted(N)}catch{}let R=this.modeSplit??X().splitOnLines,U=this.nextDestination,D=this.nextDestinationFolder,b=!!D&&D!==this.noteFolder;this.nextDestination=null,this.nextDestinationFolder=null,this.nextDestinationLabel=null,this.autoSelectNewest=!b,this.scrollToBottomOnNextRender=!b;let P=b?{targetFolder:D}:void 0,O=U??this.focusId;if(R){let L=Ti(N,X().splitMode);L.length===1?await this.createNoteUnder(L[0],O,P):L.length>1&&await this.createNotesBatch(L,O,P,N,b?D:this.noteFolder)}else await this.createNoteUnder(N,O,P);X().autofocusComposerAfterSend&&(this.focusComposerOnNextRender=!0,b&&this.composerInputEl?.focus())};x.onclick=()=>void H(),a.addEventListener("keydown",N=>{let R=this.modeEnterSubmits;if(N.key==="Escape"){N.preventDefault(),N.stopPropagation(),a.blur(),this.viewRoot.focus({preventScroll:!0});return}if(N.key==="ArrowUp"&&a.selectionStart===0&&a.selectionEnd===0){if(N.preventDefault(),a.blur(),this.viewRoot.focus({preventScroll:!0}),this.currentChildren.length>0){let U=this.lastCursorByFocus.get(this.focusId)??this.lastSelected,D=U?this.currentChildren.findIndex(b=>b.id===U):-1;this.cursorIdx=D>=0?D:this.currentChildren.length-1,this.selectCursor(!1)}return}N.key==="Enter"&&!N.isComposing&&(R?!N.shiftKey:N.shiftKey)&&(N.preventDefault(),N.stopPropagation(),N.stopImmediatePropagation(),H())}),e.createDiv({cls:"stashpad-composer-help"}).setText(this.composerHelperText(i,s))}composerPlaceholder(e,t){return k.Platform.isMobile?t?"New notes (split on newlines)":"New note":`Type a note. ${e?"Enter":"Shift+Enter"} = send, ${e?"Shift+Enter":"Enter"} = newline${t?" (each line \u2192 a note)":""}\u2026`}composerHelperText(e,t){let i=e?"Enter":"Shift+Enter",s=e?"Shift+Enter":"Enter",n=X().bindings,o=h=>{let g=n[h];return g.primary&&g.secondary?g.preferRight?g.secondary:g.primary:g.primary||g.secondary},a=rs(o("toggleSplit")),l=rs(o("pickDestination")),c=rs(o("search")),d=this.nextDestination?` \u2022 destination: ${this.destinationLabel()}`:"";return`${i} sends \xB7 ${s} newline \xB7 ${a} split: ${t?"ON":"off"} \xB7 ${l} destination \xB7 ${c} search${d}`}destinationLabel(){if(!this.nextDestination)return"current";if(this.nextDestinationFolder)return this.nextDestinationLabel??this.nextDestinationFolder;if(this.nextDestination===F)return"Home";let e=this.tree.get(this.nextDestination);return e?this.titleForNode(e).trim():"?"}renderMobileNav(e){let t=e.createDiv({cls:"stashpad-mobile-nav"});t.createEl("button",{text:"Home"}).onclick=()=>this.navigateTo(F),t.createEl("button",{text:"Back"}).onclick=()=>this.navigateUp(),t.createEl("button",{text:"Bookmarks"}).onclick=()=>this.openBookmarks()}handleRenderedClick(e,t){let i=e.target,s=i?.closest?.(".tag");if(s){e.preventDefault(),e.stopPropagation();let a=(s.getAttribute("href")||s.textContent||"").replace(/^#/,"").trim();if(a){let l=this.app.internalPlugins?.plugins?.["global-search"],c=l?.instance?.openGlobalSearch?.bind(l.instance);c&&c(`tag:#${a}`)}return}let n=i?.closest?.(".internal-link");if(n){e.preventDefault(),e.stopPropagation();let o=n.getAttribute("data-href")||n.getAttribute("href");if(o){let a=t.file?.path||"";this.app.workspace.openLinkText(o,a,!0)}}}handleRowClick(e,t,i){let s=this.shouldAbsorbDismissTap();if(this.traceTap("click",e,t,s),s){(!this.aimedTapTargetId||Date.now()-this.aimedTapAt>gs.AIMED_TAP_WINDOW_MS)&&(this.aimedTapTargetId=i.id,this.aimedTapAt=Date.now()),e.stopPropagation();return}this.aimedTapTargetId=null,this.cursorHasMoved=!0;let n=e.target,o=n?.closest?.(".tag");if(o){e.preventDefault(),e.stopPropagation();let d=(o.getAttribute("href")||o.textContent||"").replace(/^#/,"").trim();if(d){let h=this.app.internalPlugins?.plugins?.["global-search"],g=h?.instance?.openGlobalSearch?.bind(h.instance);g&&g(`tag:#${d}`)}return}let a=n?.closest?.(".internal-link");if(a){e.preventDefault(),e.stopPropagation();let c=a.getAttribute("data-href")||a.getAttribute("href");if(c){let d=i.file?.path||"";this.app.workspace.openLinkText(c,d,!0)}return}if(n?.tagName==="A"&&n.href)return;if(e.stopPropagation(),this.inListPicker){this.inListPicker.activeIdx=t,this.commitInListPicker();return}let l=this.selection.size===0;if(e.shiftKey&&this.lastSelected){this.cursorIdx=t;let c=this.currentChildren.findIndex(d=>d.id===this.lastSelected);if(l&&(this.firstSelectedId=this.lastSelected),c!==-1){let[d,h]=c<t?[c,t]:[t,c];for(let g=d;g<=h;g++)this.selection.add(this.currentChildren[g].id)}else this.selection.add(i.id)}else if(e.metaKey||e.ctrlKey)if(this.selection.has(i.id))if(this.selection.delete(i.id),this.firstSelectedId===i.id&&(this.firstSelectedId=null),this.lastSelected===i.id&&(this.lastSelected=this.selection.size>0?[...this.selection][this.selection.size-1]:null),this.selection.size===0)this.cursorIdx=-1;else{let c=this.lastSelected??[...this.selection][this.selection.size-1],d=c?this.currentChildren.findIndex(h=>h.id===c):-1;d>=0&&(this.cursorIdx=d)}else this.cursorIdx=t,l&&(this.firstSelectedId=i.id),this.selection.add(i.id);else this.mobileSelectMode?(this.cursorIdx=t,this.selection.has(i.id)?(this.selection.delete(i.id),this.firstSelectedId===i.id&&(this.firstSelectedId=null)):this.selection.add(i.id)):(this.cursorIdx=t,this.selection.clear(),this.selection.add(i.id),this.firstSelectedId=i.id,this.lastSelected=i.id);this.selection.size===0&&(this.firstSelectedId=null),this.selection.has(i.id)&&(this.lastSelected=i.id),this.viewRoot.focus({preventScroll:!0}),this.repaintSelectionClasses(),this.revealCursorRow(),this.stampSelectedCursor(),this.plugin.notifyStashpadSelectionChanged()}revealCursorRow(){let e=()=>{if(this.cursorIdx<0)return;let t=this.listEl?.querySelector(`[data-idx="${this.cursorIdx}"]`);if(!t||!this.listEl)return;let i=this.listEl,s=i.getBoundingClientRect(),n=t.getBoundingClientRect(),o=4,a=s.top+o,l=s.bottom-o;if(k.Platform.isMobile&&document.body.classList.contains("stashpad-keyboard-open")){let c=this.viewRoot?.querySelector(".stashpad-composer");c&&(l=Math.min(l,c.getBoundingClientRect().top-o))}n.top<a?i.scrollTop+=n.top-a:n.bottom>l&&(i.scrollTop+=n.bottom-l)};e(),requestAnimationFrame(e),setTimeout(e,60),setTimeout(e,200)}shouldAbsorbDismissTap(){return k.Platform.isMobile&&Date.now()<this.tapSettleUntil}traceTap(e,t,i,s){if(!this.plugin.settings.debugTrace||!this.listEl)return;let n=this.listEl,o=window.visualViewport,a=n.querySelector(`.stashpad-note[data-idx="${i}"]`)?.getBoundingClientRect();this.plugin.trace(`tap:${e}`,{absorbed:s,clientY:Math.round(t.clientY),clientX:Math.round(t.clientX),idx:i,rowTop:a?Math.round(a.top):null,listTop:Math.round(n.getBoundingClientRect().top),listScrollTop:Math.round(n.scrollTop),listScrollH:Math.round(n.scrollHeight),listClientH:Math.round(n.clientHeight),innerH:window.innerHeight,vvHeight:o?Math.round(o.height):null,vvOffsetTop:o?Math.round(o.offsetTop):null,tapSettleInMs:Math.max(0,this.tapSettleUntil-Date.now()),kbOpen:document.body.classList.contains("stashpad-keyboard-open")})}jumpToTop(){this.currentChildren.length!==0&&(this.cursorIdx=0,this.selectCursor(!1))}jumpToBottom(){this.currentChildren.length!==0&&(this.cursorIdx=this.currentChildren.length-1,this.selectCursor(!1))}selectCursor(e){let t=this.currentChildren[this.cursorIdx];if(t){if(this.cursorHasMoved=!0,!e)this.selection.clear(),this.selection.add(t.id),this.firstSelectedId=t.id,this.lastSelected=t.id;else{let i=this.firstSelectedId??t.id,s=this.currentChildren.findIndex(n=>n.id===i);if(s===-1)this.selection.add(t.id),this.firstSelectedId=t.id;else{let[n,o]=s<this.cursorIdx?[s,this.cursorIdx]:[this.cursorIdx,s];this.selection.clear();for(let a=n;a<=o;a++)this.selection.add(this.currentChildren[a].id);this.firstSelectedId=i}this.lastSelected=t.id}this.repaintSelectionClasses(),this.revealCursorRow(),this.stampSelectedCursor(),this.plugin.notifyStashpadSelectionChanged()}}repaintSelectionClasses(){if(!this.listEl)return;let e=!!this.plugin.settings.autoExpandCursorRow,t=this.inListPicker?.activeIdx??-1;this.listEl.querySelectorAll(".stashpad-note").forEach(s=>{let n=Number(s.dataset.idx),o=s.dataset.id??"",a=n===this.cursorIdx;s.classList.toggle("is-cursor",a),s.classList.toggle("is-selected",this.selection.has(o)),a||this.cursorExpandOverride.delete(o),s.classList.toggle("is-cursor-expanded",e&&a&&this.cursorHasMoved&&!this.cursorExpandOverride.has(o)),s.classList.toggle("is-pick-target",n===t)})}revealRowAt(e){if(!this.listEl)return;let t=this.listEl.querySelector(`.stashpad-note[data-idx="${e}"]`);if(!t)return;let i=t.getBoundingClientRect(),s=this.listEl.getBoundingClientRect();(i.top<s.top||i.bottom>s.bottom)&&t.scrollIntoView({block:"nearest",behavior:"auto"})}getActionTargets(){if(this.selection.size>0)return[...this.selection].map(t=>this.tree.get(t)).filter(t=>!!t&&!!t.file);let e=this.currentChildren[this.cursorIdx];return e?[e]:[]}async cmdLockSelection(){if(!this.plugin.encryption?.isConfigured?.()){new k.Notice("Set up encryption first (Settings \u2192 Stashpad \u2192 Encryption).");return}let e=this.getActionTargets();if(e.length===0)return;let t=new Set(e.map(o=>o.id)),i=new Set((this.plugin.settings.lockedSubtrees??[]).map(o=>o.rootId).filter(o=>!!o)),s=e.filter(o=>{if(i.has(o.id))return!1;let a=o.parent;for(;a;){if(t.has(a))return!1;a=this.tree.get(a)?.parent??null}return!0});if(s.length===0){new k.Notice("Nothing to lock (already locked).");return}let n=0;for(let o of s){let a=this.order.getOrder(this.noteFolder,o.parent??F),l=a.indexOf(o.id),c=l>0?a[l-1]:null;await this.plugin.lockNoteSubtree(this.noteFolder,o.id,c,{silent:!0})&&n++}n>0&&(this.selection.clear(),this.lastSelected=null,this.render(),this.plugin.notifications.show({message:`Locked ${n} stash${n===1?"":"es"}.`,kind:"success",category:"system",folder:this.noteFolder}))}async cmdMoveToArchive(){let e=this.getActionTargets();if(e.length===0)return;let t=(this.noteFolder??"").replace(/\/+$/,"");if(ys(t)){new k.Notice("These notes are already in an archive.",6e3);return}this.archiveSources(e,Ct(t))}async archiveSources(e,t){let i=this.noteFolder,s=t.replace(/\/+$/,""),n=s.replace(/\/archive$/,""),o=this.plugin.archiveEncryptFor(n);try{await this.app.vault.adapter.exists(s)||await this.app.vault.createFolder(s)}catch{}if(!o){let g=new Set(e.map(B=>B.id)),f=e.filter(B=>{let w=B.parent;for(;w;){if(g.has(w))return!1;w=this.tree.get(w)?.parent??null}return!0});if(f.length===0)return;let p=[],u=new Set,I=B=>{if(!u.has(B.id)){u.add(B.id),B.file&&p.push(B.file);for(let w of this.tree.getChildren(B.id))I(w)}};for(let B of f)I(B);let m=[];for(let B of p){let w=B.path,y=B.name,v=y.lastIndexOf("."),E=v>0?y.slice(0,v):y,Q=v>0?y.slice(v):"",S=`${s}/${y}`;for(let x=1;await this.app.vault.adapter.exists(S);x++)S=`${s}/${E}-${x}${Q}`;try{await this.app.fileManager.renameFile(B,S),m.push({from:w,to:S})}catch(x){console.warn("[Stashpad] plaintext archive move failed",w,x)}}this.selection.clear(),this.lastSelected=null,this.tree.rebuild(i),this.render();let C=s.split("/").pop()||s;this.plugin.notifications.show({message:`Moved ${m.length} note${m.length===1?"":"s"} \u2192 plaintext archive \u201C${C}\u201D (de-indexed, not encrypted). Undo to bring ${m.length===1?"it":"them"} back.`,kind:"success",category:"system",folder:i}),this.plugin.getUndoStack(i).push({label:`Archive (plaintext, ${f.length})`,undo:async()=>{for(let B of m){let w=this.app.vault.getAbstractFileByPath(B.to);if(w instanceof k.TFile)try{await this.app.fileManager.renameFile(w,B.from)}catch(y){console.warn("[Stashpad] plaintext archive undo failed",B.to,y)}}this.tree.rebuild(i),this.render()}});return}if(!this.plugin.encryption?.isConfigured?.()){new k.Notice(`Archiving here encrypts the notes ("Encrypt archived notes" is on for \u201C${n.split("/").pop()}\u201D), but encryption isn't set up yet (Settings \u2192 Stashpad \u2192 Encryption). Set it up \u2014 or turn that toggle off for a plaintext archive.`,9e3);return}let a=new Set(e.map(g=>g.id)),l=e.filter(g=>{let f=g.parent;for(;f;){if(a.has(f))return!1;f=this.tree.get(f)?.parent??null}return!0});if(l.length===0)return;let c=l.map(g=>g.id),d=[];for(let g of l){let f=this.order.getOrder(i,g.parent??F),p=f.indexOf(g.id),u=p>0?f[p-1]:null,I=await this.plugin.lockNoteSubtree(i,g.id,u,{silent:!0,blobFolder:t});I&&d.push(I.blobPath)}if(d.length===0)return;this.selection.clear(),this.lastSelected=null,this.tree.rebuild(i),this.render();let h=t.split("/").pop()||t;this.plugin.notifications.show({message:`Archived ${d.length} note${d.length===1?"":"s"} \u2192 \u201C${h}\u201D. Undo to bring ${d.length===1?"it":"them"} back.`,kind:"success",category:"system",folder:i}),this.plugin.getUndoStack(i).push({label:`Archive (${d.length})`,undo:async()=>{let g=[];for(let f of d)try{await this.plugin.unlockBundleAt(f,{silent:!0,destFolder:i})||g.push(f)}catch(p){g.push(f),console.warn("[Stashpad] archive undo failed",f,p)}d=g,g.length&&new k.Notice(`Undo incomplete: ${g.length} archived item${g.length===1?"":"s"} couldn't be restored (they're still in the archive). Try again, or restore from the Archived view.`,8e3),this.tree.rebuild(i),this.render()},redo:async()=>{d=[];for(let g of c){let f=await this.plugin.lockNoteSubtree(i,g,null,{silent:!0,blobFolder:t});f&&d.push(f.blobPath)}this.tree.rebuild(i),this.render()}})}async cmdEncryptDelete(){if(!this.plugin.encryption?.isConfigured?.()){new k.Notice("Set up encryption first (Settings \u2192 Stashpad \u2192 Encryption).");return}let e=this.getActionTargets();if(e.length===0)return;let t=new Set(e.map(n=>n.id)),i=e.filter(n=>{let o=n.parent;for(;o;){if(t.has(o))return!1;o=this.tree.get(o)?.parent??null}return!0});if(i.length===0)return;let s=i.length;new ie(this.app,`Encrypt & delete ${s} note${s===1?"":"s"}?`,[`The selected note${s===1?"":"s"} (and any children) will be encrypted and moved to Stashpad's encrypted trash.`,"","\u2022 The readable copy is permanently removed from the folder.",`\u2022 You can restore ${s===1?"it":"them"} later from the encrypted trash \u2014 you'll need your encryption password.`,`\u2022 If you lose your password, ${s===1?"it's":"they're"} gone for good.`].join(` +`),"Encrypt & delete",async n=>{n&&await this.secureDeleteSources(i)}).open()}async secureDeleteSources(e){if(!this.plugin.encryption?.isConfigured?.()){new k.Notice("Set up encryption first (Settings \u2192 Stashpad \u2192 Encryption).");return}let t=new Set(e.map(a=>a.id)),i=e.filter(a=>{let l=a.parent;for(;l;){if(t.has(l))return!1;l=this.tree.get(l)?.parent??null}return!0});if(i.length===0)return;let s=this.noteFolder,n=i.map(a=>a.id),o=[];for(let a of n){let l=await this.plugin.encryptDeleteSubtree(s,a);l&&o.push(l)}o.length!==0&&(this.selection.clear(),this.lastSelected=null,this.tree.rebuild(s),this.render(),this.plugin.notifications.show({message:`Securely deleted ${o.length} note${o.length===1?"":"s"} \u2192 encrypted trash. Undo to bring ${o.length===1?"it":"them"} back.`,kind:"success",category:"system",folder:s}),this.plugin.getUndoStack(s).push({label:`Secure delete (${o.length})`,undo:async()=>{let a=[];for(let l of o)try{await this.plugin.restoreDeletedAt(l,{silent:!0})||a.push(l)}catch(c){a.push(l),console.warn("[Stashpad] secure-delete undo failed",l,c)}o=a,a.length&&new k.Notice(`Undo incomplete: ${a.length} deleted item${a.length===1?"":"s"} couldn't be restored (still in Trash). Try again, or restore from the Trash view.`,8e3),this.tree.rebuild(s),this.render()},redo:async()=>{o=[];for(let a of n){let l=await this.plugin.encryptDeleteSubtree(s,a);l&&o.push(l)}this.tree.rebuild(s),this.render()}}))}async cmdUnlockAll(){if(!this.plugin.encryption?.isConfigured?.()){new k.Notice("Set up encryption first (Settings \u2192 Stashpad \u2192 Encryption).");return}let e=new Set,t=new Set,i=this.getActionTargets().map(n=>n.id);for(;i.length;){let n=i.pop();if(!t.has(n)){t.add(n);for(let o of this.tree.getChildren(n))i.push(o.id)}}for(let n of t)for(let o of this.plugin.lockedSubtreesFor(this.noteFolder,n))e.add(o.blob);if(e.size===0){let n=(this.tree.get(this.focusId)??this.tree.getRoot()).id;for(let o of this.plugin.lockedSubtreesFor(this.noteFolder,n))e.add(o.blob)}if(e.size===0){new k.Notice("No locked notes to unlock here.");return}if(!await this.plugin.ensureEncryptionUnlocked())return;let s=0;for(let n of e)try{await this.plugin.unlockBundleAt(n,{silent:!0})&&s++}catch(o){console.warn("[Stashpad] batch unlock skipped",n,o)}s>0&&(this.selection.clear(),this.lastSelected=null,this.render(),this.plugin.notifications.show({message:`Unlocked ${s} stash${s===1?"":"es"}.`,kind:"success",category:"system",folder:this.noteFolder}))}toggleSplit(){let e=this.modeSplit??X().splitOnLines;this.modeSplit=!e,this.render(),this.composerInputEl?.focus()}openDestinationPicker(e=!1){let t=!1;new it(this.app,this.tree,i=>this.titleForNode(i),{mode:"pick",placeholder:"Send next note(s) under which note?",allowCreate:!0,onPick:async i=>{if(t=!0,i.crossFolder){let s=i.crossId??i.id.replace(/^cross:/,"");this.nextDestination=s,this.nextDestinationFolder=i.crossFolder;let n=i.crossFolder.split("/").pop()||i.crossFolder,o=s===F?"Home":(i.crossFile?.basename??"note").replace(/-[a-z0-9]{4,12}$/,"").replace(/-/g," ");this.nextDestinationLabel=`${n} \u25B8 ${o}`,this.render(),this.composerInputEl?.focus();return}this.nextDestination=i.id,this.nextDestinationFolder=null,this.nextDestinationLabel=null,this.render(),this.composerInputEl?.focus()},onCreate:async i=>{t=!0;let s=await this.createNoteUnder(i,this.focusId);s&&(this.nextDestination=s,this.nextDestinationFolder=null,this.nextDestinationLabel=null,this.render(),this.composerInputEl?.focus())},crossFolderNotes:()=>this.collectCrossFolderDestinations(),excludedFolderNotes:this.excludedSearchFolders().length>0?()=>this.collectExcludedFolderNotes():void 0,onClose:()=>{e&&!t&&this.composerInputEl?.focus()}}).open()}collectCrossFolderDestinations(){let e=this.collectCrossFolderNotes(),t=this.plugin.searchableFolders(this.noteFolder).filter(n=>n!==this.noteFolder),i=new Map;for(let n of this.app.vault.getMarkdownFiles()){let o=n.parent?.path?.replace(/\/+$/,"")??"";if(!t.includes(o))continue;this.app.metadataCache.getFileCache(n)?.frontmatter?.id===F&&i.set(o,n)}return[...t.map(n=>({file:i.get(n),folder:n,id:F,title:`Home \u2014 ${n.split("/").pop()||n}`,body:""})),...e]}openSearchInParentModal(){let e=this.focusId,t=s=>{if(s===e)return!0;let n=new Set,o=this.tree.get(s);for(;o&&o.id!==F&&!n.has(o.id);){if(n.add(o.id),o.parent===e||o.id===e)return!0;if(!o.parent)return!1;o=this.tree.get(o.parent)}return e===F},i=new Proxy(this.tree,{get:(s,n)=>n==="getRoot"?()=>s.get(e)??s.getRoot():n==="getChildren"?o=>s.getChildren(o):s[n]});new it(this.app,i,s=>this.titleForNode(s),{mode:"search",placeholder:`Search in "${this.titleForNode(this.tree.get(e)??this.tree.getRoot()).trim()}"\u2026`,allowCreate:!1,onPick:s=>{s.node&&t(s.node.id)?this.navigateTo(s.node.id):s.node&&this.navigateTo(s.node.id)}}).open()}openSearchModal(){if(this.openSearchInstance){let i=this.openSearchInstance.inputEl;i&&(i.focus(),i.select());return}let e=new it(this.app,this.tree,i=>this.titleForNode(i),{mode:"search",placeholder:"Search Stashpad notes\u2026",allowCreate:!0,onCreate:async i=>{let s=i.trim();s&&new it(this.app,this.tree,n=>this.titleForNode(n),{mode:"pick",placeholder:`Create "${s}" under which note?`,allowCreate:!1,crossFolderNotes:()=>this.collectCrossFolderDestinations(),excludedFolderNotes:this.excludedSearchFolders().length>0?()=>this.collectExcludedFolderNotes():void 0,folderResults:()=>this.plugin.discoverStashpadFolders().filter(n=>n!==this.noteFolder),localFolder:this.noteFolder,onPick:async n=>{let o=n.crossFolder?n.crossId??n.id.replace(/^cross:/,""):n.node?.id,a=n.crossFolder??this.noteFolder;if(!o)return;let l=(this.plugin.settings.folder||"Stashpad").trim().replace(/^\/+|\/+$/g,"")||"Stashpad",c=this.app.workspace,d=c.getLeaf("tab");await d.setViewState({type:z,active:!0,state:{focusId:o,folderOverride:a===l?null:a}}),c.revealLeaf(d);let h=d.view;if(h&&typeof h.createNoteUnder=="function"){let g=await h.createNoteUnder(s,o);g&&typeof h.navigateTo=="function"&&h.navigateTo(g)}}}).open()},onPick:i=>{if(i.kind==="folder-open"&&i.folder){this.openFolderInNewTab(i.folder);return}let s=this.plugin.settings.searchOpensInNewTab!==!1;if(i.locked){let n=i.locked.parentId??F;s?this.openNoteInNewTab(this.noteFolder,n):this.navigateTo(n);return}if(i.crossFolder&&i.crossFile){let n=i.crossId??i.id.replace(/^cross:/,"");s?this.openNoteInNewTab(i.crossFolder,n):this.switchToFolderAndFocus(i.crossFolder,n);return}if(i.node){let n=this.plugin.settings.searchOpensInContext!==!1,o=n?i.node.parent??F:i.node.id,a=n?i.node.id:void 0;s?this.openNoteInNewTab(this.noteFolder,o,a):(a&&(this.pendingCursorId=a),this.navigateTo(o))}},crossFolderNotes:()=>this.collectCrossFolderDestinations(),excludedFolderNotes:this.excludedSearchFolders().length>0?()=>this.collectExcludedFolderNotes():void 0,folderResults:()=>this.plugin.discoverStashpadFolders().filter(i=>i!==this.noteFolder),showFilterChips:!0,localFolder:this.noteFolder,lockedNotes:()=>this.plugin.lockedSubtreesInFolder(this.noteFolder),hideLockedTitles:!1});this.openSearchInstance=e;let t=e.onClose.bind(e);e.onClose=()=>{t(),this.openSearchInstance===e&&(this.openSearchInstance=null)},e.open()}excludedSearchFolders(){let e=new Set(this.plugin.searchableFolders(this.noteFolder));return this.plugin.discoverStashpadFolders().filter(t=>t!==this.noteFolder&&!e.has(t))}collectExcludedFolderNotes(){let e=this.excludedSearchFolders();if(!e.length)return[];let t=this.collectCrossFolderNotes(e),i=new Map;for(let n of this.app.vault.getMarkdownFiles()){let o=n.parent?.path?.replace(/\/+$/,"")??"";if(!e.includes(o))continue;this.app.metadataCache.getFileCache(n)?.frontmatter?.id===F&&i.set(o,n)}return[...e.map(n=>({file:i.get(n),folder:n,id:F,title:`Home \u2014 ${n.split("/").pop()||n}`,body:""})),...t]}collectCrossFolderNotes(e){let t=[],i=(e??this.plugin.searchableFolders(this.noteFolder)).filter(o=>o!==this.noteFolder);if(!i.length)return t;let s=new Set(i),n=new Map;for(let o of this.app.vault.getMarkdownFiles()){let a=o.parent?.path?.replace(/\/+$/,"")??"";if(!s.has(a))continue;let l=n.get(a);l||(l=[],n.set(a,l)),l.push(o)}for(let o of i){let a=n.get(o)??[],l=new Map;for(let c of a){let d=this.app.metadataCache.getFileCache(c)?.frontmatter;typeof d?.id=="string"&&l.set(d.id,c)}for(let c of a){let d=this.app.metadataCache.getFileCache(c)?.frontmatter,h=typeof d?.id=="string"?d.id:"";if(!h||h===F)continue;let g=c.basename.replace(/-[a-z0-9]{4,12}$/,"").replace(/-/g," "),f,p=d?.parent??null;if(p&&p!==F){let u=l.get(p);u&&(f=u.basename.replace(/-[a-z0-9]{4,12}$/,"").replace(/-/g," "))}t.push({file:c,folder:o,id:h,title:g,body:"",parentBlurb:f,parentId:p??null})}}return t}async switchToFolderAndFocus(e,t){await this.setFolderOverride(e),this.tree.get(t)&&this.navigateTo(t)}async cmdOutdent(){let e=this.getActionTargets();if(!e.length)return;let t=[],i=[];for(let s of e){let n=s.parent?this.tree.get(s.parent):null;if(!n||n.id===F){i.push(s.id);continue}let o=n.parent??F;await this.changeParent(s,o),t.push(s)}if(t.length===0){new k.Notice(i.length?"Already at the top level.":"Nothing to outdent.");return}if(this.render(),i.length&&this.plugin.notifications.show({message:this.bulkActionMessage({verb:"Outdented",nodes:t,suffix:i.length?`(${i.length} already at root)`:void 0}),kind:"success",category:"move",affectedIds:t.map(s=>s.id),folder:this.noteFolder}),this.plugin.settings.autoNavOnMoveOut&&t.length>0){let s=t[0].parent;s!=null&&t.every(o=>o.parent===s)&&s!==this.focusId&&this.navigateTo(s)}}cmdSetColor(){let e=this.getActionTargets();if(!e.length)return;let t=new Set(e.map(n=>this.colorForNode(n)??"")),i=t.size===1&&Array.from(t)[0]||null,s=this.plugin.settings.customPalette??[];new Kt(this.app,i,s,async(n,o)=>{let a=[];for(let h of e)if(h.file){a.push({id:h.id,path:h.file.path,was:this.colorForNode(h)??null});try{await this.app.fileManager.processFrontMatter(h.file,g=>{n?g.color=n:delete g.color})}catch(g){new k.Notice(`Couldn't set color for ${h.id}: ${g.message}`)}}if(o.addToPalette&&typeof n=="string"){let h=[...this.plugin.settings.customPalette??[]],g=n.toLowerCase();h.some(f=>f.toLowerCase()===g)||(h.push(n),this.plugin.settings.customPalette=h,await this.plugin.persistSettingsQuiet(),await this.log.append({type:"palette_color_add",id:F,payload:{color:n}}))}this.render();let l=this.noteFolder,c=n,d=async h=>{for(let g of h){let f=this.app.vault.getAbstractFileByPath(g.path);if(f)try{await this.app.fileManager.processFrontMatter(f,p=>{g.col?p.color=g.col:delete p.color})}catch{}}this.tree.rebuild(l),this.render()};this.plugin.getUndoStack(l).push({label:a.length===1?"Color change":`Color change (${a.length})`,undo:()=>d(a.map(h=>({path:h.path,col:h.was}))),redo:()=>d(a.map(h=>({path:h.path,col:c})))})},async n=>{let o=(this.plugin.settings.customPalette??[]).filter(a=>a.toLowerCase()!==n.toLowerCase());return this.plugin.settings.customPalette=o,await this.plugin.persistSettingsQuiet(),await this.log.append({type:"palette_color_remove",id:F,payload:{color:n}}),o}).open()}cmdMovePicker(){let e=this.getActionTargets();e.length&&new it(this.app,this.tree,t=>this.titleForNode(t),{mode:"pick",placeholder:"Move to which note?",allowCreate:!0,onPick:async t=>{if(t.crossFolder){let o=t.crossId??t.id.replace(/^cross:/,"");await this.moveAcrossFolders(e,t.crossFolder,o),this.selection.clear(),this.render();return}let i=t.id,s=new Map(e.map(o=>[o.id,this.tree.getChildren(o.id).length])),n=[];for(let o of e)await this.changeParent(o,i,{silentSuccess:!0})&&n.push(o);this.notifyBatchMove(n,i,s),this.selection.clear(),this.render(),this.plugin.settings.autoNavOnMoveOut&&i!==this.focusId&&this.navigateTo(i)},onCreate:async t=>{let i=await this.createNoteUnder(t,this.focusId);if(!i)return;let s=new Map(e.map(o=>[o.id,this.tree.getChildren(o.id).length])),n=[];for(let o of e)await this.changeParent(o,i,{silentSuccess:!0})&&n.push(o);this.notifyBatchMove(n,i,s),this.selection.clear(),this.render(),this.plugin.settings.autoNavOnMoveOut&&this.navigateTo(i)},crossFolderNotes:()=>this.collectCrossFolderDestinations(),excludedFolderNotes:this.excludedSearchFolders().length>0?()=>this.collectExcludedFolderNotes():void 0,showFilterChips:!0}).open()}async moveAcrossFolders(e,t,i){if(!e.length)return;let s=(t||"").replace(/\/+$/,"");if(!s){new k.Notice("Target folder is empty");return}let n=[],o=new Set;for(let g of this.app.vault.getMarkdownFiles())(g.parent?.path?.replace(/\/+$/,"")??"")===s&&o.add(g.path);let a=(g,f)=>{if(!g.file)return;let p=g.file,u=`${s}/${p.name}`;if(o.has(u)){let I=p.basename.match(/^(.*)(-[a-z0-9]{4,12})$/),m=I?I[1]:p.basename,C=I?I[2]:"";for(let B=1;B<1e3;B++){let w=`${m}-${B}${C}.md`,y=`${s}/${w}`;if(!o.has(y)){u=y;break}}}o.add(u),n.push({id:g.id,file:p,oldPath:p.path,newPath:u,oldParent:g.parent,isRoot:f});for(let I of this.tree.getChildren(g.id))a(I,!1)};for(let g of e)a(g,!0);if(!n.length)return;await this.ensureFolder(s);for(let g of n)try{await this.app.fileManager.renameFile(g.file,g.newPath),g.isRoot&&await this.app.fileManager.processFrontMatter(g.file,f=>{f.parent=i}),await this.log.append({type:"parent_change",id:g.id,payload:{from:g.oldParent,to:g.isRoot?i:g.oldParent,crossFolder:{from:this.noteFolder,to:s}}})}catch(f){new k.Notice(`Move failed for ${g.id}: ${f.message}`)}if(X().writeRecoveryLinks){let g=this.noteFolder;window.setTimeout(()=>{Ii(this.app,g),Ii(this.app,s)},350)}let l=this.titleList(e);this.tree.rebuild(this.noteFolder);let c=i===F?"Open home":"Open parent",d=Math.max(0,n.length-e.length),h=e.length===1?`Moved ${l}${d>0?` and its ${d} nested note${d===1?"":"s"}`:""} \u2192 \`${s}\``:`Moved ${l}${d>0?` (${d} nested)`:""} \u2192 \`${s}\``;this.plugin.notifications.show({message:h,kind:"success",category:"move",duration:0,affectedIds:e.map(g=>g.id),folder:this.noteFolder,actions:[{label:c,onClick:()=>{this.switchToFolderAndFocus(s,i)}}]}),this.plugin.getUndoStack(this.noteFolder).push({label:`Cross-Stashpad move (${n.length})`,undo:async()=>{for(let g of n){let f=this.app.vault.getAbstractFileByPath(g.newPath);if(f)try{await this.app.fileManager.renameFile(f,g.oldPath),g.isRoot&&await this.app.fileManager.processFrontMatter(f,p=>{p.parent=g.oldParent})}catch{}}this.tree.rebuild(this.noteFolder),this.render()},redo:async()=>{for(let g of n){let f=this.app.vault.getAbstractFileByPath(g.oldPath);if(f)try{await this.app.fileManager.renameFile(f,g.newPath),g.isRoot&&await this.app.fileManager.processFrontMatter(f,p=>{p.parent=i})}catch{}}this.tree.rebuild(this.noteFolder),this.render()}})}nextPickableIdx(e,t){for(let i=e+t;i>=0&&i<this.currentChildren.length;i+=t){let s=this.currentChildren[i];if(s&&!this.selection.has(s.id))return i}return e}cmdInListPicker(){if(this.currentChildren.length===0)return;let e=this.cursorIdx>0?this.cursorIdx-1:0;if(this.currentChildren[e]&&this.selection.has(this.currentChildren[e].id)){let n=this.nextPickableIdx(e,-1);e=n!==e?n:this.nextPickableIdx(e,1)}this.inListPicker={activeIdx:e};let t=X().bindings.move,i=rs(t.primary||t.secondary||"M");new k.Notice(`Arrows to pick parent, Enter confirms, ${i} for the full picker, Esc cancels.`);let s=this.listEl?.scrollTop??0;if(this.render(),this.listEl){let n=this.listEl;n.scrollTop=s,requestAnimationFrame(()=>{n.scrollTop=s}),setTimeout(()=>{n.scrollTop=s},60)}}async commitInListPicker(){if(!this.inListPicker)return;let e=this.currentChildren[this.inListPicker.activeIdx];if(this.inListPicker=null,!e){this.render();return}let t=this.getActionTargets().filter(a=>a.id!==e.id),i=new Map(t.map(a=>[a.id,this.tree.getChildren(a.id).length])),s=[];for(let a of t)await this.changeParent(a,e.id,{silentSuccess:!0})&&s.push(a);if(this.notifyBatchMove(s,e.id,i),this.plugin.settings.autoNavOnMoveIn){this.navigateTo(e.id);return}this.selection.clear(),this.cursorIdx=-1,this.pendingFocusIds=[e.id],this.render({kind:"follow-cursor"});let n=this.selectionGuardKey,o=()=>{if(this.selectionGuardKey!==n)return;let a=this.currentChildren.findIndex(l=>l.id===e.id);a<0||this.selection.size===1&&this.selection.has(e.id)&&this.cursorIdx===a||(this.selection.clear(),this.selection.add(e.id),this.cursorIdx=a,this.render({kind:"follow-cursor"}))};setTimeout(o,120),setTimeout(o,400)}async cmdMerge(){let e=this.getActionTargets();if(e.length<2){new k.Notice("Select 2+ notes to merge.");return}e.sort((u,I)=>(u.created||"").localeCompare(I.created||""));let t=e[0];if(!t.file)return;let i=t.file.path,s=await this.app.vault.read(t.file),n=await this.snapshotNotes(e.slice(1),!1),o=[],a=[];for(let u of e){if(!u.file)continue;let I=await this.app.vault.cachedRead(u.file);a.push(this.stripFrontmatter(I).trim())}let l=a.map(u=>u.trim()).filter(Boolean).join(` `),c=await this.app.vault.read(t.file),d=c.startsWith("---")?c.indexOf(` ---`,3)+4:0,g=`${c.slice(0,d)} ${l} -`;await this.app.vault.modify(t.file,g);for(let f=1;f<e.length;f++){let I=e[f];if(I.file){for(let m of this.tree.getChildren(I.id))m.file&&o.push({childId:m.id,childPath:m.file.path,oldParent:m.parent}),await this.changeParent(m,t.id,{record:!1});await this.app.fileManager.trashFile(I.file),await this.log.append({type:"delete",id:I.id,payload:{mergedInto:t.id}})}}this.selection.clear(),this.cursorIdx=-1,this.pendingFocusIds=[t.id];let u=this.titleForNode(t);this.plugin.notifications.show({message:this.bulkActionMessage({verb:"Merged",nodes:e,destination:`\u2192 kept "${u}"`}),kind:"success",category:"merge",affectedIds:e.map(f=>f.id),folder:this.noteFolder}),this.tree.rebuild(this.noteFolder),this.render({kind:"follow-cursor"});{let f=t.id,I=this.selectionGuardKey,m=()=>{if(this.selectionGuardKey!==I||this.selection.has(f))return;let C=this.currentChildren.findIndex(B=>B.id===f);C<0||(this.selection.add(f),this.cursorIdx=C,this.render({kind:"follow-cursor"}))};setTimeout(m,120),setTimeout(m,400)}let p=this.noteFolder;this.plugin.getUndoStack(p).push({label:`Merge ${e.length} notes`,undo:async()=>{await this.restoreSnapshots(n,e.map(I=>I.id));let f=this.app.vault.getAbstractFileByPath(i);f&&await this.app.vault.modify(f,s);for(let I of o){let m=this.app.vault.getAbstractFileByPath(I.childPath);m&&await this.app.fileManager.processFrontMatter(m,C=>{C.parent=I.oldParent})}this.pendingFocusIds=e.map(I=>I.id),this.tree.rebuild(p),this.render({kind:"follow-cursor"})},redo:async()=>{await this.trashNotesAndAttachments(n);let f=this.app.vault.getAbstractFileByPath(i);f&&await this.app.vault.modify(f,g);for(let I of o){let m=this.app.vault.getAbstractFileByPath(I.childPath);m&&await this.app.fileManager.processFrontMatter(m,C=>{C.parent=t.id})}this.tree.rebuild(p),this.render()}})}cmdCopy(){return So(this)}cmdCopyCodeBlock(){return dr(this)}cmdCopyTree(){return Ar(this)}cmdCopyOutline(){return hr(this)}cmdToggleExpand(){let e=this.getActionTargets();if(!e.length)return;let t=e.some(i=>!this.isNoteExpanded(i.id));for(let i of e)this.setNoteExpanded(i.id,t);this.render()}isNoteExpanded(e){let t=this.expandedNotes.has(e);return this.plugin.settings.expandBodiesByDefault?!t:t}setNoteExpanded(e,t){let i=this.plugin.settings.expandBodiesByDefault;t===i?this.expandedNotes.delete(e):this.expandedNotes.add(e)}cmdExpandAll(){for(let e of this.currentChildren)this.setNoteExpanded(e.id,!0);this.render()}cmdCollapseAll(){for(let e of this.currentChildren)this.setNoteExpanded(e.id,!1);this.render()}async cloneSubtree(e,t,i,s={}){if(!e.file)return null;if(e.id===t||this.isDescendant(t,e.id))return new F.Notice(`Can't insert "${this.titleForNode(e)}" into itself or a descendant \u2014 that would loop forever.`),null;let n=this.tree.getChildren(e.id).slice(),o=e.file,a=await this.app.vault.read(o),l=this.stripFrontmatter(a),c=this.app.metadataCache.getFileCache(o)?.frontmatter??{},d=Z(),h=be(l,this.activeStopwords()),g=De(h,d),u=`${this.noteFolder}/${g}`,p=new Date().toISOString(),f=s.preserveCreated&&e.created?e.created:p,I=this.extractAttachments(l),m=["---",`id: ${d}`,`parent: ${t}`,`created: ${f}`];if(I.length>0){m.push("attachments:");for(let B of I)m.push(` - "${B.replace(/"/g,'\\"')}"`)}else m.push("attachments: []");m.push("---",l),await this.ensureFolder(this.noteFolder),await this.app.vault.create(u,m.join(` -`)),i.push(u);let C=this.app.vault.getAbstractFileByPath(u);if(C){try{await this.app.fileManager.processFrontMatter(C,B=>{for(let[w,y]of Object.entries(c))Ct.includes(w)||Un.includes(w)||(B[w]=y);s.preserveCreated&&(B.modified=p),s.copyKind==="fork"&&(B[Hn]=p)})}catch(B){console.warn("[Stashpad] cloneSubtree: processFrontMatter failed",B)}try{this.tree.insertSynthetic({id:d,parent:t,children:[],file:C,created:f})}catch{}this.fmSync.scheduleParentChange(d,null,t)}for(let B of n)await this.cloneSubtree(B,d,i,s);return d}async cmdClone(){let e=this.getActionTargets();if(!e.length){new F.Notice("Nothing to clone.");return}let t=this.noteFolder,i=[],s=[];for(let l of e){if(!l.file)continue;let c=l.parent??this.focusId,d=await this.cloneSubtree(l,c,i);d&&s.push(d)}if(!s.length)return;this.tree.rebuild(t),this.pendingFocusIds=s.slice(),this.render();let n=i.map(l=>this.app.vault.getAbstractFileByPath(l)).filter(l=>!!l&&l.extension==="md").map(l=>({id:yt(l.basename)??l.basename,parent:null,children:[],file:l,created:new Date().toISOString()})),o=await this.snapshotNotes(n,!1);this.plugin.getUndoStack(t).push({label:`Clone ${s.length} note${s.length===1?"":"s"}`,undo:async()=>{for(let l of[...i].reverse()){let c=this.app.vault.getAbstractFileByPath(l);if(c)try{await this.app.fileManager.trashFile(c)}catch{}}this.tree.rebuild(t),this.render()},redo:async()=>{await this.restoreSnapshots(o,s)}});let a=s.map(l=>this.tree.get(l)).filter(l=>!!l);this.plugin.notifications.show({message:this.bulkActionMessage({verb:"Cloned",nodes:a,suffix:`(${i.length} file${i.length===1?"":"s"} total)`}),kind:"success",category:"clone",affectedIds:s,folder:this.noteFolder})}cmdForkNote(){let e=this.getActionTargets()[0];if(!e?.file){new F.Notice("Nothing to fork.");return}let t=e.parent??k,i=t===k?null:this.tree.get(t),s=i?this.titleForNode(i):"Home";new tt(this.app,this.tree,n=>this.titleForNode(n),{mode:"pick",allowCreate:!0,placeholder:`Fork "${this.titleForNode(e)}" under\u2026 (default: ${s})`,onPick:n=>{if(n.crossFolder){new F.Notice("Fork stays in this folder for now.");return}this.forkNoteUnder(e,n.id)},onCreate:n=>{(async()=>{let o=await this.createNoteUnder(n,this.focusId);o&&await this.forkNoteUnder(e,o)})()}}).open()}async forkNoteUnder(e,t){if(!e.file)return;let i=this.noteFolder,s=[],n=await this.cloneSubtree(e,t,s,{preserveCreated:!0,copyKind:"fork"});if(!n)return;let o=s[0]?this.app.vault.getAbstractFileByPath(s[0]):null;if(o)try{await this.app.fileManager.processFrontMatter(o,d=>{d[vi]=`[[${e.file.basename}]]`})}catch{}this.tree.rebuild(i),this.pendingFocusIds=[n],this.render(),await this.syncForkSiblings(this.forkFamilyFiles({originName:e.file.basename,include:[e.file,o]}));let a=s.map(d=>this.app.vault.getAbstractFileByPath(d)).filter(d=>!!d&&d.extension==="md").map(d=>({id:yt(d.basename)??d.basename,parent:null,children:[],file:d,created:new Date().toISOString()})),l=await this.snapshotNotes(a,!1);this.plugin.getUndoStack(i).push({label:"Fork note",undo:async()=>{for(let d of[...s].reverse()){let h=this.app.vault.getAbstractFileByPath(d);if(h)try{await this.app.fileManager.trashFile(h)}catch{}}this.tree.rebuild(i),this.render()},redo:async()=>{await this.restoreSnapshots(l,[n])}});let c=this.tree.get(n);this.plugin.notifications.show({message:c?`Forked "${this.titleForNode(e)}" \u2192 "${this.titleForNode(c)}" (${s.length} file${s.length===1?"":"s"})`:"Forked note",kind:"success",category:"clone",affectedIds:[n],folder:i})}async ensureSheetGroup(e){let t=Ve(this.app.metadataCache.getFileCache(e)?.frontmatter);if(t)return t;let i=ra();return await this.app.fileManager.processFrontMatter(e,s=>{s[Qi]=i,typeof s[at]!="number"&&(s[at]=0),s[Ei]=!0}),i}forkFamilyFiles(e){let t=new Map,i=s=>{s&&t.set(s.path,s)};for(let s of this.app.vault.getMarkdownFiles()){let n=this.app.metadataCache.getFileCache(s)?.frontmatter;e.gid&&Ve(n)===e.gid&&i(s),e.originName&&(s.basename===e.originName||aa(n)===e.originName)&&i(s)}for(let s of e.include??[])i(s);return[...t.values()]}async syncForkSiblings(e){for(let t of e){let i=e.filter(s=>s.path!==t.path).map(s=>`[[${s.basename}]]`);await this.app.fileManager.processFrontMatter(t,s=>{i.length?s[ze]=i:delete s[ze]})}}async cmdForkVersion(e){this.plugin.settings.enableSheetVersions||(this.plugin.settings.enableSheetVersions=!0,await this.plugin.saveSettings(),new F.Notice("Sheet versions enabled."));let t=e??this.getActionTargets()[0];if(!t?.file){new F.Notice("Select a note to version.");return}let i=this.noteFolder,s={...this.app.metadataCache.getFileCache(t.file)?.frontmatter??{}},n=await this.ensureSheetGroup(t.file),o=t.parent??this.focusId,a=typeof s[at]=="number"?s[at]:0;for(let p of this.tree.getChildren(o)){if(p.id===t.id)continue;let f=Ce(this.app,p);if(Ve(f)!==n)continue;let I=f?.[at];typeof I=="number"&&I>a&&(a=I)}let l=a+1,c=[],d=await this.cloneSubtree(t,o,c,{preserveCreated:!0,copyKind:"fork"});if(!d||!c.length){new F.Notice("Sheets: could not create version.");return}let h=this.app.vault.getAbstractFileByPath(c[0]);h&&await this.app.fileManager.processFrontMatter(h,p=>{p[Qi]=n,p[at]=l,delete p[Yt],delete p[Ei],p[vi]=`[[${t.file.basename}]]`}),this.tree.rebuild(i),this.activeVersionByGroup.set(n,d),this.plugin.saveActiveVersion(i,n,d),this.render();let g=this.app.vault.getAbstractFileByPath(c[0]);await this.syncForkSiblings(this.forkFamilyFiles({gid:n,include:[t.file,g]})),this.plugin.getUndoStack(i).push({label:"Fork version",undo:async()=>{for(let p of[...c].reverse()){let f=this.app.vault.getAbstractFileByPath(p);if(f)try{await this.app.fileManager.trashFile(f)}catch{}}this.activeVersionByGroup.delete(n),this.tree.rebuild(i),this.render()},redo:async()=>{}});let u=c.length;new F.Notice(`Forked a new version${u>1?` (${u} notes)`:""}`)}async cmdMarkVersionFinal(e){this.plugin.settings.enableSheetVersions||(this.plugin.settings.enableSheetVersions=!0,await this.plugin.saveSettings());let t=e??this.getActionTargets()[0];if(!t?.file)return;let i=Ve(Ce(this.app,t));if(!i){new F.Notice("Not a versioned note.");return}let s=!ki(Ce(this.app,t)),n=t.parent??this.focusId,o=this.tree.getChildren(n).filter(a=>Ve(Ce(this.app,a))===i);for(let a of o){if(!a.file)continue;let l=ki(Ce(this.app,a)),c=s&&a.id===t.id;l!==c&&await this.app.fileManager.processFrontMatter(a.file,d=>{c?d[Yt]=!0:delete d[Yt]})}this.render()}async cmdCopyNotes(){let e=this.getActionTargets();if(!e.length){new F.Notice("Nothing to copy.");return}await So(this),this.plugin.clearNoteClipboard(),this.plugin.noteClipboard={mode:"copy",folder:this.noteFolder,ids:e.map(t=>t.id)},this.render()}isCutPending(e){let t=this.plugin.noteClipboard;return!!t&&t.mode==="cut"&&t.folder===this.noteFolder&&t.ids.includes(e)}isCopyPending(e){let t=this.plugin.noteClipboard;return!!t&&t.mode==="copy"&&t.folder===this.noteFolder&&t.ids.includes(e)}insertIntoComposer(e){let t=this.composerInputEl;if(!t){this.composerDraft=this.composerDraft?`${this.composerDraft} +`;await this.app.vault.modify(t.file,g);for(let u=1;u<e.length;u++){let I=e[u];if(I.file){for(let m of this.tree.getChildren(I.id))m.file&&o.push({childId:m.id,childPath:m.file.path,oldParent:m.parent}),await this.changeParent(m,t.id,{record:!1});await this.app.fileManager.trashFile(I.file),await this.log.append({type:"delete",id:I.id,payload:{mergedInto:t.id}})}}this.selection.clear(),this.cursorIdx=-1,this.pendingFocusIds=[t.id];let f=this.titleForNode(t);this.plugin.notifications.show({message:this.bulkActionMessage({verb:"Merged",nodes:e,destination:`\u2192 kept "${f}"`}),kind:"success",category:"merge",affectedIds:e.map(u=>u.id),folder:this.noteFolder}),this.tree.rebuild(this.noteFolder),this.render({kind:"follow-cursor"});{let u=t.id,I=this.selectionGuardKey,m=()=>{if(this.selectionGuardKey!==I||this.selection.has(u))return;let C=this.currentChildren.findIndex(B=>B.id===u);C<0||(this.selection.add(u),this.cursorIdx=C,this.render({kind:"follow-cursor"}))};setTimeout(m,120),setTimeout(m,400)}let p=this.noteFolder;this.plugin.getUndoStack(p).push({label:`Merge ${e.length} notes`,undo:async()=>{await this.restoreSnapshots(n,e.map(I=>I.id));let u=this.app.vault.getAbstractFileByPath(i);u&&await this.app.vault.modify(u,s);for(let I of o){let m=this.app.vault.getAbstractFileByPath(I.childPath);m&&await this.app.fileManager.processFrontMatter(m,C=>{C.parent=I.oldParent})}this.pendingFocusIds=e.map(I=>I.id),this.tree.rebuild(p),this.render({kind:"follow-cursor"})},redo:async()=>{await this.trashNotesAndAttachments(n);let u=this.app.vault.getAbstractFileByPath(i);u&&await this.app.vault.modify(u,g);for(let I of o){let m=this.app.vault.getAbstractFileByPath(I.childPath);m&&await this.app.fileManager.processFrontMatter(m,C=>{C.parent=t.id})}this.tree.rebuild(p),this.render()}})}cmdCopy(){return qo(this)}cmdCopyCodeBlock(){return ba(this)}cmdCopyTree(){return Da(this)}cmdCopyOutline(){return xa(this)}cmdToggleExpand(){let e=this.getActionTargets();if(!e.length)return;let t=e.some(i=>!this.isNoteExpanded(i.id));for(let i of e)this.setNoteExpanded(i.id,t);this.render()}isNoteExpanded(e){let t=this.expandedNotes.has(e);return this.plugin.settings.expandBodiesByDefault?!t:t}setNoteExpanded(e,t){let i=this.plugin.settings.expandBodiesByDefault;t===i?this.expandedNotes.delete(e):this.expandedNotes.add(e)}cmdExpandAll(){for(let e of this.currentChildren)this.setNoteExpanded(e.id,!0);this.render()}cmdCollapseAll(){for(let e of this.currentChildren)this.setNoteExpanded(e.id,!1);this.render()}async cloneSubtree(e,t,i,s={}){if(!e.file)return null;if(e.id===t||this.isDescendant(t,e.id))return new k.Notice(`Can't insert "${this.titleForNode(e)}" into itself or a descendant \u2014 that would loop forever.`),null;let n=this.tree.getChildren(e.id).slice(),o=e.file,a=await this.app.vault.read(o),l=this.stripFrontmatter(a),c=this.app.metadataCache.getFileCache(o)?.frontmatter??{},d=Z(),h=De(l,this.activeStopwords()),g=xe(h,d),f=`${this.noteFolder}/${g}`,p=new Date().toISOString(),u=s.preserveCreated&&e.created?e.created:p,I=this.extractAttachments(l),m=["---",`id: ${d}`,`parent: ${t}`,`created: ${u}`];if(I.length>0){m.push("attachments:");for(let B of I)m.push(` - "${B.replace(/"/g,'\\"')}"`)}else m.push("attachments: []");m.push("---",l),await this.ensureFolder(this.noteFolder),await this.app.vault.create(f,m.join(` +`)),i.push(f);let C=this.app.vault.getAbstractFileByPath(f);if(C){try{await this.app.fileManager.processFrontMatter(C,B=>{for(let[w,y]of Object.entries(c))Ve.includes(w)||Zn.includes(w)||(B[w]=y);s.preserveCreated&&(B.modified=p),s.copyKind==="fork"&&(B[Xn]=p)})}catch(B){console.warn("[Stashpad] cloneSubtree: processFrontMatter failed",B)}try{this.tree.insertSynthetic({id:d,parent:t,children:[],file:C,created:u})}catch{}this.fmSync.scheduleParentChange(d,null,t)}for(let B of n)await this.cloneSubtree(B,d,i,s);return d}async cmdClone(){let e=this.getActionTargets();if(!e.length){new k.Notice("Nothing to clone.");return}let t=this.noteFolder,i=[],s=[];for(let l of e){if(!l.file)continue;let c=l.parent??this.focusId,d=await this.cloneSubtree(l,c,i);d&&s.push(d)}if(!s.length)return;this.tree.rebuild(t),this.pendingFocusIds=s.slice(),this.render();let n=i.map(l=>this.app.vault.getAbstractFileByPath(l)).filter(l=>!!l&&l.extension==="md").map(l=>({id:wt(l.basename)??l.basename,parent:null,children:[],file:l,created:new Date().toISOString()})),o=await this.snapshotNotes(n,!1);this.plugin.getUndoStack(t).push({label:`Clone ${s.length} note${s.length===1?"":"s"}`,undo:async()=>{for(let l of[...i].reverse()){let c=this.app.vault.getAbstractFileByPath(l);if(c)try{await this.app.fileManager.trashFile(c)}catch{}}this.tree.rebuild(t),this.render()},redo:async()=>{await this.restoreSnapshots(o,s)}});let a=s.map(l=>this.tree.get(l)).filter(l=>!!l);this.plugin.notifications.show({message:this.bulkActionMessage({verb:"Cloned",nodes:a,suffix:`(${i.length} file${i.length===1?"":"s"} total)`}),kind:"success",category:"clone",affectedIds:s,folder:this.noteFolder})}cmdForkNote(){let e=this.getActionTargets()[0];if(!e?.file){new k.Notice("Nothing to fork.");return}let t=e.parent??F,i=t===F?null:this.tree.get(t),s=i?this.titleForNode(i):"Home";new it(this.app,this.tree,n=>this.titleForNode(n),{mode:"pick",allowCreate:!0,placeholder:`Fork "${this.titleForNode(e)}" under\u2026 (default: ${s})`,onPick:n=>{if(n.crossFolder){new k.Notice("Fork stays in this folder for now.");return}this.forkNoteUnder(e,n.id)},onCreate:n=>{(async()=>{let o=await this.createNoteUnder(n,this.focusId);o&&await this.forkNoteUnder(e,o)})()}}).open()}async forkNoteUnder(e,t){if(!e.file)return;let i=this.noteFolder,s=[],n=await this.cloneSubtree(e,t,s,{preserveCreated:!0,copyKind:"fork"});if(!n)return;let o=s[0]?this.app.vault.getAbstractFileByPath(s[0]):null;if(o)try{await this.app.fileManager.processFrontMatter(o,d=>{d[Di]=`[[${e.file.basename}]]`})}catch{}this.tree.rebuild(i),this.pendingFocusIds=[n],this.render(),await this.syncForkSiblings(this.forkFamilyFiles({originName:e.file.basename,include:[e.file,o]}));let a=s.map(d=>this.app.vault.getAbstractFileByPath(d)).filter(d=>!!d&&d.extension==="md").map(d=>({id:wt(d.basename)??d.basename,parent:null,children:[],file:d,created:new Date().toISOString()})),l=await this.snapshotNotes(a,!1);this.plugin.getUndoStack(i).push({label:"Fork note",undo:async()=>{for(let d of[...s].reverse()){let h=this.app.vault.getAbstractFileByPath(d);if(h)try{await this.app.fileManager.trashFile(h)}catch{}}this.tree.rebuild(i),this.render()},redo:async()=>{await this.restoreSnapshots(l,[n])}});let c=this.tree.get(n);this.plugin.notifications.show({message:c?`Forked "${this.titleForNode(e)}" \u2192 "${this.titleForNode(c)}" (${s.length} file${s.length===1?"":"s"})`:"Forked note",kind:"success",category:"clone",affectedIds:[n],folder:i})}async ensureSheetGroup(e){let t=ze(this.app.metadataCache.getFileCache(e)?.frontmatter);if(t)return t;let i=Br();return await this.app.fileManager.processFrontMatter(e,s=>{s[Si]=i,typeof s[lt]!="number"&&(s[lt]=0),s[bi]=!0}),i}forkFamilyFiles(e){let t=new Map,i=s=>{s&&t.set(s.path,s)};for(let s of this.app.vault.getMarkdownFiles()){let n=this.app.metadataCache.getFileCache(s)?.frontmatter;e.gid&&ze(n)===e.gid&&i(s),e.originName&&(s.basename===e.originName||Cr(n)===e.originName)&&i(s)}for(let s of e.include??[])i(s);return[...t.values()]}async syncForkSiblings(e){for(let t of e){let i=e.filter(s=>s.path!==t.path).map(s=>`[[${s.basename}]]`);await this.app.fileManager.processFrontMatter(t,s=>{i.length?s[Ye]=i:delete s[Ye]})}}async cmdForkVersion(e){this.plugin.settings.enableSheetVersions||(this.plugin.settings.enableSheetVersions=!0,await this.plugin.saveSettings(),new k.Notice("Sheet versions enabled."));let t=e??this.getActionTargets()[0];if(!t?.file){new k.Notice("Select a note to version.");return}let i=this.noteFolder,s={...this.app.metadataCache.getFileCache(t.file)?.frontmatter??{}},n=await this.ensureSheetGroup(t.file),o=t.parent??this.focusId,a=typeof s[lt]=="number"?s[lt]:0;for(let p of this.tree.getChildren(o)){if(p.id===t.id)continue;let u=Be(this.app,p);if(ze(u)!==n)continue;let I=u?.[lt];typeof I=="number"&&I>a&&(a=I)}let l=a+1,c=[],d=await this.cloneSubtree(t,o,c,{preserveCreated:!0,copyKind:"fork"});if(!d||!c.length){new k.Notice("Sheets: could not create version.");return}let h=this.app.vault.getAbstractFileByPath(c[0]);h&&await this.app.fileManager.processFrontMatter(h,p=>{p[Si]=n,p[lt]=l,delete p[Xt],delete p[bi],p[Di]=`[[${t.file.basename}]]`}),this.tree.rebuild(i),this.activeVersionByGroup.set(n,d),this.plugin.saveActiveVersion(i,n,d),this.render();let g=this.app.vault.getAbstractFileByPath(c[0]);await this.syncForkSiblings(this.forkFamilyFiles({gid:n,include:[t.file,g]})),this.plugin.getUndoStack(i).push({label:"Fork version",undo:async()=>{for(let p of[...c].reverse()){let u=this.app.vault.getAbstractFileByPath(p);if(u)try{await this.app.fileManager.trashFile(u)}catch{}}this.activeVersionByGroup.delete(n),this.tree.rebuild(i),this.render()},redo:async()=>{}});let f=c.length;new k.Notice(`Forked a new version${f>1?` (${f} notes)`:""}`)}async cmdMarkVersionFinal(e){this.plugin.settings.enableSheetVersions||(this.plugin.settings.enableSheetVersions=!0,await this.plugin.saveSettings());let t=e??this.getActionTargets()[0];if(!t?.file)return;let i=ze(Be(this.app,t));if(!i){new k.Notice("Not a versioned note.");return}let s=!Ni(Be(this.app,t)),n=t.parent??this.focusId,o=this.tree.getChildren(n).filter(a=>ze(Be(this.app,a))===i);for(let a of o){if(!a.file)continue;let l=Ni(Be(this.app,a)),c=s&&a.id===t.id;l!==c&&await this.app.fileManager.processFrontMatter(a.file,d=>{c?d[Xt]=!0:delete d[Xt]})}this.render()}async cmdCopyNotes(){let e=this.getActionTargets();if(!e.length){new k.Notice("Nothing to copy.");return}await qo(this),this.plugin.clearNoteClipboard(),this.plugin.noteClipboard={mode:"copy",folder:this.noteFolder,ids:e.map(t=>t.id)},this.render()}isCutPending(e){let t=this.plugin.noteClipboard;return!!t&&t.mode==="cut"&&t.folder===this.noteFolder&&t.ids.includes(e)}isCopyPending(e){let t=this.plugin.noteClipboard;return!!t&&t.mode==="copy"&&t.folder===this.noteFolder&&t.ids.includes(e)}insertIntoComposer(e){let t=this.composerInputEl;if(!t){this.composerDraft=this.composerDraft?`${this.composerDraft} -${e}`:e;return}let i=t.selectionStart??t.value.length,s=t.selectionEnd??t.value.length;t.value=t.value.slice(0,i)+e+t.value.slice(s);let n=i+e.length;try{t.setSelectionRange(n,n)}catch{}this.composerDraft=t.value}focusedInsideCut(e){let t=new Set(e),i=this.focusId,s=0;for(;i&&i!==k&&s++<1e3;){if(t.has(i))return!0;i=this.tree.get(i)?.parent??null}return!1}async cmdCutNotes(){let e=this.getActionTargets();if(!e.length){new F.Notice("Nothing to cut.");return}let t=[];for(let s of e)s.file&&t.push(this.stripFrontmatter(await this.app.vault.cachedRead(s.file)).trim());let i=t.join(` +${e}`:e;return}let i=t.selectionStart??t.value.length,s=t.selectionEnd??t.value.length;t.value=t.value.slice(0,i)+e+t.value.slice(s);let n=i+e.length;try{t.setSelectionRange(n,n)}catch{}this.composerDraft=t.value}focusedInsideCut(e){let t=new Set(e),i=this.focusId,s=0;for(;i&&i!==F&&s++<1e3;){if(t.has(i))return!0;i=this.tree.get(i)?.parent??null}return!1}async cmdCutNotes(){let e=this.getActionTargets();if(!e.length){new k.Notice("Nothing to cut.");return}let t=[];for(let s of e)s.file&&t.push(this.stripFrontmatter(await this.app.vault.cachedRead(s.file)).trim());let i=t.join(` -`);await navigator.clipboard.writeText(i),this.plugin.clearNoteClipboard(),this.plugin.noteClipboard={mode:"cut",folder:this.noteFolder,ids:e.map(s=>s.id),text:i},this.render(),this.plugin.noteClipboardNotice=this.plugin.notifications.show({message:`Cut ${this.titleList(e)} \u2014 paste in the LIST to move it there as a note; paste in a note's COMPOSER to drop its text in and delete the original (undoable). Esc cancels. Nothing happens until you paste.`,kind:"info",category:"system",affectedIds:e.map(s=>s.id),folder:this.noteFolder,duration:0})}async cmdPasteNotes(){let e=this.plugin.noteClipboard;if(!e){new F.Notice("The note clipboard is empty \u2014 copy or cut notes first.");return}if(e.folder!==this.noteFolder){let g=(this.currentChildren[this.cursorIdx]??null)?.parent??this.focusId??k,u=e.mode,p=e.folder,f=await this.plugin.crossFolderPaste(p,e.ids,this.noteFolder,g,u);if(!f||!f.rootIds.length)return;u==="cut"&&this.plugin.clearNoteClipboard();let I=this.noteFolder;this.tree.rebuild(I),this.pendingFocusIds=f.rootIds.slice(),this.render(),u==="cut"&&this.plugin.refreshOpenViewsForFolder(p);let m=p.split("/").pop(),C=f.rootIds.length,B=()=>{this.tree.rebuild(I),this.render(),this.plugin.refreshOpenViewsForFolder(p)};this.plugin.getUndoStack(I).push({label:`${u==="cut"?"Move":"Paste"} ${C} note${C===1?"":"s"} from ${m}`,undo:async()=>{await f.undo(),B()},redo:async()=>{await f.redo(),B()}});let w=u==="cut"?"Moved":"Pasted (copied)";this.plugin.notifications.show({message:`${w} ${C} note${C===1?"":"s"} (${f.noteCount} total) from "${m}" into this folder. Undo (in the list) reverses it.`,kind:"success",category:u==="cut"?"move":"clone",affectedIds:f.rootIds,folder:I,duration:0});return}let t=e.ids.map(h=>this.tree.get(h)).filter(h=>!!h&&!!h.file);if(!t.length){this.plugin.clearNoteClipboard(),this.render(),new F.Notice("Those notes no longer exist.");return}let i=this.currentChildren[this.cursorIdx]??null,s=i?.parent??this.focusId??k;if(e.mode==="cut"){let h=new Set(e.ids);for(let u=s;u&&u!==k;u=this.tree.get(u)?.parent??null)if(h.has(u)){new F.Notice("Can't paste cut notes under themselves.");return}let g=i&&!h.has(i.id)?i.id:"";this.plugin.clearNoteClipboard(),await this.moveAcrossThenReorder(t.map(u=>u.id),s,g,"after");return}let n=this.noteFolder,o=[],a=[];for(let h of t){let g=await this.cloneSubtree(h,s,o);g&&a.push(g)}if(!a.length)return;this.tree.rebuild(n),this.pendingFocusIds=a.slice(),this.render();let l=o.map(h=>this.app.vault.getAbstractFileByPath(h)).filter(h=>!!h&&h.extension==="md").map(h=>({id:yt(h.basename)??h.basename,parent:null,children:[],file:h,created:new Date().toISOString()})),c=await this.snapshotNotes(l,!1);this.plugin.getUndoStack(n).push({label:`Paste ${a.length} note${a.length===1?"":"s"}`,undo:async()=>{for(let h of[...o].reverse()){let g=this.app.vault.getAbstractFileByPath(h);if(g)try{await this.app.fileManager.trashFile(g)}catch{}}this.tree.rebuild(n),this.render()},redo:async()=>{await this.restoreSnapshots(c,a)}});let d=a.map(h=>this.tree.get(h)).filter(h=>!!h);this.plugin.notifications.show({message:this.bulkActionMessage({verb:"Pasted (duplicated)",nodes:d,suffix:`(${o.length} file${o.length===1?"":"s"} total)`}),kind:"success",category:"clone",affectedIds:a,folder:n,duration:0})}async completeCutIntoComposer(){let e=this.plugin.noteClipboard;if(!e||e.mode!=="cut")return;if(e.folder!==this.noteFolder){let g=e.folder,u=e.ids.slice();this.plugin.clearNoteClipboard();let p=await this.plugin.orderedSubtreeNodes(g,u),f=X().prefixTimestampsOnCopy,I=[];for(let{file:y,created:E,depth:v}of p)try{let Q=this.stripFrontmatter(await this.app.vault.cachedRead(y)).trim().split(/\r?\n/).join(" "),S=f?`${this.formatTimeInline(E)} `:"";I.push(`${" ".repeat(v)}- ${S}${Q}`)}catch{}this.insertIntoComposer(I.length?I.join(` -`):e.text??"");let m=await this.plugin.subtreeFilePaths(g,u),C=await this.plugin.snapshotPaths(m),B=await this.plugin.trashSubtrees(g,u);this.plugin.refreshOpenViewsForFolder(g);let w=B.filter(y=>y.extension==="md").length;this.plugin.getUndoStack(g).push({label:`Cut ${u.length} note${u.length===1?"":"s"} into composer (from ${g.split("/").pop()})`,undo:async()=>{await this.plugin.restoreSnapshot(C),this.plugin.refreshOpenViewsForFolder(g)},redo:async()=>{await this.plugin.trashSubtrees(g,u),this.plugin.refreshOpenViewsForFolder(g)}}),this.plugin.notifications.show({message:`Pasted the text of ${u.length} cut note${u.length===1?"":"s"} from "${g.split("/").pop()}" into the composer and removed the original${w===1?"":"s"} (${w} note${w===1?"":"s"}). Undo restores them.`,kind:"warning",category:"delete",affectedIds:u,folder:g,duration:0});return}this.plugin.clearNoteClipboard();let t=e.ids.map(g=>this.tree.get(g)).filter(g=>!!g&&!!g.file);if(!t.length)return;let i=[],s=new Set,n=(g,u)=>{if(!s.has(g.id)){s.add(g.id),i.push({node:g,depth:u});for(let p of this.tree.getChildren(g.id))n(p,u+1)}};for(let g of t)n(g,0);let o=this.noteFolder,a=X().prefixTimestampsOnCopy,l=[];for(let{node:g,depth:u}of i)if(g.file)try{let p=this.stripFrontmatter(await this.app.vault.cachedRead(g.file)).trim().split(/\r?\n/).join(" "),f=a?`${this.formatTimeInline(g.created)} `:"";l.push(`${" ".repeat(u)}- ${f}${p}`)}catch{}this.insertIntoComposer(l.join(` -`));let c=i.map(g=>g.node),d=await this.snapshotNotes(c,!1);for(let g of[...c].reverse())if(g.file)try{await this.app.fileManager.trashFile(g.file)}catch(u){console.warn("[Stashpad] cut-paste delete failed",g.file.path,u)}this.selection.clear(),this.tree.rebuild(o),this.render();let h=t.map(g=>g.id);this.plugin.getUndoStack(o).push({label:`Cut ${t.length} note${t.length===1?"":"s"} into composer`,undo:async()=>{await this.restoreSnapshots(d,h)},redo:async()=>{for(let g of[...d.notes]){let u=this.app.vault.getAbstractFileByPath(g.path);if(u)try{await this.app.fileManager.trashFile(u)}catch{}}this.tree.rebuild(o),this.render()}}),this.plugin.notifications.show({message:`Pasted the text of ${this.titleList(t)} into the composer and deleted the original${t.length===1?"":"s"} (${c.length} note${c.length===1?"":"s"}). Undo (in the list) restores them.`,kind:"warning",category:"delete",affectedIds:h,folder:o,duration:0})}cmdInsertTemplate(){new tt(this.app,this.tree,e=>this.titleForNode(e),{mode:"pick",placeholder:"Insert which note as a template?",allowCreate:!1,onPick:async e=>{if(e.crossFolder){new F.Notice("Cross-Stashpad templates aren't supported yet \u2014 pick a note from this Stashpad.");return}let t=this.tree.get(e.id);if(!t?.file)return;let i=this.noteFolder,s=[],n=await this.cloneSubtree(t,this.focusId,s);if(!n)return;this.tree.rebuild(i),this.pendingFocusIds=[n],this.render();let o=s.map(l=>this.app.vault.getAbstractFileByPath(l)).filter(l=>!!l&&l.extension==="md").map(l=>({id:yt(l.basename)??l.basename,parent:null,children:[],file:l,created:new Date().toISOString()})),a=await this.snapshotNotes(o,!1);this.plugin.getUndoStack(i).push({label:"Insert template",undo:async()=>{for(let l of[...s].reverse()){let c=this.app.vault.getAbstractFileByPath(l);if(c)try{await this.app.fileManager.trashFile(c)}catch{}}this.tree.rebuild(i),this.render()},redo:async()=>{await this.restoreSnapshots(a,[n])}}),this.plugin.notifications.show({message:`Inserted template (${s.length} file${s.length===1?"":"s"})`,kind:"success",category:"clone",folder:this.noteFolder})}}).open()}navigateTo(e,t={}){if(t.keepForwardStack||this.recordNavState(),this.selectionGuardKey++,this.listEl&&this.stampSelectedCursor(!0),this.focusId=e,this.persistFocus(),this.defaultCursorToLast(),this.syncComposerDraftForFocus(),this.tagFilter&&this.tagFilter!==Tt&&this.tagFilter!==Pt){let n=this.tagFilter.toLowerCase();this.collectFolderTags().some(a=>a.raw.toLowerCase()===n)||(this.tagFilter=null)}if(this.colorFilter){let n=this.colorFilter.toLowerCase();this.collectFolderColors().some(a=>a.hex===n)||(this.colorFilter=null)}let i=this.lastCursorByFocus.get(e),s;i&&this.tree.get(i)?(this.pendingFocusIds=[i],s={kind:"scroll-to-id",id:i,align:"start"}):s={kind:"preserve"},this.render(s),this.refreshHeaderTitle(),this.viewRoot.focus({preventScroll:!0}),this.plugin.notifyStashpadSelectionChanged()}navigateBack(){let e=this.navBackStack.pop();if(!e){this.focusId!==k&&this.navigateUp();return}this.navForwardSnapshots.push(this.captureNavSnapshot()),this.applyNavSnapshot(e)}navigateForward(){let e=this.navForwardSnapshots.pop();e&&(this.navBackStack.push(this.captureNavSnapshot()),this.applyNavSnapshot(e))}async applyNavSnapshot(e){if(this.tagFilter=null,this.colorFilter=null,e.folder!==this.noteFolder){await this.setFolderOverride(e.folder,{skipHistory:!0}),e.focusId!==this.focusId&&this.tree.get(e.focusId)&&(this.focusId=e.focusId,this.render({kind:"preserve"}));return}if(!this.tree.get(e.focusId))return;this.selectionGuardKey++,this.listEl&&this.stampSelectedCursor(!0),this.focusId=e.focusId,this.persistFocus(),this.defaultCursorToLast(),this.syncComposerDraftForFocus();let t=this.lastCursorByFocus.get(e.focusId),i;t&&this.tree.get(t)?(this.pendingFocusIds=[t],i={kind:"scroll-to-id",id:t,align:"start"}):i={kind:"preserve"},this.render(i),this.refreshHeaderTitle(),this.viewRoot.focus({preventScroll:!0}),this.plugin.notifyStashpadSelectionChanged()}navigateUp(){this.selectionGuardKey++,this.tagFilter=null,this.colorFilter=null;let e=this.tree.get(this.focusId);if(!e||e.parent==null){if(this.navBackStack.length>0){this.navigateBack();return}return this.navigateTo(k)}let t=this.focusId;if(this.recordNavState(),this.listEl){let o=this.currentChildren[this.cursorIdx]?.id??this.lastSelected;o&&this.plugin.saveLastCursor(this.noteFolder,t,o)}this.focusId=e.parent,this.persistFocus(),this.syncComposerDraftForFocus();let i=this.filterChildren(this.tree.getChildren(this.focusId)),s=i.findIndex(n=>n.id===t);this.selection.clear(),s>=0?(this.cursorIdx=s,this.selection.add(t),this.lastSelected=t):(this.cursorIdx=i.length-1,i.length>0&&(this.selection.add(i[i.length-1].id),this.lastSelected=i[i.length-1].id)),s>=0?this.render({kind:"scroll-to-id",id:t,align:"start"}):this.render({kind:"follow-cursor"}),this.refreshHeaderTitle(),s<0&&this.revealCursorRow(),this.plugin.notifyStashpadSelectionChanged()}openBookmarks(){let e=this.app.internalPlugins?.plugins?.bookmarks?.instance?.items??[],t=this.allowedByBases(),i=new F.Menu,s=0;for(let n of e){if(n.type!=="file"||t&&!t.has(n.path))continue;let o=this.tree.idForPath(n.path);o&&(i.addItem(a=>a.setTitle(n.title||n.path).onClick(()=>this.navigateTo(o))),s++)}s||i.addItem(n=>n.setTitle("(no bookmarks in scope)").setDisabled(!0)),i.showAtMouseEvent(new MouseEvent("click",{clientX:200,clientY:400}))}async bootstrapFolder(){if(this.bootstrappedFolders.has(this.noteFolder))return;await this.ensureFolder(this.noteFolder),await this.ensureHomeNote(),await this.migrateNullParents();let e=(this.plugin.settings.importDropFolder||"").trim().replace(/^\/+|\/+$/g,""),t=(this.plugin.settings.exportFolder||"").trim().replace(/^\/+|\/+$/g,"");e&&await this.ensureFolder(`${this.noteFolder}/${e}`),t&&await this.ensureFolder(`${this.noteFolder}/${t}`),await this.order.load(this.noteFolder),await this.sortStore.load(this.noteFolder),this.bootstrappedFolders.add(this.noteFolder)}backfillFrontmatterSync(){let e=[k],t=this.tree.getRoot(),i=s=>{for(let n of this.tree.getChildren(s))e.push(n.id),i(n.id)};for(let s of t.children)i(s);for(let s of e)this.fmSync.wouldWrite(s)&&this.fmSync.schedule(s)}installFmSyncActivityNotice(){this.fmSyncUnsubscribe||(this.fmSyncUnsubscribe=this.fmSync.onError((e,t)=>{this.plugin.notifications.show({message:`Stashpad: couldn't update recovery metadata +`);await navigator.clipboard.writeText(i),this.plugin.clearNoteClipboard(),this.plugin.noteClipboard={mode:"cut",folder:this.noteFolder,ids:e.map(s=>s.id),text:i},this.render(),this.plugin.noteClipboardNotice=this.plugin.notifications.show({message:`Cut ${this.titleList(e)} \u2014 paste in the LIST to move it there as a note; paste in a note's COMPOSER to drop its text in and delete the original (undoable). Esc cancels. Nothing happens until you paste.`,kind:"info",category:"system",affectedIds:e.map(s=>s.id),folder:this.noteFolder,duration:0})}async cmdPasteNotes(){let e=this.plugin.noteClipboard;if(!e){new k.Notice("The note clipboard is empty \u2014 copy or cut notes first.");return}if(e.folder!==this.noteFolder){let g=(this.currentChildren[this.cursorIdx]??null)?.parent??this.focusId??F,f=e.mode,p=e.folder,u=await this.plugin.crossFolderPaste(p,e.ids,this.noteFolder,g,f);if(!u||!u.rootIds.length)return;f==="cut"&&this.plugin.clearNoteClipboard();let I=this.noteFolder;this.tree.rebuild(I),this.pendingFocusIds=u.rootIds.slice(),this.render(),f==="cut"&&this.plugin.refreshOpenViewsForFolder(p);let m=p.split("/").pop(),C=u.rootIds.length,B=()=>{this.tree.rebuild(I),this.render(),this.plugin.refreshOpenViewsForFolder(p)};this.plugin.getUndoStack(I).push({label:`${f==="cut"?"Move":"Paste"} ${C} note${C===1?"":"s"} from ${m}`,undo:async()=>{await u.undo(),B()},redo:async()=>{await u.redo(),B()}});let w=f==="cut"?"Moved":"Pasted (copied)";this.plugin.notifications.show({message:`${w} ${C} note${C===1?"":"s"} (${u.noteCount} total) from "${m}" into this folder. Undo (in the list) reverses it.`,kind:"success",category:f==="cut"?"move":"clone",affectedIds:u.rootIds,folder:I,duration:0});return}let t=e.ids.map(h=>this.tree.get(h)).filter(h=>!!h&&!!h.file);if(!t.length){this.plugin.clearNoteClipboard(),this.render(),new k.Notice("Those notes no longer exist.");return}let i=this.currentChildren[this.cursorIdx]??null,s=i?.parent??this.focusId??F;if(e.mode==="cut"){let h=new Set(e.ids);for(let f=s;f&&f!==F;f=this.tree.get(f)?.parent??null)if(h.has(f)){new k.Notice("Can't paste cut notes under themselves.");return}let g=i&&!h.has(i.id)?i.id:"";this.plugin.clearNoteClipboard(),await this.moveAcrossThenReorder(t.map(f=>f.id),s,g,"after");return}let n=this.noteFolder,o=[],a=[];for(let h of t){let g=await this.cloneSubtree(h,s,o);g&&a.push(g)}if(!a.length)return;this.tree.rebuild(n),this.pendingFocusIds=a.slice(),this.render();let l=o.map(h=>this.app.vault.getAbstractFileByPath(h)).filter(h=>!!h&&h.extension==="md").map(h=>({id:wt(h.basename)??h.basename,parent:null,children:[],file:h,created:new Date().toISOString()})),c=await this.snapshotNotes(l,!1);this.plugin.getUndoStack(n).push({label:`Paste ${a.length} note${a.length===1?"":"s"}`,undo:async()=>{for(let h of[...o].reverse()){let g=this.app.vault.getAbstractFileByPath(h);if(g)try{await this.app.fileManager.trashFile(g)}catch{}}this.tree.rebuild(n),this.render()},redo:async()=>{await this.restoreSnapshots(c,a)}});let d=a.map(h=>this.tree.get(h)).filter(h=>!!h);this.plugin.notifications.show({message:this.bulkActionMessage({verb:"Pasted (duplicated)",nodes:d,suffix:`(${o.length} file${o.length===1?"":"s"} total)`}),kind:"success",category:"clone",affectedIds:a,folder:n,duration:0})}async completeCutIntoComposer(){let e=this.plugin.noteClipboard;if(!e||e.mode!=="cut")return;if(e.folder!==this.noteFolder){let g=e.folder,f=e.ids.slice();this.plugin.clearNoteClipboard();let p=await this.plugin.orderedSubtreeNodes(g,f),u=X().prefixTimestampsOnCopy,I=[];for(let{file:y,created:v,depth:E}of p)try{let Q=this.stripFrontmatter(await this.app.vault.cachedRead(y)).trim().split(/\r?\n/).join(" "),S=u?`${this.formatTimeInline(v)} `:"";I.push(`${" ".repeat(E)}- ${S}${Q}`)}catch{}this.insertIntoComposer(I.length?I.join(` +`):e.text??"");let m=await this.plugin.subtreeFilePaths(g,f),C=await this.plugin.snapshotPaths(m),B=await this.plugin.trashSubtrees(g,f);this.plugin.refreshOpenViewsForFolder(g);let w=B.filter(y=>y.extension==="md").length;this.plugin.getUndoStack(g).push({label:`Cut ${f.length} note${f.length===1?"":"s"} into composer (from ${g.split("/").pop()})`,undo:async()=>{await this.plugin.restoreSnapshot(C),this.plugin.refreshOpenViewsForFolder(g)},redo:async()=>{await this.plugin.trashSubtrees(g,f),this.plugin.refreshOpenViewsForFolder(g)}}),this.plugin.notifications.show({message:`Pasted the text of ${f.length} cut note${f.length===1?"":"s"} from "${g.split("/").pop()}" into the composer and removed the original${w===1?"":"s"} (${w} note${w===1?"":"s"}). Undo restores them.`,kind:"warning",category:"delete",affectedIds:f,folder:g,duration:0});return}this.plugin.clearNoteClipboard();let t=e.ids.map(g=>this.tree.get(g)).filter(g=>!!g&&!!g.file);if(!t.length)return;let i=[],s=new Set,n=(g,f)=>{if(!s.has(g.id)){s.add(g.id),i.push({node:g,depth:f});for(let p of this.tree.getChildren(g.id))n(p,f+1)}};for(let g of t)n(g,0);let o=this.noteFolder,a=X().prefixTimestampsOnCopy,l=[];for(let{node:g,depth:f}of i)if(g.file)try{let p=this.stripFrontmatter(await this.app.vault.cachedRead(g.file)).trim().split(/\r?\n/).join(" "),u=a?`${this.formatTimeInline(g.created)} `:"";l.push(`${" ".repeat(f)}- ${u}${p}`)}catch{}this.insertIntoComposer(l.join(` +`));let c=i.map(g=>g.node),d=await this.snapshotNotes(c,!1);for(let g of[...c].reverse())if(g.file)try{await this.app.fileManager.trashFile(g.file)}catch(f){console.warn("[Stashpad] cut-paste delete failed",g.file.path,f)}this.selection.clear(),this.tree.rebuild(o),this.render();let h=t.map(g=>g.id);this.plugin.getUndoStack(o).push({label:`Cut ${t.length} note${t.length===1?"":"s"} into composer`,undo:async()=>{await this.restoreSnapshots(d,h)},redo:async()=>{for(let g of[...d.notes]){let f=this.app.vault.getAbstractFileByPath(g.path);if(f)try{await this.app.fileManager.trashFile(f)}catch{}}this.tree.rebuild(o),this.render()}}),this.plugin.notifications.show({message:`Pasted the text of ${this.titleList(t)} into the composer and deleted the original${t.length===1?"":"s"} (${c.length} note${c.length===1?"":"s"}). Undo (in the list) restores them.`,kind:"warning",category:"delete",affectedIds:h,folder:o,duration:0})}cmdInsertTemplate(){new it(this.app,this.tree,e=>this.titleForNode(e),{mode:"pick",placeholder:"Insert which note as a template?",allowCreate:!1,onPick:async e=>{if(e.crossFolder){new k.Notice("Cross-Stashpad templates aren't supported yet \u2014 pick a note from this Stashpad.");return}let t=this.tree.get(e.id);if(!t?.file)return;let i=this.noteFolder,s=[],n=await this.cloneSubtree(t,this.focusId,s);if(!n)return;this.tree.rebuild(i),this.pendingFocusIds=[n],this.render();let o=s.map(l=>this.app.vault.getAbstractFileByPath(l)).filter(l=>!!l&&l.extension==="md").map(l=>({id:wt(l.basename)??l.basename,parent:null,children:[],file:l,created:new Date().toISOString()})),a=await this.snapshotNotes(o,!1);this.plugin.getUndoStack(i).push({label:"Insert template",undo:async()=>{for(let l of[...s].reverse()){let c=this.app.vault.getAbstractFileByPath(l);if(c)try{await this.app.fileManager.trashFile(c)}catch{}}this.tree.rebuild(i),this.render()},redo:async()=>{await this.restoreSnapshots(a,[n])}}),this.plugin.notifications.show({message:`Inserted template (${s.length} file${s.length===1?"":"s"})`,kind:"success",category:"clone",folder:this.noteFolder})}}).open()}navigateTo(e,t={}){if(t.keepForwardStack||this.recordNavState(),this.selectionGuardKey++,this.listEl&&this.stampSelectedCursor(!0),this.focusId=e,this.persistFocus(),this.defaultCursorToLast(),this.syncComposerDraftForFocus(),this.tagFilter&&this.tagFilter!==Gt&&this.tagFilter!==Lt){let n=this.tagFilter.toLowerCase();this.collectFolderTags().some(a=>a.raw.toLowerCase()===n)||(this.tagFilter=null)}if(this.colorFilter){let n=this.colorFilter.toLowerCase();this.collectFolderColors().some(a=>a.hex===n)||(this.colorFilter=null)}let i=this.lastCursorByFocus.get(e),s;i&&this.tree.get(i)?(this.pendingFocusIds=[i],s={kind:"scroll-to-id",id:i,align:"start"}):s={kind:"preserve"},this.render(s),this.refreshHeaderTitle(),this.viewRoot.focus({preventScroll:!0}),this.plugin.notifyStashpadSelectionChanged()}navigateBack(){let e=this.navBackStack.pop();if(!e){this.focusId!==F&&this.navigateUp();return}this.navForwardSnapshots.push(this.captureNavSnapshot()),this.applyNavSnapshot(e)}navigateForward(){let e=this.navForwardSnapshots.pop();e&&(this.navBackStack.push(this.captureNavSnapshot()),this.applyNavSnapshot(e))}async applyNavSnapshot(e){if(this.tagFilter=null,this.colorFilter=null,e.folder!==this.noteFolder){await this.setFolderOverride(e.folder,{skipHistory:!0}),e.focusId!==this.focusId&&this.tree.get(e.focusId)&&(this.focusId=e.focusId,this.render({kind:"preserve"}));return}if(!this.tree.get(e.focusId))return;this.selectionGuardKey++,this.listEl&&this.stampSelectedCursor(!0),this.focusId=e.focusId,this.persistFocus(),this.defaultCursorToLast(),this.syncComposerDraftForFocus();let t=this.lastCursorByFocus.get(e.focusId),i;t&&this.tree.get(t)?(this.pendingFocusIds=[t],i={kind:"scroll-to-id",id:t,align:"start"}):i={kind:"preserve"},this.render(i),this.refreshHeaderTitle(),this.viewRoot.focus({preventScroll:!0}),this.plugin.notifyStashpadSelectionChanged()}navigateUp(){this.selectionGuardKey++,this.tagFilter=null,this.colorFilter=null;let e=this.tree.get(this.focusId);if(!e||e.parent==null){if(this.navBackStack.length>0){this.navigateBack();return}return this.navigateTo(F)}let t=this.focusId;if(this.recordNavState(),this.listEl){let o=this.currentChildren[this.cursorIdx]?.id??this.lastSelected;o&&this.plugin.saveLastCursor(this.noteFolder,t,o)}this.focusId=e.parent,this.persistFocus(),this.syncComposerDraftForFocus();let i=this.filterChildren(this.tree.getChildren(this.focusId)),s=i.findIndex(n=>n.id===t);this.selection.clear(),s>=0?(this.cursorIdx=s,this.selection.add(t),this.lastSelected=t):(this.cursorIdx=i.length-1,i.length>0&&(this.selection.add(i[i.length-1].id),this.lastSelected=i[i.length-1].id)),s>=0?this.render({kind:"scroll-to-id",id:t,align:"start"}):this.render({kind:"follow-cursor"}),this.refreshHeaderTitle(),s<0&&this.revealCursorRow(),this.plugin.notifyStashpadSelectionChanged()}openBookmarks(){let e=this.app.internalPlugins?.plugins?.bookmarks?.instance?.items??[],t=this.allowedByBases(),i=new k.Menu,s=0;for(let n of e){if(n.type!=="file"||t&&!t.has(n.path))continue;let o=this.tree.idForPath(n.path);o&&(i.addItem(a=>a.setTitle(n.title||n.path).onClick(()=>this.navigateTo(o))),s++)}s||i.addItem(n=>n.setTitle("(no bookmarks in scope)").setDisabled(!0)),i.showAtMouseEvent(new MouseEvent("click",{clientX:200,clientY:400}))}async bootstrapFolder(){if(this.bootstrappedFolders.has(this.noteFolder))return;await this.ensureFolder(this.noteFolder),await this.ensureHomeNote(),await this.migrateNullParents();let e=(this.plugin.settings.importDropFolder||"").trim().replace(/^\/+|\/+$/g,""),t=(this.plugin.settings.exportFolder||"").trim().replace(/^\/+|\/+$/g,"");e&&await this.ensureFolder(`${this.noteFolder}/${e}`),t&&await this.ensureFolder(`${this.noteFolder}/${t}`),await this.order.load(this.noteFolder),await this.sortStore.load(this.noteFolder),this.bootstrappedFolders.add(this.noteFolder)}backfillFrontmatterSync(){let e=[F],t=this.tree.getRoot(),i=s=>{for(let n of this.tree.getChildren(s))e.push(n.id),i(n.id)};for(let s of t.children)i(s);for(let s of e)this.fmSync.wouldWrite(s)&&this.fmSync.schedule(s)}installFmSyncActivityNotice(){this.fmSyncUnsubscribe||(this.fmSyncUnsubscribe=this.fmSync.onError((e,t)=>{this.plugin.notifications.show({message:`Stashpad: couldn't update recovery metadata File: \`${e}\` -Error: ${t.message}`,kind:"error",category:"system",duration:0,affectedPaths:[e],folder:this.noteFolder})}))}async ensureHomeNote(){let e=this.noteFolder,t=`${e}/${this.buildHomeFilename(e)}`,i=this.app.vault.getMarkdownFiles().filter(o=>o.path.startsWith(e+"/"));for(let o of i){if(this.app.metadataCache.getFileCache(o)?.frontmatter?.id!==k)continue;if(o.path===t||this.app.vault.getAbstractFileByPath(t))return o;try{await this.app.fileManager.renameFile(o,t);let c=this.app.vault.getAbstractFileByPath(t);if(c instanceof F.TFile)return c}catch(c){console.warn("[Stashpad] home note rename failed; keeping legacy path",c)}return o}let s=new Date().toISOString(),n=["---",`id: ${k}`,"parent: null",`created: ${s}`,"attachments: []","---","","# Home","","This is your Stashpad home note. Edit me freely \u2014 everything else nests below.",""].join(` -`);return this.app.vault.create(t,n)}buildHomeFilename(e){return`Home-${(e.split("/").filter(Boolean).pop()??"Stashpad").replace(/[^A-Za-z0-9_-]+/g,"-").replace(/-+/g,"-").replace(/^-+|-+$/g,"")||"Stashpad"}.md`}async migrateNullParents(){let e=this.noteFolder,t=this.app.vault.getMarkdownFiles().filter(i=>i.path.startsWith(e+"/"));for(let i of t){let s=this.app.metadataCache.getFileCache(i)?.frontmatter,n=s?.id;if(!n||n===k)continue;let o=s?.parent;(o==null||o===""||o==="null")&&(await this.app.fileManager.processFrontMatter(i,a=>{a.parent=k}),await this.log.append({type:"parent_change",id:n,payload:{from:null,to:k,reason:"migration"}}))}}async openInNewStashpadTab(e){let t=this.app.workspace,i=this.leaf,s=t.getLeaf("tab");await s.setViewState({type:z,active:!0,state:{focusId:e,timeFilter:this.timeFilter,folderOverride:this.folderOverride}}),t.setActiveLeaf(s,{focus:!0}),t.revealLeaf(s),pt(t,s,i)}async openNoteInNewTab(e,t,i){let s=(e||"").trim().replace(/^\/+|\/+$/g,"");if(!s||!t)return;let n=(this.plugin.settings.folder||"Stashpad").trim().replace(/^\/+|\/+$/g,"")||"Stashpad",o=this.app.workspace,a=this.leaf,l=o.getLeaf("tab");await l.setViewState({type:z,active:!0,state:{focusId:t,folderOverride:s===n?null:s,...i?{cursorId:i}:{}}}),o.setActiveLeaf(l,{focus:!0}),o.revealLeaf(l),pt(o,l,a)}async openFolderInNewTab(e){let t=(e||"").trim().replace(/^\/+|\/+$/g,"");if(!t)return;let i=(this.plugin.settings.folder||"Stashpad").trim().replace(/^\/+|\/+$/g,"")||"Stashpad",s=this.app.workspace,n=this.leaf,o=s.getLeaf("tab");await o.setViewState({type:z,active:!0,state:{focusId:k,folderOverride:t===i?null:t}}),s.setActiveLeaf(o,{focus:!0}),s.revealLeaf(o),pt(s,o,n)}cmdOpenInEditor(e){if(e){this.openFileAtEnd(e.file);return}let t=this.getActionTargets();if(t.length)for(let i of t)i.file&&this.openFileAtEnd(i.file)}cmdOpenParentInEditor(){let e=this.tree.get(this.focusId);if(!e?.file){new F.Notice("No focused parent to open.");return}this.openFileAtEnd(e.file)}async openFileAtEnd(e){let t=this.app.workspace,i=this.leaf,s=t.getLeaf("tab");await s.openFile(e,{active:!0}),t.setActiveLeaf(s,{focus:!0}),t.revealLeaf(s),pt(t,s,i);let o=s.view?.editor;o&&requestAnimationFrame(()=>{try{let a=o.lastLine(),l=o.getLine(a)?.length??0;o.setCursor({line:a,ch:l}),o.scrollIntoView({from:{line:a,ch:l},to:{line:a,ch:l}},!0),o.focus()}catch{}})}cmdSelectAll(){if(this.currentChildren.length!==0){this.selection.clear();for(let e of this.currentChildren)this.selection.add(e.id);this.firstSelectedId=this.currentChildren[0].id,this.lastSelected=this.currentChildren[this.currentChildren.length-1].id,this.cursorIdx=this.currentChildren.length-1,this.render()}}async cmdTogglePin(){let e=this.getActionTargets();if(e.length===0){let n=this.tree.get(this.focusId);n?.file&&(e=[n])}if(e.length===0){new F.Notice("Nothing to pin.");return}let t=e.some(n=>!this.plugin.isPinned({folder:this.noteFolder,id:n.id})),i=0,s=0;for(let n of e){let o={folder:this.noteFolder,id:n.id};t?this.plugin.isPinned(o)||(await this.plugin.pinNote(o),i++):this.plugin.isPinned(o)&&(await this.plugin.unpinNote(o),s++)}i>0?new F.Notice(`Pinned ${i} note${i===1?"":"s"} to sidebar.`):s>0&&new F.Notice(`Unpinned ${s} note${s===1?"":"s"} from sidebar.`)}isListPinned(e){let t=this.tree.get(e);if(!t?.file)return!1;let i=this.listPinnedState.get(t.file.path);return i?i.pinned:this.app.metadataCache.getFileCache(t.file)?.frontmatter?.listPinned===!0}listPinnedAt(e){let t=this.tree.get(e);if(!t?.file)return 0;let i=this.listPinnedState.get(t.file.path);if(i)return i.at;let s=this.app.metadataCache.getFileCache(t.file)?.frontmatter?.listPinnedAt,n=typeof s=="string"?Date.parse(s):NaN;return Number.isNaN(n)?0:n}hoistListPinned(e,t){let i=this.tree.getChildren(e).map(l=>l.id);if(!i.some(l=>this.isListPinned(l)))return t;let s=new Set(t),n=[...t,...i.filter(l=>!s.has(l))],o=n.filter(l=>this.isListPinned(l)).sort((l,c)=>this.listPinnedAt(l)-this.listPinnedAt(c)),a=n.filter(l=>!this.isListPinned(l));return[...o,...a]}async cmdToggleListPin(){let e=this.getActionTargets();if(e.length===0){let a=this.tree.get(this.focusId);a?.file&&(e=[a])}if(e.length===0){new F.Notice("Nothing to pin.");return}let t=e.some(a=>!this.isListPinned(a.id)),i=[],s=new Date().toISOString(),n=[];for(let a of e){if(!a.file)continue;let l=this.app.metadataCache.getFileCache(a.file)?.frontmatter;i.push({path:a.file.path,listPinned:l?.listPinned,listPinnedAt:l?.listPinnedAt});let c=t?typeof l?.listPinnedAt=="string"&&Date.parse(l.listPinnedAt)||Date.parse(s):0;this.listPinnedState.set(a.file.path,{pinned:t,at:c}),await this.app.fileManager.processFrontMatter(a.file,d=>{t?(d.listPinned=!0,d.listPinnedAt||(d.listPinnedAt=s)):(delete d.listPinned,delete d.listPinnedAt)}),n.push(a.id)}let o=this.noteFolder;this.tree.rebuild(o),this.render(),this.plugin.notifications.show({message:t?`Pinned ${n.length} to top of list`:`Unpinned ${n.length} from list`,kind:"success",category:"edit",affectedIds:n,folder:o}),this.plugin.getUndoStack(o).push({label:t?`Pin to top (${n.length})`:`Unpin from top (${n.length})`,undo:async()=>{for(let a of i){let l=this.app.vault.getAbstractFileByPath(a.path);if(!l)continue;await this.app.fileManager.processFrontMatter(l,h=>{a.listPinned===void 0?delete h.listPinned:h.listPinned=a.listPinned,a.listPinnedAt===void 0?delete h.listPinnedAt:h.listPinnedAt=a.listPinnedAt});let c=a.listPinned===!0,d=typeof a.listPinnedAt=="string"&&Date.parse(a.listPinnedAt)||0;this.listPinnedState.set(a.path,{pinned:c,at:d})}this.tree.rebuild(o),this.render()}})}async cmdToggleComplete(){let e=this.getActionTargets();if(e.length===0){let a=this.tree.get(this.focusId);a?.file&&(e=[a])}if(e.length===0){new F.Notice("Nothing to toggle.");return}let i=e.some(a=>!this.isCompleted(a)),s=[],n=[];for(let a of e){if(!a.file)continue;let l=this.isCompleted(a);s.push({id:a.id,path:a.file.path,was:l}),l!==i&&(await this.app.fileManager.processFrontMatter(a.file,c=>{i?c.completed=!0:delete c.completed}),this.completedState.set(a.file.path,i),n.push(a.id))}if(this.render(),n.length>0){await this.log.append({type:i?"complete":"uncomplete",id:n[0],payload:{ids:n,count:n.length}});let a=n.map(l=>this.tree.get(l)).filter(l=>!!l);this.plugin.notifications.show({message:this.bulkActionMessage({verb:i?"Marked complete":"Unmarked",nodes:a}),kind:"success",category:i?"complete":"uncomplete",affectedIds:n,folder:this.noteFolder})}let o=this.noteFolder;this.plugin.getUndoStack(o).push({label:`${i?"Mark complete":"Unmark complete"} (${e.length})`,undo:async()=>{let a=[];for(let l of s){let c=this.app.vault.getAbstractFileByPath(l.path);c&&(await this.app.fileManager.processFrontMatter(c,d=>{l.was?d.completed=!0:delete d.completed}),n.includes(l.id)&&a.push(l.id))}a.length>0&&await this.log.append({type:i?"uncomplete":"complete",id:a[0],payload:{ids:a,count:a.length,undo:!0}}),this.tree.rebuild(o),this.render()},redo:async()=>{for(let a of s){let l=this.app.vault.getAbstractFileByPath(a.path);l&&await this.app.fileManager.processFrontMatter(l,c=>{i?c.completed=!0:delete c.completed})}n.length>0&&await this.log.append({type:i?"complete":"uncomplete",id:n[0],payload:{ids:n,count:n.length,redo:!0}}),this.tree.rebuild(o),this.render()}})}isCompleted(e){if(!e.file)return!1;let t=this.completedState.get(e.file.path);return t!==void 0?t:!!this.app.metadataCache.getFileCache(e.file)?.frontmatter?.completed}taggedFromFm(e){return e?Vt(e,"task")||e.task===!0:!1}cmdSetDue(){let e=this.getActionTargets();if(e.length===0){let a=this.tree.get(this.focusId);a?.file&&(e=[a])}if(e.length===0){new F.Notice("Nothing to schedule.");return}let t=e[0],i=t.file?this.app.metadataCache.getFileCache(t.file)?.frontmatter:null,s=i&&(typeof i.due=="string"||typeof i.due=="number")?String(i.due):null,n=this.plugin.collectKnownAuthors(),o=Mt(i??{});new Ke(this.app,s,a=>{this.applyDue(e,a.iso,a.assignees)},{knownAuthors:n,currentAssignees:o,quickAdjusts:this.plugin.settings.dueQuickAdjusts}).open()}cmdSnooze(e){let t;if(e)t=[e];else if(t=this.getActionTargets(),t.length===0){let o=this.tree.get(this.focusId);o?.file&&(t=[o])}if(t.length===0){new F.Notice("Nothing to snooze.");return}let i=t[0],s=i.file?this.app.metadataCache.getFileCache(i.file)?.frontmatter:null,n=s&&(typeof s.due=="string"||typeof s.due=="number")?String(s.due):null;new Ke(this.app,n,o=>{this.applyDue(t,o.iso,[],!0)},{title:"Snooze \u2014 reschedule",hideAssignees:!0,quickAdjusts:this.plugin.settings.dueQuickAdjusts}).open()}async applyDue(e,t,i=[],s=!1){let n=[],o=[],a=this.authorship.currentAuthorLink();for(let d of i)await this.plugin.ensureAuthorStubFor(this.noteFolder,d.id,d.name);let l=i.map(d=>this.plugin.authorRefFor(this.noteFolder,d.id,d.name));for(let d of e){if(!d.file)continue;let h=this.app.metadataCache.getFileCache(d.file)?.frontmatter,g=this.isTaskTagged(d);n.push({id:d.id,path:d.file.path,due:h?.due,task:h?.task,assignedTo:h?.assignedTo,assignedBy:h?.assignedBy,wasTagged:g}),await this.app.fileManager.processFrontMatter(d.file,p=>{t===null?delete p.due:(p.due=t,p.task=!0),!s&&(l.length>0?(p.assignedTo=l,a&&(p.assignedBy=a.link),p.task=!0):(delete p.assignedTo,delete p.assignedBy))});let u=t!==null||l.length>0;this.taskTaggedState.set(d.file.path,u||g),o.push(d.id)}if(this.render(),o.length>0){let d=o.map(h=>this.tree.get(h)).filter(h=>!!h);this.plugin.notifications.show({message:this.bulkActionMessage({verb:t===null?"Cleared due date":`Due ${ct(Date.parse(t),this.plugin.settings)}`,nodes:d}),kind:"success",category:"edit",affectedIds:o,folder:this.noteFolder})}let c=this.noteFolder;this.plugin.getUndoStack(c).push({label:t===null?`Clear due date (${e.length})`:`Set due date (${e.length})`,undo:async()=>{for(let d of n){let h=this.app.vault.getAbstractFileByPath(d.path);h&&(await this.app.fileManager.processFrontMatter(h,g=>{d.due===void 0?delete g.due:g.due=d.due,d.task===void 0?delete g.task:g.task=d.task,d.assignedTo===void 0?delete g.assignedTo:g.assignedTo=d.assignedTo,d.assignedBy===void 0?delete g.assignedBy:g.assignedBy=d.assignedBy}),this.taskTaggedState.set(d.path,d.wasTagged))}this.tree.rebuild(c),this.render()}})}cmdAssign(){let e=this.getActionTargets();if(e.length===0){let a=this.tree.get(this.focusId);a?.file&&(e=[a])}if(e.length===0){new F.Notice("Nothing to assign.");return}let t=e[0],i=t.file?this.app.metadataCache.getFileCache(t.file)?.frontmatter:null,s=i&&(typeof i.due=="string"||typeof i.due=="number")?String(i.due):null,n=this.plugin.collectKnownAuthors(),o=Mt(i??{});new Ke(this.app,s,a=>{this.applyDue(e,a.iso,a.assignees)},{knownAuthors:n,currentAssignees:o,title:"Assign / schedule task"}).open()}isTask(e){return e.file?this.isTaskTagged(e)?!0:this.app.metadataCache.getFileCache(e.file)?.frontmatter?.completed!==void 0:!1}async cmdToggleTask(){let e=this.getActionTargets();if(e.length===0){let a=this.tree.get(this.focusId);a?.file&&(e=[a])}if(e.length===0){new F.Notice("Nothing to toggle.");return}let t=e.some(a=>!this.isTaskTagged(a)),i=[],s=[];for(let a of e){if(!a.file)continue;let l=this.isTaskTagged(a),c=this.app.metadataCache.getFileCache(a.file)?.frontmatter;if(i.push({id:a.id,path:a.file.path,tags:c?.tags,completed:c?.completed,task:c?.task,wasTagged:l}),l===t)continue;let d=!1;await this.app.fileManager.processFrontMatter(a.file,h=>{t?(Ln(h,"task"),h.completed===void 0&&(h.completed=!1),d=h.completed===!0,delete h.task):(qn(h,"task"),delete h.completed,delete h.task,d=!1)}),this.completedState.set(a.file.path,d),this.taskTaggedState.set(a.file.path,t),s.push(a.id)}if(this.render(),s.length>0){let a=t?"marked as task":"unmarked as task",l;if(s.length===1){let c=this.tree.get(s[0]);l=`"${c&&this.titleForNode(c).trim()||"(untitled)"}" ${a}`}else l=`${s.length} notes ${a}`;this.plugin.notifications.show({message:l,kind:"success",category:"edit",affectedIds:s,folder:this.noteFolder})}let n=this.noteFolder,o=async()=>{for(let a of i){let l=this.app.vault.getAbstractFileByPath(a.path);l&&(await this.app.fileManager.processFrontMatter(l,c=>{a.tags===void 0?delete c.tags:c.tags=a.tags,a.completed===void 0?delete c.completed:c.completed=a.completed,a.task===void 0?delete c.task:c.task=a.task}),this.completedState.set(a.path,!!a.completed),this.taskTaggedState.set(a.path,a.wasTagged))}this.tree.rebuild(n),this.render()};this.plugin.getUndoStack(n).push({label:`${t?"Mark task":"Unmark task"} (${e.length})`,undo:o,redo:async()=>{for(let a of i){let l=this.app.vault.getAbstractFileByPath(a.path);if(!l)continue;let c=!1;await this.app.fileManager.processFrontMatter(l,d=>{t?(Ln(d,"task"),d.completed===void 0&&(d.completed=!1),c=d.completed===!0,delete d.task):(qn(d,"task"),delete d.completed,delete d.task,c=!1)}),this.completedState.set(a.path,c),this.taskTaggedState.set(a.path,t)}this.tree.rebuild(n),this.render()}})}async toggleCompletedForNode(e){if(!e.file)return;let t=e.file.path,i=this.isCompleted(e);await this.app.fileManager.processFrontMatter(e.file,n=>{n.completed=!i}),this.completedState.set(t,!i),await this.log.append({type:i?"uncomplete":"complete",id:e.id}),this.render();let s=this.noteFolder;this.plugin.getUndoStack(s).push({label:i?"Mark incomplete":"Mark complete",undo:async()=>{let n=this.app.vault.getAbstractFileByPath(t);n&&(await this.app.fileManager.processFrontMatter(n,o=>{o.completed=i}),this.tree.rebuild(s),this.render())}})}isTaskTagged(e){if(!e.file)return!1;let t=this.taskTaggedState.get(e.file.path);if(t!==void 0)return t;let i=this.app.metadataCache.getFileCache(e.file)?.frontmatter;return this.taggedFromFm(i)}colorForNode(e){if(!e.file)return null;let t=this.app.metadataCache.getFileCache(e.file)?.frontmatter?.color;if(typeof t!="string")return null;let i=t.trim();return/^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(i)?i:null}inheritedColorForNode(e){let t=e,i=0,s=new Set;for(;t&&t.id!==k&&!s.has(t.id);){s.add(t.id);let n=this.colorForNode(t);if(n)return{hex:n,depth:i};t=t.parent?this.tree.get(t.parent):void 0,i+=1}return null}isDescendant(e,t){let i=this.tree.get(e),s=new Set;for(;i&&i.parent&&!s.has(i.id);){if(i.parent===t)return!0;s.add(i.id),i=this.tree.get(i.parent)}return!1}async moveAcrossThenReorder(e,t,i,s){let n=[],o=new Set;for(let v of e){let Q=this.tree.get(v);Q?.file&&(n.push({id:v,path:Q.file.path,oldParent:Q.parent}),o.add(Q.parent??k))}o.add(t);let a=this.authorship.collectAuthorIds(e.map(v=>this.tree.get(v)).filter(v=>!!v)),l=this.noteFolder,c={};for(let v of o)c[v]=this.order.getOrder(l,v).slice();for(let v of n){let Q=this.app.vault.getAbstractFileByPath(v.path);Q&&(await this.app.fileManager.processFrontMatter(Q,S=>{S.parent=t}),this.fmSync.scheduleParentChange(v.id,v.oldParent,t),await this.log.append({type:"parent_change",id:v.id,payload:{from:v.oldParent,to:t,reason:"drag"}}),this.order.removeChild(l,v.id))}this.tree.rebuild(l);let d=this.tree.getChildren(t).map(v=>v.id),h=new Set(e),g=d.filter(v=>!h.has(v)),u=g.indexOf(i);u<0&&(u=g.length),s==="after"&&(u+=1);let p=[...g.slice(0,u),...e.filter(v=>!!this.tree.get(v)),...g.slice(u)];this.order.setOrder(l,t,p),await this.order.save(l),await this.forceManualMode(t),await this.log.append({type:"reorder",id:t,payload:{dir:"drag-cross",parent:t,ids:e,count:e.length}});let f=this.focusId===t,I=f?e[0]:t,m=f?e.slice():[t];f?this.pendingFocusIds=m:(this.selection.clear(),this.cursorIdx=-1,this.pendingFocusIds=m),this.tree.rebuild(l),this.render({kind:"follow-cursor"});let C=this.selectionGuardKey,B=()=>{if(this.selectionGuardKey!==C||this.selection.has(I))return;let v=this.currentChildren.findIndex(Q=>Q.id===I);v<0||(this.selection.add(I),this.cursorIdx=v,this.render({kind:"follow-cursor"}))};setTimeout(B,120),setTimeout(B,400);let w=e.map(v=>this.tree.get(v)).filter(v=>!!v),y=this.tree.get(t),E=y?this.titleForNode(y):"(root)";this.plugin.notifications.show({message:this.bulkActionMessage({verb:"Reparented",nodes:w,destination:`\u2192 "${E}"`}),kind:"success",category:"move",affectedIds:e,affectedAuthorIds:a,folder:l,actions:t===k?[]:[{label:"Jump to parent",onClick:()=>this.navigateTo(t)}]}),this.plugin.getUndoStack(l).push({label:`Move + reorder (${e.length})`,undo:async()=>{for(let v of n){let Q=this.app.vault.getAbstractFileByPath(v.path);Q&&(await this.app.fileManager.processFrontMatter(Q,S=>{v.oldParent===null||v.oldParent===void 0?S.parent=k:S.parent=v.oldParent}),await this.log.append({type:"parent_change",id:v.id,payload:{from:t,to:v.oldParent,reason:"drag-undo"}}))}for(let[v,Q]of Object.entries(c))if(Q.length===0){let S=this.order.cache.get(l)??{};delete S[v],this.order.cache.set(l,S)}else this.order.setOrder(l,v,Q);await this.order.save(l),this.pendingFocusIds=e.slice(),this.selection.clear(),this.cursorIdx=-1,this.tree.rebuild(l),this.render()},redo:async()=>{for(let v of n){let Q=this.app.vault.getAbstractFileByPath(v.path);Q&&(await this.app.fileManager.processFrontMatter(Q,S=>{S.parent=t}),this.order.removeChild(l,v.id))}this.order.setOrder(l,t,p),await this.order.save(l),this.pendingFocusIds=e.slice(),this.focusId!==t&&(this.selection.clear(),this.cursorIdx=-1),this.tree.rebuild(l),this.render()}})}async reorderToTarget(e,t,i){let s=this.tree.get(t);if(!s)return;let n=e.map(C=>this.tree.get(C)).filter(C=>!!C&&!!C.file);if(n.length===0)return;if(n.some(C=>C.id===t)){this.plugin.notifications.show({message:"Can't move a note into itself.",kind:"warning",category:"move",folder:this.noteFolder});return}if(i==="into"){for(let C of n)if(this.isDescendant(t,C.id)){this.plugin.notifications.show({message:`Can't nest "${this.titleForNode(C)}" under one of its own descendants \u2014 that would create a cycle.`,kind:"warning",category:"move",folder:this.noteFolder});return}}let o=i==="into"?t:s.parent??k;if(n.some(C=>(C.parent??k)!==o)){let C=X(),B=async()=>{i==="into"?await this.moveAcrossThenReorder(n.map(w=>w.id),o,"","after"):await this.moveAcrossThenReorder(n.map(w=>w.id),o,t,i)};if(C.confirmCrossParentDrag){let w=this.titleForNode(s),y=n.length,E=i==="into"?"Nest":"Move",v=i==="into"?"as children of":"under";new ie(this.app,i==="into"?"Nest under target?":"Move under different parent?",`${E} ${y} note${y===1?"":"s"} ${v} "${w}"? Their parent will change.`,E,Q=>{Q&&B()}).open()}else await B();return}let l=o,c=n.map(C=>C.id),d=this.tree.getChildren(l).map(C=>C.id),h=new Set(c),g=d.filter(C=>!h.has(C)),u=g.indexOf(t);if(u<0)return;i==="after"&&(u+=1);let p=[...g.slice(0,u),...c,...g.slice(u)];if(Kn(p,d))return;let f=this.noteFolder,I=this.order.getOrder(f,l).slice();this.order.setOrder(f,l,p),await this.order.save(f),await this.forceManualMode(l),await this.log.append({type:"reorder",id:l,payload:{dir:"drag",parent:l,ids:c,count:c.length}}),this.pendingFocusIds=c.slice(),this.tree.rebuild(f),this.render();let m=c.map(C=>this.tree.get(C)).filter(C=>!!C);this.plugin.notifications.show({message:this.bulkActionMessage({verb:"Reordered",nodes:m}),kind:"success",category:"reorder",affectedIds:c,folder:f}),this.plugin.getUndoStack(f).push({label:`Reorder (drag, ${c.length})`,undo:async()=>{if(I.length===0){let C=this.order.cache.get(f)??{};delete C[l],this.order.cache.set(f,C)}else this.order.setOrder(f,l,I);await this.order.save(f),await this.log.append({type:"reorder",id:l,payload:{dir:"undo",parent:l,ids:c,count:c.length}}),this.pendingFocusIds=c.slice(),this.tree.rebuild(f),this.render()},redo:async()=>{this.order.setOrder(f,l,p),await this.order.save(f),await this.log.append({type:"reorder",id:l,payload:{dir:"redo:drag",parent:l,ids:c,count:c.length}}),this.pendingFocusIds=c.slice(),this.tree.rebuild(f),this.render()}})}cmdMoveUp(){this.reorderSelection("up")}cmdMoveDown(){this.reorderSelection("down")}cmdMoveToTop(){this.reorderSelection("top")}cmdMoveToBottom(){this.reorderSelection("bottom")}async reorderSelection(e){let t=[];if(this.selection.size>0){let h=[...this.selection].map(u=>this.tree.get(u)).filter(u=>!!u&&!!u.file);if(h.length===0)return;if(new Set(h.map(u=>u.parent)).size>1){new F.Notice("Reorder requires a single-parent selection.");return}t=h}else this.cursorIdx>=0&&this.currentChildren[this.cursorIdx]&&(t=[this.currentChildren[this.cursorIdx]]);if(t.length===0){new F.Notice("Nothing to reorder.");return}let i=t[0].parent??k,s=this.tree.getChildren(i).map(h=>h.id);if(s.length===0)return;let n=new Set(t.map(h=>h.id)),o=s.filter(h=>n.has(h));if(o.length===0)return;let a=Ia(s,o,e);if(Kn(a,s))return;let l=this.noteFolder,c=this.order.getOrder(l,i).slice();this.order.setOrder(l,i,a),await this.order.save(l),await this.forceManualMode(i),await this.log.append({type:"reorder",id:i,payload:{dir:e,parent:i,ids:o,count:o.length}}),this.pendingFocusIds=o.slice(),this.tree.rebuild(l),this.render({kind:"follow-cursor"});let d=o.map(h=>this.tree.get(h)).filter(h=>!!h);this.plugin.notifications.show({message:this.bulkActionMessage({verb:"Moved",nodes:d,destination:e}),kind:"success",category:"reorder",affectedIds:o,folder:l}),this.plugin.getUndoStack(l).push({label:`Reorder (${e})`,undo:async()=>{if(c.length===0){let h=this.order.cache.get(l)??{};delete h[i],this.order.cache.set(l,h)}else this.order.setOrder(l,i,c);await this.order.save(l),await this.log.append({type:"reorder",id:i,payload:{dir:"undo",parent:i,ids:o,count:o.length}}),this.pendingFocusIds=o.slice(),this.tree.rebuild(l),this.render()},redo:async()=>{this.order.setOrder(l,i,a),await this.order.save(l),await this.log.append({type:"reorder",id:i,payload:{dir:`redo:${e}`,parent:i,ids:o,count:o.length}}),this.pendingFocusIds=o.slice(),this.tree.rebuild(l),this.render()}})}async cmdDelete(){let e=this.getActionTargets();if(e.length===0){let C=this.tree.get(this.focusId);C?.file&&(e=[C])}if(e.length===0){new F.Notice("Nothing selected to delete.");return}let t=(this.noteFolder??"").replace(/\/+$/,""),i=(this.plugin.settings.folderEncPrefs??{})[t]??{},s=i.trashEncryptContent??this.plugin.settings.encryptTrash??!1,n=i.trashHandling?i.trashHandling==="obsidian":this.plugin.settings.encryptTrashFollowObsidian??!1;if(s&&!n){if(!this.plugin.encryption?.isConfigured?.()){new F.Notice("\u201CEncrypt items sent to trash\u201D is ON but encryption isn't set up (Settings \u2192 Stashpad \u2192 Encryption). Nothing was deleted.");return}await this.secureDeleteSources(e);return}if(e.length===1){await this.deleteNote(e[0]);return}let o=[],a=new Set,l=C=>{if(!a.has(C.id)){a.add(C.id);for(let B of this.tree.getChildren(C.id))l(B);o.push(C)}};for(let C of e)l(C);let c=o.filter(C=>!!C.file),d=await Promise.all(c.map(C=>this.app.vault.read(C.file))),h=[];for(let C=0;C<c.length;C++){let B=c[C];h.push(...this.extractAttachments(this.stripFrontmatter(d[C])));let w=this.app.metadataCache.getFileCache(B.file)?.frontmatter;if(Array.isArray(w?.attachments))for(let y of w.attachments)typeof y=="string"&&y.trim()&&h.push(Nt(y))}let g=[...new Set(h)],u=o.length-e.length,p=async C=>{let B=await this.snapshotNotes(o,C),w=0;if(C)for(let D of g){let b=this.app.metadataCache.getFirstLinkpathDest(D,"");if(b)try{await this.app.fileManager.trashFile(b),await this.log.append({type:"attachment_remove",id:k,payload:{path:b.path}}),this.plugin.notifications.show({message:`Deleted attachment "${b.name}"`,kind:"warning",category:"attachment",affectedPaths:[b.path],folder:this.noteFolder}),w+=1}catch{}}let y=new Set;for(let D of o)D.parent&&y.add(D.parent);let E=this.authorship.collectAuthorIds(o),v=new Set(e.map(D=>D.id)),Q=this.currentChildren.map((D,b)=>v.has(D.id)?b:-1).filter(D=>D>=0),S=Q.length>0?Q[0]:-1,x=null;if(S>=0){for(let D=S+1;D<this.currentChildren.length;D++)if(!v.has(this.currentChildren[D].id)){x=this.currentChildren[D].id;break}if(!x){for(let D=S-1;D>=0;D--)if(!v.has(this.currentChildren[D].id)){x=this.currentChildren[D].id;break}}}for(let D of o)if(D.file){try{await this.app.fileManager.trashFile(D.file)}catch{}await this.log.append({type:"delete",id:D.id,payload:{path:D.file.path,attachmentsRemoved:C?g:[]}})}this.selection.clear(),this.cursorIdx=-1,x&&(this.pendingFocusIds=[x]),this.tree.rebuild(this.noteFolder);for(let D of y)o.some(b=>b.id===D)||this.fmSync.scheduleParentOfDeleted(D);this.render({kind:"follow-cursor"});let H=w>0?` with ${w} attachment${w===1?"":"s"}`:"",G=this.noteFolder,N=e.map(D=>D.id),U=!1,R=async()=>{U||(U=!0,this.selection.clear(),this.cursorIdx=-1,await this.restoreSnapshots(B,N.slice()))};this.plugin.notifications.show({message:this.bulkActionMessage({verb:"Deleted",nodes:e,suffix:H.trim()||void 0}),kind:"warning",category:"delete",duration:0,affectedIds:e.map(D=>D.id),affectedAuthorIds:E,folder:this.noteFolder,actions:[{label:"Undo delete",onClick:()=>void R()}]}),this.plugin.getUndoStack(G).push({label:`Delete ${e.length} note${e.length===1?"":"s"}`,undo:async()=>{await R()},redo:async()=>{this.selection.clear(),this.cursorIdx=-1,U=!1,await this.trashNotesAndAttachments(B)}}),this.focusView()},f=X(),I=f.confirmBulkDelete,m=g.length>0&&f.confirmAttachmentDelete;if(!I&&!m){await p(!1);return}new ei(this.app,`${e.length} selected note${e.length===1?"":"s"}`,u,g.length,m,p).open()}async cmdSplit(e){let t=e??this.resolveActionTarget();if(!t?.file){new F.Notice("Pick a note to split.");return}let i=t.file,s=await this.app.vault.read(i),n=this.stripFrontmatter(s).replace(/\s+$/,""),o=n.split(/\r?\n/);if(n.trim().length<2){new F.Notice("Note is too short to split.");return}let a=s,l=i.path,c=async(h,g,u)=>{if(!h.trim()||!g.trim()){new F.Notice("Split would leave one part empty.");return}try{let p=s.startsWith("---")?s.slice(0,s.indexOf(` ----`,3)+4):"",f=p+(p?` +Error: ${t.message}`,kind:"error",category:"system",duration:0,affectedPaths:[e],folder:this.noteFolder})}))}async ensureHomeNote(){let e=this.noteFolder,t=`${e}/${this.buildHomeFilename(e)}`,i=this.app.vault.getMarkdownFiles().filter(o=>o.path.startsWith(e+"/"));for(let o of i){if(this.app.metadataCache.getFileCache(o)?.frontmatter?.id!==F)continue;if(o.path===t||this.app.vault.getAbstractFileByPath(t))return o;try{await this.app.fileManager.renameFile(o,t);let c=this.app.vault.getAbstractFileByPath(t);if(c instanceof k.TFile)return c}catch(c){console.warn("[Stashpad] home note rename failed; keeping legacy path",c)}return o}let s=new Date().toISOString(),n=["---",`id: ${F}`,"parent: null",`created: ${s}`,"attachments: []","---","","# Home","","This is your Stashpad home note. Edit me freely \u2014 everything else nests below.",""].join(` +`);return this.app.vault.create(t,n)}buildHomeFilename(e){return`Home-${(e.split("/").filter(Boolean).pop()??"Stashpad").replace(/[^A-Za-z0-9_-]+/g,"-").replace(/-+/g,"-").replace(/^-+|-+$/g,"")||"Stashpad"}.md`}async migrateNullParents(){let e=this.noteFolder,t=this.app.vault.getMarkdownFiles().filter(i=>i.path.startsWith(e+"/"));for(let i of t){let s=this.app.metadataCache.getFileCache(i)?.frontmatter,n=s?.id;if(!n||n===F)continue;let o=s?.parent;(o==null||o===""||o==="null")&&(await this.app.fileManager.processFrontMatter(i,a=>{a.parent=F}),await this.log.append({type:"parent_change",id:n,payload:{from:null,to:F,reason:"migration"}}))}}async openInNewStashpadTab(e){let t=this.app.workspace,i=this.leaf,s=t.getLeaf("tab");await s.setViewState({type:z,active:!0,state:{focusId:e,timeFilter:this.timeFilter,folderOverride:this.folderOverride}}),t.setActiveLeaf(s,{focus:!0}),t.revealLeaf(s),gt(t,s,i,n=>this.plugin.registerEvent(n))}async openNoteInNewTab(e,t,i){let s=(e||"").trim().replace(/^\/+|\/+$/g,"");if(!s||!t)return;let n=(this.plugin.settings.folder||"Stashpad").trim().replace(/^\/+|\/+$/g,"")||"Stashpad",o=this.app.workspace,a=this.leaf,l=o.getLeaf("tab");await l.setViewState({type:z,active:!0,state:{focusId:t,folderOverride:s===n?null:s,...i?{cursorId:i}:{}}}),o.setActiveLeaf(l,{focus:!0}),o.revealLeaf(l),gt(o,l,a,c=>this.plugin.registerEvent(c))}async openFolderInNewTab(e){let t=(e||"").trim().replace(/^\/+|\/+$/g,"");if(!t)return;let i=(this.plugin.settings.folder||"Stashpad").trim().replace(/^\/+|\/+$/g,"")||"Stashpad",s=this.app.workspace,n=this.leaf,o=s.getLeaf("tab");await o.setViewState({type:z,active:!0,state:{focusId:F,folderOverride:t===i?null:t}}),s.setActiveLeaf(o,{focus:!0}),s.revealLeaf(o),gt(s,o,n,a=>this.plugin.registerEvent(a))}cmdOpenInEditor(e){if(e){this.openFileAtEnd(e.file);return}let t=this.getActionTargets();if(t.length)for(let i of t)i.file&&this.openFileAtEnd(i.file)}cmdOpenParentInEditor(){let e=this.tree.get(this.focusId);if(!e?.file){new k.Notice("No focused parent to open.");return}this.openFileAtEnd(e.file)}async openFileAtEnd(e){let t=this.app.workspace,i=this.leaf,s=t.getLeaf("tab");await s.openFile(e,{active:!0}),t.setActiveLeaf(s,{focus:!0}),t.revealLeaf(s),gt(t,s,i,a=>this.plugin.registerEvent(a));let o=s.view?.editor;o&&requestAnimationFrame(()=>{try{let a=o.lastLine(),l=o.getLine(a)?.length??0;o.setCursor({line:a,ch:l}),o.scrollIntoView({from:{line:a,ch:l},to:{line:a,ch:l}},!0),o.focus()}catch{}})}cmdSelectAll(){if(this.currentChildren.length!==0){this.selection.clear();for(let e of this.currentChildren)this.selection.add(e.id);this.firstSelectedId=this.currentChildren[0].id,this.lastSelected=this.currentChildren[this.currentChildren.length-1].id,this.cursorIdx=this.currentChildren.length-1,this.render()}}async cmdTogglePin(){let e=this.getActionTargets();if(e.length===0){let n=this.tree.get(this.focusId);n?.file&&(e=[n])}if(e.length===0){new k.Notice("Nothing to pin.");return}let t=e.some(n=>!this.plugin.isPinned({folder:this.noteFolder,id:n.id})),i=0,s=0;for(let n of e){let o={folder:this.noteFolder,id:n.id};t?this.plugin.isPinned(o)||(await this.plugin.pinNote(o),i++):this.plugin.isPinned(o)&&(await this.plugin.unpinNote(o),s++)}i>0?new k.Notice(`Pinned ${i} note${i===1?"":"s"} to sidebar.`):s>0&&new k.Notice(`Unpinned ${s} note${s===1?"":"s"} from sidebar.`)}isListPinned(e){let t=this.tree.get(e);if(!t?.file)return!1;let i=this.listPinnedState.get(t.file.path);return i?i.pinned:this.app.metadataCache.getFileCache(t.file)?.frontmatter?.listPinned===!0}listPinnedAt(e){let t=this.tree.get(e);if(!t?.file)return 0;let i=this.listPinnedState.get(t.file.path);if(i)return i.at;let s=this.app.metadataCache.getFileCache(t.file)?.frontmatter?.listPinnedAt,n=typeof s=="string"?Date.parse(s):NaN;return Number.isNaN(n)?0:n}hoistListPinned(e,t){let i=this.tree.getChildren(e).map(l=>l.id);if(!i.some(l=>this.isListPinned(l)))return t;let s=new Set(t),n=[...t,...i.filter(l=>!s.has(l))],o=n.filter(l=>this.isListPinned(l)).sort((l,c)=>this.listPinnedAt(l)-this.listPinnedAt(c)),a=n.filter(l=>!this.isListPinned(l));return[...o,...a]}async cmdToggleListPin(){let e=this.getActionTargets();if(e.length===0){let a=this.tree.get(this.focusId);a?.file&&(e=[a])}if(e.length===0){new k.Notice("Nothing to pin.");return}let t=e.some(a=>!this.isListPinned(a.id)),i=[],s=new Date().toISOString(),n=[];for(let a of e){if(!a.file)continue;let l=this.app.metadataCache.getFileCache(a.file)?.frontmatter;i.push({path:a.file.path,listPinned:l?.listPinned,listPinnedAt:l?.listPinnedAt});let c=t?typeof l?.listPinnedAt=="string"&&Date.parse(l.listPinnedAt)||Date.parse(s):0;this.listPinnedState.set(a.file.path,{pinned:t,at:c}),await this.app.fileManager.processFrontMatter(a.file,d=>{t?(d.listPinned=!0,d.listPinnedAt||(d.listPinnedAt=s)):(delete d.listPinned,delete d.listPinnedAt)}),n.push(a.id)}let o=this.noteFolder;this.tree.rebuild(o),this.render(),this.plugin.notifications.show({message:t?`Pinned ${n.length} to top of list`:`Unpinned ${n.length} from list`,kind:"success",category:"edit",affectedIds:n,folder:o}),this.plugin.getUndoStack(o).push({label:t?`Pin to top (${n.length})`:`Unpin from top (${n.length})`,undo:async()=>{for(let a of i){let l=this.app.vault.getAbstractFileByPath(a.path);if(!l)continue;await this.app.fileManager.processFrontMatter(l,h=>{a.listPinned===void 0?delete h.listPinned:h.listPinned=a.listPinned,a.listPinnedAt===void 0?delete h.listPinnedAt:h.listPinnedAt=a.listPinnedAt});let c=a.listPinned===!0,d=typeof a.listPinnedAt=="string"&&Date.parse(a.listPinnedAt)||0;this.listPinnedState.set(a.path,{pinned:c,at:d})}this.tree.rebuild(o),this.render()}})}async cmdToggleComplete(){let e=this.getActionTargets();if(e.length===0){let l=this.tree.get(this.focusId);l?.file&&(e=[l])}if(e.length===0){new k.Notice("Nothing to toggle.");return}let i=e.some(l=>!this.isCompleted(l)),s=[],n=[],o=[];for(let l of e){if(!l.file)continue;let c=this.isCompleted(l),d=this.app.metadataCache.getFileCache(l.file)?.frontmatter,h=d?.due!=null?String(d.due):null,g={id:l.id,path:l.file.path,was:c,dueBefore:h,rolledTo:null};if(s.push(g),c===i)continue;let f=!1;await this.app.fileManager.processFrontMatter(l.file,p=>{let u=i?sn(p.repeat):null;if(u){let I=p.due!=null?Date.parse(String(p.due)):NaN,m=nn(u,Number.isFinite(I)?I:null,Date.now());p.due=new Date(m).toISOString(),delete p.completed,g.rolledTo=p.due,o.push({title:l.file.basename.replace(/-[a-z0-9]{4,12}$/,"").replace(/-/g," ").trim()||"task",when:m}),f=!0}else i?p.completed=!0:delete p.completed}),this.completedState.set(l.file.path,f?!1:i),n.push(l.id)}this.render();for(let l of o)this.plugin.notifications.show({message:`\u{1F501} Rescheduled \u201C${l.title}\u201D \u2192 ${Xe(l.when,this.plugin.settings)}.`,kind:"success",category:"system",folder:this.noteFolder});if(n.length>0){await this.log.append({type:i?"complete":"uncomplete",id:n[0],payload:{ids:n,count:n.length}});let l=n.map(c=>this.tree.get(c)).filter(c=>!!c);this.plugin.notifications.show({message:this.bulkActionMessage({verb:i?"Marked complete":"Unmarked",nodes:l}),kind:"success",category:i?"complete":"uncomplete",affectedIds:n,folder:this.noteFolder})}let a=this.noteFolder;this.plugin.getUndoStack(a).push({label:`${i?"Mark complete":"Unmark complete"} (${e.length})`,undo:async()=>{let l=[];for(let c of s){let d=this.app.vault.getAbstractFileByPath(c.path);d&&(await this.app.fileManager.processFrontMatter(d,h=>{c.rolledTo!=null?(c.dueBefore!=null?h.due=c.dueBefore:delete h.due,delete h.completed):c.was?h.completed=!0:delete h.completed}),n.includes(c.id)&&l.push(c.id))}l.length>0&&await this.log.append({type:i?"uncomplete":"complete",id:l[0],payload:{ids:l,count:l.length,undo:!0}}),this.tree.rebuild(a),this.render()},redo:async()=>{for(let l of s){let c=this.app.vault.getAbstractFileByPath(l.path);c&&await this.app.fileManager.processFrontMatter(c,d=>{l.rolledTo!=null?(d.due=l.rolledTo,delete d.completed):i?d.completed=!0:delete d.completed})}n.length>0&&await this.log.append({type:i?"complete":"uncomplete",id:n[0],payload:{ids:n,count:n.length,redo:!0}}),this.tree.rebuild(a),this.render()}})}isCompleted(e){if(!e.file)return!1;let t=this.completedState.get(e.file.path);return t!==void 0?t:!!this.app.metadataCache.getFileCache(e.file)?.frontmatter?.completed}taggedFromFm(e){return e?_t(e,"task")||e.task===!0:!1}cmdSetDue(){let e=this.getActionTargets();if(e.length===0){let a=this.tree.get(this.focusId);a?.file&&(e=[a])}if(e.length===0){new k.Notice("Nothing to schedule.");return}let t=e[0],i=t.file?this.app.metadataCache.getFileCache(t.file)?.frontmatter:null,s=i&&(typeof i.due=="string"||typeof i.due=="number")?String(i.due):null,n=this.plugin.collectKnownAuthors(),o=Ut(i??{});new Oe(this.app,s,a=>{this.applyDue(e,a.iso,a.assignees,!1,{repeat:a.repeat,autoDoneAfter:a.autoDoneAfter,remindEvery:a.remindEvery})},{knownAuthors:n,currentAssignees:o,quickAdjusts:this.plugin.settings.dueQuickAdjusts,showRecurrence:e.length===1,currentRepeat:typeof i?.repeat=="string"?i.repeat:"",currentAutoDoneAfter:typeof i?.autoDoneAfter=="string"?i.autoDoneAfter:"",currentRemindEvery:typeof i?.remindEvery=="string"?i.remindEvery:""}).open()}cmdSnooze(e){let t;if(e)t=[e];else if(t=this.getActionTargets(),t.length===0){let o=this.tree.get(this.focusId);o?.file&&(t=[o])}if(t.length===0){new k.Notice("Nothing to snooze.");return}let i=t[0],s=i.file?this.app.metadataCache.getFileCache(i.file)?.frontmatter:null,n=s&&(typeof s.due=="string"||typeof s.due=="number")?String(s.due):null;new Oe(this.app,n,o=>{this.applyDue(t,o.iso,[],!0)},{title:"Snooze \u2014 reschedule",hideAssignees:!0,quickAdjusts:this.plugin.settings.dueQuickAdjusts}).open()}async applyDue(e,t,i=[],s=!1,n){let o=[],a=[],l=this.authorship.currentAuthorLink();for(let h of i)await this.plugin.ensureAuthorStubFor(this.noteFolder,h.id,h.name);let c=i.map(h=>this.plugin.authorRefFor(this.noteFolder,h.id,h.name));for(let h of e){if(!h.file)continue;let g=this.app.metadataCache.getFileCache(h.file)?.frontmatter,f=this.isTaskTagged(h);o.push({id:h.id,path:h.file.path,due:g?.due,task:g?.task,assignedTo:g?.assignedTo,assignedBy:g?.assignedBy,wasTagged:f,repeat:g?.repeat,autoDoneAfter:g?.autoDoneAfter,remindEvery:g?.remindEvery}),await this.app.fileManager.processFrontMatter(h.file,u=>{if(t===null?delete u.due:(u.due=t,u.task=!0),n){let I=(m,C)=>{let B=(C??"").trim();B?(u[m]=B,u.task=!0):delete u[m]};I("repeat",n.repeat),I("autoDoneAfter",n.autoDoneAfter),I("remindEvery",n.remindEvery)}s||(c.length>0?(u.assignedTo=c,l&&(u.assignedBy=l.link),u.task=!0):(delete u.assignedTo,delete u.assignedBy))});let p=t!==null||c.length>0;this.taskTaggedState.set(h.file.path,p||f),a.push(h.id)}if(this.render(),a.length>0){let h=a.map(g=>this.tree.get(g)).filter(g=>!!g);this.plugin.notifications.show({message:this.bulkActionMessage({verb:t===null?"Cleared due date":`Due ${Xe(Date.parse(t),this.plugin.settings)}`,nodes:h}),kind:"success",category:"edit",affectedIds:a,folder:this.noteFolder})}let d=this.noteFolder;this.plugin.getUndoStack(d).push({label:t===null?`Clear due date (${e.length})`:`Set due date (${e.length})`,undo:async()=>{for(let h of o){let g=this.app.vault.getAbstractFileByPath(h.path);g&&(await this.app.fileManager.processFrontMatter(g,f=>{h.due===void 0?delete f.due:f.due=h.due,h.task===void 0?delete f.task:f.task=h.task,h.assignedTo===void 0?delete f.assignedTo:f.assignedTo=h.assignedTo,h.assignedBy===void 0?delete f.assignedBy:f.assignedBy=h.assignedBy,h.repeat===void 0?delete f.repeat:f.repeat=h.repeat,h.autoDoneAfter===void 0?delete f.autoDoneAfter:f.autoDoneAfter=h.autoDoneAfter,h.remindEvery===void 0?delete f.remindEvery:f.remindEvery=h.remindEvery}),this.taskTaggedState.set(h.path,h.wasTagged))}this.tree.rebuild(d),this.render()}})}cmdAssign(){let e=this.getActionTargets();if(e.length===0){let a=this.tree.get(this.focusId);a?.file&&(e=[a])}if(e.length===0){new k.Notice("Nothing to assign.");return}let t=e[0],i=t.file?this.app.metadataCache.getFileCache(t.file)?.frontmatter:null,s=i&&(typeof i.due=="string"||typeof i.due=="number")?String(i.due):null,n=this.plugin.collectKnownAuthors(),o=Ut(i??{});new Oe(this.app,s,a=>{this.applyDue(e,a.iso,a.assignees)},{knownAuthors:n,currentAssignees:o,title:"Assign / schedule task"}).open()}isTask(e){return e.file?this.isTaskTagged(e)?!0:this.app.metadataCache.getFileCache(e.file)?.frontmatter?.completed!==void 0:!1}async cmdToggleTask(){let e=this.getActionTargets();if(e.length===0){let a=this.tree.get(this.focusId);a?.file&&(e=[a])}if(e.length===0){new k.Notice("Nothing to toggle.");return}let t=e.some(a=>!this.isTaskTagged(a)),i=[],s=[];for(let a of e){if(!a.file)continue;let l=this.isTaskTagged(a),c=this.app.metadataCache.getFileCache(a.file)?.frontmatter;if(i.push({id:a.id,path:a.file.path,tags:c?.tags,completed:c?.completed,task:c?.task,wasTagged:l}),l===t)continue;let d=!1;await this.app.fileManager.processFrontMatter(a.file,h=>{t?(eo(h,"task"),h.completed===void 0&&(h.completed=!1),d=h.completed===!0,delete h.task):(to(h,"task"),delete h.completed,delete h.task,d=!1)}),this.completedState.set(a.file.path,d),this.taskTaggedState.set(a.file.path,t),s.push(a.id)}if(this.render(),s.length>0){let a=t?"marked as task":"unmarked as task",l;if(s.length===1){let c=this.tree.get(s[0]);l=`"${c&&this.titleForNode(c).trim()||"(untitled)"}" ${a}`}else l=`${s.length} notes ${a}`;this.plugin.notifications.show({message:l,kind:"success",category:"edit",affectedIds:s,folder:this.noteFolder})}let n=this.noteFolder,o=async()=>{for(let a of i){let l=this.app.vault.getAbstractFileByPath(a.path);l&&(await this.app.fileManager.processFrontMatter(l,c=>{a.tags===void 0?delete c.tags:c.tags=a.tags,a.completed===void 0?delete c.completed:c.completed=a.completed,a.task===void 0?delete c.task:c.task=a.task}),this.completedState.set(a.path,!!a.completed),this.taskTaggedState.set(a.path,a.wasTagged))}this.tree.rebuild(n),this.render()};this.plugin.getUndoStack(n).push({label:`${t?"Mark task":"Unmark task"} (${e.length})`,undo:o,redo:async()=>{for(let a of i){let l=this.app.vault.getAbstractFileByPath(a.path);if(!l)continue;let c=!1;await this.app.fileManager.processFrontMatter(l,d=>{t?(eo(d,"task"),d.completed===void 0&&(d.completed=!1),c=d.completed===!0,delete d.task):(to(d,"task"),delete d.completed,delete d.task,c=!1)}),this.completedState.set(a.path,c),this.taskTaggedState.set(a.path,t)}this.tree.rebuild(n),this.render()}})}async toggleCompletedForNode(e){if(!e.file)return;let t=e.file.path,i=this.isCompleted(e);await this.app.fileManager.processFrontMatter(e.file,n=>{n.completed=!i}),this.completedState.set(t,!i),await this.log.append({type:i?"uncomplete":"complete",id:e.id}),this.render();let s=this.noteFolder;this.plugin.getUndoStack(s).push({label:i?"Mark incomplete":"Mark complete",undo:async()=>{let n=this.app.vault.getAbstractFileByPath(t);n&&(await this.app.fileManager.processFrontMatter(n,o=>{o.completed=i}),this.tree.rebuild(s),this.render())}})}isTaskTagged(e){if(!e.file)return!1;let t=this.taskTaggedState.get(e.file.path);if(t!==void 0)return t;let i=this.app.metadataCache.getFileCache(e.file)?.frontmatter;return this.taggedFromFm(i)}colorForNode(e){if(!e.file)return null;let t=this.app.metadataCache.getFileCache(e.file)?.frontmatter?.color;if(typeof t!="string")return null;let i=t.trim();return/^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(i)?i:null}inheritedColorForNode(e){let t=e,i=0,s=new Set;for(;t&&t.id!==F&&!s.has(t.id);){s.add(t.id);let n=this.colorForNode(t);if(n)return{hex:n,depth:i};t=t.parent?this.tree.get(t.parent):void 0,i+=1}return null}isDescendant(e,t){let i=this.tree.get(e),s=new Set;for(;i&&i.parent&&!s.has(i.id);){if(i.parent===t)return!0;s.add(i.id),i=this.tree.get(i.parent)}return!1}async moveAcrossThenReorder(e,t,i,s){let n=[],o=new Set;for(let E of e){let Q=this.tree.get(E);Q?.file&&(n.push({id:E,path:Q.file.path,oldParent:Q.parent}),o.add(Q.parent??F))}o.add(t);let a=this.authorship.collectAuthorIds(e.map(E=>this.tree.get(E)).filter(E=>!!E)),l=this.noteFolder,c={};for(let E of o)c[E]=this.order.getOrder(l,E).slice();for(let E of n){let Q=this.app.vault.getAbstractFileByPath(E.path);Q&&(await this.app.fileManager.processFrontMatter(Q,S=>{S.parent=t}),this.fmSync.scheduleParentChange(E.id,E.oldParent,t),await this.log.append({type:"parent_change",id:E.id,payload:{from:E.oldParent,to:t,reason:"drag"}}),this.order.removeChild(l,E.id))}this.tree.rebuild(l);let d=this.tree.getChildren(t).map(E=>E.id),h=new Set(e),g=d.filter(E=>!h.has(E)),f=g.indexOf(i);f<0&&(f=g.length),s==="after"&&(f+=1);let p=[...g.slice(0,f),...e.filter(E=>!!this.tree.get(E)),...g.slice(f)];this.order.setOrder(l,t,p),await this.order.save(l),await this.forceManualMode(t),await this.log.append({type:"reorder",id:t,payload:{dir:"drag-cross",parent:t,ids:e,count:e.length}});let u=this.focusId===t,I=u?e[0]:t,m=u?e.slice():[t];u?this.pendingFocusIds=m:(this.selection.clear(),this.cursorIdx=-1,this.pendingFocusIds=m),this.tree.rebuild(l),this.render({kind:"follow-cursor"});let C=this.selectionGuardKey,B=()=>{if(this.selectionGuardKey!==C||this.selection.has(I))return;let E=this.currentChildren.findIndex(Q=>Q.id===I);E<0||(this.selection.add(I),this.cursorIdx=E,this.render({kind:"follow-cursor"}))};setTimeout(B,120),setTimeout(B,400);let w=e.map(E=>this.tree.get(E)).filter(E=>!!E),y=this.tree.get(t),v=y?this.titleForNode(y):"(root)";this.plugin.notifications.show({message:this.bulkActionMessage({verb:"Reparented",nodes:w,destination:`\u2192 "${v}"`}),kind:"success",category:"move",affectedIds:e,affectedAuthorIds:a,folder:l,actions:t===F?[]:[{label:"Jump to parent",onClick:()=>this.navigateTo(t)}]}),this.plugin.getUndoStack(l).push({label:`Move + reorder (${e.length})`,undo:async()=>{for(let E of n){let Q=this.app.vault.getAbstractFileByPath(E.path);Q&&(await this.app.fileManager.processFrontMatter(Q,S=>{E.oldParent===null||E.oldParent===void 0?S.parent=F:S.parent=E.oldParent}),await this.log.append({type:"parent_change",id:E.id,payload:{from:t,to:E.oldParent,reason:"drag-undo"}}))}for(let[E,Q]of Object.entries(c))if(Q.length===0){let S=this.order.cache.get(l)??{};delete S[E],this.order.cache.set(l,S)}else this.order.setOrder(l,E,Q);await this.order.save(l),this.pendingFocusIds=e.slice(),this.selection.clear(),this.cursorIdx=-1,this.tree.rebuild(l),this.render()},redo:async()=>{for(let E of n){let Q=this.app.vault.getAbstractFileByPath(E.path);Q&&(await this.app.fileManager.processFrontMatter(Q,S=>{S.parent=t}),this.order.removeChild(l,E.id))}this.order.setOrder(l,t,p),await this.order.save(l),this.pendingFocusIds=e.slice(),this.focusId!==t&&(this.selection.clear(),this.cursorIdx=-1),this.tree.rebuild(l),this.render()}})}async reorderToTarget(e,t,i){let s=this.tree.get(t);if(!s)return;let n=e.map(C=>this.tree.get(C)).filter(C=>!!C&&!!C.file);if(n.length===0)return;if(n.some(C=>C.id===t)){this.plugin.notifications.show({message:"Can't move a note into itself.",kind:"warning",category:"move",folder:this.noteFolder});return}let o=i==="into"?t:s.parent??F;for(let C of n)if(o===C.id||this.isDescendant(o,C.id)){this.plugin.notifications.show({message:`Can't move "${this.titleForNode(C)}" under itself or one of its own descendants \u2014 that would create a cycle.`,kind:"warning",category:"move",folder:this.noteFolder});return}if(n.some(C=>(C.parent??F)!==o)){let C=X(),B=async()=>{i==="into"?await this.moveAcrossThenReorder(n.map(w=>w.id),o,"","after"):await this.moveAcrossThenReorder(n.map(w=>w.id),o,t,i)};if(C.confirmCrossParentDrag){let w=this.titleForNode(s),y=n.length,v=i==="into"?"Nest":"Move",E=i==="into"?"as children of":"under";new ie(this.app,i==="into"?"Nest under target?":"Move under different parent?",`${v} ${y} note${y===1?"":"s"} ${E} "${w}"? Their parent will change.`,v,Q=>{Q&&B()}).open()}else await B();return}let l=o,c=n.map(C=>C.id),d=this.tree.getChildren(l).map(C=>C.id),h=new Set(c),g=d.filter(C=>!h.has(C)),f=g.indexOf(t);if(f<0)return;i==="after"&&(f+=1);let p=[...g.slice(0,f),...c,...g.slice(f)];if(io(p,d))return;let u=this.noteFolder,I=this.order.getOrder(u,l).slice();this.order.setOrder(u,l,p),await this.order.save(u),await this.forceManualMode(l),await this.log.append({type:"reorder",id:l,payload:{dir:"drag",parent:l,ids:c,count:c.length}}),this.pendingFocusIds=c.slice(),this.tree.rebuild(u),this.render();let m=c.map(C=>this.tree.get(C)).filter(C=>!!C);this.plugin.notifications.show({message:this.bulkActionMessage({verb:"Reordered",nodes:m}),kind:"success",category:"reorder",affectedIds:c,folder:u}),this.plugin.getUndoStack(u).push({label:`Reorder (drag, ${c.length})`,undo:async()=>{if(I.length===0){let C=this.order.cache.get(u)??{};delete C[l],this.order.cache.set(u,C)}else this.order.setOrder(u,l,I);await this.order.save(u),await this.log.append({type:"reorder",id:l,payload:{dir:"undo",parent:l,ids:c,count:c.length}}),this.pendingFocusIds=c.slice(),this.tree.rebuild(u),this.render()},redo:async()=>{this.order.setOrder(u,l,p),await this.order.save(u),await this.log.append({type:"reorder",id:l,payload:{dir:"redo:drag",parent:l,ids:c,count:c.length}}),this.pendingFocusIds=c.slice(),this.tree.rebuild(u),this.render()}})}cmdMoveUp(){this.reorderSelection("up")}cmdMoveDown(){this.reorderSelection("down")}cmdMoveToTop(){this.reorderSelection("top")}cmdMoveToBottom(){this.reorderSelection("bottom")}async reorderSelection(e){let t=[];if(this.selection.size>0){let h=[...this.selection].map(f=>this.tree.get(f)).filter(f=>!!f&&!!f.file);if(h.length===0)return;if(new Set(h.map(f=>f.parent)).size>1){new k.Notice("Reorder requires a single-parent selection.");return}t=h}else this.cursorIdx>=0&&this.currentChildren[this.cursorIdx]&&(t=[this.currentChildren[this.cursorIdx]]);if(t.length===0){new k.Notice("Nothing to reorder.");return}let i=t[0].parent??F,s=this.tree.getChildren(i).map(h=>h.id);if(s.length===0)return;let n=new Set(t.map(h=>h.id)),o=s.filter(h=>n.has(h));if(o.length===0)return;let a=Nr(s,o,e);if(io(a,s))return;let l=this.noteFolder,c=this.order.getOrder(l,i).slice();this.order.setOrder(l,i,a),await this.order.save(l),await this.forceManualMode(i),await this.log.append({type:"reorder",id:i,payload:{dir:e,parent:i,ids:o,count:o.length}}),this.pendingFocusIds=o.slice(),this.tree.rebuild(l),this.render({kind:"follow-cursor"});let d=o.map(h=>this.tree.get(h)).filter(h=>!!h);this.plugin.notifications.show({message:this.bulkActionMessage({verb:"Moved",nodes:d,destination:e}),kind:"success",category:"reorder",affectedIds:o,folder:l}),this.plugin.getUndoStack(l).push({label:`Reorder (${e})`,undo:async()=>{if(c.length===0){let h=this.order.cache.get(l)??{};delete h[i],this.order.cache.set(l,h)}else this.order.setOrder(l,i,c);await this.order.save(l),await this.log.append({type:"reorder",id:i,payload:{dir:"undo",parent:i,ids:o,count:o.length}}),this.pendingFocusIds=o.slice(),this.tree.rebuild(l),this.render()},redo:async()=>{this.order.setOrder(l,i,a),await this.order.save(l),await this.log.append({type:"reorder",id:i,payload:{dir:`redo:${e}`,parent:i,ids:o,count:o.length}}),this.pendingFocusIds=o.slice(),this.tree.rebuild(l),this.render()}})}async cmdDelete(){let e=this.getActionTargets();if(e.length===0){let C=this.tree.get(this.focusId);C?.file&&(e=[C])}if(e.length===0){new k.Notice("Nothing selected to delete.");return}let t=(this.noteFolder??"").replace(/\/+$/,""),i=(this.plugin.settings.folderEncPrefs??{})[t]??{},s=i.trashEncryptContent??this.plugin.settings.encryptTrash??!1,n=i.trashHandling==="obsidian";if(s&&!n){if(!this.plugin.encryption?.isConfigured?.()){new k.Notice("\u201CEncrypt items sent to trash\u201D is ON but encryption isn't set up (Settings \u2192 Stashpad \u2192 Encryption). Nothing was deleted.");return}await this.secureDeleteSources(e);return}if(e.length===1){await this.deleteNote(e[0]);return}let o=[],a=new Set,l=C=>{if(!a.has(C.id)){a.add(C.id);for(let B of this.tree.getChildren(C.id))l(B);o.push(C)}};for(let C of e)l(C);let c=o.filter(C=>!!C.file),d=await Promise.all(c.map(C=>this.app.vault.read(C.file))),h=[];for(let C=0;C<c.length;C++){let B=c[C];h.push(...this.extractAttachments(this.stripFrontmatter(d[C])));let w=this.app.metadataCache.getFileCache(B.file)?.frontmatter;if(Array.isArray(w?.attachments))for(let y of w.attachments)typeof y=="string"&&y.trim()&&h.push(Rt(y))}let g=[...new Set(h)],f=o.length-e.length,p=async C=>{let B=await this.snapshotNotes(o,C),w=0;if(C)for(let D of g){let b=this.app.metadataCache.getFirstLinkpathDest(D,"");if(b)try{await this.app.fileManager.trashFile(b),await this.log.append({type:"attachment_remove",id:F,payload:{path:b.path}}),this.plugin.notifications.show({message:`Deleted attachment "${b.name}"`,kind:"warning",category:"attachment",affectedPaths:[b.path],folder:this.noteFolder}),w+=1}catch{}}let y=new Set;for(let D of o)D.parent&&y.add(D.parent);let v=this.authorship.collectAuthorIds(o),E=new Set(e.map(D=>D.id)),Q=this.currentChildren.map((D,b)=>E.has(D.id)?b:-1).filter(D=>D>=0),S=Q.length>0?Q[0]:-1,x=null;if(S>=0){for(let D=S+1;D<this.currentChildren.length;D++)if(!E.has(this.currentChildren[D].id)){x=this.currentChildren[D].id;break}if(!x){for(let D=S-1;D>=0;D--)if(!E.has(this.currentChildren[D].id)){x=this.currentChildren[D].id;break}}}for(let D of o)if(D.file){try{await this.app.fileManager.trashFile(D.file)}catch{}await this.log.append({type:"delete",id:D.id,payload:{path:D.file.path,attachmentsRemoved:C?g:[]}})}this.selection.clear(),this.cursorIdx=-1,x&&(this.pendingFocusIds=[x]),this.tree.rebuild(this.noteFolder);for(let D of y)o.some(b=>b.id===D)||this.fmSync.scheduleParentOfDeleted(D);this.render({kind:"follow-cursor"});let H=w>0?` with ${w} attachment${w===1?"":"s"}`:"",G=this.noteFolder,N=e.map(D=>D.id),R=!1,U=async()=>{R||(R=!0,this.selection.clear(),this.cursorIdx=-1,await this.restoreSnapshots(B,N.slice()))};this.plugin.notifications.show({message:this.bulkActionMessage({verb:"Deleted",nodes:e,suffix:H.trim()||void 0}),kind:"warning",category:"delete",duration:0,affectedIds:e.map(D=>D.id),affectedAuthorIds:v,folder:this.noteFolder,actions:[{label:"Undo delete",onClick:()=>void U()}]}),this.plugin.getUndoStack(G).push({label:`Delete ${e.length} note${e.length===1?"":"s"}`,undo:async()=>{await U()},redo:async()=>{this.selection.clear(),this.cursorIdx=-1,R=!1,await this.trashNotesAndAttachments(B)}}),this.focusView()},u=X(),I=u.confirmBulkDelete,m=g.length>0&&u.confirmAttachmentDelete;if(!I&&!m){await p(!1);return}new si(this.app,`${e.length} selected note${e.length===1?"":"s"}`,f,g.length,m,p).open()}async cmdSplit(e){let t=e??this.resolveActionTarget();if(!t?.file){new k.Notice("Pick a note to split.");return}let i=t.file,s=await this.app.vault.read(i),n=this.stripFrontmatter(s).replace(/\s+$/,""),o=n.split(/\r?\n/);if(n.trim().length<2){new k.Notice("Note is too short to split.");return}let a=s,l=i.path,c=async(h,g,f)=>{if(!h.trim()||!g.trim()){new k.Notice("Split would leave one part empty.");return}try{let p=s.startsWith("---")?s.slice(0,s.indexOf(` +---`,3)+4):"",u=p+(p?` `:"")+h+` -`;await this.app.vault.modify(i,f);let I=t.parent??k,m=Date.parse(t.created||""),C=Number.isFinite(m)?new Date(m+1).toISOString():new Date().toISOString(),B=await this.createNoteUnder(g,I,{record:!1,createdOverride:C});await this.log.append({type:"rename",id:t.id,payload:{action:"split",into:B,...u}}),this.tree.rebuild(this.noteFolder),this.render(),this.suppressComposerFocusUntil=Date.now()+500,this.viewRoot?.focus({preventScroll:!0}),this.plugin.notifications.show({message:`Split "${this.titleForNode(t)}" into two`,kind:"success",category:"split",affectedIds:[t.id],folder:this.noteFolder});let w=B?this.tree.get(B):void 0,y=w?.file?.path,E=y?await this.app.vault.read(w.file):null,v=this.noteFolder;this.plugin.getUndoStack(v).push({label:"Split note",undo:async()=>{if(y){let S=this.app.vault.getAbstractFileByPath(y);if(S)try{await this.app.fileManager.trashFile(S)}catch{}}let Q=this.app.vault.getAbstractFileByPath(l);Q&&await this.app.vault.modify(Q,a),this.tree.rebuild(v),this.render()},redo:async()=>{let Q=this.app.vault.getAbstractFileByPath(l);Q&&await this.app.vault.modify(Q,f),y&&E&&!await this.app.vault.adapter.exists(y)&&await this.app.vault.create(y,E),this.tree.rebuild(v),this.render()}})}catch(p){new F.Notice(`Stashpad: split failed (${p.message})`),console.error(p)}},d=async h=>{if(!(h.length<2))try{let g=s.startsWith("---")?s.slice(0,s.indexOf(` ----`,3)+4):"",u=h[0].replace(/\s+$/,"");if(!u.trim()){new F.Notice("Split would leave the first part empty.");return}let p=g+(g?` -`:"")+u+` -`;await this.app.vault.modify(i,p);let f=t.parent??k,I=Date.parse(t.created||""),m=Number.isFinite(I)?I:Date.now(),C=[];this.beginBulkRender();try{for(let y=1;y<h.length;y++)await this.createNoteUnder(h[y],f,{record:!1,createdOverride:new Date(m+y).toISOString(),deferRender:!0,collectInto:C})}finally{try{await this.fmSync.flush()}catch{}this.endBulkRender()}await this.log.append({type:"rename",id:t.id,payload:{action:"split-many",parts:h.length}}),this.suppressComposerFocusUntil=Date.now()+500,this.viewRoot?.focus({preventScroll:!0}),this.plugin.notifications.show({message:`Split "${this.titleForNode(t)}" into ${h.length}`,kind:"success",category:"split",affectedIds:[t.id],folder:this.noteFolder});let B=C.slice(),w=this.noteFolder;this.plugin.getUndoStack(w).push({label:`Split note into ${h.length}`,undo:async()=>{for(let{path:E}of B){let v=this.app.vault.getAbstractFileByPath(E);if(v)try{await this.app.fileManager.trashFile(v)}catch{}}let y=this.app.vault.getAbstractFileByPath(l);y&&await this.app.vault.modify(y,a),this.tree.rebuild(w),this.render()},redo:async()=>{let y=this.app.vault.getAbstractFileByPath(l);y&&await this.app.vault.modify(y,p);for(let{path:E,content:v}of B)await this.app.vault.adapter.exists(E)||await this.app.vault.create(E,v);this.tree.rebuild(w),this.render()}})}catch(g){new F.Notice(`Stashpad: split failed (${g.message})`),console.error(g)}};new Ti(this.app,n,async h=>{let g=o.slice(0,h).join(` -`).replace(/\s+$/,""),u=o.slice(h).join(` -`).replace(/^\s+|\s+$/g,"");await c(g,u,{mode:"line",splitAtLine:h})},async h=>{let g=n.slice(0,h).replace(/\s+$/,""),u=n.slice(h).replace(/^\s+|\s+$/g,"");await c(g,u,{mode:"cursor",splitAtChar:h})},async h=>{await d(h)}).open()}cmdOpenInNewStashpadTab(e){let t=e??this.resolveActionTarget();t?.file&&this.openInNewStashpadTab(t.id)}cmdCloneStashpadTab(){let e=this.tree.get(this.focusId);e?.file?this.cmdOpenInNewStashpadTab(e):this.openInNewStashpadTab(this.focusId)}resolveActionTarget(){if(this.cursorIdx>=0&&this.currentChildren[this.cursorIdx])return this.currentChildren[this.cursorIdx];let e=this.tree.get(this.focusId);return e?.file?e:void 0}cmdExportStash(e){return _r(this,e)}cmdExportOkf(e){return tl(this,e)}cmdImportStash(){return nl(this)}processStashFile(e){return zo(this,e)}async createNoteUnder(e,t,i={record:!0}){let s=(i.targetFolder??this.noteFolder).replace(/\/+$/,""),n=s!==this.noteFolder;await this.ensureFolder(s);let o=Z(),a=null;{let I=(this.plugin.settings.noteTemplates??{})[s];if(I){let m=this.app.vault.getAbstractFileByPath(I);if(m&&m.extension==="md")try{let C=await this.app.vault.cachedRead(m),B=this.stripFrontmatter(C);a=this.app.metadataCache.getFileCache(m)?.frontmatter??{},B.includes("{{body}}")?e=B.replace(/\{\{body\}\}/g,e):e.trim()?B.trim()&&(e=`${e} +`;await this.app.vault.modify(i,u);let I=t.parent??F,m=Date.parse(t.created||""),C=Number.isFinite(m)?new Date(m+1).toISOString():new Date().toISOString(),B=await this.createNoteUnder(g,I,{record:!1,createdOverride:C});await this.log.append({type:"rename",id:t.id,payload:{action:"split",into:B,...f}}),this.tree.rebuild(this.noteFolder),this.render(),this.suppressComposerFocusUntil=Date.now()+500,this.viewRoot?.focus({preventScroll:!0}),this.plugin.notifications.show({message:`Split "${this.titleForNode(t)}" into two`,kind:"success",category:"split",affectedIds:[t.id],folder:this.noteFolder});let w=B?this.tree.get(B):void 0,y=w?.file?.path,v=y?await this.app.vault.read(w.file):null,E=this.noteFolder;this.plugin.getUndoStack(E).push({label:"Split note",undo:async()=>{if(y){let S=this.app.vault.getAbstractFileByPath(y);if(S)try{await this.app.fileManager.trashFile(S)}catch{}}let Q=this.app.vault.getAbstractFileByPath(l);Q&&await this.app.vault.modify(Q,a),this.tree.rebuild(E),this.render()},redo:async()=>{let Q=this.app.vault.getAbstractFileByPath(l);Q&&await this.app.vault.modify(Q,u),y&&v&&!await this.app.vault.adapter.exists(y)&&await this.app.vault.create(y,v),this.tree.rebuild(E),this.render()}})}catch(p){new k.Notice(`Stashpad: split failed (${p.message})`),console.error(p)}},d=async h=>{if(!(h.length<2))try{let g=s.startsWith("---")?s.slice(0,s.indexOf(` +---`,3)+4):"",f=h[0].replace(/\s+$/,"");if(!f.trim()){new k.Notice("Split would leave the first part empty.");return}let p=g+(g?` +`:"")+f+` +`;await this.app.vault.modify(i,p);let u=t.parent??F,I=Date.parse(t.created||""),m=Number.isFinite(I)?I:Date.now(),C=[];this.beginBulkRender();try{for(let y=1;y<h.length;y++)await this.createNoteUnder(h[y],u,{record:!1,createdOverride:new Date(m+y).toISOString(),deferRender:!0,collectInto:C})}finally{try{await this.fmSync.flush()}catch{}this.endBulkRender()}await this.log.append({type:"rename",id:t.id,payload:{action:"split-many",parts:h.length}}),this.suppressComposerFocusUntil=Date.now()+500,this.viewRoot?.focus({preventScroll:!0}),this.plugin.notifications.show({message:`Split "${this.titleForNode(t)}" into ${h.length}`,kind:"success",category:"split",affectedIds:[t.id],folder:this.noteFolder});let B=C.slice(),w=this.noteFolder;this.plugin.getUndoStack(w).push({label:`Split note into ${h.length}`,undo:async()=>{for(let{path:v}of B){let E=this.app.vault.getAbstractFileByPath(v);if(E)try{await this.app.fileManager.trashFile(E)}catch{}}let y=this.app.vault.getAbstractFileByPath(l);y&&await this.app.vault.modify(y,a),this.tree.rebuild(w),this.render()},redo:async()=>{let y=this.app.vault.getAbstractFileByPath(l);y&&await this.app.vault.modify(y,p);for(let{path:v,content:E}of B)await this.app.vault.adapter.exists(v)||await this.app.vault.create(v,E);this.tree.rebuild(w),this.render()}})}catch(g){new k.Notice(`Stashpad: split failed (${g.message})`),console.error(g)}};new Ui(this.app,n,async h=>{let g=o.slice(0,h).join(` +`).replace(/\s+$/,""),f=o.slice(h).join(` +`).replace(/^\s+|\s+$/g,"");await c(g,f,{mode:"line",splitAtLine:h})},async h=>{let g=n.slice(0,h).replace(/\s+$/,""),f=n.slice(h).replace(/^\s+|\s+$/g,"");await c(g,f,{mode:"cursor",splitAtChar:h})},async h=>{await d(h)}).open()}cmdOpenInNewStashpadTab(e){let t=e??this.resolveActionTarget();t?.file&&this.openInNewStashpadTab(t.id)}cmdCloneStashpadTab(){let e=this.tree.get(this.focusId);e?.file?this.cmdOpenInNewStashpadTab(e):this.openInNewStashpadTab(this.focusId)}resolveActionTarget(){if(this.cursorIdx>=0&&this.currentChildren[this.cursorIdx])return this.currentChildren[this.cursorIdx];let e=this.tree.get(this.focusId);return e?.file?e:void 0}cmdExportStash(e){return Cl(this,e)}cmdExportOkf(e){return yl(this,e)}cmdImportStash(){return Ql(this)}processStashFile(e){return lr(this,e)}async createNoteUnder(e,t,i={record:!0}){let s=(i.targetFolder??this.noteFolder).replace(/\/+$/,""),n=s!==this.noteFolder;await this.ensureFolder(s);let o=Z(),a=null;{let I=(this.plugin.settings.noteTemplates??{})[s];if(I){let m=this.app.vault.getAbstractFileByPath(I);if(m&&m.extension==="md")try{let C=await this.app.vault.cachedRead(m),B=this.stripFrontmatter(C);a=this.app.metadataCache.getFileCache(m)?.frontmatter??{},B.includes("{{body}}")?e=B.replace(/\{\{body\}\}/g,e):e.trim()?B.trim()&&(e=`${e} -${B}`):e=B}catch(C){console.warn("[Stashpad] template read failed",C)}}}let l=be(e,this.activeStopwords()),c=De(l,o),d=`${s}/${c}`,h=t??this.focusId,g=i.createdOverride??new Date().toISOString(),u=this.extractAttachments(e),p=this.authorship.currentAuthorLink();p&&this.authorship.ensureAuthorFile(p);let f=["---",`id: ${o}`,`parent: ${h}`,`created: ${g}`,`modified: ${g}`];if(p&&f.push(`author: "${p.link.replace(/"/g,'\\"')}"`),u.length>0){f.push("attachments:");for(let I of u)f.push(` - "${I.replace(/"/g,'\\"')}"`)}else f.push("attachments: []");f.push("---",e);try{let I=f.join(` -`);this.plugin.importService.suppress(d,6e4),await ke.timeAsync("write.createNote.file",()=>this.app.vault.create(d,I)),i.collectInto?.push({path:d,content:I});try{let m=this.app.vault.getAbstractFileByPath(d);if(m&&m.extension==="md"){if(!n)this.tree.insertSynthetic({id:o,parent:h,children:[],file:m,created:g}),i.deferRender||this.render(),this.fmSync.scheduleParentChange(o,null,h);else{i.deferRender||this.render();let C=s.split("/").pop()||s,B=(e.split(` -`).find(w=>w.trim())??"note").trim().slice(0,60);i.deferUndo||this.plugin.notifications.show({message:`"${B}" landed in \`${C}\``,kind:"success",category:"create",duration:0,folder:s,affectedIds:[o],actions:[{label:"Jump to note",onClick:()=>{this.switchToFolderAndFocus(s,o)}}]})}if(a)try{await this.app.fileManager.processFrontMatter(m,C=>{for(let[B,w]of Object.entries(a))Ct.includes(B)||Un.includes(B)||C[B]===void 0&&(C[B]=w)})}catch(C){console.warn("[Stashpad] template fm overlay failed",C)}}}catch{}if(await this.log.append({type:"create",id:o,payload:{path:d,parent:h}}),i.record!==!1&&!i.deferUndo){let m=e;this.plugin.getUndoStack(s).push({label:n?"Send note":"Create note",undo:async()=>{let C=this.app.vault.getAbstractFileByPath(d);if(C)try{await this.app.fileManager.trashFile(C)}catch{}if(this.composerDraft=m,this.saveDraft(m),this.recordLastSubmitted(""),this.composerInputEl){this.composerInputEl.value=m;let B=m.length;this.composerInputEl.setSelectionRange(B,B),this.composerInputEl.focus()}n||this.tree.rebuild(this.noteFolder),this.render()},redo:async()=>{await this.app.vault.adapter.exists(d)||await this.app.vault.create(d,I),this.composerDraft="",this.saveDraft(""),this.recordLastSubmitted(m),this.composerInputEl&&(this.composerInputEl.value=""),n||this.tree.rebuild(this.noteFolder),this.render()}})}return o}catch(I){return new F.Notice(`Stashpad: failed to create note (${I.message})`),null}}async createNotesBatch(e,t,i,s,n){let o=e.length,a=[],l=!!i?.targetFolder&&i.targetFolder!==this.noteFolder,c=8,d=null;if(o>=c){let h=new F.Notice("",0),g=h.messageEl;g.empty(),g.createDiv({cls:"stashpad-split-progress-title",text:`Splitting into ${o} notes\u2026`});let p=g.createDiv({cls:"stashpad-split-progress-bar"}).createDiv({cls:"stashpad-split-progress-fill"}),f=g.createDiv({cls:"stashpad-split-progress-label",text:`0 / ${o}`});d={notice:h,fill:p,label:f}}this.beginBulkRender();try{for(let h=0;h<o;h++)if(await this.createNoteUnder(e[h],t,{...i??{},deferRender:!0,deferUndo:!0,collectInto:a}),d){let g=h+1;d.label.setText(`${g} / ${o}`),d.fill.setCssStyles({width:`${Math.round(g/o*100)}%`}),g%5===0&&await new Promise(u=>window.setTimeout(u,0))}}finally{d?.notice.hide();try{await this.fmSync.flush()}catch{}this.endBulkRender()}if(a.length){let h=a.slice();this.plugin.getUndoStack(n).push({label:`Create ${h.length} notes`,undo:async()=>{for(let{path:g}of h){let u=this.app.vault.getAbstractFileByPath(g);if(u)try{await this.app.fileManager.trashFile(u)}catch{}}if(this.composerDraft=s,this.saveDraft(s),this.recordLastSubmitted(""),this.composerInputEl){this.composerInputEl.value=s;let g=s.length;this.composerInputEl.setSelectionRange(g,g),this.composerInputEl.focus()}l||this.tree.rebuild(this.noteFolder),this.render()},redo:async()=>{for(let{path:g,content:u}of h)await this.app.vault.adapter.exists(g)||await this.app.vault.create(g,u);this.composerDraft="",this.saveDraft(""),this.composerInputEl&&(this.composerInputEl.value=""),l||this.tree.rebuild(this.noteFolder),this.render()}})}o>=c&&this.plugin.notifications.show({message:`Created ${a.length} notes${l?` in \`${n.split("/").pop()}\``:""}.`,kind:"success",category:"create"})}extractAttachments(e){let t=[],i=/!\[\[([^\]\|]+)(?:\|[^\]]+)?\]\]/g,s;for(;(s=i.exec(e))!==null;)t.push(s[1]);return t}async ensureFolder(e){if(await this.app.vault.adapter.exists(e)){let t=this.app.vault.getAbstractFileByPath(e);if(t&&!(t instanceof F.TFolder))throw new Error(`${e} exists and is not a folder`);return}try{await this.app.vault.createFolder(e)}catch(t){let i=t?.message??"";if(!/already exists/i.test(i))throw t}}async importAttachment(e){try{let t=await e.arrayBuffer(),i=`${this.noteFolder}/_attachments`;await this.ensureFolder(i);let s=e.name.replace(/[^\w.\-]+/g,"_"),n=Date.now().toString(36),o=`${i}/${ws(s,n)}`;return await this.app.vault.createBinary(o,t),await this.log.append({type:"attachment_add",id:k,payload:{path:o,name:e.name,size:e.size}}),this.plugin.notifications.show({message:`Attached ${e.name}`,kind:"success",category:"attachment",affectedPaths:[o],folder:this.noteFolder}),`![[${o}]]`}catch(t){return new F.Notice(`Stashpad: attachment failed (${t.message})`),null}}claimSelectedAsAuthor(){this.authorship.claimSelectedAsAuthor()}claimFolderAsAuthor(){this.authorship.claimFolderAsAuthor()}claimSelectedWithContributor(){this.authorship.claimSelectedWithContributor()}claimFolderWithContributor(){this.authorship.claimFolderWithContributor()}renderAuthorshipFooter(e,t){if(!t.file)return;let i=this.plugin.settings;if(!i.showAuthor&&!i.showContributors&&!i.showLastEdit)return;let s=this.app.metadataCache.getFileCache(t.file)?.frontmatter??{},n=typeof s.author=="string"?s.author:"",o=Array.isArray(s.contributors)?s.contributors.filter(p=>typeof p=="string"&&p.trim()!==""):[],a=typeof s.modified=="string"?s.modified:typeof s.created=="string"?s.created:"",l=i.showAuthor&&!!n,c=i.showContributors&&o.length>0,d=i.showLastEdit&&!!a;if(!l&&!c&&!d)return;let h=e.createDiv({cls:"stashpad-note-authorship"}),g=(p,f)=>{let I=f.replace(/^\[\[/,"").replace(/\]\]$/,""),m=I.indexOf("|"),C=m>=0?I.slice(0,m):I,B=m>=0?I.slice(m+1):I.split("/").pop()??I,w=p.createEl("a",{cls:"internal-link",text:B});w.setAttribute("data-href",C),w.setAttribute("href",C)},u=[];l&&u.push(p=>{p.createSpan({cls:"stashpad-authorship-label",text:"by "}),g(p,n)}),c&&u.push(p=>{p.createSpan({cls:"stashpad-authorship-label",text:"with "}),o.forEach((f,I)=>{I>0&&p.createSpan({text:", "}),g(p,f)})}),d&&u.push(p=>{p.createSpan({cls:"stashpad-authorship-label",text:"edited "}),p.createSpan({text:this.formatTimeInline(a)})}),u.forEach((p,f)=>{f>0&&h.createSpan({cls:"stashpad-authorship-sep",text:"\xB7"});let I=h.createSpan({cls:"stashpad-authorship-piece"});p(I)}),h.addEventListener("click",p=>this.handleRenderedClick(p,t))}activeStopwords(){return this.plugin.settings.slugStopWords??Ze}scheduleSlugRename(e){let t=this.slugDebouncers.get(e.path);t&&t.cancel(),t=(0,F.debounce)(()=>void this.maybeRenameForSlug(e),3e4),this.slugDebouncers.set(e.path,t),t()}async maybeRenameForSlug(e){let t=yt(e.basename);if(!t||t===k)return;let i=await this.app.vault.cachedRead(e),s=this.stripFrontmatter(i),n=be(s,this.activeStopwords()),o=De(n,t);if(e.name===o)return;let a=e.parent?`${e.parent.path}/${o}`:o;if(this.app.vault.getAbstractFileByPath(a))return;let l=e.path;try{await this.app.fileManager.renameFile(e,a),await this.log.append({type:"rename",id:t,payload:{from:l,to:a}})}catch{}}scheduleAttachmentSync(e){let t=this.attachmentDebouncers.get(e.path);t&&t.cancel(),t=(0,F.debounce)(()=>void this.syncAttachmentsFrontmatter(e),1500),this.attachmentDebouncers.set(e.path,t),t()}async syncAttachmentsFrontmatter(e){let t=await this.app.vault.cachedRead(e),i=this.stripFrontmatter(t),s=this.extractAttachments(i),n=this.app.metadataCache.getFileCache(e)?.frontmatter,a=(Array.isArray(n?.attachments)?n.attachments:[]).filter(d=>typeof d=="string").map(d=>Nt(d));if(a.length===s.length&&a.every((d,h)=>d===s[h]))return;let c=s.map(d=>We(d));await this.app.fileManager.processFrontMatter(e,d=>{d.attachments=c})}stripFrontmatter(e){let t=e.replace(/^\uFEFF/,""),i=t.match(/^\s*---\s*\r?\n[\s\S]*?\r?\n---\s*(?:\r?\n|$)/);return i?t.slice(i[0].length):t}formatTime(e){if(!e)return"";let t=(0,F.moment)(e);if(!t.isValid())return"";let i=X();if(i.useTemplatesFormat){let n=Ls(this.app);if(n)return`${t.format(n.dateFormat)} -${t.format(n.timeFormat)}`}let s=t.valueOf();return`${Xt(s,i)} -${Zt(s,i)}`}formatTimeInline(e){if(!e)return"";let t=(0,F.moment)(e);if(!t.isValid())return"";if(X().useTemplatesFormat){let s=Ls(this.app);if(s){let n=/:ss/.test(s.timeFormat)?s.timeFormat:s.timeFormat.replace(/(:mm)/,"$1:ss");return`${t.format(s.dateFormat)} ${t.format(n)}`}}return`${t.format("YYYY.MM.DD")} ${t.format("HH:mm:ss A")}`}scrollListToBottom(){let e=this.listEl;if(!e)return;if(this.stickToListBottom=!0,e.scrollTop=e.scrollHeight,F.Platform.isMobile){let a=0,l=()=>{!this.stickToListBottom||a>=8||(a++,Date.now()>=this.keyboardTransitionUntil&&(e.scrollTop=e.scrollHeight),window.setTimeout(l,120))};window.setTimeout(l,60);return}this.stickyRowObserver?.disconnect();let t=()=>{if(!this.stickToListBottom){this.stickyRowObserver?.disconnect(),this.stickyRowObserver=null;return}e.scrollTop=e.scrollHeight},i=new ResizeObserver(t);for(let a of Array.from(e.children))a instanceof HTMLElement&&i.observe(a);this.stickyRowObserver=i;let s=performance.now(),n=e.scrollHeight,o=()=>{if(!this.stickToListBottom)return;let a=e.scrollHeight;a!==n&&(e.scrollTop=a,n=a),performance.now()-s<3e4?requestAnimationFrame(o):(this.stickToListBottom=!1,this.stickyRowObserver?.disconnect(),this.stickyRowObserver=null)};requestAnimationFrame(o)}openNoteMenu(e,t){if(!t.file)return;let i=t.file,s=new F.Menu;s.addItem(d=>d.setTitle("Open in new Stashpad tab").setIcon("layout-grid").onClick(()=>{this.openInNewStashpadTab(t.id)})),s.addItem(d=>d.setTitle("Open in editor").setIcon("file-text").onClick(()=>{this.openFileAtEnd(i)})),s.addItem(d=>d.setTitle("Focus in Stashpad").setIcon("arrow-right").onClick(()=>this.navigateTo(t.id))),s.addSeparator(),s.addItem(d=>d.setTitle("Split note\u2026").setIcon("split").onClick(()=>void this.cmdSplit(t))),s.addItem(d=>d.setTitle("Copy text").setIcon("copy").onClick(()=>{l(),this.cmdCopy()})),s.addItem(d=>d.setTitle("Clone (duplicate / copy)").setIcon("files").onClick(()=>{this.selection.has(t.id)||(this.selection.clear(),this.selection.add(t.id),this.lastSelected=t.id),this.cmdClone()})),s.addItem(d=>d.setTitle("Fork into a separate note\u2026").setIcon("git-branch").onClick(()=>{this.selection.has(t.id)||(this.selection.clear(),this.selection.add(t.id),this.lastSelected=t.id),this.cmdForkNote()})),s.addItem(d=>d.setTitle("Export to .stash").setIcon("package").onClick(()=>{this.selection.has(t.id)||(this.selection.clear(),this.selection.add(t.id),this.lastSelected=t.id),this.cmdExportStash()})),this.plugin.settings.okfEnabled&&s.addItem(d=>d.setTitle("Export as OKF\u2026").setIcon("book-marked").onClick(()=>{this.selection.has(t.id)||(this.selection.clear(),this.selection.add(t.id),this.lastSelected=t.id),this.cmdExportOkf()})),this.plugin.encryption?.isConfigured?.()&&s.addItem(d=>d.setTitle("Encrypt (lock) note + children").setIcon("lock").onClick(async()=>{let h=this.order.getOrder(this.noteFolder,t.parent??k),g=h.indexOf(t.id),u=g>0?h[g-1]:null;await this.plugin.lockNoteSubtree(this.noteFolder,t.id,u)&&this.render()})),s.addSeparator(),s.addItem(d=>d.setTitle("Move to\u2026").setIcon("move").onClick(()=>this.cmdMovePicker())),s.addItem(d=>d.setTitle("Move to Home").setIcon("home").onClick(async()=>{await this.changeParent(t,k),this.plugin.settings.autoNavOnMoveOut&&this.focusId!==k&&this.navigateTo(k)}));let n={folder:this.noteFolder,id:t.id},o=this.plugin.isPinned(n);s.addItem(d=>d.setTitle(o?"Unpin from sidebar":"Pin to sidebar").setIcon(o?"pin-off":"pin").onClick(async()=>{o?await this.plugin.unpinNote(n):await this.plugin.pinNote(n)}));let a=this.isListPinned(t.id);s.addItem(d=>d.setTitle(a?"Unpin from top of list":"Pin to top of list").setIcon(a?"pin-off":"arrow-up-to-line").onClick(()=>{this.selection.has(t.id)||(this.selection.clear(),this.selection.add(t.id),this.lastSelected=t.id),this.cmdToggleListPin()})),s.addItem(d=>d.setTitle("Set color\u2026").setIcon("palette").onClick(()=>{this.selection.has(t.id)||(this.selection.clear(),this.selection.add(t.id),this.lastSelected=t.id),this.cmdSetColor()}));let l=()=>{this.selection.has(t.id)||(this.selection.clear(),this.selection.add(t.id),this.lastSelected=t.id)},c=d=>{let h=this.isTask(t);if(h){let g=this.isCompleted(t);d.addItem(u=>u.setTitle(g?"Mark incomplete":"Mark complete").setIcon(g?"circle":"check-circle").onClick(()=>{l(),this.cmdToggleComplete()}))}else d.addItem(g=>g.setTitle("Mark complete").setIcon("check-circle").onClick(()=>{l(),this.toggleCompletedForNode(t)})),d.addItem(g=>g.setTitle("Turn into task").setIcon("check-square").onClick(()=>{l(),this.cmdToggleTask()}));d.addItem(g=>g.setTitle("Assign / schedule\u2026").setIcon("user-plus").onClick(()=>{l(),this.cmdAssign()})),h&&(d.addItem(g=>g.setTitle("Snooze (reschedule)\u2026").setIcon("alarm-clock").onClick(()=>{l(),this.cmdSnooze(t)})),d.addItem(g=>g.setTitle("Remove from tasks").setIcon("square").onClick(()=>{l(),this.cmdToggleTask()})))};s.addItem(d=>{d.setTitle("Task").setIcon("check-square");let h=typeof d.setSubmenu=="function"?d.setSubmenu():null;h&&typeof h.addItem=="function"?c(h):d.onClick(()=>this.openCommandPalette())}),s.addSeparator(),s.addItem(d=>d.setTitle("Delete").setIcon("trash").onClick(async()=>{this.selection.has(t.id)||(this.selection.clear(),this.selection.add(t.id),this.lastSelected=t.id),await this.cmdDelete()})),s.addSeparator(),s.addItem(d=>d.setTitle("More commands\u2026").setIcon("terminal").onClick(()=>this.openCommandPalette())),s.showAtMouseEvent(e)}async deleteNote(e){if(!e.file)return;let t=[],i=p=>{for(let f of this.tree.getChildren(p.id))i(f),t.push(f)};i(e);let s=[...t,e],n=s.filter(p=>!!p.file),o=await Promise.all(n.map(p=>this.app.vault.cachedRead(p.file))),a=[];for(let p=0;p<n.length;p++){let f=n[p];a.push(...this.extractAttachments(this.stripFrontmatter(o[p])));let I=this.app.metadataCache.getFileCache(f.file)?.frontmatter;if(Array.isArray(I?.attachments))for(let m of I.attachments)typeof m=="string"&&m.trim()&&a.push(Nt(m))}let l=[...new Set(a)],c=this.authorship.collectAuthorIds(s),d=async p=>{let f=await this.snapshotNotes(s,p),I=0;if(p)for(let x of l){let H=this.app.metadataCache.getFirstLinkpathDest(x,"");if(H)try{await this.app.fileManager.trashFile(H),await this.log.append({type:"attachment_remove",id:k,payload:{path:H.path}}),this.plugin.notifications.show({message:`Deleted attachment "${H.name}"`,kind:"warning",category:"attachment",affectedPaths:[H.path],folder:this.noteFolder}),I+=1}catch{}}let m=new Set;for(let x of s)x.parent&&m.add(x.parent);let C=this.currentChildren.findIndex(x=>x.id===e.id),B=null;if(C>=0){for(let x=C+1;x<this.currentChildren.length;x++)if(this.currentChildren[x].id!==e.id){B=this.currentChildren[x].id;break}if(!B){for(let x=C-1;x>=0;x--)if(this.currentChildren[x].id!==e.id){B=this.currentChildren[x].id;break}}}for(let x of s)if(x.file){try{await this.app.fileManager.trashFile(x.file)}catch{}await this.log.append({type:"delete",id:x.id,payload:{path:x.file.path,attachmentsRemoved:p?l:[]}})}this.selection.clear(),this.cursorIdx=-1,B&&(this.pendingFocusIds=[B]),this.tree.rebuild(this.noteFolder),this.render({kind:"follow-cursor"});for(let x of m)s.some(H=>H.id===x)||this.fmSync.scheduleParentOfDeleted(x);let w=this.noteFolder,y=`Delete "${this.titleForNode(e)}"`,E=e.id,v=I>0?` with ${I} attachment${I===1?"":"s"}`:"",Q=!1,S=async()=>{Q||(Q=!0,this.selection.clear(),this.cursorIdx=-1,await this.restoreSnapshots(f,[E]))};this.plugin.notifications.show({message:`Deleted "${this.titleForNode(e)}"${v}`,kind:"warning",category:"delete",duration:0,affectedIds:[e.id],affectedAuthorIds:c,folder:this.noteFolder,actions:[{label:"Undo delete",onClick:()=>void S()}]}),this.plugin.getUndoStack(w).push({label:y,undo:async()=>{await S()},redo:async()=>{this.selection.clear(),this.cursorIdx=-1,Q=!1,await this.trashNotesAndAttachments(f)}})},h=X(),g=t.length>0&&h.confirmBulkDelete,u=l.length>0&&h.confirmAttachmentDelete;if(!g&&!u){await d(!1),this.focusView();return}new ei(this.app,this.titleForNode(e),t.length,l.length,u,async p=>{await d(p),this.focusView()}).open()}async cmdSwapWithParent(){let e=this.cursorIdx>=0?this.currentChildren[this.cursorIdx]:null;if(!e?.file){new F.Notice("Pick a note first (move the cursor onto it).");return}if(!e.parent||e.parent===k){new F.Notice("Already at Home \u2014 no parent to swap with.");return}let t=this.tree.get(e.parent);if(!t?.file){new F.Notice("Couldn't find the parent note.");return}let i=t.parent??k,s=this.titleForNode(e),n=this.titleForNode(t),o=this.tree.getChildren(t.id).filter(l=>l.id!==e.id).length,a=[`"${n}" becomes a child of "${s}".`];o>0&&a.push(`${o} sibling${o===1?"":"s"} move with it.`),new ie(this.app,"Swap notes?",a.join(` +${B}`):e=B}catch(C){console.warn("[Stashpad] template read failed",C)}}}let l=De(e,this.activeStopwords()),c=xe(l,o),d=`${s}/${c}`,h=t??this.focusId,g=i.createdOverride??new Date().toISOString(),f=this.extractAttachments(e),p=this.authorship.currentAuthorLink();p&&this.authorship.ensureAuthorFile(p);let u=["---",`id: ${o}`,`parent: ${h}`,`created: ${g}`,`modified: ${g}`];if(p&&u.push(`author: "${p.link.replace(/"/g,'\\"')}"`),f.length>0){u.push("attachments:");for(let I of f)u.push(` - "${I.replace(/"/g,'\\"')}"`)}else u.push("attachments: []");u.push("---",e);try{let I=u.join(` +`);this.plugin.importService.suppress(d,6e4),await Se.timeAsync("write.createNote.file",()=>this.app.vault.create(d,I)),i.collectInto?.push({path:d,content:I});try{let m=this.app.vault.getAbstractFileByPath(d);if(m&&m.extension==="md"){if(!n)this.tree.insertSynthetic({id:o,parent:h,children:[],file:m,created:g}),i.deferRender||this.render(),this.fmSync.scheduleParentChange(o,null,h);else{i.deferRender||this.render();let C=s.split("/").pop()||s,B=(e.split(` +`).find(w=>w.trim())??"note").trim().slice(0,60);i.deferUndo||this.plugin.notifications.show({message:`"${B}" landed in \`${C}\``,kind:"success",category:"create",duration:0,folder:s,affectedIds:[o],actions:[{label:"Jump to note",onClick:()=>{this.switchToFolderAndFocus(s,o)}}]})}if(a)try{await this.app.fileManager.processFrontMatter(m,C=>{for(let[B,w]of Object.entries(a))Ve.includes(B)||Zn.includes(B)||C[B]===void 0&&(C[B]=w)})}catch(C){console.warn("[Stashpad] template fm overlay failed",C)}}}catch{}if(await this.log.append({type:"create",id:o,payload:{path:d,parent:h}}),i.record!==!1&&!i.deferUndo){let m=e;this.plugin.getUndoStack(s).push({label:n?"Send note":"Create note",undo:async()=>{let C=this.app.vault.getAbstractFileByPath(d);if(C)try{await this.app.fileManager.trashFile(C)}catch{}if(this.composerDraft=m,this.saveDraft(m),this.recordLastSubmitted(""),this.composerInputEl){this.composerInputEl.value=m;let B=m.length;this.composerInputEl.setSelectionRange(B,B),this.composerInputEl.focus()}n||this.tree.rebuild(this.noteFolder),this.render()},redo:async()=>{await this.app.vault.adapter.exists(d)||await this.app.vault.create(d,I),this.composerDraft="",this.saveDraft(""),this.recordLastSubmitted(m),this.composerInputEl&&(this.composerInputEl.value=""),n||this.tree.rebuild(this.noteFolder),this.render()}})}return o}catch(I){return new k.Notice(`Stashpad: failed to create note (${I.message})`),null}}async createNotesBatch(e,t,i,s,n){let o=e.length,a=[],l=!!i?.targetFolder&&i.targetFolder!==this.noteFolder,c=8,d=null;if(o>=c){let h=new k.Notice("",0),g=h.messageEl;g.empty(),g.createDiv({cls:"stashpad-split-progress-title",text:`Splitting into ${o} notes\u2026`});let p=g.createDiv({cls:"stashpad-split-progress-bar"}).createDiv({cls:"stashpad-split-progress-fill"}),u=g.createDiv({cls:"stashpad-split-progress-label",text:`0 / ${o}`});d={notice:h,fill:p,label:u}}this.beginBulkRender();try{for(let h=0;h<o;h++)if(await this.createNoteUnder(e[h],t,{...i??{},deferRender:!0,deferUndo:!0,collectInto:a}),d){let g=h+1;d.label.setText(`${g} / ${o}`),d.fill.setCssStyles({width:`${Math.round(g/o*100)}%`}),g%5===0&&await new Promise(f=>window.setTimeout(f,0))}}finally{d?.notice.hide();try{await this.fmSync.flush()}catch{}this.endBulkRender()}if(a.length){let h=a.slice();this.plugin.getUndoStack(n).push({label:`Create ${h.length} notes`,undo:async()=>{for(let{path:g}of h){let f=this.app.vault.getAbstractFileByPath(g);if(f)try{await this.app.fileManager.trashFile(f)}catch{}}if(this.composerDraft=s,this.saveDraft(s),this.recordLastSubmitted(""),this.composerInputEl){this.composerInputEl.value=s;let g=s.length;this.composerInputEl.setSelectionRange(g,g),this.composerInputEl.focus()}l||this.tree.rebuild(this.noteFolder),this.render()},redo:async()=>{for(let{path:g,content:f}of h)await this.app.vault.adapter.exists(g)||await this.app.vault.create(g,f);this.composerDraft="",this.saveDraft(""),this.composerInputEl&&(this.composerInputEl.value=""),l||this.tree.rebuild(this.noteFolder),this.render()}})}o>=c&&this.plugin.notifications.show({message:`Created ${a.length} notes${l?` in \`${n.split("/").pop()}\``:""}.`,kind:"success",category:"create"})}extractAttachments(e){let t=[],i=/!\[\[([^\]\|]+)(?:\|[^\]]+)?\]\]/g,s;for(;(s=i.exec(e))!==null;)t.push(s[1]);return t}async ensureFolder(e){if(await this.app.vault.adapter.exists(e)){let t=this.app.vault.getAbstractFileByPath(e);if(t&&!(t instanceof k.TFolder))throw new Error(`${e} exists and is not a folder`);return}try{await this.app.vault.createFolder(e)}catch(t){let i=t?.message??"";if(!/already exists/i.test(i))throw t}}async importAttachment(e){try{let t=await e.arrayBuffer(),i=`${this.noteFolder}/_attachments`;await this.ensureFolder(i);let s=e.name.replace(/[^\w.\-]+/g,"_"),n=Date.now().toString(36),o=`${i}/${Ns(s,n)}`;return await this.app.vault.createBinary(o,t),await this.log.append({type:"attachment_add",id:F,payload:{path:o,name:e.name,size:e.size}}),this.plugin.notifications.show({message:`Attached ${e.name}`,kind:"success",category:"attachment",affectedPaths:[o],folder:this.noteFolder}),`![[${o}]]`}catch(t){return new k.Notice(`Stashpad: attachment failed (${t.message})`),null}}claimSelectedAsAuthor(){this.authorship.claimSelectedAsAuthor()}claimFolderAsAuthor(){this.authorship.claimFolderAsAuthor()}claimSelectedWithContributor(){this.authorship.claimSelectedWithContributor()}claimFolderWithContributor(){this.authorship.claimFolderWithContributor()}renderAuthorshipFooter(e,t){if(!t.file)return;let i=this.plugin.settings;if(!i.showAuthor&&!i.showContributors&&!i.showLastEdit)return;let s=this.app.metadataCache.getFileCache(t.file)?.frontmatter??{},n=typeof s.author=="string"?s.author:"",o=Array.isArray(s.contributors)?s.contributors.filter(p=>typeof p=="string"&&p.trim()!==""):[],a=typeof s.modified=="string"?s.modified:typeof s.created=="string"?s.created:"",l=i.showAuthor&&!!n,c=i.showContributors&&o.length>0,d=i.showLastEdit&&!!a;if(!l&&!c&&!d)return;let h=e.createDiv({cls:"stashpad-note-authorship"}),g=(p,u)=>{let I=u.replace(/^\[\[/,"").replace(/\]\]$/,""),m=I.indexOf("|"),C=m>=0?I.slice(0,m):I,B=m>=0?I.slice(m+1):I.split("/").pop()??I,w=p.createEl("a",{cls:"internal-link",text:B});w.setAttribute("data-href",C),w.setAttribute("href",C)},f=[];l&&f.push(p=>{p.createSpan({cls:"stashpad-authorship-label",text:"by "}),g(p,n)}),c&&f.push(p=>{p.createSpan({cls:"stashpad-authorship-label",text:"with "}),o.forEach((u,I)=>{I>0&&p.createSpan({text:", "}),g(p,u)})}),d&&f.push(p=>{p.createSpan({cls:"stashpad-authorship-label",text:"edited "}),p.createSpan({text:this.formatTimeInline(a)})}),f.forEach((p,u)=>{u>0&&h.createSpan({cls:"stashpad-authorship-sep",text:"\xB7"});let I=h.createSpan({cls:"stashpad-authorship-piece"});p(I)}),h.addEventListener("click",p=>this.handleRenderedClick(p,t))}activeStopwords(){return this.plugin.settings.slugStopWords??je}scheduleSlugRename(e){let t=this.slugDebouncers.get(e.path);t&&t.cancel(),t=(0,k.debounce)(()=>void this.maybeRenameForSlug(e),3e4),this.slugDebouncers.set(e.path,t),t()}async maybeRenameForSlug(e){let t=wt(e.basename);if(!t||t===F)return;let i=await this.app.vault.cachedRead(e),s=this.stripFrontmatter(i),n=De(s,this.activeStopwords()),o=xe(n,t);if(e.name===o)return;let a=e.parent?`${e.parent.path}/${o}`:o;if(this.app.vault.getAbstractFileByPath(a))return;let l=e.path;try{await this.app.fileManager.renameFile(e,a),await this.log.append({type:"rename",id:t,payload:{from:l,to:a}})}catch{}}scheduleAttachmentSync(e){let t=this.attachmentDebouncers.get(e.path);t&&t.cancel(),t=(0,k.debounce)(()=>void this.syncAttachmentsFrontmatter(e),1500),this.attachmentDebouncers.set(e.path,t),t()}async syncAttachmentsFrontmatter(e){let t=await this.app.vault.cachedRead(e),i=this.stripFrontmatter(t),s=this.extractAttachments(i),n=this.app.metadataCache.getFileCache(e)?.frontmatter,a=(Array.isArray(n?.attachments)?n.attachments:[]).filter(d=>typeof d=="string").map(d=>Rt(d));if(a.length===s.length&&a.every((d,h)=>d===s[h]))return;let c=s.map(d=>We(d));await this.app.fileManager.processFrontMatter(e,d=>{d.attachments=c})}stripFrontmatter(e){let t=e.replace(/^\uFEFF/,""),i=t.match(/^\s*---\s*\r?\n[\s\S]*?\r?\n---\s*(?:\r?\n|$)/);return i?t.slice(i[0].length):t}formatTime(e){if(!e)return"";let t=(0,k.moment)(e);if(!t.isValid())return"";let i=X();if(i.useTemplatesFormat){let n=js(this.app);if(n)return`${t.format(n.dateFormat)} +${t.format(n.timeFormat)}`}let s=t.valueOf();return`${Bt(s,i)} +${ei(s,i)}`}formatTimeInline(e){if(!e)return"";let t=(0,k.moment)(e);if(!t.isValid())return"";if(X().useTemplatesFormat){let s=js(this.app);if(s){let n=/:ss/.test(s.timeFormat)?s.timeFormat:s.timeFormat.replace(/(:mm)/,"$1:ss");return`${t.format(s.dateFormat)} ${t.format(n)}`}}return`${t.format("YYYY.MM.DD")} ${t.format("HH:mm:ss A")}`}scrollListToBottom(){let e=this.listEl;if(!e)return;if(this.stickToListBottom=!0,e.scrollTop=e.scrollHeight,k.Platform.isMobile){let a=0,l=()=>{!this.stickToListBottom||a>=8||(a++,Date.now()>=this.keyboardTransitionUntil&&(e.scrollTop=e.scrollHeight),window.setTimeout(l,120))};window.setTimeout(l,60);return}this.stickyRowObserver?.disconnect();let t=()=>{if(!this.stickToListBottom){this.stickyRowObserver?.disconnect(),this.stickyRowObserver=null;return}e.scrollTop=e.scrollHeight},i=new ResizeObserver(t);for(let a of Array.from(e.children))a instanceof HTMLElement&&i.observe(a);this.stickyRowObserver=i;let s=performance.now(),n=e.scrollHeight,o=()=>{if(!this.stickToListBottom)return;let a=e.scrollHeight;a!==n&&(e.scrollTop=a,n=a),performance.now()-s<3e4?requestAnimationFrame(o):(this.stickToListBottom=!1,this.stickyRowObserver?.disconnect(),this.stickyRowObserver=null)};requestAnimationFrame(o)}openNoteMenu(e,t){if(!t.file)return;let i=t.file,s=new k.Menu;s.addItem(d=>d.setTitle("Open in new Stashpad tab").setIcon("layout-grid").onClick(()=>{this.openInNewStashpadTab(t.id)})),s.addItem(d=>d.setTitle("Open in editor").setIcon("file-text").onClick(()=>{this.openFileAtEnd(i)})),s.addItem(d=>d.setTitle("Focus in Stashpad").setIcon("arrow-right").onClick(()=>this.navigateTo(t.id))),s.addSeparator(),s.addItem(d=>d.setTitle("Split note\u2026").setIcon("split").onClick(()=>void this.cmdSplit(t))),s.addItem(d=>d.setTitle("Copy text").setIcon("copy").onClick(()=>{l(),this.cmdCopy()})),s.addItem(d=>d.setTitle("Clone (duplicate / copy)").setIcon("files").onClick(()=>{this.selection.has(t.id)||(this.selection.clear(),this.selection.add(t.id),this.lastSelected=t.id),this.cmdClone()})),s.addItem(d=>d.setTitle("Fork into a separate note\u2026").setIcon("git-branch").onClick(()=>{this.selection.has(t.id)||(this.selection.clear(),this.selection.add(t.id),this.lastSelected=t.id),this.cmdForkNote()})),s.addItem(d=>d.setTitle("Export to .stash").setIcon("package").onClick(()=>{this.selection.has(t.id)||(this.selection.clear(),this.selection.add(t.id),this.lastSelected=t.id),this.cmdExportStash()})),this.plugin.settings.okfEnabled&&s.addItem(d=>d.setTitle("Export as OKF\u2026").setIcon("book-marked").onClick(()=>{this.selection.has(t.id)||(this.selection.clear(),this.selection.add(t.id),this.lastSelected=t.id),this.cmdExportOkf()})),this.plugin.encryption?.isConfigured?.()&&s.addItem(d=>d.setTitle("Encrypt (lock) note + children").setIcon("lock").onClick(async()=>{let h=this.order.getOrder(this.noteFolder,t.parent??F),g=h.indexOf(t.id),f=g>0?h[g-1]:null;await this.plugin.lockNoteSubtree(this.noteFolder,t.id,f)&&this.render()})),s.addSeparator(),s.addItem(d=>d.setTitle("Move to\u2026").setIcon("move").onClick(()=>this.cmdMovePicker())),s.addItem(d=>d.setTitle("Move to Home").setIcon("home").onClick(async()=>{await this.changeParent(t,F),this.plugin.settings.autoNavOnMoveOut&&this.focusId!==F&&this.navigateTo(F)}));let n={folder:this.noteFolder,id:t.id},o=this.plugin.isPinned(n);s.addItem(d=>d.setTitle(o?"Unpin from sidebar":"Pin to sidebar").setIcon(o?"pin-off":"pin").onClick(async()=>{o?await this.plugin.unpinNote(n):await this.plugin.pinNote(n)}));let a=this.isListPinned(t.id);s.addItem(d=>d.setTitle(a?"Unpin from top of list":"Pin to top of list").setIcon(a?"pin-off":"arrow-up-to-line").onClick(()=>{this.selection.has(t.id)||(this.selection.clear(),this.selection.add(t.id),this.lastSelected=t.id),this.cmdToggleListPin()})),s.addItem(d=>d.setTitle("Set color\u2026").setIcon("palette").onClick(()=>{this.selection.has(t.id)||(this.selection.clear(),this.selection.add(t.id),this.lastSelected=t.id),this.cmdSetColor()}));let l=()=>{this.selection.has(t.id)||(this.selection.clear(),this.selection.add(t.id),this.lastSelected=t.id)},c=d=>{let h=this.isTask(t);if(h){let g=this.isCompleted(t);d.addItem(f=>f.setTitle(g?"Mark incomplete":"Mark complete").setIcon(g?"circle":"check-circle").onClick(()=>{l(),this.cmdToggleComplete()}))}else d.addItem(g=>g.setTitle("Mark complete").setIcon("check-circle").onClick(()=>{l(),this.toggleCompletedForNode(t)})),d.addItem(g=>g.setTitle("Turn into task").setIcon("check-square").onClick(()=>{l(),this.cmdToggleTask()}));d.addItem(g=>g.setTitle("Assign / schedule\u2026").setIcon("user-plus").onClick(()=>{l(),this.cmdAssign()})),h&&(d.addItem(g=>g.setTitle("Snooze (reschedule)\u2026").setIcon("alarm-clock").onClick(()=>{l(),this.cmdSnooze(t)})),d.addItem(g=>g.setTitle("Remove from tasks").setIcon("square").onClick(()=>{l(),this.cmdToggleTask()})))};s.addItem(d=>{d.setTitle("Task").setIcon("check-square");let h=typeof d.setSubmenu=="function"?d.setSubmenu():null;h&&typeof h.addItem=="function"?c(h):d.onClick(()=>this.openCommandPalette())}),s.addSeparator(),s.addItem(d=>d.setTitle("Delete").setIcon("trash").onClick(async()=>{this.selection.has(t.id)||(this.selection.clear(),this.selection.add(t.id),this.lastSelected=t.id),await this.cmdDelete()})),s.addSeparator(),s.addItem(d=>d.setTitle("More commands\u2026").setIcon("terminal").onClick(()=>this.openCommandPalette())),s.showAtMouseEvent(e)}async deleteNote(e){if(!e.file)return;let t=[],i=p=>{for(let u of this.tree.getChildren(p.id))i(u),t.push(u)};i(e);let s=[...t,e],n=s.filter(p=>!!p.file),o=await Promise.all(n.map(p=>this.app.vault.cachedRead(p.file))),a=[];for(let p=0;p<n.length;p++){let u=n[p];a.push(...this.extractAttachments(this.stripFrontmatter(o[p])));let I=this.app.metadataCache.getFileCache(u.file)?.frontmatter;if(Array.isArray(I?.attachments))for(let m of I.attachments)typeof m=="string"&&m.trim()&&a.push(Rt(m))}let l=[...new Set(a)],c=this.authorship.collectAuthorIds(s),d=async p=>{let u=await this.snapshotNotes(s,p),I=0;if(p)for(let x of l){let H=this.app.metadataCache.getFirstLinkpathDest(x,"");if(H)try{await this.app.fileManager.trashFile(H),await this.log.append({type:"attachment_remove",id:F,payload:{path:H.path}}),this.plugin.notifications.show({message:`Deleted attachment "${H.name}"`,kind:"warning",category:"attachment",affectedPaths:[H.path],folder:this.noteFolder}),I+=1}catch{}}let m=new Set;for(let x of s)x.parent&&m.add(x.parent);let C=this.currentChildren.findIndex(x=>x.id===e.id),B=null;if(C>=0){for(let x=C+1;x<this.currentChildren.length;x++)if(this.currentChildren[x].id!==e.id){B=this.currentChildren[x].id;break}if(!B){for(let x=C-1;x>=0;x--)if(this.currentChildren[x].id!==e.id){B=this.currentChildren[x].id;break}}}for(let x of s)if(x.file){try{await this.app.fileManager.trashFile(x.file)}catch{}await this.log.append({type:"delete",id:x.id,payload:{path:x.file.path,attachmentsRemoved:p?l:[]}})}this.selection.clear(),this.cursorIdx=-1,B&&(this.pendingFocusIds=[B]),this.tree.rebuild(this.noteFolder),this.render({kind:"follow-cursor"});for(let x of m)s.some(H=>H.id===x)||this.fmSync.scheduleParentOfDeleted(x);let w=this.noteFolder,y=`Delete "${this.titleForNode(e)}"`,v=e.id,E=I>0?` with ${I} attachment${I===1?"":"s"}`:"",Q=!1,S=async()=>{Q||(Q=!0,this.selection.clear(),this.cursorIdx=-1,await this.restoreSnapshots(u,[v]))};this.plugin.notifications.show({message:`Deleted "${this.titleForNode(e)}"${E}`,kind:"warning",category:"delete",duration:0,affectedIds:[e.id],affectedAuthorIds:c,folder:this.noteFolder,actions:[{label:"Undo delete",onClick:()=>void S()}]}),this.plugin.getUndoStack(w).push({label:y,undo:async()=>{await S()},redo:async()=>{this.selection.clear(),this.cursorIdx=-1,Q=!1,await this.trashNotesAndAttachments(u)}})},h=X(),g=t.length>0&&h.confirmBulkDelete,f=l.length>0&&h.confirmAttachmentDelete;if(!g&&!f){await d(!1),this.focusView();return}new si(this.app,this.titleForNode(e),t.length,l.length,f,async p=>{await d(p),this.focusView()}).open()}async cmdSwapWithParent(){let e=this.cursorIdx>=0?this.currentChildren[this.cursorIdx]:null;if(!e?.file){new k.Notice("Pick a note first (move the cursor onto it).");return}if(!e.parent||e.parent===F){new k.Notice("Already at Home \u2014 no parent to swap with.");return}let t=this.tree.get(e.parent);if(!t?.file){new k.Notice("Couldn't find the parent note.");return}let i=t.parent??F,s=this.titleForNode(e),n=this.titleForNode(t),o=this.tree.getChildren(t.id).filter(l=>l.id!==e.id).length,a=[`"${n}" becomes a child of "${s}".`];o>0&&a.push(`${o} sibling${o===1?"":"s"} move with it.`),new ie(this.app,"Swap notes?",a.join(` `),"Swap",async l=>{l&&await this.swapParentChild(t,e,i)}).open()}offerSwapForDescendantMove(e,t){let i=this.tree.get(t);if(!i?.file){this.plugin.notifications.show({message:`Can't nest "${this.titleForNode(e)}" under one of its own descendants \u2014 that would create a cycle.`,kind:"warning",category:"move",affectedIds:[e.id],folder:this.noteFolder});return}if(i.parent!==e.id){this.plugin.notifications.show({message:`Can't nest "${this.titleForNode(e)}" under "${this.titleForNode(i)}" \u2014 it's a deeper descendant. Only direct parent \u2194 child swaps are supported (try moving "${this.titleForNode(i)}" up first, then swap).`,kind:"warning",category:"move",affectedIds:[e.id,i.id],folder:this.noteFolder});return}let s=this.titleForNode(e),n=this.titleForNode(i),o=this.tree.getChildren(e.id).filter(l=>l.id!==i.id).length,a=[`"${s}" becomes a child of "${n}".`];o>0&&a.push(`${o} sibling${o===1?"":"s"} move with it.`),new ie(this.app,"Confirm Note Swap",a.join(` -`),"Swap",async l=>{if(!l)return;let c=e.parent??k;await this.swapParentChild(e,i,c)}).open()}async swapParentChild(e,t,i){if(!e.file||!t.file)return;if(t.parent!==e.id){new F.Notice("Swap aborted: parent/child relationship changed.");return}let s=this.noteFolder,n=e.parent,o=this.tree.getChildren(e.id).filter(u=>u.id!==t.id).filter(u=>!!u.file),a=o.map(u=>({id:u.id,path:u.file.path,was:u.parent})),l=this.order.getOrder(s,i).slice(),c=this.order.getOrder(s,t.id).slice(),d=this.order.getOrder(s,e.id).slice();await this.app.fileManager.processFrontMatter(t.file,u=>{u.parent=i}),this.fmSync.scheduleParentChange(t.id,e.id,i),await this.app.fileManager.processFrontMatter(e.file,u=>{u.parent=t.id}),this.fmSync.scheduleParentChange(e.id,n,t.id);for(let u of o)await this.app.fileManager.processFrontMatter(u.file,p=>{p.parent=t.id}),this.fmSync.scheduleParentChange(u.id,e.id,t.id);if(l.length>0){let u=l.includes(e.id)?l.map(p=>p===e.id?t.id:p):[...l.filter(p=>p!==t.id),t.id];this.order.setOrder(s,i,u)}let h=o.map(u=>u.id),g=[e.id,...c.filter(u=>u!==e.id&&!h.includes(u)),...h];this.order.setOrder(s,t.id,g),this.order.setOrder(s,e.id,[]),await this.order.save(s),await this.log.append({type:"parent_change",id:t.id,payload:{from:e.id,to:i,reason:"swap"}}),await this.log.append({type:"parent_change",id:e.id,payload:{from:n,to:t.id,reason:"swap"}});for(let u of a)await this.log.append({type:"parent_change",id:u.id,payload:{from:u.was,to:t.id,reason:"swap"}});this.tree.rebuild(s),this.pendingFocusIds=[t.id],this.render({kind:"follow-cursor"}),this.plugin.notifications.show({message:`Swapped "${this.titleForNode(t)}" \u2194 "${this.titleForNode(e)}".`,kind:"success",category:"move",affectedIds:[t.id,e.id,...h],folder:s}),this.plugin.getUndoStack(s).push({label:`Swap "${this.titleForNode(t)}" \u2194 parent`,undo:async()=>{let u=this.tree.get(e.id),p=this.tree.get(t.id);u?.file&&await this.app.fileManager.processFrontMatter(u.file,f=>{n==null||n===k?(delete f.parent,f.parent=k):f.parent=n}),p?.file&&await this.app.fileManager.processFrontMatter(p.file,f=>{f.parent=e.id});for(let f of a){let I=this.app.vault.getAbstractFileByPath(f.path);I&&await this.app.fileManager.processFrontMatter(I,m=>{f.was==null?m.parent=k:m.parent=f.was})}this.order.setOrder(s,i,l),this.order.setOrder(s,t.id,c),this.order.setOrder(s,e.id,d),await this.order.save(s),this.tree.rebuild(s),this.pendingFocusIds=[e.id],this.render({kind:"follow-cursor"})},redo:async()=>{await this.swapParentChild(e,t,i)}})}async changeParent(e,t,i={record:!0}){if(!e.file)return!1;let s=e.file,n=e.parent,o=l=>l??k;if(o(n)===o(t)){if(!i.quiet){let l=this.titleForNode(e),c=t===k?"Home":`"${this.titleForNode(this.tree.get(t)??e)}"`;this.plugin.notifications.show({message:`"${l}" is already under ${c}.`,kind:"info",category:"move",affectedIds:[e.id],folder:this.noteFolder})}return!1}if(t===e.id)return i.quiet||this.plugin.notifications.show({message:`Can't move "${this.titleForNode(e)}" into itself.`,kind:"warning",category:"move",affectedIds:[e.id],folder:this.noteFolder}),!1;if(t!==k&&this.isDescendant(t,e.id))return i.quiet||this.offerSwapForDescendantMove(e,t),!1;let a=this.authorship.collectAuthorIds([e]);if(await this.app.fileManager.processFrontMatter(s,l=>{l.parent=t}),this.fmSync.scheduleParentChange(e.id,n,t),await this.log.append({type:"parent_change",id:e.id,payload:{from:n,to:t}}),this.pendingFocusIds=[e.id],this.focusId!==t&&this.focusId!==n?(this.selection.clear(),this.cursorIdx=-1):this.focusId===n&&(this.selection.clear(),this.cursorIdx=-1,this.pendingFocusIds=null),!i.quiet&&!i.silentSuccess){let l=this.tree.get(t),c=l?this.titleForNode(l):"(root)";this.plugin.notifications.show({message:`Reparented "${this.titleForNode(e)}" \u2192 "${c}"`,kind:"success",category:"move",affectedIds:[e.id],affectedAuthorIds:a,folder:this.noteFolder,actions:t===k?[]:[{label:"Jump to parent",onClick:()=>this.navigateTo(t)}]})}if(i.record!==!1){let l=this.noteFolder,c=s.path,d=e.id;this.plugin.getUndoStack(l).push({label:"Move note",undo:async()=>{let h=this.app.vault.getAbstractFileByPath(c);if(h){await this.app.fileManager.processFrontMatter(h,g=>{g.parent=n}),this.pendingFocusIds=[d],this.focusId!==n&&this.focusId!==t?(this.selection.clear(),this.cursorIdx=-1):this.focusId===t&&(this.selection.clear(),this.cursorIdx=-1,this.pendingFocusIds=null),this.tree.rebuild(l),this.render({kind:"follow-cursor"});{let g=this.selectionGuardKey,u=()=>{if(this.selectionGuardKey!==g||this.selection.has(d))return;let p=this.currentChildren.findIndex(f=>f.id===d);p<0||(this.selection.add(d),this.cursorIdx=p,this.render({kind:"follow-cursor"}))};setTimeout(u,120),setTimeout(u,400)}}},redo:async()=>{let h=this.app.vault.getAbstractFileByPath(c);if(h){await this.app.fileManager.processFrontMatter(h,g=>{g.parent=t}),this.pendingFocusIds=[d],this.focusId!==t&&this.focusId!==n?(this.selection.clear(),this.cursorIdx=-1):this.focusId===n&&(this.selection.clear(),this.cursorIdx=-1,this.pendingFocusIds=null),this.tree.rebuild(l),this.render({kind:"follow-cursor"});{let g=this.selectionGuardKey,u=()=>{if(this.selectionGuardKey!==g||this.selection.has(d))return;let p=this.currentChildren.findIndex(f=>f.id===d);p<0||(this.selection.add(d),this.cursorIdx=p,this.render({kind:"follow-cursor"}))};setTimeout(u,120),setTimeout(u,400)}}}})}return!0}notifyBatchMove(e,t,i){if(!e.length)return;let s=this.tree.get(t),n=t===k?"Home":`"${s?this.titleForNode(s):"the destination"}"`,o=l=>i.get(l.id)??0,a;if(e.length===1){let l=this.titleForNode(e[0]),c=o(e[0]);a=c>0?`Moved "${l}" and its ${c} ${c===1?"child":"children"} \u2192 ${n}`:`Moved "${l}" \u2192 ${n}`}else{let l=e.reduce((c,d)=>c+o(d),0);a=l>0?`Moved ${e.length} notes (${l} nested) \u2192 ${n}`:`Moved ${e.length} notes \u2192 ${n}`}this.plugin.notifications.show({message:a,kind:"success",category:"move",duration:t===k?void 0:0,affectedIds:e.map(l=>l.id),affectedAuthorIds:this.authorship.collectAuthorIds(e),folder:this.noteFolder,actions:t===k?[]:[{label:"Jump to destination",onClick:()=>this.navigateTo(t)}]})}};rs.AIMED_TAP_WINDOW_MS=600;var gn=rs,Vo=class extends F.SuggestModal{constructor(e,t,i){super(e);this.folders=t;this.onPick=i;this.setPlaceholder("Move to which archive folder?")}getSuggestions(e){let t=e.toLowerCase();return this.folders.filter(i=>i.toLowerCase().includes(t))}renderSuggestion(e,t){t.createDiv({text:e.split("/").pop()||e}),t.createEl("small",{text:e,cls:"stashpad-suggest-path"})}onChooseSuggestion(e){this.onPick(e)}},un=class extends F.SuggestModal{constructor(e,t,i){super(e);this.entries=t;this.onPick=i;this.setPlaceholder("Restore which deleted note?")}getSuggestions(e){let t=e.toLowerCase();return this.entries.filter(i=>`${i.label} ${i.folder}`.toLowerCase().includes(t))}renderSuggestion(e,t){t.createDiv({text:e.label}),t.createEl("small",{text:`from ${e.folder}`,cls:"stashpad-suggest-path"})}onChooseSuggestion(e){this.onPick(e.blob)}};var me=require("obsidian");le();Be();var ol=require("obsidian"),id="byfolder";function fn(A,r,e,t){let i=A.createDiv({cls:"stashpad-trash-modes"}),s=i.createEl("button",{cls:"stashpad-agg-reset",attr:{"aria-label":"Reset to default view"}});(0,ol.setIcon)(s,"rotate-ccw"),s.onclick=()=>t(id);let n=[{key:"byfolder",label:"By folder",count:e.total},{key:"separated",label:"Separated",count:e.total},{key:"mixed",label:"Mixed (by date)",count:e.total},{key:"encrypted",label:"Encrypted",count:e.enc},{key:"unencrypted",label:"Unencrypted",count:e.dec}];for(let o of n){let a=i.createEl("button",{cls:"stashpad-triage-chip"});r===o.key&&a.addClass("is-active"),a.createSpan({text:o.label}),a.createSpan({cls:"stashpad-triage-chip-count",text:String(o.count)}),a.onclick=()=>t(o.key)}}var In=me.moment,mn=class extends me.ItemView{constructor(e,t){super(e);this.plugin=t;this.renderPending=!1;this.renderGen=0;this.selected=new Set;this.order=[];this.anchorIdx=null;this.trashMode="byfolder"}getViewType(){return $t}getDisplayText(){return"Trash"}getIcon(){return"trash-2"}async onOpen(){this.containerEl.addClass("stashpad-trash-view"),this.registerEvent(this.app.vault.on("create",e=>{e.path.startsWith("_deleted/")&&this.scheduleRender()})),this.registerEvent(this.app.vault.on("delete",e=>{e.path.startsWith("_deleted/")&&this.scheduleRender()})),this.registerDomEvent(document,"keydown",e=>{if(this.app.workspace.activeLeaf!==this.leaf)return;let t=e.target;if(!(t&&(t.tagName==="INPUT"||t.tagName==="TEXTAREA"||t.isContentEditable))&&(e.metaKey||e.ctrlKey)&&!e.altKey&&(e.key==="a"||e.key==="A")){if(this.order.length===0)return;e.preventDefault(),this.selectAll()}}),await this.render()}scheduleRender(){this.renderPending||(this.renderPending=!0,window.setTimeout(()=>{this.renderPending=!1,this.render()},150))}async render(){let e=++this.renderGen,t=this.contentEl;t.empty(),t.addClass("stashpad-trash-view-body");let i=t.createDiv({cls:"stashpad-trash-view-header"});i.createEl("h3",{text:"Trash"});let s=i.createEl("button",{cls:"stashpad-trash-iconbtn"});(0,me.setIcon)(s,"refresh-cw"),s.setAttr("aria-label","Refresh"),s.onclick=()=>void this.render();let n=this.plugin.encryption?.isConfigured?.()?await this.plugin.listDeletedTrash():[];if(e!==this.renderGen)return;let o=await this.plugin.listRawTrashStashpadNotes();if(e!==this.renderGen)return;if(n.length===0&&o.length===0){t.createDiv({cls:"stashpad-trash-empty",text:"Trash is empty. Securely-deleted notes land in the encrypted trash; plain deletes go to Obsidian's trash \u2014 both show here."});return}if(fn(t,this.trashMode,{total:n.length+o.length,enc:n.length,dec:o.length},c=>{this.trashMode=c,this.clearSelection(),this.render()}),this.trashMode==="mixed"){this.renderMixed(t,n,o);return}let a=this.trashMode!=="unencrypted"&&n.length>0,l=this.trashMode!=="encrypted"&&o.length>0;if(!a&&!l){t.createDiv({cls:"stashpad-trash-empty",text:"Nothing in this view."});return}if(a){let c=i.createEl("button",{cls:"stashpad-trash-restore",text:"Restore all"});(0,me.setIcon)(c.createSpan({cls:"stashpad-btn-icon"}),"rotate-ccw"),c.onclick=async()=>{c.disabled=!0,await this.plugin.restoreAllTrash(),this.clearSelection(),await this.render()};let d=new Set(n.map(h=>h.blob));for(let h of[...this.selected])d.has(h)||this.selected.delete(h);if(this.selected.size>0){let h=t.createDiv({cls:"stashpad-trash-selbar"});h.createSpan({cls:"stashpad-trash-selcount",text:`${this.selected.size} selected`});let g=h.createEl("button",{cls:"stashpad-trash-restore",text:"Restore selected"});g.onclick=async()=>{g.disabled=!0;for(let f of[...this.selected])await this.plugin.restoreDeletedAt(f,{silent:!0});this.clearSelection(),await this.render()};let u=h.createEl("button",{cls:"stashpad-trash-delete mod-warning",text:"Delete selected"});u.onclick=()=>this.confirmPurge([...this.selected]);let p=h.createEl("button",{cls:"stashpad-trash-iconbtn",text:"Clear"});p.onclick=()=>{this.clearSelection(),this.render()}}}if(this.order=[],a)if(this.trashMode==="byfolder"){let c=new Map;for(let h of n){let g=h.meta?.title?h.meta.originalFolder||"(unknown folder)":" hidden";(c.get(g)??c.set(g,[]).get(g)).push(h)}let d=[...c.keys()].sort((h,g)=>h===" hidden"?1:g===" hidden"?-1:h.localeCompare(g));for(let h of d){let g=h===" hidden",u=t.createDiv({cls:"stashpad-trash-group"}),p=u.createDiv({cls:"stashpad-trash-group-head"});(0,me.setIcon)(p.createSpan({cls:"stashpad-trash-group-icon"}),g?"lock":"folder"),p.createSpan({text:g?"Hidden (title obscured)":h.split("/").pop()||h}),p.createSpan({cls:"stashpad-trash-group-count",text:String(c.get(h).length)});for(let f of c.get(h))this.encRow(u,f,g)}}else{let c=t.createDiv({cls:"stashpad-trash-group"}),d=c.createDiv({cls:"stashpad-trash-group-head"});(0,me.setIcon)(d.createSpan({cls:"stashpad-trash-group-icon"}),"lock"),d.createSpan({text:"Encrypted"}),d.createSpan({cls:"stashpad-trash-group-count",text:String(n.length)});for(let h of n)this.encRow(c,h,!h.meta?.title)}l&&this.renderRawSection(t,o)}encRow(e,t,i){let s=this.order.length;this.order.push(t.blob);let n=e.createDiv({cls:"stashpad-trash-row"});this.selected.has(t.blob)&&n.addClass("is-selected"),n.addEventListener("click",h=>{h.target.closest("button")||this.onRowClick(s,t.blob,h)});let o=n.createDiv({cls:"stashpad-trash-row-main"});o.createSpan({cls:"stashpad-trash-title",text:i?"Locked note":t.meta?.title||"Locked note"});let a=t.meta?.deletedAt?`deleted ${In(t.meta.deletedAt).fromNow()}`:"deleted",l=t.meta&&t.meta.count>1?` \xB7 ${t.meta.count} ${t.meta.kind==="rawtrash"?"files":"notes"}`:"";o.createSpan({cls:"stashpad-trash-sub",text:a+l});let c=n.createEl("button",{cls:"stashpad-trash-restore",text:"Restore"});(0,me.setIcon)(c.createSpan({cls:"stashpad-btn-icon"}),"rotate-ccw"),c.onclick=async()=>{c.disabled=!0,await this.plugin.restoreDeletedAt(t.blob)?await this.render():c.disabled=!1};let d=n.createEl("button",{cls:"stashpad-trash-delete",attr:{"aria-label":"Delete permanently"}});(0,me.setIcon)(d,"trash-2"),d.onclick=()=>this.confirmPurge([t.blob])}renderMixed(e,t,i){let s=[];for(let o of t)s.push({ts:o.meta?.deletedAt&&Date.parse(String(o.meta.deletedAt))||0,kind:"enc",enc:o});for(let o of i)s.push({ts:o.mtime||0,kind:"raw",raw:o});s.sort((o,a)=>a.ts-o.ts);let n=e.createDiv({cls:"stashpad-trash-group"});for(let o of s){let a=n.createDiv({cls:"stashpad-trash-row"}),l=a.createDiv({cls:"stashpad-trash-row-main"}),c=l.createSpan({cls:`stashpad-trash-kind is-${o.kind}`});if((0,me.setIcon)(c,o.kind==="enc"?"lock":"trash"),o.kind==="enc"&&o.enc){let d=o.enc,h=!d.meta?.title;l.createSpan({cls:"stashpad-trash-title",text:h?"Locked note":d.meta?.title||"Locked note"});let g=d.meta?.deletedAt?`deleted ${In(d.meta.deletedAt).fromNow()}`:"deleted";l.createSpan({cls:"stashpad-trash-sub",text:`${g} \xB7 encrypted`});let u=a.createEl("button",{cls:"stashpad-trash-restore",text:"Restore"});(0,me.setIcon)(u.createSpan({cls:"stashpad-btn-icon"}),"rotate-ccw"),u.onclick=async()=>{u.disabled=!0,await this.plugin.restoreDeletedAt(d.blob)?await this.render():u.disabled=!1};let p=a.createEl("button",{cls:"stashpad-trash-delete",attr:{"aria-label":"Delete permanently"}});(0,me.setIcon)(p,"trash-2"),p.onclick=()=>this.confirmPurge([d.blob])}else if(o.raw){let d=o.raw;l.createSpan({cls:"stashpad-trash-title",text:d.title});let h=d.mtime?`deleted ${In(d.mtime).fromNow()}`:"in Obsidian trash";l.createSpan({cls:"stashpad-trash-sub",text:`${h} \xB7 unencrypted`});let g=a.createEl("button",{cls:"stashpad-trash-restore",text:"Restore"});(0,me.setIcon)(g.createSpan({cls:"stashpad-btn-icon"}),"rotate-ccw"),g.onclick=async()=>{g.disabled=!0,await this.plugin.restoreRawTrashNote(d.path)?await this.render():g.disabled=!1}}}}renderRawSection(e,t){if(t.length===0)return;let i=e.createDiv({cls:"stashpad-trash-group"}),s=i.createDiv({cls:"stashpad-trash-group-head"});(0,me.setIcon)(s.createSpan({cls:"stashpad-trash-group-icon"}),"trash"),s.createSpan({text:"Obsidian trash (unencrypted)"}),s.createSpan({cls:"stashpad-trash-group-count",text:String(t.length)});for(let n of t){let o=i.createDiv({cls:"stashpad-trash-row"}),a=o.createDiv({cls:"stashpad-trash-row-main"});a.createSpan({cls:"stashpad-trash-title",text:n.title});let l=n.mtime?`deleted ${In(n.mtime).fromNow()}`:"in Obsidian trash";a.createSpan({cls:"stashpad-trash-sub",text:l});let c=o.createEl("button",{cls:"stashpad-trash-restore",text:"Restore"});(0,me.setIcon)(c.createSpan({cls:"stashpad-btn-icon"}),"rotate-ccw"),c.onclick=async()=>{c.disabled=!0,await this.plugin.restoreRawTrashNote(n.path)?await this.render():c.disabled=!1}}}clearSelection(){this.selected.clear(),this.anchorIdx=null}selectAll(){if(this.order.length!==0){for(let e of this.order)this.selected.add(e);this.anchorIdx=this.order.length-1,this.render()}}onRowClick(e,t,i){if(i.shiftKey&&this.anchorIdx!==null){let[s,n]=this.anchorIdx<e?[this.anchorIdx,e]:[e,this.anchorIdx];this.selected.clear();for(let o=s;o<=n;o++)this.selected.add(this.order[o])}else i.metaKey||i.ctrlKey?(this.selected.has(t)?this.selected.delete(t):this.selected.add(t),this.anchorIdx=e):(this.selected.size===1&&this.selected.has(t)?this.selected.clear():(this.selected.clear(),this.selected.add(t)),this.anchorIdx=e);this.render()}confirmPurge(e){if(e.length===0)return;let t=e.length;new ie(this.app,t===1?"Delete permanently?":`Delete ${t} items permanently?`,`${t===1?"This encrypted item":`These ${t} encrypted items`} will be PERMANENTLY deleted and CANNOT be restored \u2014 there is no decrypted copy. Continue?`,"Delete forever",async i=>{if(i){for(let s of e)await this.plugin.purgeDeletedAt(s);this.clearSelection(),await this.render()}}).open()}async onClose(){this.contentEl.empty()}};async function al(A){let{workspace:r}=A.app,e=r.getLeavesOfType($t);if(e.length>0){r.revealLeaf(e[0]);return}let t=r.getMostRecentLeaf(),i=r.getLeaf("tab");await i.setViewState({type:$t,active:!0}),r.revealLeaf(i),pt(r,i,t)}var Pe=require("obsidian");le();var Wo=Pe.moment,Cn=class extends Pe.ItemView{constructor(e,t){super(e);this.plugin=t;this.mode="encrypted";this.taskState=Cs();this.archiveSubMode="byfolder";this.renderPending=!1}getViewType(){return zt}getDisplayText(){return this.mode==="archived"?"All archived":this.mode==="tasks"?"All tasks":"All encrypted"}getIcon(){return this.mode==="archived"?"archive":this.mode==="tasks"?"check-square":"lock"}getState(){return{...super.getState(),mode:this.mode}}async setState(e,t){(e?.mode==="archived"||e?.mode==="encrypted"||e?.mode==="tasks")&&(this.mode=e.mode),await super.setState(e,t),await this.render()}async onOpen(){this.containerEl.addClass("stashpad-aggregate-view");let e=t=>t.endsWith(".stashenc")&&!t.startsWith("_deleted/");this.registerEvent(this.app.vault.on("create",t=>{e(t.path)&&this.scheduleRender()})),this.registerEvent(this.app.vault.on("delete",t=>{e(t.path)&&this.scheduleRender()})),await this.render()}scheduleRender(){this.renderPending||(this.renderPending=!0,window.setTimeout(()=>{this.renderPending=!1,this.render()},150))}cleanFolder(e){return(e||"").replace(/\/+$/,"")}archiveFolders(){let e=new Set((this.plugin.settings.archiveFolders??[]).map(i=>this.cleanFolder(i))),t=this.plugin.settings.folderEncPrefs??{};for(let[i,s]of Object.entries(t))s?.archive&&e.add(this.cleanFolder(i));return[...e].sort((i,s)=>i.localeCompare(s))}async render(){let e=this.contentEl;e.empty(),e.addClass("stashpad-aggregate-body");let t=e.createDiv({cls:"stashpad-aggregate-header"});t.createEl("h3",{text:this.getDisplayText()});let i=t.createEl("button",{cls:"stashpad-trash-iconbtn"});if((0,Pe.setIcon)(i,"refresh-cw"),i.setAttr("aria-label","Refresh"),i.onclick=()=>void this.render(),this.mode==="tasks"){Ki(e.createDiv(),this.app,this.plugin,this.taskState,{onOpen:(s,n)=>void this.plugin.revealNoteByRef(s,n)});return}if(this.mode==="archived"){this.renderArchived(e);return}this.renderEncrypted(e)}renderEncrypted(e){let t=this.plugin.settings.lockedSubtrees??[];if(t.length===0){e.createDiv({cls:"stashpad-trash-empty",text:"Nothing is encrypted yet. Lock a note or folder and it shows up here."});return}let i=new Map;for(let n of t){let o=this.cleanFolder(n.folder)||"(vault root)";(i.get(o)??i.set(o,[]).get(o)).push(n)}let s=[...i.keys()].sort((n,o)=>n.localeCompare(o));e.createDiv({cls:"stashpad-aggregate-sub",text:`${t.length} locked ${t.length===1?"item":"items"} in ${s.length} ${s.length===1?"folder":"folders"}.`});for(let n of s){let o=e.createDiv({cls:"stashpad-trash-group"}),a=o.createDiv({cls:"stashpad-trash-group-head"});(0,Pe.setIcon)(a.createSpan({cls:"stashpad-trash-group-icon"}),"folder"),a.createSpan({text:n.split("/").pop()||n}),a.createSpan({cls:"stashpad-trash-group-count",text:String(i.get(n).length)});let l=a.createEl("button",{cls:"stashpad-trash-iconbtn",text:"Open"});l.onclick=()=>void this.plugin.activateViewForFolder(n);for(let c of i.get(n)){let d=o.createDiv({cls:"stashpad-trash-row"}),h=d.createDiv({cls:"stashpad-trash-row-main"});h.createSpan({cls:"stashpad-trash-title",text:c.title||"Locked note"});let g=c.created?`locked ${Wo(c.created).fromNow()}`:"locked",u=c.count>1?` \xB7 ${c.count} notes`:"";h.createSpan({cls:"stashpad-trash-sub",text:g+u});let p=d.createEl("button",{cls:"stashpad-trash-iconbtn",text:"Export"});p.setAttr("aria-label","Export to .stash (encrypted with a password you choose)"),p.onclick=()=>void this.plugin.exportLockedSubtree(c.blob);let f=d.createSpan({cls:"stashpad-aggregate-lockbadge"});(0,Pe.setIcon)(f,"lock")}}}renderArchived(e){let t=this.archiveFolders();if(t.length===0){e.createDiv({cls:"stashpad-trash-empty",text:"No archive folders yet. Toggle Archive on a folder to collect it here."});return}let i=new Set(t),s=this.plugin.settings.lockedSubtrees??[],n=new Map;for(let f of s){let I=this.cleanFolder(f.folder);i.has(I)&&(n.get(I)??n.set(I,[]).get(I)).push(f)}let o=new Map,a=0;for(let f of t){let I=this.plugin.archivedPlainNotesIn(f);o.set(f,I),a+=I.length}let l=[...n.values()].reduce((f,I)=>f+I.length,0),c=a+l,d=t.flatMap(f=>o.get(f)??[]),h=t.flatMap(f=>n.get(f)??[]);e.createDiv({cls:"stashpad-aggregate-sub",text:`${t.length} archive ${t.length===1?"folder":"folders"}, ${c} archived ${c===1?"item":"items"} (${a} plain \xB7 ${l} locked).`}),fn(e,this.archiveSubMode,{total:c,enc:l,dec:a},f=>{this.archiveSubMode=f,this.render()});let g=this.archiveSubMode;if(g==="mixed"){let f=[];for(let m of d)f.push({ts:m.stat.mtime||0,kind:"plain",f:m});for(let m of h)f.push({ts:m.created&&Date.parse(m.created)||0,kind:"locked",e:m});f.sort((m,C)=>C.ts-m.ts);let I=e.createDiv({cls:"stashpad-trash-group"});for(let m of f)m.kind==="plain"&&m.f?this.archivePlainRow(I,m.f):m.e&&this.archiveLockedRow(I,m.e);return}if(g==="byfolder"){for(let f of t){let I=n.get(f)??[],m=o.get(f)??[],C=e.createDiv({cls:"stashpad-trash-group"}),B=C.createDiv({cls:"stashpad-trash-group-head"});(0,Pe.setIcon)(B.createSpan({cls:"stashpad-trash-group-icon"}),"archive"),B.createSpan({text:f.split("/").pop()||f}),B.createSpan({cls:"stashpad-trash-group-count",text:String(I.length+m.length)});let w=B.createEl("button",{cls:"stashpad-trash-iconbtn",text:"Open"});if(w.onclick=()=>void this.plugin.activateViewForFolder(f),I.length===0&&m.length===0){C.createDiv({cls:"stashpad-trash-sub",text:"No archived items."});continue}for(let y of m)this.archivePlainRow(C,y);for(let y of I)this.archiveLockedRow(C,y)}return}let u=g!=="encrypted"&&d.length>0,p=g!=="unencrypted"&&h.length>0;if(!u&&!p){e.createDiv({cls:"stashpad-trash-empty",text:"Nothing in this view."});return}if(u){let f=e.createDiv({cls:"stashpad-trash-group"}),I=f.createDiv({cls:"stashpad-trash-group-head"});(0,Pe.setIcon)(I.createSpan({cls:"stashpad-trash-group-icon"}),"file-text"),I.createSpan({text:"Plain notes"}),I.createSpan({cls:"stashpad-trash-group-count",text:String(d.length)});for(let m of d)this.archivePlainRow(f,m)}if(p){let f=e.createDiv({cls:"stashpad-trash-group"}),I=f.createDiv({cls:"stashpad-trash-group-head"});(0,Pe.setIcon)(I.createSpan({cls:"stashpad-trash-group-icon"}),"lock"),I.createSpan({text:"Locked (encrypted)"}),I.createSpan({cls:"stashpad-trash-group-count",text:String(h.length)});for(let m of h)this.archiveLockedRow(f,m)}}archivePlainRow(e,t){let i=e.createDiv({cls:"stashpad-trash-row"}),s=i.createDiv({cls:"stashpad-trash-row-main"});s.createSpan({cls:"stashpad-trash-title",text:t.basename.replace(/-[a-z0-9]{4,12}$/,"").replace(/-/g," ").trim()||t.basename}),s.createSpan({cls:"stashpad-trash-sub",text:`archived ${Wo(t.stat.mtime).fromNow()}`});let n=i.createEl("button",{cls:"stashpad-trash-restore",text:"Restore"});(0,Pe.setIcon)(n.createSpan({cls:"stashpad-btn-icon"}),"rotate-ccw"),n.onclick=async()=>{n.disabled=!0,await this.plugin.unarchiveNote(t)?this.render():n.disabled=!1}}archiveLockedRow(e,t){let i=e.createDiv({cls:"stashpad-trash-row"}),s=i.createDiv({cls:"stashpad-trash-row-main"});s.createSpan({cls:"stashpad-trash-title",text:t.title||"Locked note"});let n=t.created?`archived ${Wo(t.created).fromNow()}`:"archived",o=(t.count??0)>1?` \xB7 ${t.count} notes`:"";s.createSpan({cls:"stashpad-trash-sub",text:n+o});let a=i.createEl("button",{cls:"stashpad-trash-iconbtn",text:"Export"});a.setAttr("aria-label","Export to .stash (encrypted with a password you choose)"),a.onclick=()=>void this.plugin.exportLockedSubtree(t.blob);let l=i.createSpan({cls:"stashpad-aggregate-lockbadge"});(0,Pe.setIcon)(l,"lock")}async onClose(){this.contentEl.empty()}};async function Bn(A,r){let{workspace:e}=A.app,t=e.getLeavesOfType(zt).find(n=>n.view?.getState?.().mode===r);if(t){e.revealLeaf(t);return}let i=e.getMostRecentLeaf(),s=e.getLeaf("tab");await s.setViewState({type:zt,active:!0,state:{mode:r}}),e.revealLeaf(s),pt(e,s,i)}le();var V=require("obsidian");le();Be();var yn=class A extends V.ItemView{constructor(e,t){super(e);this.plugin=t;this.renderTimer=null;this.pinExpanded=new Set;this.folderPinExpanded=new Set;this.homeColorByFolder=new Map}getViewType(){return mt}getDisplayText(){return"Stashpad folders"}getIcon(){return"folders"}async onOpen(){this.render(),this.registerEvent(this.app.workspace.on("active-leaf-change",()=>this.scheduleRender())),this.registerEvent(this.app.workspace.on("layout-change",()=>this.scheduleRender())),this.registerEvent(this.app.vault.on("create",()=>this.scheduleRender())),this.registerEvent(this.app.vault.on("delete",()=>this.scheduleRender())),this.registerEvent(this.app.vault.on("rename",()=>this.scheduleRender())),this.registerEvent(this.app.metadataCache.on("changed",()=>this.scheduleRender()))}refresh(){this.scheduleRender()}scheduleRender(){this.renderTimer==null&&(this.renderTimer=window.setTimeout(()=>{this.renderTimer=null,this.containerEl.isConnected&&this.render()},100))}render(){let e=this.contentEl;e.empty(),e.addClass("stashpad-folderpanel-root");let t=this.clampFrac(this.plugin.settings.folderPanelPinnedFraction??.5),i=e.createDiv({cls:"stashpad-folderpanel-section stashpad-folderpanel-pinned"});i.style.flex=`0 0 ${(t*100).toFixed(2)}%`;let s=i.createDiv({cls:"stashpad-folderpanel-heading stashpad-folderpanel-heading-row"});s.createSpan({cls:"stashpad-folderpanel-heading-title",text:"Pinned"});let n=s.createEl("button",{cls:"stashpad-folderpanel-iconbtn"});(0,V.setIcon)(n,"list"),n.setAttr("aria-label","Pinned view options"),n.onclick=h=>{h.stopPropagation(),this.openPinnedOptionsMenu(h)},this.renderPinned(i.createDiv({cls:"stashpad-folderpanel-list stashpad-folderpanel-pins"}));let o=e.createDiv({cls:"stashpad-folderpanel-divider"});o.createDiv({cls:"stashpad-folderpanel-divider-grip"}),this.attachDividerDrag(e,i,o);let a=e.createDiv({cls:"stashpad-folderpanel-section stashpad-folderpanel-folders"});a.setCssStyles({flex:"1 1 0"});let l=a.createDiv({cls:"stashpad-folderpanel-heading stashpad-folderpanel-heading-row"}),c=l.createSpan({cls:"stashpad-folderpanel-heading-title stashpad-folderpanel-heading-switch",text:"Folders"});c.setAttr("aria-label","Open folder switcher"),c.onmousedown=h=>{h.button===0&&(h.preventDefault(),this.plugin.openFolderPicker())};let d=l.createEl("button",{cls:"stashpad-folderpanel-iconbtn stashpad-folderpanel-heading-newfolder"});if((0,V.setIcon)(d,"folder-plus"),d.setAttr("aria-label","New Stashpad folder (folder switcher)"),d.onmousedown=h=>{h.button===0&&(h.preventDefault(),h.stopPropagation(),this.plugin.openFolderPicker())},this.plugin.encryption?.isConfigured?.()){let h=l.createEl("button",{cls:"stashpad-folderpanel-iconbtn stashpad-folderpanel-heading-trash"});(0,V.setIcon)(h,"trash-2"),h.setAttr("aria-label","Open encrypted trash"),h.onmousedown=g=>{g.button===0&&(g.preventDefault(),g.stopPropagation(),this.plugin.openEncryptedTrash())}}this.renderFolders(a.createDiv({cls:"stashpad-folderpanel-list"}))}clampFrac(e){return Number.isFinite(e)?Math.max(.15,Math.min(.85,e)):.5}attachDividerDrag(e,t,i){let s=null,n=a=>{let l=e.getBoundingClientRect();if(l.height<=0)return;let c=this.clampFrac((a.clientY-l.top)/l.height);s=c,t.style.flex=`0 0 ${(c*100).toFixed(2)}%`},o=a=>{document.removeEventListener("pointermove",n),document.removeEventListener("pointerup",o),document.body.removeClass("stashpad-folderpanel-resizing");try{i.releasePointerCapture(a.pointerId)}catch{}s!=null&&(this.plugin.settings.folderPanelPinnedFraction=s,this.plugin.saveSettings())};i.addEventListener("pointerdown",a=>{a.preventDefault(),document.body.addClass("stashpad-folderpanel-resizing");try{i.setPointerCapture(a.pointerId)}catch{}document.addEventListener("pointermove",n),document.addEventListener("pointerup",o)})}openPinnedOptionsMenu(e){let t=this.plugin.settings.folderPanelPinnedGrouping??"pin-order",i=new V.Menu;i.addItem(s=>s.setTitle("Sort by pin order").setChecked(t==="pin-order").onClick(()=>void this.setPinnedGrouping("pin-order"))),i.addItem(s=>s.setTitle("Group by folder").setChecked(t==="folder").onClick(()=>void this.setPinnedGrouping("folder"))),i.showAtMouseEvent(e)}async setPinnedGrouping(e){(this.plugin.settings.folderPanelPinnedGrouping??"pin-order")!==e&&(this.plugin.settings.folderPanelPinnedGrouping=e,await this.plugin.saveSettings(),this.render())}renderPinned(e){let t=this.plugin.discoverStashpadFolders().filter(n=>this.folderState(n)==="pinned"),i=this.plugin.listPinnedNotes();if(t.length===0&&i.length===0){e.createDiv({cls:"stashpad-folderpanel-empty",text:"Nothing pinned yet \u2014 pin a note or folder from its right-click menu."});return}if(t.length>0){let n=this.openFolders();for(let o of t)this.renderFolderRow(e,o,n)}if(i.length===0)return;if((this.plugin.settings.folderPanelPinnedGrouping??"pin-order")==="folder"){let n=new Map;i.forEach((l,c)=>{let d=n.get(l.folder);d||(d=[],n.set(l.folder,d)),d.push({...l,idx:c})});let o=this.plugin.lastActiveStashpadLeaf?.view?.noteFolder,a=Array.from(n.keys());o&&n.has(o)&&(a.splice(a.indexOf(o),1),a.unshift(o));for(let l of a){let c=e.createDiv({cls:"stashpad-pinned-group-header"});l===o&&c.addClass("is-active-folder"),c.createSpan({cls:"stashpad-pinned-group-name",text:l.split("/").pop()||l});for(let d of n.get(l)??[])this.renderPinNote(e,d.folder,d.id,d.file,d.idx)}}else i.forEach((n,o)=>this.renderPinNote(e,n.folder,n.id,n.file,o))}async reorderPin(e,t){let i=this.plugin.listPinnedNotes();if(e<0||e>=i.length)return;let s=i[e],n=i.filter((d,h)=>h!==e),o=Math.max(0,Math.min(t>e?t-1:t,n.length)),a=n[o-1],l=n[o],c;!a&&!l?c=Date.now():a?l?c=(a.pinnedAt+l.pinnedAt)/2:c=a.pinnedAt+1e3:c=l.pinnedAt-1e3;try{await this.app.fileManager.processFrontMatter(s.file,d=>{d.pinnedAt=c})}catch(d){console.warn("[Stashpad] pin reorder failed",d)}this.render()}renderPinNote(e,t,i,s,n){let o=this.app.metadataCache.getFileCache(s)?.frontmatter??{},a=typeof o.color=="string"?o.color:null,l=o.completed===!0,c=this.childrenOf(t,i),d=c.length>0,h=`${t}|${i}`,g=this.pinExpanded.has(h),u=e.createDiv({cls:"stashpad-pinned-row"});a&&(u.addClass("has-color"),u.style.setProperty("--stashpad-note-color",a)),l&&u.addClass("is-completed"),u.draggable=!0,u.dataset.pinIdx=String(n),u.addEventListener("dragstart",m=>{m.dataTransfer?.setData("text/plain",String(n)),m.dataTransfer&&(m.dataTransfer.effectAllowed="move"),u.addClass("is-dragging")}),u.addEventListener("dragend",()=>u.removeClass("is-dragging")),u.addEventListener("dragover",m=>{m.preventDefault(),m.dataTransfer&&(m.dataTransfer.dropEffect="move");let C=u.getBoundingClientRect(),B=m.clientY-C.top<C.height/2;u.toggleClass("drop-before",B),u.toggleClass("drop-after",!B)}),u.addEventListener("dragleave",()=>{u.removeClass("drop-before"),u.removeClass("drop-after")}),u.addEventListener("drop",m=>{m.preventDefault(),u.removeClass("drop-before"),u.removeClass("drop-after");let C=parseInt(m.dataTransfer?.getData("text/plain")??"",10);if(!Number.isFinite(C)||C===n)return;let B=u.getBoundingClientRect(),w=m.clientY-B.top<B.height/2;this.reorderPin(C,w?n:n+1)});let p=u.createSpan({cls:"stashpad-pinned-toggle"});d&&(Bt(p,c.length,g),p.onclick=m=>{m.stopPropagation(),this.pinExpanded.has(h)?this.pinExpanded.delete(h):this.pinExpanded.add(h),this.render()});let f=u.createSpan({cls:"stashpad-pinned-icon"});(0,V.setIcon)(f,d?"folder-tree":"file-text"),a&&(f.style.color=a);let I=u.createSpan({cls:"stashpad-pinned-label",text:this.titleFromFile(s)});if(I.onclick=()=>{this.onNavigateAway(),this.plugin.revealNoteInStashpad(s)},u.createSpan({cls:"stashpad-pinned-folder",text:t.split("/").pop()||t}),u.oncontextmenu=m=>{m.preventDefault();let C=new V.Menu;C.addItem(B=>B.setTitle("Unpin from sidebar").setIcon("pin-off").onClick(()=>void this.plugin.unpinNote({folder:t,id:i}))),C.showAtMouseEvent(m)},d&&g){let m=e.createDiv({cls:"stashpad-pinned-children"});this.renderPinSubtree(m,t,i,1)}}renderPinSubtree(e,t,i,s){for(let n of this.childrenOf(t,i)){let o=this.app.metadataCache.getFileCache(n)?.frontmatter??{},a=typeof o.id=="string"?o.id:null;if(!a)continue;let l=typeof o.color=="string"?o.color:null,c=o.completed===!0,d=this.childrenOf(t,a),h=d.length>0,g=`${t}|${a}`,u=this.pinExpanded.has(g),p=e.createDiv({cls:"stashpad-pinned-subrow"});c&&p.addClass("is-completed"),p.style.paddingLeft=`${s*16}px`;let f=p.createSpan({cls:"stashpad-pinned-toggle"});h&&(Bt(f,d.length,u),f.onclick=C=>{C.stopPropagation(),this.pinExpanded.has(g)?this.pinExpanded.delete(g):this.pinExpanded.add(g),this.render()});let I=p.createSpan({cls:"stashpad-pinned-icon"});(0,V.setIcon)(I,"file-text"),l&&(I.style.color=l);let m=p.createSpan({cls:"stashpad-pinned-label",text:this.titleFromFile(n)});m.onclick=()=>{this.onNavigateAway(),this.plugin.revealNoteInStashpad(n)},h&&u&&this.renderPinSubtree(e,t,a,s+1)}}childrenOf(e,t){let i=[];for(let s of this.app.vault.getMarkdownFiles()){if((s.parent?.path?.replace(/\/+$/,"")??"")!==e)continue;let o=this.app.metadataCache.getFileCache(s)?.frontmatter;if(!o||typeof o.id!="string")continue;let a=o.parent;if(a===t||t===k&&(a==null||a===k)){if(o.id===k)continue;i.push(s)}}return i.sort((s,n)=>{let o=this.app.metadataCache.getFileCache(s)?.frontmatter?.created??"",a=this.app.metadataCache.getFileCache(n)?.frontmatter?.created??"";return String(o).localeCompare(String(a))}),i}onNavigateAway(){V.Platform.isMobile&&(this.plugin.suppressComposerAutofocusUntil=Date.now()+1500,this.app.workspace.leftSplit?.collapse?.())}titleFromFile(e){return e.basename.replace(/-[a-z0-9]{4,12}$/,"").replace(/-/g," ").trim()||e.basename}openFolders(){let e=new Set,t=(this.plugin.settings.folder||"Stashpad").replace(/\/+$/,"");for(let i of this.app.workspace.getLeavesOfType(z)){let s=(i.view?.noteFolder??"").replace(/\/+$/,"");s||(s=(((i.getViewState?.()?.state??{}).folderOverride??"")||t).replace(/\/+$/,"")),s&&e.add(s)}return e}static clean(e){return e.replace(/\/+$/,"")}folderState(e){let t=A.clean(e),i=this.plugin.settings;return(i.folderPanelPinned??[]).includes(t)?"pinned":(i.folderPanelDownranked??[]).includes(t)?"downranked":(i.folderPanelHidden??[]).includes(t)?"hidden":"normal"}async setFolderState(e,t){let i=A.clean(e),s=this.plugin.settings;s.folderPanelPinned=(s.folderPanelPinned??[]).filter(n=>n!==i),s.folderPanelDownranked=(s.folderPanelDownranked??[]).filter(n=>n!==i),s.folderPanelHidden=(s.folderPanelHidden??[]).filter(n=>n!==i),t==="pinned"?s.folderPanelPinned.push(i):t==="downranked"?s.folderPanelDownranked.push(i):t==="hidden"&&s.folderPanelHidden.push(i),await this.plugin.saveSettings(),this.render()}folderHomeColor(e){return this.homeColorByFolder.get(A.clean(e))??null}rebuildHomeColors(){this.homeColorByFolder.clear();for(let e of this.app.vault.getMarkdownFiles()){let t=this.app.metadataCache.getFileCache(e)?.frontmatter;if(!t||t.id!==k||typeof t.color!="string"||!t.color.trim())continue;let i=(e.parent?.path??"").replace(/\/+$/,"");i&&this.homeColorByFolder.set(i,t.color)}}renderFolders(e){let t=this.plugin.discoverStashpadFolders();if(t.length===0){e.createDiv({cls:"stashpad-folderpanel-empty",text:"No Stashpad folders yet."});return}this.rebuildHomeColors();let i=this.openFolders(),s=[],n=[],o=[],a=[];for(let c of t)switch(this.folderState(c)){case"pinned":s.push(c);break;case"downranked":o.push(c);break;case"hidden":a.push(c);break;default:n.push(c)}let l=[...s,...n,...o];if(l.length===0&&a.length===0){e.createDiv({cls:"stashpad-folderpanel-empty",text:"No Stashpad folders yet."});return}for(let c of l)this.renderFolderRow(e,c,i);a.length>0&&this.renderHiddenSection(e,a)}renderFolderRow(e,t,i){let s=this.folderState(t),n=i.has(A.clean(t)),o=e.createDiv({cls:"stashpad-folderpanel-row stashpad-folderpanel-folder-row"});n&&o.addClass("is-open"),s==="downranked"&&o.addClass("is-downranked"),s==="pinned"&&o.addClass("is-pinned");let a=o.createSpan({cls:"stashpad-folderpanel-dot"});if(a.setAttr("aria-label",n?"Open in a tab":"Not open"),n&&a.setAttr("title","Open in a tab"),s==="pinned"){let m=o.createSpan({cls:"stashpad-folderpanel-pinmark"});(0,V.setIcon)(m,"pin"),m.setAttr("aria-label","Pinned")}let l=this.folderHomePinnedNotes(t),c=A.clean(t),d=this.folderPinExpanded.has(c);if(l.length>0){let m=o.createSpan({cls:"stashpad-folderpanel-pinreveal"});Bt(m,l.length,d),m.setAttr("aria-label",d?"Hide pinned notes":`${l.length} pinned note${l.length===1?"":"s"}`),m.addEventListener("mousedown",C=>{C.stopPropagation(),C.preventDefault()}),m.onclick=C=>{C.stopPropagation(),this.folderPinExpanded.has(c)?this.folderPinExpanded.delete(c):this.folderPinExpanded.add(c),this.render()}}let h=this.plugin.isArchiveFolder(t),g=o.createSpan({cls:"stashpad-folderpanel-folder-icon"});(0,V.setIcon)(g,this.plugin.getFolderIcon(t)??(h?"archive":"folder")),h&&g.setAttr("aria-label","Archive folder \u2014 notes moved in are auto-encrypted");let u=this.folderHomeColor(t);u&&(g.style.color=u);let p=t.split("/").pop()||t;o.createSpan({cls:"stashpad-folderpanel-row-label",text:p});let I=o.createDiv({cls:"stashpad-folderpanel-actions"}).createEl("button",{cls:"stashpad-folderpanel-iconbtn"});if((0,V.setIcon)(I,"plus-square"),I.setAttr("aria-label","Open in new tab"),I.onmousedown=m=>{m.button===0&&(m.preventDefault(),m.stopPropagation(),this.onNavigateAway(),this.plugin.activateViewForFolder(t))},o.addEventListener("mousedown",m=>{m.button===0&&(m.target?.closest?.(".stashpad-folderpanel-actions")||(this.onNavigateAway(),this.jumpToFolder(t)))}),o.oncontextmenu=m=>{m.preventDefault(),this.openFolderMenu(m,t)},l.length>0&&d){let m=e.createDiv({cls:"stashpad-folderpanel-pinlist"});for(let C of l){let B=m.createDiv({cls:"stashpad-folderpanel-pinrow"}),w=this.app.metadataCache.getFileCache(C)?.frontmatter??{},y=B.createSpan({cls:"stashpad-folderpanel-pinrow-icon"});(0,V.setIcon)(y,"arrow-up-to-line"),typeof w.color=="string"&&(y.style.color=w.color),B.createSpan({cls:"stashpad-folderpanel-pinrow-label",text:this.titleFromFile(C)}),B.onclick=()=>{this.onNavigateAway(),this.plugin.revealNoteInStashpad(C)}}}}folderHomePinnedNotes(e){return this.childrenOf(e,k).filter(t=>this.app.metadataCache.getFileCache(t)?.frontmatter?.listPinned===!0)}renderHiddenSection(e,t){let i=e.createDiv({cls:"stashpad-folderpanel-hidden"}),s=i.createDiv({cls:"stashpad-folderpanel-hidden-header"}),n=s.createSpan({cls:"stashpad-folderpanel-hidden-caret"});(0,V.setIcon)(n,"chevron-right"),s.createSpan({cls:"stashpad-folderpanel-hidden-title",text:`Hidden (${t.length})`});let o=i.createDiv({cls:"stashpad-folderpanel-hidden-body"});o.setCssStyles({display:"none"}),s.onclick=()=>{let a=o.style.display!=="none";o.setCssStyles({display:a?"none":""}),(0,V.setIcon)(n,a?"chevron-right":"chevron-down")};for(let a of t){let l=o.createDiv({cls:"stashpad-folderpanel-row stashpad-folderpanel-hidden-row"}),c=a.split("/").pop()||a;l.createSpan({cls:"stashpad-folderpanel-row-label",text:c});let d=l.createEl("button",{cls:"stashpad-folderpanel-iconbtn"});(0,V.setIcon)(d,"eye"),d.setAttr("aria-label","Unhide"),d.onclick=h=>{h.stopPropagation(),this.setFolderState(a,"normal")}}}jumpToFolder(e){this.plugin.openFolderInStashpad(e)}revealFolder(e){let t=this.app.vault.getAbstractFileByPath(e.replace(/\/+$/,""));if(!(t instanceof V.TFolder)){new V.Notice("Couldn't find that folder.");return}let i=this.app.workspace.getLeavesOfType("file-explorer")[0];if(!i){new V.Notice("File explorer isn't available.");return}this.app.workspace.revealLeaf(i),i.view?.revealInFolder?.(t)}openFolderMenu(e,t){let i=new V.Menu;i.addItem(n=>n.setTitle("Open in new tab").setIcon("plus-square").onClick(()=>void this.plugin.activateViewForFolder(t))),i.addItem(n=>n.setTitle("Reveal in file explorer").setIcon("folder-search").onClick(()=>this.revealFolder(t))),i.addSeparator();let s=this.folderState(t);if(i.addItem(n=>n.setTitle(s==="pinned"?"Unpin":"Pin to top").setIcon("pin").onClick(()=>void this.setFolderState(t,s==="pinned"?"normal":"pinned"))),i.addItem(n=>n.setTitle(s==="downranked"?"Remove downrank":"Downrank").setIcon("arrow-down").onClick(()=>void this.setFolderState(t,s==="downranked"?"normal":"downranked"))),i.addItem(n=>n.setTitle("Hide from list").setIcon("eye-off").onClick(()=>void this.setFolderState(t,"hidden"))),i.addSeparator(),i.addItem(n=>n.setTitle("Rename\u2026").setIcon("pencil").onClick(()=>this.renameFolder(t))),this.plugin.encryption?.isConfigured?.()){i.addSeparator(),i.addItem(a=>a.setTitle("Encrypt (lock) all notes in folder").setIcon("lock").onClick(()=>void this.plugin.lockFolder(t))),this.app.vault.getFiles().some(a=>a.extension==="stashenc"&&(a.parent?.path?.replace(/\/+$/,"")??"")===t.replace(/\/+$/,""))&&i.addItem(a=>a.setTitle("Decrypt (unlock) all notes in folder").setIcon("unlock").onClick(()=>void this.plugin.unlockFolder(t)));let n=t.replace(/\/+$/,""),o=this.plugin.isArchiveFolder(n);i.addItem(a=>a.setTitle(o?"Unmark archive folder":"Mark as archive folder\u2026").setIcon("archive").onClick(async()=>{if(o){this.plugin.settings.archiveFolders=(this.plugin.settings.archiveFolders??[]).filter(l=>l!==n),await this.plugin.saveSettings(),new V.Notice(`"${n.split("/").pop()}" is no longer an archive folder. Existing locked notes stay locked.`,0),this.render();return}new ie(this.app,`Make "${n.split("/").pop()}" an archive folder?`,["An archive folder automatically LOCKS (encrypts) any note you move into it.","","What that means in plain terms:",`\u2022 "Encrypting" scrambles the note with your encryption password so its text can't be read by anyone \u2014 or any app \u2014 without that password.`,"\u2022 The normal, readable copy is permanently removed from your vault. What's left is an unreadable, locked \u{1F512} placeholder.","\u2022 To read or edit the note again, you unlock it with your encryption password (one click on the placeholder). Then you can re-archive it later.","\u2022 If you ever lose your encryption password, the locked notes are gone for good \u2014 there is no backdoor or recovery, on purpose.",'\u2022 Notes ALREADY in this folder are not touched \u2014 only notes moved in from now on. (To lock the ones already here, use "Encrypt all notes in folder".)',"","Good for folders of things you want kept private at rest: finished or sensitive material you'd rather not have readable if someone opened your vault."].join(` -`),"Make it an archive folder",async l=>{l&&(this.plugin.settings.archiveFolders=[...this.plugin.settings.archiveFolders??[],n],await this.plugin.saveSettings(),new V.Notice(`"${n.split("/").pop()}" is now an archive folder \u2014 notes moved in will be encrypted.`,0),this.render())}).open()})),i.addItem(a=>a.setTitle("Open encrypted trash").setIcon("rotate-ccw").onClick(()=>this.plugin.openEncryptedTrash()))}i.addSeparator(),i.addItem(n=>{n.setTitle("Delete folder\u2026").setIcon("trash").onClick(()=>this.deleteFolder(t)),n.setWarning?.(!0)}),i.showAtMouseEvent(e)}renameFolder(e){let t=e.replace(/\/+$/,""),i=this.app.vault.getAbstractFileByPath(t);if(!(i instanceof V.TFolder)){new V.Notice("Couldn't find that folder.");return}let s=i.name;new Xo(this.app,s,async n=>{let o=n.trim().replace(/[\\/:]+/g,"").trim();if(!o||o===s)return;let l=`${i.parent?.path&&i.parent.path!=="/"?`${i.parent.path}/`:""}${o}`;if(this.app.vault.getAbstractFileByPath(l)){new V.Notice(`"${o}" already exists.`);return}try{await this.app.fileManager.renameFile(i,l),(this.plugin.settings.folder||"").replace(/\/+$/,"")===t&&(this.plugin.settings.folder=l,await this.plugin.saveSettings()),new V.Notice(`Renamed to "${o}".`)}catch(c){console.warn("[Stashpad] folder rename failed",c),new V.Notice("Rename failed (see console).")}}).open()}deleteFolder(e){let t=e.replace(/\/+$/,""),i=this.app.vault.getAbstractFileByPath(t);if(!(i instanceof V.TFolder)){new V.Notice("Couldn't find that folder.");return}let s=this.app.vault.getMarkdownFiles().filter(o=>(o.parent?.path?.replace(/\/+$/,"")??"")===t||o.path.startsWith(t+"/")).length,n=i.name;new ie(this.app,`Delete "${n}"?`,`This moves the entire folder \u2014 about ${s} note${s===1?"":"s"} plus its attachments and exports \u2014 to the trash. -You can restore it from your system/Obsidian trash.`,"Delete folder",async o=>{o&&await this.plugin.deleteStashpadFolderWithUndo(i)}).open()}},Xo=class extends V.Modal{constructor(e,t,i){super(e);this.current=t;this.onSubmit=i;this.delivered=!1}onOpen(){this.modalEl.addClass("stashpad-compact-modal"),this.contentEl.empty(),this.titleEl.setText("Rename folder");let e=this.contentEl.createEl("input",{type:"text"});e.addClass("stashpad-folderpanel-rename-input"),e.value=this.current;let t=this.contentEl.createDiv({cls:"stashpad-folderpanel-rename-footer"});t.createEl("button",{text:"Cancel"}).onclick=()=>this.close();let i=t.createEl("button",{cls:"mod-cta",text:"Rename"}),s=()=>{this.delivered=!0;let n=e.value;this.close(),this.onSubmit(n)};i.onclick=s,this.scope.register([],"Enter",n=>{n.preventDefault(),e.value.trim()&&s()}),requestAnimationFrame(()=>{e.focus(),e.select()})}onClose(){this.contentEl.empty()}};async function wn(A){let r=A.workspace.getLeavesOfType(mt);if(r.length>0){A.workspace.revealLeaf(r[0]);return}let e=A.workspace.getLeftLeaf(!1);if(!e){new V.Notice("Stashpad: couldn't open the folder panel.");return}await e.setViewState({type:mt,active:!0}),A.workspace.revealLeaf(e)}Be();si();le();var Zo="OKF Template.md",sd=`--- +`),"Swap",async l=>{if(!l)return;let c=e.parent??F;await this.swapParentChild(e,i,c)}).open()}async swapParentChild(e,t,i){if(!e.file||!t.file)return;if(t.parent!==e.id){new k.Notice("Swap aborted: parent/child relationship changed.");return}let s=this.noteFolder,n=e.parent,o=this.tree.getChildren(e.id).filter(f=>f.id!==t.id).filter(f=>!!f.file),a=o.map(f=>({id:f.id,path:f.file.path,was:f.parent})),l=this.order.getOrder(s,i).slice(),c=this.order.getOrder(s,t.id).slice(),d=this.order.getOrder(s,e.id).slice();await this.app.fileManager.processFrontMatter(t.file,f=>{f.parent=i}),this.fmSync.scheduleParentChange(t.id,e.id,i),await this.app.fileManager.processFrontMatter(e.file,f=>{f.parent=t.id}),this.fmSync.scheduleParentChange(e.id,n,t.id);for(let f of o)await this.app.fileManager.processFrontMatter(f.file,p=>{p.parent=t.id}),this.fmSync.scheduleParentChange(f.id,e.id,t.id);if(l.length>0){let f=l.includes(e.id)?l.map(p=>p===e.id?t.id:p):[...l.filter(p=>p!==t.id),t.id];this.order.setOrder(s,i,f)}let h=o.map(f=>f.id),g=[e.id,...c.filter(f=>f!==e.id&&!h.includes(f)),...h];this.order.setOrder(s,t.id,g),this.order.setOrder(s,e.id,[]),await this.order.save(s),await this.log.append({type:"parent_change",id:t.id,payload:{from:e.id,to:i,reason:"swap"}}),await this.log.append({type:"parent_change",id:e.id,payload:{from:n,to:t.id,reason:"swap"}});for(let f of a)await this.log.append({type:"parent_change",id:f.id,payload:{from:f.was,to:t.id,reason:"swap"}});this.tree.rebuild(s),this.pendingFocusIds=[t.id],this.render({kind:"follow-cursor"}),this.plugin.notifications.show({message:`Swapped "${this.titleForNode(t)}" \u2194 "${this.titleForNode(e)}".`,kind:"success",category:"move",affectedIds:[t.id,e.id,...h],folder:s}),this.plugin.getUndoStack(s).push({label:`Swap "${this.titleForNode(t)}" \u2194 parent`,undo:async()=>{let f=this.tree.get(e.id),p=this.tree.get(t.id);f?.file&&await this.app.fileManager.processFrontMatter(f.file,u=>{n==null||n===F?(delete u.parent,u.parent=F):u.parent=n}),p?.file&&await this.app.fileManager.processFrontMatter(p.file,u=>{u.parent=e.id});for(let u of a){let I=this.app.vault.getAbstractFileByPath(u.path);I&&await this.app.fileManager.processFrontMatter(I,m=>{u.was==null?m.parent=F:m.parent=u.was})}this.order.setOrder(s,i,l),this.order.setOrder(s,t.id,c),this.order.setOrder(s,e.id,d),await this.order.save(s),this.tree.rebuild(s),this.pendingFocusIds=[e.id],this.render({kind:"follow-cursor"})},redo:async()=>{await this.swapParentChild(e,t,i)}})}async changeParent(e,t,i={record:!0}){if(!e.file)return!1;let s=e.file,n=e.parent,o=l=>l??F;if(o(n)===o(t)){if(!i.quiet){let l=this.titleForNode(e),c=t===F?"Home":`"${this.titleForNode(this.tree.get(t)??e)}"`;this.plugin.notifications.show({message:`"${l}" is already under ${c}.`,kind:"info",category:"move",affectedIds:[e.id],folder:this.noteFolder})}return!1}if(t===e.id)return i.quiet||this.plugin.notifications.show({message:`Can't move "${this.titleForNode(e)}" into itself.`,kind:"warning",category:"move",affectedIds:[e.id],folder:this.noteFolder}),!1;if(t!==F&&this.isDescendant(t,e.id))return i.quiet||this.offerSwapForDescendantMove(e,t),!1;let a=this.authorship.collectAuthorIds([e]);if(await this.app.fileManager.processFrontMatter(s,l=>{l.parent=t}),this.fmSync.scheduleParentChange(e.id,n,t),await this.log.append({type:"parent_change",id:e.id,payload:{from:n,to:t}}),this.pendingFocusIds=[e.id],this.focusId!==t&&this.focusId!==n?(this.selection.clear(),this.cursorIdx=-1):this.focusId===n&&(this.selection.clear(),this.cursorIdx=-1,this.pendingFocusIds=null),!i.quiet&&!i.silentSuccess){let l=this.tree.get(t),c=l?this.titleForNode(l):"(root)";this.plugin.notifications.show({message:`Reparented "${this.titleForNode(e)}" \u2192 "${c}"`,kind:"success",category:"move",affectedIds:[e.id],affectedAuthorIds:a,folder:this.noteFolder,actions:t===F?[]:[{label:"Jump to parent",onClick:()=>this.navigateTo(t)}]})}if(i.record!==!1){let l=this.noteFolder,c=s.path,d=e.id;this.plugin.getUndoStack(l).push({label:"Move note",undo:async()=>{let h=this.app.vault.getAbstractFileByPath(c);if(h){await this.app.fileManager.processFrontMatter(h,g=>{g.parent=n}),this.pendingFocusIds=[d],this.focusId!==n&&this.focusId!==t?(this.selection.clear(),this.cursorIdx=-1):this.focusId===t&&(this.selection.clear(),this.cursorIdx=-1,this.pendingFocusIds=null),this.tree.rebuild(l),this.render({kind:"follow-cursor"});{let g=this.selectionGuardKey,f=()=>{if(this.selectionGuardKey!==g||this.selection.has(d))return;let p=this.currentChildren.findIndex(u=>u.id===d);p<0||(this.selection.add(d),this.cursorIdx=p,this.render({kind:"follow-cursor"}))};setTimeout(f,120),setTimeout(f,400)}}},redo:async()=>{let h=this.app.vault.getAbstractFileByPath(c);if(h){await this.app.fileManager.processFrontMatter(h,g=>{g.parent=t}),this.pendingFocusIds=[d],this.focusId!==t&&this.focusId!==n?(this.selection.clear(),this.cursorIdx=-1):this.focusId===n&&(this.selection.clear(),this.cursorIdx=-1,this.pendingFocusIds=null),this.tree.rebuild(l),this.render({kind:"follow-cursor"});{let g=this.selectionGuardKey,f=()=>{if(this.selectionGuardKey!==g||this.selection.has(d))return;let p=this.currentChildren.findIndex(u=>u.id===d);p<0||(this.selection.add(d),this.cursorIdx=p,this.render({kind:"follow-cursor"}))};setTimeout(f,120),setTimeout(f,400)}}}})}return!0}notifyBatchMove(e,t,i){if(!e.length)return;let s=this.tree.get(t),n=t===F?"Home":`"${s?this.titleForNode(s):"the destination"}"`,o=l=>i.get(l.id)??0,a;if(e.length===1){let l=this.titleForNode(e[0]),c=o(e[0]);a=c>0?`Moved "${l}" and its ${c} ${c===1?"child":"children"} \u2192 ${n}`:`Moved "${l}" \u2192 ${n}`}else{let l=e.reduce((c,d)=>c+o(d),0);a=l>0?`Moved ${e.length} notes (${l} nested) \u2192 ${n}`:`Moved ${e.length} notes \u2192 ${n}`}this.plugin.notifications.show({message:a,kind:"success",category:"move",duration:t===F?void 0:0,affectedIds:e.map(l=>l.id),affectedAuthorIds:this.authorship.collectAuthorIds(e),folder:this.noteFolder,actions:t===F?[]:[{label:"Jump to destination",onClick:()=>this.navigateTo(t)}]})}};gs.AIMED_TAP_WINDOW_MS=600;var kn=gs,Sn=class extends k.SuggestModal{constructor(e,t,i){super(e);this.entries=t;this.onPick=i;this.setPlaceholder("Restore which deleted note?")}getSuggestions(e){let t=e.toLowerCase();return this.entries.filter(i=>`${i.label} ${i.folder}`.toLowerCase().includes(t))}renderSuggestion(e,t){t.createDiv({text:e.label}),t.createEl("small",{text:`from ${e.folder}`,cls:"stashpad-suggest-path"})}onChooseSuggestion(e){this.onPick(e.blob)}};var fe=require("obsidian");le();ye();var vl=require("obsidian"),Qd="byfolder";function bn(A,r,e,t){let i=A.createDiv({cls:"stashpad-trash-modes"}),s=i.createEl("button",{cls:"stashpad-agg-reset",attr:{"aria-label":"Reset to default view"}});(0,vl.setIcon)(s,"rotate-ccw"),s.onclick=()=>t(Qd);let n=[{key:"byfolder",label:"By folder",count:e.total},{key:"separated",label:"Separated",count:e.total},{key:"mixed",label:"Mixed (by date)",count:e.total},{key:"encrypted",label:"Encrypted",count:e.enc},{key:"unencrypted",label:"Unencrypted",count:e.dec}];for(let o of n){let a=i.createEl("button",{cls:"stashpad-triage-chip"});r===o.key&&a.addClass("is-active"),a.createSpan({text:o.label}),a.createSpan({cls:"stashpad-triage-chip-count",text:String(o.count)}),a.onclick=()=>t(o.key)}}var Dn=fe.moment,xn=class extends fe.ItemView{constructor(e,t){super(e);this.plugin=t;this.renderPending=!1;this.renderGen=0;this.selected=new Set;this.order=[];this.anchorBlob=null;this.trashMode="byfolder"}getViewType(){return Zt}getDisplayText(){return"Trash"}getIcon(){return"trash-2"}async onOpen(){this.containerEl.addClass("stashpad-trash-view"),this.registerEvent(this.app.vault.on("create",e=>{e.path.startsWith("_deleted/")&&this.scheduleRender()})),this.registerEvent(this.app.vault.on("delete",e=>{e.path.startsWith("_deleted/")&&this.scheduleRender()})),this.registerDomEvent(document,"keydown",e=>{if(this.app.workspace.activeLeaf!==this.leaf)return;let t=e.target;if(!(t&&(t.tagName==="INPUT"||t.tagName==="TEXTAREA"||t.isContentEditable))&&(e.metaKey||e.ctrlKey)&&!e.altKey&&(e.key==="a"||e.key==="A")){if(this.order.length===0)return;e.preventDefault(),this.selectAll()}}),await this.render()}scheduleRender(){this.renderPending||(this.renderPending=!0,window.setTimeout(()=>{this.renderPending=!1,this.render()},150))}async render(){let e=++this.renderGen,t=this.contentEl;t.empty(),t.addClass("stashpad-trash-view-body"),this.order=[];let i=t.createDiv({cls:"stashpad-trash-view-header"});i.createEl("h3",{text:"Trash"});let s=i.createEl("button",{cls:"stashpad-trash-iconbtn"});(0,fe.setIcon)(s,"refresh-cw"),s.setAttr("aria-label","Refresh"),s.onclick=()=>void this.render();let n=this.plugin.encryption?.isConfigured?.()?await this.plugin.listDeletedTrash():[];if(e!==this.renderGen)return;let o=await this.plugin.listRawTrashStashpadNotes();if(e!==this.renderGen)return;if(n.length===0&&o.length===0){t.createDiv({cls:"stashpad-trash-empty",text:"Trash is empty. Securely-deleted notes land in the encrypted trash; plain deletes go to Obsidian's trash \u2014 both show here."});return}if(bn(t,this.trashMode,{total:n.length+o.length,enc:n.length,dec:o.length},c=>{this.trashMode=c,this.clearSelection(),this.render()}),this.trashMode==="mixed"){this.renderMixed(t,n,o);return}let a=this.trashMode!=="unencrypted"&&n.length>0,l=this.trashMode!=="encrypted"&&o.length>0;if(!a&&!l){t.createDiv({cls:"stashpad-trash-empty",text:"Nothing in this view."});return}if(a){let c=i.createEl("button",{cls:"stashpad-trash-restore",text:"Restore all"});(0,fe.setIcon)(c.createSpan({cls:"stashpad-btn-icon"}),"rotate-ccw"),c.onclick=async()=>{c.disabled=!0,await this.plugin.restoreAllTrash(),this.clearSelection(),await this.render()};let d=new Set(n.map(h=>h.blob));for(let h of[...this.selected])d.has(h)||this.selected.delete(h);if(this.selected.size>0){let h=t.createDiv({cls:"stashpad-trash-selbar"});h.createSpan({cls:"stashpad-trash-selcount",text:`${this.selected.size} selected`});let g=h.createEl("button",{cls:"stashpad-trash-restore",text:"Restore selected"});g.onclick=async()=>{g.disabled=!0;let u=0;for(let I of[...this.selected])await this.plugin.restoreDeletedAt(I,{silent:!0})||u++;u>0&&new fe.Notice(`${u} item${u===1?"":"s"} couldn't be restored (locked key?) \u2014 still in trash.`),this.clearSelection(),await this.render()};let f=h.createEl("button",{cls:"stashpad-trash-delete mod-warning",text:"Delete selected"});f.onclick=()=>this.confirmPurge([...this.selected]);let p=h.createEl("button",{cls:"stashpad-trash-iconbtn",text:"Clear"});p.onclick=()=>{this.clearSelection(),this.render()}}}if(this.order=[],a)if(this.trashMode==="byfolder"){let c=new Map;for(let h of n){let g=h.meta?.title?h.meta.originalFolder||"(unknown folder)":" hidden";(c.get(g)??c.set(g,[]).get(g)).push(h)}let d=[...c.keys()].sort((h,g)=>h===" hidden"?1:g===" hidden"?-1:h.localeCompare(g));for(let h of d){let g=h===" hidden",f=t.createDiv({cls:"stashpad-trash-group"}),p=f.createDiv({cls:"stashpad-trash-group-head"});(0,fe.setIcon)(p.createSpan({cls:"stashpad-trash-group-icon"}),g?"lock":"folder"),p.createSpan({text:g?"Hidden (title obscured)":h.split("/").pop()||h}),p.createSpan({cls:"stashpad-trash-group-count",text:String(c.get(h).length)});for(let u of c.get(h))this.encRow(f,u,g)}}else{let c=t.createDiv({cls:"stashpad-trash-group"}),d=c.createDiv({cls:"stashpad-trash-group-head"});(0,fe.setIcon)(d.createSpan({cls:"stashpad-trash-group-icon"}),"lock"),d.createSpan({text:"Encrypted"}),d.createSpan({cls:"stashpad-trash-group-count",text:String(n.length)});for(let h of n)this.encRow(c,h,!h.meta?.title)}l&&this.renderRawSection(t,o)}encRow(e,t,i){let s=this.order.length;this.order.push(t.blob);let n=e.createDiv({cls:"stashpad-trash-row"});this.selected.has(t.blob)&&n.addClass("is-selected"),n.addEventListener("click",h=>{h.target.closest("button")||this.onRowClick(s,t.blob,h)});let o=n.createDiv({cls:"stashpad-trash-row-main"});o.createSpan({cls:"stashpad-trash-title",text:i?"Locked note":t.meta?.title||"Locked note"});let a=t.meta?.deletedAt?`deleted ${Dn(t.meta.deletedAt).fromNow()}`:"deleted",l=t.meta&&t.meta.count>1?` \xB7 ${t.meta.count} ${t.meta.kind==="rawtrash"?"files":"notes"}`:"";o.createSpan({cls:"stashpad-trash-sub",text:a+l});let c=n.createEl("button",{cls:"stashpad-trash-restore",text:"Restore"});(0,fe.setIcon)(c.createSpan({cls:"stashpad-btn-icon"}),"rotate-ccw"),c.onclick=async()=>{c.disabled=!0,await this.plugin.restoreDeletedAt(t.blob)?await this.render():c.disabled=!1};let d=n.createEl("button",{cls:"stashpad-trash-delete",attr:{"aria-label":"Delete permanently"}});(0,fe.setIcon)(d,"trash-2"),d.onclick=()=>this.confirmPurge([t.blob])}renderMixed(e,t,i){let s=[];for(let o of t)s.push({ts:o.meta?.deletedAt&&Date.parse(String(o.meta.deletedAt))||0,kind:"enc",enc:o});for(let o of i)s.push({ts:o.mtime||0,kind:"raw",raw:o});s.sort((o,a)=>a.ts-o.ts);let n=e.createDiv({cls:"stashpad-trash-group"});for(let o of s){let a=n.createDiv({cls:"stashpad-trash-row"}),l=a.createDiv({cls:"stashpad-trash-row-main"}),c=l.createSpan({cls:`stashpad-trash-kind is-${o.kind}`});if((0,fe.setIcon)(c,o.kind==="enc"?"lock":"trash"),o.kind==="enc"&&o.enc){let d=o.enc,h=!d.meta?.title;l.createSpan({cls:"stashpad-trash-title",text:h?"Locked note":d.meta?.title||"Locked note"});let g=d.meta?.deletedAt?`deleted ${Dn(d.meta.deletedAt).fromNow()}`:"deleted";l.createSpan({cls:"stashpad-trash-sub",text:`${g} \xB7 encrypted`});let f=a.createEl("button",{cls:"stashpad-trash-restore",text:"Restore"});(0,fe.setIcon)(f.createSpan({cls:"stashpad-btn-icon"}),"rotate-ccw"),f.onclick=async()=>{f.disabled=!0,await this.plugin.restoreDeletedAt(d.blob)?await this.render():f.disabled=!1};let p=a.createEl("button",{cls:"stashpad-trash-delete",attr:{"aria-label":"Delete permanently"}});(0,fe.setIcon)(p,"trash-2"),p.onclick=()=>this.confirmPurge([d.blob])}else if(o.raw){let d=o.raw;l.createSpan({cls:"stashpad-trash-title",text:d.title});let h=d.mtime?`deleted ${Dn(d.mtime).fromNow()}`:"in Obsidian trash";l.createSpan({cls:"stashpad-trash-sub",text:`${h} \xB7 unencrypted`});let g=a.createEl("button",{cls:"stashpad-trash-restore",text:"Restore"});(0,fe.setIcon)(g.createSpan({cls:"stashpad-btn-icon"}),"rotate-ccw"),g.onclick=async()=>{g.disabled=!0,await this.plugin.restoreRawTrashNote(d.path)?await this.render():g.disabled=!1}}}}renderRawSection(e,t){if(t.length===0)return;let i=e.createDiv({cls:"stashpad-trash-group"}),s=i.createDiv({cls:"stashpad-trash-group-head"});(0,fe.setIcon)(s.createSpan({cls:"stashpad-trash-group-icon"}),"trash"),s.createSpan({text:"Obsidian trash (unencrypted)"}),s.createSpan({cls:"stashpad-trash-group-count",text:String(t.length)});for(let n of t){let o=i.createDiv({cls:"stashpad-trash-row"}),a=o.createDiv({cls:"stashpad-trash-row-main"});a.createSpan({cls:"stashpad-trash-title",text:n.title});let l=n.mtime?`deleted ${Dn(n.mtime).fromNow()}`:"in Obsidian trash";a.createSpan({cls:"stashpad-trash-sub",text:l});let c=o.createEl("button",{cls:"stashpad-trash-restore",text:"Restore"});(0,fe.setIcon)(c.createSpan({cls:"stashpad-btn-icon"}),"rotate-ccw"),c.onclick=async()=>{c.disabled=!0,await this.plugin.restoreRawTrashNote(n.path)?await this.render():c.disabled=!1}}}clearSelection(){this.selected.clear(),this.anchorBlob=null}selectAll(){if(this.order.length!==0){for(let e of this.order)this.selected.add(e);this.anchorBlob=this.order[this.order.length-1]??null,this.render()}}onRowClick(e,t,i){let s=this.anchorBlob!==null?this.order.indexOf(this.anchorBlob):-1;if(i.shiftKey&&s>=0){let[n,o]=s<e?[s,e]:[e,s];this.selected.clear();for(let a=n;a<=o;a++)this.selected.add(this.order[a])}else i.metaKey||i.ctrlKey?(this.selected.has(t)?this.selected.delete(t):this.selected.add(t),this.anchorBlob=t):(this.selected.size===1&&this.selected.has(t)?this.selected.clear():(this.selected.clear(),this.selected.add(t)),this.anchorBlob=t);this.render()}confirmPurge(e){if(e.length===0)return;let t=e.length;new ie(this.app,t===1?"Delete permanently?":`Delete ${t} items permanently?`,`${t===1?"This encrypted item":`These ${t} encrypted items`} will be PERMANENTLY deleted and CANNOT be restored \u2014 there is no decrypted copy. Continue?`,"Delete forever",async i=>{if(i){for(let s of e)await this.plugin.purgeDeletedAt(s);this.clearSelection(),await this.render()}},"Cancel",!0).open()}async onClose(){this.contentEl.empty()}};async function Fl(A){let{workspace:r}=A.app,e=r.getLeavesOfType(Zt);if(e.length>0){r.revealLeaf(e[0]);return}let t=r.getMostRecentLeaf(),i=r.getLeaf("tab");await i.setViewState({type:Zt,active:!0}),r.revealLeaf(i),gt(r,i,t,s=>A.registerEvent(s))}var Wt=require("obsidian"),us=class us{constructor(r){this.plugin=r;this.tick=null;this.lastTouch=new Map;this.counting=new Set;this.countdownTimers=new Set}key(r,e){return`${r} ${e}`}delayMin(r){let e=r.peekMin;if(e!=null)return e;let i=(this.plugin.settings.folderEncPrefs??{})[r.folder.replace(/\/+$/,"")]?.reEncryptAfterMin;return i??this.plugin.settings.reEncryptAfterMin??0}anyArmed(){if((this.plugin.settings.reEncryptAfterMin??0)>0)return!0;for(let r of Object.values(this.plugin.settings.folderEncPrefs??{}))if((r?.reEncryptAfterMin??0)>0)return!0;return this.plugin.reEncryptWatching().some(r=>(r.peekMin??0)>0)}start(){let r=e=>{e&&this.lastTouch.set(e.replace(/\/+$/,""),Date.now())};this.plugin.registerEvent(this.plugin.app.vault.on("modify",e=>{e instanceof Wt.TFile&&r(e.parent?.path??"")})),this.plugin.registerEvent(this.plugin.app.workspace.on("file-open",e=>{e&&r(e.parent?.path??"")})),this.tick=window.setInterval(()=>void this.poll(),3e4),this.plugin.registerInterval(this.tick)}folderHasOpenEditor(r){let e=r.replace(/\/+$/,""),t=!1;return this.plugin.app.workspace.iterateAllLeaves(i=>{let s=i.view;s instanceof Wt.MarkdownView&&s.file&&(s.file.parent?.path?.replace(/\/+$/,"")??"")===e&&(t=!0)}),t}keyReady(r){let e=this.plugin.encryption;return e?.isConfigured?.()?e.hasOwnFolderKey(r)?e.isFolderUnlocked(r):e.isUnlocked():!1}async poll(){if(!this.anyArmed())return;let r=Date.now();for(let e of this.plugin.reEncryptWatching()){let t=this.delayMin(e);if(t<=0)continue;let i=e.folder.replace(/\/+$/,""),s=this.key(i,e.rootId);if(this.counting.has(s))continue;let n=Math.max(Date.parse(e.unlockedAt)||0,this.lastTouch.get(i)??0);if(!(r<n+t*6e4)&&this.keyReady(i)){if(this.folderHasOpenEditor(i)){vd(this.lastTouch,i,r);continue}this.countdownThenLock(e,s)}}}countdownThenLock(r,e){this.counting.add(e);let t=r.folder.replace(/\/+$/,""),i=r.title||"a note",s=Date.now(),n=us.COUNTDOWN_SECS,o=!1,a=new Wt.Notice("",0),l=()=>{window.clearInterval(h),this.countdownTimers.delete(h),a.hide(),this.counting.delete(e)},c=()=>{this.lastTouch.set(t,Date.now()),l()},d=()=>{a.noticeEl.empty(),a.noticeEl.createSpan({text:`\u{1F512} Re-encrypting \u201C${i}\u201D in ${n}s\u2026 `}),a.noticeEl.createEl("button",{text:"Keep unlocked"}).onclick=()=>{o=!0,c()}};d();let h=window.setInterval(async()=>{if(o)return;if((this.lastTouch.get(t)??0)>s){c();return}if(n-=1,n>0){d();return}if(l(),!this.plugin.reEncryptWatching().some(f=>f.folder===r.folder&&f.rootId===r.rootId))return;if(this.folderHasOpenEditor(t)){this.lastTouch.set(t,Date.now());return}if(!this.keyReady(t)){this.lastTouch.set(t,Date.now());return}await this.plugin.lockNoteSubtree(r.folder,r.rootId,null,{silent:!0})&&new Wt.Notice(`Re-encrypted \u201C${i}\u201D.`)},1e3);this.countdownTimers.add(h),this.plugin.registerInterval(h)}dispose(){for(let r of this.countdownTimers)window.clearInterval(r);this.countdownTimers.clear(),this.counting.clear()}};us.COUNTDOWN_SECS=10,us.RECENT_EDIT_MS=2e4;var Nn=us;function vd(A,r,e){A.set(r.replace(/\/+$/,""),e)}var Ce=require("obsidian");le();var Mn=Ce.moment,Tn=class extends Ce.ItemView{constructor(e,t){super(e);this.plugin=t;this.mode="encrypted";this.taskState=bs();this.archiveSubMode="byfolder";this.renderPending=!1;this.watchFolderFilter="";this.watchViaFilter="";this.watchQuery=""}getViewType(){return jt}getDisplayText(){return this.mode==="archived"?"All archived":this.mode==="tasks"?"All tasks":this.mode==="watch"?"Previously encrypted":"All encrypted"}getIcon(){return this.mode==="archived"?"archive":this.mode==="tasks"?"check-square":this.mode==="watch"?"history":"lock"}getState(){return{...super.getState(),mode:this.mode}}async setState(e,t){(e?.mode==="archived"||e?.mode==="encrypted"||e?.mode==="tasks"||e?.mode==="watch")&&(this.mode=e.mode),await super.setState(e,t),await this.render()}async onOpen(){this.containerEl.addClass("stashpad-aggregate-view");let e=t=>t.endsWith(".stashenc")&&!t.startsWith("_deleted/");this.registerEvent(this.app.vault.on("create",t=>{e(t.path)&&this.scheduleRender()})),this.registerEvent(this.app.vault.on("delete",t=>{e(t.path)&&this.scheduleRender()})),await this.render()}scheduleRender(){this.renderPending||(this.renderPending=!0,window.setTimeout(()=>{this.renderPending=!1,this.render()},150))}cleanFolder(e){return(e||"").replace(/\/+$/,"")}archiveFolders(){let e=new Set;for(let i of this.plugin.discoverStashpadFolders()){let s=Ct(i);this.plugin.app.vault.getAbstractFileByPath(s)&&e.add(s)}for(let i of this.plugin.settings.archiveFolders??[])e.add(this.cleanFolder(i));let t=this.plugin.settings.folderEncPrefs??{};for(let[i,s]of Object.entries(t))s?.archive&&e.add(this.cleanFolder(i));return[...e].sort((i,s)=>i.localeCompare(s))}async render(){let e=this.contentEl;e.empty(),e.addClass("stashpad-aggregate-body");let t=e.createDiv({cls:"stashpad-aggregate-header"});t.createEl("h3",{text:this.getDisplayText()});let i=t.createEl("button",{cls:"stashpad-trash-iconbtn"});if((0,Ce.setIcon)(i,"refresh-cw"),i.setAttr("aria-label","Refresh"),i.onclick=()=>void this.render(),this.mode==="tasks"){zi(e.createDiv(),this.app,this.plugin,this.taskState,{onOpen:(s,n)=>void this.plugin.revealNoteByRef(s,n)});return}if(this.mode==="archived"){this.renderArchived(e);return}if(this.mode==="watch"){this.renderWatch(e);return}this.renderEncrypted(e)}renderEncrypted(e){let t=this.plugin.settings.lockedSubtrees??[];if(t.length===0){e.createDiv({cls:"stashpad-trash-empty",text:"Nothing is encrypted yet. Lock a note or folder and it shows up here."});return}let i=new Map;for(let n of t){let o=this.cleanFolder(n.folder)||"(vault root)";(i.get(o)??i.set(o,[]).get(o)).push(n)}let s=[...i.keys()].sort((n,o)=>n.localeCompare(o));e.createDiv({cls:"stashpad-aggregate-sub",text:`${t.length} locked ${t.length===1?"item":"items"} in ${s.length} ${s.length===1?"folder":"folders"}.`});for(let n of s){let o=e.createDiv({cls:"stashpad-trash-group"}),a=o.createDiv({cls:"stashpad-trash-group-head"});(0,Ce.setIcon)(a.createSpan({cls:"stashpad-trash-group-icon"}),"folder"),a.createSpan({text:n.split("/").pop()||n}),a.createSpan({cls:"stashpad-trash-group-count",text:String(i.get(n).length)});let l=a.createEl("button",{cls:"stashpad-trash-iconbtn",text:"Open"});l.onclick=()=>void this.plugin.activateViewForFolder(n);for(let c of i.get(n)){let d=o.createDiv({cls:"stashpad-trash-row"}),h=d.createDiv({cls:"stashpad-trash-row-main"});h.createSpan({cls:"stashpad-trash-title",text:c.title||"Locked note"});let g=c.created?`locked ${Mn(c.created).fromNow()}`:"locked",f=c.count>1?` \xB7 ${c.count} notes`:"";h.createSpan({cls:"stashpad-trash-sub",text:g+f});let p=d.createEl("button",{cls:"stashpad-trash-iconbtn",text:"Export"});p.setAttr("aria-label","Export to .stash (encrypted with a password you choose)"),p.onclick=()=>void this.plugin.exportLockedSubtree(c.blob);let u=d.createSpan({cls:"stashpad-aggregate-lockbadge"});(0,Ce.setIcon)(u,"lock")}}}renderArchived(e){let t=this.archiveFolders();if(t.length===0){e.createDiv({cls:"stashpad-trash-empty",text:"No archive folders yet. Toggle Archive on a folder to collect it here."});return}let i=new Set(t),s=this.plugin.settings.lockedSubtrees??[],n=new Map;for(let u of s){let I=this.cleanFolder(u.folder);i.has(I)&&(n.get(I)??n.set(I,[]).get(I)).push(u)}let o=new Map,a=0;for(let u of t){let I=this.plugin.archivedPlainNotesIn(u);o.set(u,I),a+=I.length}let l=[...n.values()].reduce((u,I)=>u+I.length,0),c=a+l,d=t.flatMap(u=>o.get(u)??[]),h=t.flatMap(u=>n.get(u)??[]);e.createDiv({cls:"stashpad-aggregate-sub",text:`${t.length} archive ${t.length===1?"folder":"folders"}, ${c} archived ${c===1?"item":"items"} (${a} plain \xB7 ${l} locked).`}),bn(e,this.archiveSubMode,{total:c,enc:l,dec:a},u=>{this.archiveSubMode=u,this.render()});let g=this.archiveSubMode;if(g==="mixed"){let u=[];for(let m of d)u.push({ts:m.stat.mtime||0,kind:"plain",f:m});for(let m of h)u.push({ts:m.created&&Date.parse(m.created)||0,kind:"locked",e:m});u.sort((m,C)=>C.ts-m.ts);let I=e.createDiv({cls:"stashpad-trash-group"});for(let m of u)m.kind==="plain"&&m.f?this.archivePlainRow(I,m.f):m.e&&this.archiveLockedRow(I,m.e);return}if(g==="byfolder"){for(let u of t){let I=n.get(u)??[],m=o.get(u)??[],C=e.createDiv({cls:"stashpad-trash-group"}),B=C.createDiv({cls:"stashpad-trash-group-head"});(0,Ce.setIcon)(B.createSpan({cls:"stashpad-trash-group-icon"}),"archive"),B.createSpan({text:u.split("/").pop()||u}),B.createSpan({cls:"stashpad-trash-group-count",text:String(I.length+m.length)});let w=B.createEl("button",{cls:"stashpad-trash-iconbtn",text:"Open"});if(w.onclick=()=>void this.plugin.activateViewForFolder(u),I.length===0&&m.length===0){C.createDiv({cls:"stashpad-trash-sub",text:"No archived items."});continue}for(let y of m)this.archivePlainRow(C,y);for(let y of I)this.archiveLockedRow(C,y)}return}let f=g!=="encrypted"&&d.length>0,p=g!=="unencrypted"&&h.length>0;if(!f&&!p){e.createDiv({cls:"stashpad-trash-empty",text:"Nothing in this view."});return}if(f){let u=e.createDiv({cls:"stashpad-trash-group"}),I=u.createDiv({cls:"stashpad-trash-group-head"});(0,Ce.setIcon)(I.createSpan({cls:"stashpad-trash-group-icon"}),"file-text"),I.createSpan({text:"Plain notes"}),I.createSpan({cls:"stashpad-trash-group-count",text:String(d.length)});for(let m of d)this.archivePlainRow(u,m)}if(p){let u=e.createDiv({cls:"stashpad-trash-group"}),I=u.createDiv({cls:"stashpad-trash-group-head"});(0,Ce.setIcon)(I.createSpan({cls:"stashpad-trash-group-icon"}),"lock"),I.createSpan({text:"Locked (encrypted)"}),I.createSpan({cls:"stashpad-trash-group-count",text:String(h.length)});for(let m of h)this.archiveLockedRow(u,m)}}archivePlainRow(e,t){let i=e.createDiv({cls:"stashpad-trash-row"}),s=i.createDiv({cls:"stashpad-trash-row-main"});s.createSpan({cls:"stashpad-trash-title",text:t.basename.replace(/-[a-z0-9]{4,12}$/,"").replace(/-/g," ").trim()||t.basename}),s.createSpan({cls:"stashpad-trash-sub",text:`archived ${Mn(t.stat.mtime).fromNow()}`});let n=i.createEl("button",{cls:"stashpad-trash-restore",text:"Restore"});(0,Ce.setIcon)(n.createSpan({cls:"stashpad-btn-icon"}),"rotate-ccw"),n.onclick=async()=>{n.disabled=!0,await this.plugin.unarchiveNote(t)?this.render():n.disabled=!1}}archiveLockedRow(e,t){let i=e.createDiv({cls:"stashpad-trash-row"}),s=i.createDiv({cls:"stashpad-trash-row-main"});s.createSpan({cls:"stashpad-trash-title",text:t.title||"Locked note"});let n=t.created?`archived ${Mn(t.created).fromNow()}`:"archived",o=(t.count??0)>1?` \xB7 ${t.count} notes`:"";s.createSpan({cls:"stashpad-trash-sub",text:n+o});let a=i.createEl("button",{cls:"stashpad-trash-restore",text:"Restore"});(0,Ce.setIcon)(a.createSpan({cls:"stashpad-btn-icon"}),"rotate-ccw"),a.setAttr("aria-label","Unlock and restore (needs the password)"),a.onclick=async()=>{a.disabled=!0;let d=t.blob.split("/"),h=d.lastIndexOf("archive"),g=h>0?d.slice(0,h).join("/"):void 0;await this.plugin.unlockBundleAt(t.blob,g?{destFolder:g}:{})?this.render():a.disabled=!1};let l=i.createEl("button",{cls:"stashpad-trash-iconbtn",text:"Export"});l.setAttr("aria-label","Export to .stash (encrypted with a password you choose)"),l.onclick=()=>void this.plugin.exportLockedSubtree(t.blob);let c=i.createSpan({cls:"stashpad-aggregate-lockbadge"});(0,Ce.setIcon)(c,"lock")}renderWatch(e){this.plugin.pruneReEncryptWatch()&&this.plugin.saveSettings();let t=this.plugin.settings.reEncryptWatch??[],i=t.filter(u=>!u.removed),s=t.filter(u=>u.removed);if(t.length===0){e.createDiv({cls:"stashpad-trash-empty",text:"Nothing here yet. When you unlock an encrypted note (or restore one from the trash), it shows up here so you can re-encrypt it later."});return}let n=e.createDiv({cls:"stashpad-trash-modes"}),o=n.createEl("select",{cls:"dropdown"});o.createEl("option",{value:"",text:"All folders"});for(let u of[...new Set(i.map(I=>I.folder))].sort())o.createEl("option",{value:u,text:u});o.value=this.watchFolderFilter,o.onchange=()=>{this.watchFolderFilter=o.value,this.render()};let a=n.createEl("select",{cls:"dropdown"});a.createEl("option",{value:"",text:"Unlocked + restored"}),a.createEl("option",{value:"unlock",text:"Unlocked"}),a.createEl("option",{value:"restore",text:"Restored from trash"}),a.value=this.watchViaFilter,a.onchange=()=>{this.watchViaFilter=a.value,this.render()};let l=n.createEl("input",{type:"text",attr:{placeholder:"Search\u2026"}});l.value=this.watchQuery,l.oninput=()=>{this.watchQuery=l.value,p()};let c=(u,I)=>u.toLowerCase().split(/\s+/).filter(Boolean).every(m=>I.toLowerCase().includes(m)),d=u=>(!this.watchFolderFilter||u.folder===this.watchFolderFilter)&&(!this.watchViaFilter||u.via===this.watchViaFilter)&&(!this.watchQuery.trim()||c(this.watchQuery,`${u.title} ${u.folder}`)),h=async()=>{await this.plugin.saveSettings(),this.render()},g=(u,I,m)=>{let C=u.createDiv({cls:"stashpad-trash-row"}),B=C.createDiv({cls:"stashpad-trash-row-main"});B.createSpan({cls:"stashpad-trash-title",text:I.title||"(untitled)"});let w=Mn(I.unlockedAt).fromNow(),y=I.count>1?` \xB7 ${I.count} notes`:"";if(B.createSpan({cls:"stashpad-trash-sub",text:`${I.via==="restore"?"restored":"unlocked"} ${w} \xB7 ${I.folder.split("/").pop()||I.folder}${y}`}),m){let v=C.createEl("button",{cls:"stashpad-trash-iconbtn",text:"Un-remove"});v.onclick=()=>{delete I.removed,delete I.removedAt,h()}}else{let v=C.createEl("button",{cls:"stashpad-trash-restore",text:"Re-encrypt"});(0,Ce.setIcon)(v.createSpan({cls:"stashpad-btn-icon"}),"lock"),v.onclick=async()=>{v.disabled=!0,await this.plugin.lockNoteSubtree(I.folder,I.rootId,null,{})||(v.disabled=!1),this.render()};let E=C.createEl("input",{type:"number",cls:"stashpad-watch-peek",attr:{placeholder:"auto",title:"Auto re-encrypt after N idle minutes (blank = folder/global default, 0 = off)",min:"0"}});E.value=I.peekMin==null?"":String(I.peekMin),E.onchange=()=>{let S=E.value.trim(),x=Number(S);if(S==="")delete I.peekMin;else if(Number.isFinite(x)&&x>=0)I.peekMin=Math.floor(x);else{E.value=I.peekMin==null?"":String(I.peekMin);return}this.plugin.saveSettings()};let Q=C.createEl("button",{cls:"stashpad-trash-iconbtn"});(0,Ce.setIcon)(Q,"x"),Q.setAttr("aria-label","Remove from watchlist (recoverable below)"),Q.onclick=()=>{I.removed=!0,I.removedAt=new Date().toISOString(),h()}}},f=e.createDiv(),p=()=>{f.empty();let u=i.filter(d).sort((C,B)=>(B.unlockedAt||"").localeCompare(C.unlockedAt||"")),I=f.createDiv({cls:"stashpad-trash-group-head"});if((0,Ce.setIcon)(I.createSpan({cls:"stashpad-trash-group-icon"}),"history"),I.createSpan({text:"Watching"}),I.createSpan({cls:"stashpad-trash-group-count",text:String(u.length)}),i.length>0){let C=I.createEl("button",{cls:"stashpad-trash-iconbtn",text:"Re-encrypt all applicable"});C.onclick=()=>void this.plugin.encryptEverythingApplicable()}u.length===0&&f.createDiv({cls:"stashpad-trash-sub",text:i.length?"No matches for the current filters.":"Nothing being watched."});let m=f.createDiv({cls:"stashpad-trash-group"});for(let C of u)g(m,C,!1);if(s.length>0){let C=f.createEl("details",{cls:"stashpad-watch-removed"});C.createEl("summary",{text:`Removed from watchlist (${s.length})`});let B=C.createDiv({cls:"stashpad-trash-group"});for(let w of s.filter(d))g(B,w,!0)}};p()}async onClose(){this.contentEl.empty()}};async function Qi(A,r){let{workspace:e}=A.app,t=e.getLeavesOfType(jt).find(n=>n.view?.getState?.().mode===r);if(t){e.revealLeaf(t);return}let i=e.getMostRecentLeaf(),s=e.getLeaf("tab");await s.setViewState({type:jt,active:!0,state:{mode:r}}),e.revealLeaf(s),gt(e,s,i,n=>A.registerEvent(n))}le();var W=require("obsidian");le();ye();var Pn=class A extends W.ItemView{constructor(e,t){super(e);this.plugin=t;this.renderTimer=null;this.dividerCleanup=null;this.pinExpanded=new Set;this.folderPinExpanded=new Set;this.homeColorByFolder=new Map}getViewType(){return mt}getDisplayText(){return"Stashpad folders"}getIcon(){return"folders"}async onOpen(){this.render(),this.registerEvent(this.app.workspace.on("active-leaf-change",()=>this.scheduleRender())),this.registerEvent(this.app.workspace.on("layout-change",()=>this.scheduleRender())),this.registerEvent(this.app.vault.on("create",()=>this.scheduleRender())),this.registerEvent(this.app.vault.on("delete",()=>this.scheduleRender())),this.registerEvent(this.app.vault.on("rename",()=>this.scheduleRender())),this.registerEvent(this.app.metadataCache.on("changed",()=>this.scheduleRender()))}refresh(){this.scheduleRender()}scheduleRender(){this.renderTimer==null&&(this.renderTimer=window.setTimeout(()=>{this.renderTimer=null,this.containerEl.isConnected&&this.render()},100))}render(){let e=this.contentEl;e.empty(),e.addClass("stashpad-folderpanel-root");let t=this.clampFrac(this.plugin.settings.folderPanelPinnedFraction??.5),i=e.createDiv({cls:"stashpad-folderpanel-section stashpad-folderpanel-pinned"});i.style.flex=`0 0 ${(t*100).toFixed(2)}%`;let s=i.createDiv({cls:"stashpad-folderpanel-heading stashpad-folderpanel-heading-row"});s.createSpan({cls:"stashpad-folderpanel-heading-title",text:"Pinned"});let n=s.createEl("button",{cls:"stashpad-folderpanel-iconbtn"});(0,W.setIcon)(n,"list"),n.setAttr("aria-label","Pinned view options"),n.onclick=h=>{h.stopPropagation(),this.openPinnedOptionsMenu(h)},this.renderPinned(i.createDiv({cls:"stashpad-folderpanel-list stashpad-folderpanel-pins"}));let o=e.createDiv({cls:"stashpad-folderpanel-divider"});o.createDiv({cls:"stashpad-folderpanel-divider-grip"}),this.attachDividerDrag(e,i,o);let a=e.createDiv({cls:"stashpad-folderpanel-section stashpad-folderpanel-folders"});a.setCssStyles({flex:"1 1 0"});let l=a.createDiv({cls:"stashpad-folderpanel-heading stashpad-folderpanel-heading-row"}),c=l.createSpan({cls:"stashpad-folderpanel-heading-title stashpad-folderpanel-heading-switch",text:"Folders"});c.setAttr("aria-label","Open folder switcher"),c.onmousedown=h=>{h.button===0&&(h.preventDefault(),this.plugin.openFolderPicker())};let d=l.createEl("button",{cls:"stashpad-folderpanel-iconbtn stashpad-folderpanel-heading-newfolder"});if((0,W.setIcon)(d,"folder-plus"),d.setAttr("aria-label","New Stashpad folder (folder switcher)"),d.onmousedown=h=>{h.button===0&&(h.preventDefault(),h.stopPropagation(),this.plugin.openFolderPicker())},this.plugin.encryption?.isConfigured?.()){let h=l.createEl("button",{cls:"stashpad-folderpanel-iconbtn stashpad-folderpanel-heading-trash"});(0,W.setIcon)(h,"trash-2"),h.setAttr("aria-label","Open encrypted trash"),h.onmousedown=g=>{g.button===0&&(g.preventDefault(),g.stopPropagation(),this.plugin.openEncryptedTrash())}}this.renderFolders(a.createDiv({cls:"stashpad-folderpanel-list"}))}clampFrac(e){return Number.isFinite(e)?Math.max(.15,Math.min(.85,e)):.5}attachDividerDrag(e,t,i){let s=null,n=l=>{let c=e.getBoundingClientRect();if(c.height<=0)return;let d=this.clampFrac((l.clientY-c.top)/c.height);s=d,t.style.flex=`0 0 ${(d*100).toFixed(2)}%`},o=()=>{document.removeEventListener("pointermove",n),document.removeEventListener("pointerup",a),document.removeEventListener("pointercancel",a),document.body.removeClass("stashpad-folderpanel-resizing"),this.dividerCleanup=null},a=l=>{try{i.releasePointerCapture(l.pointerId)}catch{}l.type==="pointerup"&&s!=null&&(this.plugin.settings.folderPanelPinnedFraction=s,this.plugin.saveSettings()),o()};this.registerDomEvent(i,"pointerdown",l=>{l.preventDefault(),document.body.addClass("stashpad-folderpanel-resizing");try{i.setPointerCapture(l.pointerId)}catch{}document.addEventListener("pointermove",n),document.addEventListener("pointerup",a),document.addEventListener("pointercancel",a),this.dividerCleanup=o})}openPinnedOptionsMenu(e){let t=this.plugin.settings.folderPanelPinnedGrouping??"pin-order",i=new W.Menu;i.addItem(s=>s.setTitle("Sort by pin order").setChecked(t==="pin-order").onClick(()=>void this.setPinnedGrouping("pin-order"))),i.addItem(s=>s.setTitle("Group by folder").setChecked(t==="folder").onClick(()=>void this.setPinnedGrouping("folder"))),i.showAtMouseEvent(e)}async setPinnedGrouping(e){(this.plugin.settings.folderPanelPinnedGrouping??"pin-order")!==e&&(this.plugin.settings.folderPanelPinnedGrouping=e,await this.plugin.saveSettings(),this.render())}renderPinned(e){let t=this.plugin.discoverStashpadFolders().filter(n=>this.folderState(n)==="pinned"),i=this.plugin.listPinnedNotes();if(t.length===0&&i.length===0){e.createDiv({cls:"stashpad-folderpanel-empty",text:"Nothing pinned yet \u2014 pin a note or folder from its right-click menu."});return}if(t.length>0){let n=this.openFolders();for(let o of t)this.renderFolderRow(e,o,n)}if(i.length===0)return;if((this.plugin.settings.folderPanelPinnedGrouping??"pin-order")==="folder"){let n=new Map;i.forEach((l,c)=>{let d=n.get(l.folder);d||(d=[],n.set(l.folder,d)),d.push({...l,idx:c})});let o=this.plugin.lastActiveStashpadLeaf?.view?.noteFolder,a=Array.from(n.keys());o&&n.has(o)&&(a.splice(a.indexOf(o),1),a.unshift(o));for(let l of a){let c=e.createDiv({cls:"stashpad-pinned-group-header"});l===o&&c.addClass("is-active-folder"),c.createSpan({cls:"stashpad-pinned-group-name",text:l.split("/").pop()||l});for(let d of n.get(l)??[])this.renderPinNote(e,d.folder,d.id,d.file,d.idx)}}else i.forEach((n,o)=>this.renderPinNote(e,n.folder,n.id,n.file,o))}async reorderPin(e,t){let i=this.plugin.listPinnedNotes();if(e<0||e>=i.length)return;let s=i[e],n=i.filter((d,h)=>h!==e),o=Math.max(0,Math.min(t>e?t-1:t,n.length)),a=n[o-1],l=n[o],c;!a&&!l?c=Date.now():a?l?c=(a.pinnedAt+l.pinnedAt)/2:c=a.pinnedAt+1e3:c=l.pinnedAt-1e3;try{await this.app.fileManager.processFrontMatter(s.file,d=>{d.pinnedAt=c})}catch(d){console.warn("[Stashpad] pin reorder failed",d)}this.render()}renderPinNote(e,t,i,s,n){let o=this.app.metadataCache.getFileCache(s)?.frontmatter??{},a=typeof o.color=="string"?o.color:null,l=o.completed===!0,c=this.childrenOf(t,i),d=c.length>0,h=`${t}|${i}`,g=this.pinExpanded.has(h),f=e.createDiv({cls:"stashpad-pinned-row"});a&&(f.addClass("has-color"),f.style.setProperty("--stashpad-note-color",a)),l&&f.addClass("is-completed"),f.draggable=!0,f.dataset.pinIdx=String(n),f.addEventListener("dragstart",m=>{m.dataTransfer?.setData("text/plain",String(n)),m.dataTransfer&&(m.dataTransfer.effectAllowed="move"),f.addClass("is-dragging")}),f.addEventListener("dragend",()=>f.removeClass("is-dragging")),f.addEventListener("dragover",m=>{m.preventDefault(),m.dataTransfer&&(m.dataTransfer.dropEffect="move");let C=f.getBoundingClientRect(),B=m.clientY-C.top<C.height/2;f.toggleClass("drop-before",B),f.toggleClass("drop-after",!B)}),f.addEventListener("dragleave",()=>{f.removeClass("drop-before"),f.removeClass("drop-after")}),f.addEventListener("drop",m=>{m.preventDefault(),f.removeClass("drop-before"),f.removeClass("drop-after");let C=parseInt(m.dataTransfer?.getData("text/plain")??"",10);if(!Number.isFinite(C)||C===n)return;let B=f.getBoundingClientRect(),w=m.clientY-B.top<B.height/2;this.reorderPin(C,w?n:n+1)});let p=f.createSpan({cls:"stashpad-pinned-toggle"});d&&(yt(p,c.length,g),p.onclick=m=>{m.stopPropagation(),this.pinExpanded.has(h)?this.pinExpanded.delete(h):this.pinExpanded.add(h),this.render()});let u=f.createSpan({cls:"stashpad-pinned-icon"});(0,W.setIcon)(u,d?"folder-tree":"file-text"),a&&(u.style.color=a);let I=f.createSpan({cls:"stashpad-pinned-label",text:this.titleFromFile(s)});if(I.onclick=()=>{this.onNavigateAway(),this.plugin.revealNoteInStashpad(s)},f.createSpan({cls:"stashpad-pinned-folder",text:t.split("/").pop()||t}),f.oncontextmenu=m=>{m.preventDefault();let C=new W.Menu;C.addItem(B=>B.setTitle("Unpin from sidebar").setIcon("pin-off").onClick(()=>void this.plugin.unpinNote({folder:t,id:i}))),C.showAtMouseEvent(m)},d&&g){let m=e.createDiv({cls:"stashpad-pinned-children"});this.renderPinSubtree(m,t,i,1)}}renderPinSubtree(e,t,i,s){for(let n of this.childrenOf(t,i)){let o=this.app.metadataCache.getFileCache(n)?.frontmatter??{},a=typeof o.id=="string"?o.id:null;if(!a)continue;let l=typeof o.color=="string"?o.color:null,c=o.completed===!0,d=this.childrenOf(t,a),h=d.length>0,g=`${t}|${a}`,f=this.pinExpanded.has(g),p=e.createDiv({cls:"stashpad-pinned-subrow"});c&&p.addClass("is-completed"),p.style.paddingLeft=`${s*16}px`;let u=p.createSpan({cls:"stashpad-pinned-toggle"});h&&(yt(u,d.length,f),u.onclick=C=>{C.stopPropagation(),this.pinExpanded.has(g)?this.pinExpanded.delete(g):this.pinExpanded.add(g),this.render()});let I=p.createSpan({cls:"stashpad-pinned-icon"});(0,W.setIcon)(I,"file-text"),l&&(I.style.color=l);let m=p.createSpan({cls:"stashpad-pinned-label",text:this.titleFromFile(n)});m.onclick=()=>{this.onNavigateAway(),this.plugin.revealNoteInStashpad(n)},h&&f&&this.renderPinSubtree(e,t,a,s+1)}}childrenOf(e,t){let i=[];for(let s of this.app.vault.getMarkdownFiles()){if((s.parent?.path?.replace(/\/+$/,"")??"")!==e)continue;let o=this.app.metadataCache.getFileCache(s)?.frontmatter;if(!o||typeof o.id!="string")continue;let a=o.parent;if(a===t||t===F&&(a==null||a===F)){if(o.id===F)continue;i.push(s)}}return i.sort((s,n)=>{let o=this.app.metadataCache.getFileCache(s)?.frontmatter?.created??"",a=this.app.metadataCache.getFileCache(n)?.frontmatter?.created??"";return String(o).localeCompare(String(a))}),i}onNavigateAway(){W.Platform.isMobile&&(this.plugin.suppressComposerAutofocusUntil=Date.now()+1500,this.app.workspace.leftSplit?.collapse?.())}titleFromFile(e){return e.basename.replace(/-[a-z0-9]{4,12}$/,"").replace(/-/g," ").trim()||e.basename}openFolders(){let e=new Set,t=(this.plugin.settings.folder||"Stashpad").replace(/\/+$/,"");for(let i of this.app.workspace.getLeavesOfType(z)){let s=(i.view?.noteFolder??"").replace(/\/+$/,"");s||(s=(((i.getViewState?.()?.state??{}).folderOverride??"")||t).replace(/\/+$/,"")),s&&e.add(s)}return e}static clean(e){return e.replace(/\/+$/,"")}folderState(e){let t=A.clean(e),i=this.plugin.settings;return(i.folderPanelPinned??[]).includes(t)?"pinned":(i.folderPanelDownranked??[]).includes(t)?"downranked":(i.folderPanelHidden??[]).includes(t)?"hidden":"normal"}async setFolderState(e,t){let i=A.clean(e),s=this.plugin.settings;s.folderPanelPinned=(s.folderPanelPinned??[]).filter(n=>n!==i),s.folderPanelDownranked=(s.folderPanelDownranked??[]).filter(n=>n!==i),s.folderPanelHidden=(s.folderPanelHidden??[]).filter(n=>n!==i),t==="pinned"?s.folderPanelPinned.push(i):t==="downranked"?s.folderPanelDownranked.push(i):t==="hidden"&&s.folderPanelHidden.push(i),await this.plugin.saveSettings(),this.render()}folderHomeColor(e){return this.homeColorByFolder.get(A.clean(e))??null}rebuildHomeColors(){this.homeColorByFolder.clear();for(let e of this.app.vault.getMarkdownFiles()){let t=this.app.metadataCache.getFileCache(e)?.frontmatter;if(!t||t.id!==F||typeof t.color!="string"||!t.color.trim())continue;let i=(e.parent?.path??"").replace(/\/+$/,"");i&&this.homeColorByFolder.set(i,t.color)}}renderFolders(e){let t=this.plugin.discoverStashpadFolders();if(t.length===0){e.createDiv({cls:"stashpad-folderpanel-empty",text:"No Stashpad folders yet."});return}this.rebuildHomeColors();let i=this.openFolders(),s=[],n=[],o=[],a=[];for(let c of t)switch(this.folderState(c)){case"pinned":s.push(c);break;case"downranked":o.push(c);break;case"hidden":a.push(c);break;default:n.push(c)}let l=[...s,...n,...o];if(l.length===0&&a.length===0){e.createDiv({cls:"stashpad-folderpanel-empty",text:"No Stashpad folders yet."});return}for(let c of l)this.renderFolderRow(e,c,i);a.length>0&&this.renderHiddenSection(e,a)}renderFolderRow(e,t,i){let s=this.folderState(t),n=i.has(A.clean(t)),o=e.createDiv({cls:"stashpad-folderpanel-row stashpad-folderpanel-folder-row"});n&&o.addClass("is-open"),s==="downranked"&&o.addClass("is-downranked"),s==="pinned"&&o.addClass("is-pinned");let a=o.createSpan({cls:"stashpad-folderpanel-dot"});if(a.setAttr("aria-label",n?"Open in a tab":"Not open"),n&&a.setAttr("title","Open in a tab"),s==="pinned"){let m=o.createSpan({cls:"stashpad-folderpanel-pinmark"});(0,W.setIcon)(m,"pin"),m.setAttr("aria-label","Pinned")}let l=this.folderHomePinnedNotes(t),c=A.clean(t),d=this.folderPinExpanded.has(c);if(l.length>0){let m=o.createSpan({cls:"stashpad-folderpanel-pinreveal"});yt(m,l.length,d),m.setAttr("aria-label",d?"Hide pinned notes":`${l.length} pinned note${l.length===1?"":"s"}`),m.addEventListener("mousedown",C=>{C.stopPropagation(),C.preventDefault()}),m.onclick=C=>{C.stopPropagation(),this.folderPinExpanded.has(c)?this.folderPinExpanded.delete(c):this.folderPinExpanded.add(c),this.render()}}let h=this.plugin.isArchiveFolder(t),g=o.createSpan({cls:"stashpad-folderpanel-folder-icon"});(0,W.setIcon)(g,this.plugin.getFolderIcon(t)??(h?"archive":"folder")),h&&g.setAttr("aria-label","Archive folder \u2014 de-indexed from search");let f=this.folderHomeColor(t);f&&(g.style.color=f);let p=t.split("/").pop()||t;o.createSpan({cls:"stashpad-folderpanel-row-label",text:p});let I=o.createDiv({cls:"stashpad-folderpanel-actions"}).createEl("button",{cls:"stashpad-folderpanel-iconbtn"});if((0,W.setIcon)(I,"plus-square"),I.setAttr("aria-label","Open in new tab"),I.onmousedown=m=>{m.button===0&&(m.preventDefault(),m.stopPropagation(),this.onNavigateAway(),this.plugin.activateViewForFolder(t))},o.addEventListener("mousedown",m=>{m.button===0&&(m.target?.closest?.(".stashpad-folderpanel-actions")||(this.onNavigateAway(),this.jumpToFolder(t)))}),o.oncontextmenu=m=>{m.preventDefault(),this.openFolderMenu(m,t)},l.length>0&&d){let m=e.createDiv({cls:"stashpad-folderpanel-pinlist"});for(let C of l){let B=m.createDiv({cls:"stashpad-folderpanel-pinrow"}),w=this.app.metadataCache.getFileCache(C)?.frontmatter??{},y=B.createSpan({cls:"stashpad-folderpanel-pinrow-icon"});(0,W.setIcon)(y,"arrow-up-to-line"),typeof w.color=="string"&&(y.style.color=w.color),B.createSpan({cls:"stashpad-folderpanel-pinrow-label",text:this.titleFromFile(C)}),B.onclick=()=>{this.onNavigateAway(),this.plugin.revealNoteInStashpad(C)}}}}folderHomePinnedNotes(e){return this.childrenOf(e,F).filter(t=>this.app.metadataCache.getFileCache(t)?.frontmatter?.listPinned===!0)}renderHiddenSection(e,t){let i=e.createDiv({cls:"stashpad-folderpanel-hidden"}),s=i.createDiv({cls:"stashpad-folderpanel-hidden-header"}),n=s.createSpan({cls:"stashpad-folderpanel-hidden-caret"});(0,W.setIcon)(n,"chevron-right"),s.createSpan({cls:"stashpad-folderpanel-hidden-title",text:`Hidden (${t.length})`});let o=i.createDiv({cls:"stashpad-folderpanel-hidden-body"});o.setCssStyles({display:"none"}),s.onclick=()=>{let a=o.style.display!=="none";o.setCssStyles({display:a?"none":""}),(0,W.setIcon)(n,a?"chevron-right":"chevron-down")};for(let a of t){let l=o.createDiv({cls:"stashpad-folderpanel-row stashpad-folderpanel-hidden-row"}),c=a.split("/").pop()||a;l.createSpan({cls:"stashpad-folderpanel-row-label",text:c});let d=l.createEl("button",{cls:"stashpad-folderpanel-iconbtn"});(0,W.setIcon)(d,"eye"),d.setAttr("aria-label","Unhide"),d.onclick=h=>{h.stopPropagation(),this.setFolderState(a,"normal")}}}jumpToFolder(e){this.plugin.openFolderInStashpad(e)}revealFolder(e){let t=this.app.vault.getAbstractFileByPath(e.replace(/\/+$/,""));if(!(t instanceof W.TFolder)){new W.Notice("Couldn't find that folder.");return}let i=this.app.workspace.getLeavesOfType("file-explorer")[0];if(!i){new W.Notice("File explorer isn't available.");return}this.app.workspace.revealLeaf(i),i.view?.revealInFolder?.(t)}openFolderMenu(e,t){let i=new W.Menu;i.addItem(n=>n.setTitle("Open in new tab").setIcon("plus-square").onClick(()=>void this.plugin.activateViewForFolder(t))),i.addItem(n=>n.setTitle("Reveal in file explorer").setIcon("folder-search").onClick(()=>this.revealFolder(t))),i.addSeparator();let s=this.folderState(t);i.addItem(n=>n.setTitle(s==="pinned"?"Unpin":"Pin to top").setIcon("pin").onClick(()=>void this.setFolderState(t,s==="pinned"?"normal":"pinned"))),i.addItem(n=>n.setTitle(s==="downranked"?"Remove downrank":"Downrank").setIcon("arrow-down").onClick(()=>void this.setFolderState(t,s==="downranked"?"normal":"downranked"))),i.addItem(n=>n.setTitle("Hide from list").setIcon("eye-off").onClick(()=>void this.setFolderState(t,"hidden"))),i.addSeparator(),i.addItem(n=>n.setTitle("Rename\u2026").setIcon("pencil").onClick(()=>this.renameFolder(t))),this.plugin.encryption?.isConfigured?.()&&(i.addSeparator(),i.addItem(n=>n.setTitle("Encrypt (lock) all notes in folder").setIcon("lock").onClick(()=>void this.plugin.lockFolder(t))),this.app.vault.getFiles().some(n=>n.extension==="stashenc"&&(n.parent?.path?.replace(/\/+$/,"")??"")===t.replace(/\/+$/,""))&&i.addItem(n=>n.setTitle("Decrypt (unlock) all notes in folder").setIcon("unlock").onClick(()=>void this.plugin.unlockFolder(t))),i.addItem(n=>n.setTitle("Open encrypted trash").setIcon("rotate-ccw").onClick(()=>this.plugin.openEncryptedTrash()))),i.addSeparator(),i.addItem(n=>{n.setTitle("Delete folder\u2026").setIcon("trash").onClick(()=>this.deleteFolder(t)),n.setWarning?.(!0)}),i.showAtMouseEvent(e)}renameFolder(e){let t=e.replace(/\/+$/,""),i=this.app.vault.getAbstractFileByPath(t);if(!(i instanceof W.TFolder)){new W.Notice("Couldn't find that folder.");return}let s=i.name;new cr(this.app,s,async n=>{let o=n.trim().replace(/[\\/:]+/g,"").trim();if(!o||o===s)return;let l=`${i.parent?.path&&i.parent.path!=="/"?`${i.parent.path}/`:""}${o}`;if(this.app.vault.getAbstractFileByPath(l)){new W.Notice(`"${o}" already exists.`);return}if(!(this.app.vault.getAbstractFileByPath(t)instanceof W.TFolder)){new W.Notice("That folder no longer exists.");return}try{await this.app.fileManager.renameFile(i,l),await this.plugin.remapFolderPathInSettings(t,l),new W.Notice(`Renamed to "${o}".`)}catch(c){console.warn("[Stashpad] folder rename failed",c),new W.Notice("Rename failed (see console).")}}).open()}deleteFolder(e){let t=e.replace(/\/+$/,""),i=this.app.vault.getAbstractFileByPath(t);if(!(i instanceof W.TFolder)){new W.Notice("Couldn't find that folder.");return}let s=this.app.vault.getMarkdownFiles().filter(o=>(o.parent?.path?.replace(/\/+$/,"")??"")===t||o.path.startsWith(t+"/")).length,n=i.name;new ie(this.app,`Delete "${n}"?`,`This moves the entire folder \u2014 about ${s} note${s===1?"":"s"} plus its attachments and exports \u2014 to the trash. +You can restore it from your system/Obsidian trash.`,"Delete folder",async o=>{o&&await this.plugin.deleteStashpadFolderWithUndo(i)}).open()}async onClose(){this.dividerCleanup?.(),this.contentEl.empty()}},cr=class extends W.Modal{constructor(e,t,i){super(e);this.current=t;this.onSubmit=i;this.delivered=!1}onOpen(){this.modalEl.addClass("stashpad-compact-modal"),this.contentEl.empty(),this.titleEl.setText("Rename folder");let e=this.contentEl.createEl("input",{type:"text"});e.addClass("stashpad-folderpanel-rename-input"),e.value=this.current;let t=this.contentEl.createDiv({cls:"stashpad-folderpanel-rename-footer"});t.createEl("button",{text:"Cancel"}).onclick=()=>this.close();let i=t.createEl("button",{cls:"mod-cta",text:"Rename"}),s=()=>{this.delivered=!0;let n=e.value;this.close(),this.onSubmit(n)};i.onclick=s,this.scope.register([],"Enter",n=>{n.preventDefault(),e.value.trim()&&s()}),requestAnimationFrame(()=>{e.focus(),e.select()})}onClose(){this.contentEl.empty()}};async function Hn(A){let r=A.workspace.getLeavesOfType(mt);if(r.length>0){A.workspace.revealLeaf(r[0]);return}let e=A.workspace.getLeftLeaf(!1);if(!e){new W.Notice("Stashpad: couldn't open the folder panel.");return}await e.setViewState({type:mt,active:!0}),A.workspace.revealLeaf(e)}ye();ri();le();var dr="OKF Template.md",Fd=`--- okfType: concept --- {{body}} -`,jo=["> Generated by Stashpad \u2014 this folder is an Open Knowledge Format (OKF) bundle.","> Field legend: `okfType`\u2192`type`, `okfTitle`\u2192`title`, `okfDescription`\u2192`description`, `okfTags`\u2192`tags`, `okfTimestamp`\u2192`timestamp`; `okfParent`/`okfChildren` are relative-markdown cross-links. Stashpad's own `id`/`parent` are extensions \u2014 ignore them for OKF."].join(` -`);function ls(A){return A.basename.replace(/-[a-z0-9]{4,12}$/,"").replace(/-/g," ").trim()||A.basename}function rl(A){return`[${ls(A)}](${encodeURI(A.name)})`}async function ll(A,r=Zo){try{await A.vault.adapter.exists(r)||await A.vault.create(r,sd)}catch{}return r}function cl(A,r){if(!r)return[];let e=r.replace(/^\/+/,"");return Object.entries(A??{}).filter(([,t])=>(t??"").replace(/^\/+/,"")===e).map(([t])=>t.replace(/\/+$/,""))}async function _o(A,r){let e=r.replace(/\/+$/,""),t=new Map,i=new Map;for(let o of A.vault.getMarkdownFiles()){if((o.parent?.path?.replace(/\/+$/,"")??"")!==e)continue;let a;try{a=he(await A.vault.read(o)).fm}catch{continue}let l=typeof a.id=="string"?a.id:null;if(!l)continue;let c=typeof a.parent=="string"?a.parent:k;t.set(l,{file:o,id:l,parent:c,modified:typeof a.modified=="string"?a.modified:"",created:typeof a.created=="string"?a.created:""});let d=i.get(c)??[];d.push(l),i.set(c,d)}let s=0,n=0;for(let o of t.values()){if(o.id===k)continue;s+=1;let a=o.parent&&o.parent!==k&&t.get(o.parent)?rl(t.get(o.parent).file):null,l=(i.get(o.id)??[]).map(c=>t.get(c)).filter(c=>!!c).map(c=>rl(c.file));try{let c=!1;await A.fileManager.processFrontMatter(o.file,d=>{a?d.okfParent!==a&&(d.okfParent=a,c=!0):d.okfParent!==void 0&&(delete d.okfParent,c=!0);let h=Array.isArray(d.okfChildren)?d.okfChildren:[],g=h.length===l.length&&h.every((u,p)=>u===l[p]);if(l.length?g||(d.okfChildren=l,c=!0):d.okfChildren!==void 0&&(delete d.okfChildren,c=!0),d.okfType===void 0&&(d.okfType="concept",c=!0),d.okfTitle===void 0&&(d.okfTitle=ls(o.file),c=!0),d.okfTimestamp===void 0){let u=o.modified||o.created;u&&(d.okfTimestamp=u,c=!0)}}),c&&(n+=1,await new Promise(d=>setTimeout(d,50)))}catch(c){console.warn("[Stashpad] OKF rebuild failed",o.file.path,c)}}try{await A.vault.adapter.write(`${e}/index.md`,await ea(A,e))}catch(o){console.warn("[Stashpad] OKF index.md write failed",e,o)}return{checked:s,written:n}}async function ea(A,r,e){let t=r.replace(/\/+$/,""),i=new Map,s=new Map;for(let d of A.vault.getMarkdownFiles()){if((d.parent?.path?.replace(/\/+$/,"")??"")!==t||d.name==="index.md")continue;let h;try{h=he(await A.vault.read(d)).fm}catch{continue}let g=typeof h.id=="string"?h.id:null;!g||g===k||e&&!e.has(g)||i.set(g,{id:g,file:d,parent:typeof h.parent=="string"?h.parent:k,title:typeof h.okfTitle=="string"&&h.okfTitle?h.okfTitle:ls(d),type:typeof h.okfType=="string"&&h.okfType?h.okfType:"concept",position:typeof h.position=="number"?h.position:Number.MAX_SAFE_INTEGER,created:typeof h.created=="string"?h.created:""})}for(let d of i.values()){let h=s.get(d.parent)??[];h.push(d),s.set(d.parent,h)}for(let d of s.values())d.sort((h,g)=>h.position-g.position||h.created.localeCompare(g.created));let n=[],o=(d,h,g)=>{if(!g.has(d.id)){g.add(d.id),n.push(`${" ".repeat(h)}- [${d.title}](${encodeURI(d.file.name)}) \u2014 ${d.type}`);for(let u of s.get(d.id)??[])o(u,h+1,g)}},a=new Set,l=[...i.values()].filter(d=>d.parent===k||!i.has(d.parent)).sort((d,h)=>d.position-h.position||d.created.localeCompare(h.created));for(let d of l)o(d,0,a);let c=t.split("/").pop()||t;return["---","type: index",`title: ${c} (OKF index)`,"---","",`# ${c}`,"",jo,"","## Concepts",n.length?n.join(` +`,Ar=["> Generated by Stashpad \u2014 this folder is an Open Knowledge Format (OKF) bundle.","> Field legend: `okfType`\u2192`type`, `okfTitle`\u2192`title`, `okfDescription`\u2192`description`, `okfTags`\u2192`tags`, `okfTimestamp`\u2192`timestamp`; `okfParent`/`okfChildren` are relative-markdown cross-links. Stashpad's own `id`/`parent` are extensions \u2014 ignore them for OKF."].join(` +`);function fs(A){return A.basename.replace(/-[a-z0-9]{4,12}$/,"").replace(/-/g," ").trim()||A.basename}function kl(A){return`[${fs(A)}](${encodeURI(A.name)})`}async function Sl(A,r=dr){try{await A.vault.adapter.exists(r)||await A.vault.create(r,Fd)}catch{}return r}function bl(A,r){if(!r)return[];let e=r.replace(/^\/+/,"");return Object.entries(A??{}).filter(([,t])=>(t??"").replace(/^\/+/,"")===e).map(([t])=>t.replace(/\/+$/,""))}async function hr(A,r){let e=r.replace(/\/+$/,""),t=new Map,i=new Map;for(let o of A.vault.getMarkdownFiles()){if((o.parent?.path?.replace(/\/+$/,"")??"")!==e)continue;let a;try{a=he(await A.vault.read(o)).fm}catch{continue}let l=typeof a.id=="string"?a.id:null;if(!l)continue;let c=typeof a.parent=="string"?a.parent:F;t.set(l,{file:o,id:l,parent:c,modified:typeof a.modified=="string"?a.modified:"",created:typeof a.created=="string"?a.created:""});let d=i.get(c)??[];d.push(l),i.set(c,d)}let s=0,n=0;for(let o of t.values()){if(o.id===F)continue;s+=1;let a=o.parent&&o.parent!==F&&t.get(o.parent)?kl(t.get(o.parent).file):null,l=(i.get(o.id)??[]).map(c=>t.get(c)).filter(c=>!!c).map(c=>kl(c.file));try{let c=!1;await A.fileManager.processFrontMatter(o.file,d=>{a?d.okfParent!==a&&(d.okfParent=a,c=!0):d.okfParent!==void 0&&(delete d.okfParent,c=!0);let h=Array.isArray(d.okfChildren)?d.okfChildren:[],g=h.length===l.length&&h.every((f,p)=>f===l[p]);if(l.length?g||(d.okfChildren=l,c=!0):d.okfChildren!==void 0&&(delete d.okfChildren,c=!0),d.okfType===void 0&&(d.okfType="concept",c=!0),d.okfTitle===void 0&&(d.okfTitle=fs(o.file),c=!0),d.okfTimestamp===void 0){let f=o.modified||o.created;f&&(d.okfTimestamp=f,c=!0)}}),c&&(n+=1,await new Promise(d=>setTimeout(d,50)))}catch(c){console.warn("[Stashpad] OKF rebuild failed",o.file.path,c)}}try{await A.vault.adapter.write(`${e}/index.md`,await pr(A,e))}catch(o){console.warn("[Stashpad] OKF index.md write failed",e,o)}return{checked:s,written:n}}async function pr(A,r,e){let t=r.replace(/\/+$/,""),i=new Map,s=new Map;for(let d of A.vault.getMarkdownFiles()){if((d.parent?.path?.replace(/\/+$/,"")??"")!==t||d.name==="index.md")continue;let h;try{h=he(await A.vault.read(d)).fm}catch{continue}let g=typeof h.id=="string"?h.id:null;!g||g===F||e&&!e.has(g)||i.set(g,{id:g,file:d,parent:typeof h.parent=="string"?h.parent:F,title:typeof h.okfTitle=="string"&&h.okfTitle?h.okfTitle:fs(d),type:typeof h.okfType=="string"&&h.okfType?h.okfType:"concept",position:typeof h.position=="number"?h.position:Number.MAX_SAFE_INTEGER,created:typeof h.created=="string"?h.created:""})}for(let d of i.values()){let h=s.get(d.parent)??[];h.push(d),s.set(d.parent,h)}for(let d of s.values())d.sort((h,g)=>h.position-g.position||h.created.localeCompare(g.created));let n=[],o=(d,h,g)=>{if(!g.has(d.id)){g.add(d.id),n.push(`${" ".repeat(h)}- [${d.title}](${encodeURI(d.file.name)}) \u2014 ${d.type}`);for(let f of s.get(d.id)??[])o(f,h+1,g)}},a=new Set,l=[...i.values()].filter(d=>d.parent===F||!i.has(d.parent)).sort((d,h)=>d.position-h.position||d.created.localeCompare(h.created));for(let d of l)o(d,0,a);let c=t.split("/").pop()||t;return["---","type: index",`title: ${c} (OKF index)`,"---","",`# ${c}`,"",Ar,"","## Concepts",n.length?n.join(` `):"_No concepts yet._",""].join(` -`)}var nd=new TextEncoder,Qn=A=>nd.encode(A);function cs(A){if(typeof A=="number"||typeof A=="boolean")return String(A);let r=String(A??"");return/[:#[\]{}",&*!|>%@`]|^\s|\s$|^$/.test(r)?JSON.stringify(r):r}function od(A,r){let{fm:e}=he(A),t=[];t.push(`type: ${cs(typeof e.okfType=="string"&&e.okfType?e.okfType:"concept")}`),t.push(`title: ${cs(typeof e.okfTitle=="string"&&e.okfTitle?e.okfTitle:ls(r))}`),typeof e.okfDescription=="string"&&e.okfDescription&&t.push(`description: ${cs(e.okfDescription)}`);let i=e.okfTags;if(Array.isArray(i)&&i.length){t.push("tags:");for(let a of i)t.push(` - ${cs(a)}`)}let s=typeof e.okfTimestamp=="string"&&e.okfTimestamp||typeof e.modified=="string"&&e.modified||typeof e.created=="string"&&e.created||"";s&&t.push(`timestamp: ${cs(s)}`);let n=t.join(` +`)}var kd=new TextEncoder,Rn=A=>kd.encode(A);function Is(A){if(typeof A=="number"||typeof A=="boolean")return String(A);let r=String(A??"");return/[:#[\]{}",&*!|>%@`]|^\s|\s$|^$/.test(r)?JSON.stringify(r):r}function Sd(A,r){let{fm:e}=he(A),t=[];t.push(`type: ${Is(typeof e.okfType=="string"&&e.okfType?e.okfType:"concept")}`),t.push(`title: ${Is(typeof e.okfTitle=="string"&&e.okfTitle?e.okfTitle:fs(r))}`),typeof e.okfDescription=="string"&&e.okfDescription&&t.push(`description: ${Is(e.okfDescription)}`);let i=e.okfTags;if(Array.isArray(i)&&i.length){t.push("tags:");for(let a of i)t.push(` - ${Is(a)}`)}let s=typeof e.okfTimestamp=="string"&&e.okfTimestamp||typeof e.modified=="string"&&e.modified||typeof e.created=="string"&&e.created||"";s&&t.push(`timestamp: ${Is(s)}`);let n=t.join(` `),o=A.match(/^---\r?\n/);return o?A.slice(0,o[0].length)+n+` `+A.slice(o[0].length):`--- ${n} --- -${A}`}async function dl(A,r,e,t){let i=[],s=new Map;for(let n of r){let o=await A.vault.read(n);i.push({name:n.name,data:Qn(od(o,n))});for(let a of o.match(/!\[\[([^\]]+?)\]\]/g)??[]){let l=a.slice(3,-2).split("|")[0].split("#")[0].trim(),c=A.metadataCache.getFirstLinkpathDest(l,n.path);c&&!s.has(c.name)&&s.set(c.name,c)}}for(let[n,o]of s)i.push({name:`_attachments/${n}`,data:new Uint8Array(await A.vault.readBinary(o))});return i.push({name:"index.md",data:Qn(await ea(A,e,t))}),i.push({name:"_okf.md",data:Qn(`# About this bundle +${A}`}async function Dl(A,r,e,t){let i=[],s=new Map;for(let n of r){let o=await A.vault.read(n);i.push({name:n.name,data:Rn(Sd(o,n))});for(let a of o.match(/!\[\[([^\]]+?)\]\]/g)??[]){let l=a.slice(3,-2).split("|")[0].split("#")[0].trim(),c=A.metadataCache.getFirstLinkpathDest(l,n.path);c&&!s.has(c.name)&&s.set(c.name,c)}}for(let[n,o]of s)i.push({name:`_attachments/${n}`,data:new Uint8Array(await A.vault.readBinary(o))});return i.push({name:"index.md",data:Rn(await pr(A,e,t))}),i.push({name:"_okf.md",data:Rn(`# About this bundle -${jo.replace(/^> /gm,"")} -`)}),i}async function Al(A){let{zipFiles:r}=await Promise.resolve().then(()=>(nn(),br));return r(A.map(e=>({name:e.name,data:e.data})))}function Bi(A,r){return A.toString(8).padStart(r-1,"0")+"\0"}function ad(A,r){let e=new Uint8Array(512),t=(s,n,o)=>{let a=Qn(s);e.set(a.subarray(0,o),n)};t(A.slice(0,100),0,100),t(Bi(420,8),100,8),t(Bi(0,8),108,8),t(Bi(0,8),116,8),t(Bi(r,12),124,12),t(Bi(0,12),136,12),t(" ",148,8),e[156]=48,t("ustar\0",257,6),t("00",263,2);let i=0;for(let s=0;s<512;s++)i+=e[s];return t(Bi(i,8),148,8),e}function rd(A){let r=[],e=0;for(let s of A){let n=ad(s.name,s.data.length);r.push(n,s.data),e+=512+s.data.length;let o=(512-s.data.length%512)%512;o&&(r.push(new Uint8Array(o)),e+=o)}r.push(new Uint8Array(1024)),e+=1024;let t=new Uint8Array(e),i=0;for(let s of r)t.set(s,i),i+=s.length;return t}async function ld(A){let r=new CompressionStream("gzip"),e=new Response(A).body.pipeThrough(r);return new Uint8Array(await new Response(e).arrayBuffer())}async function hl(A){return ld(rd(A))}var En=class{constructor(r,e,t){this.app=r;this.dirOk=!1;this.writeChain=Promise.resolve();this.baseDir=e.replace(/\/+$/,""),this.logPath=`${this.baseDir}/log.jsonl`,this.statePath=`${this.baseDir}/state.json`,this.getAuthor=t??(()=>"")}getLogPath(){return this.logPath}getDir(){return this.baseDir}async ensureDir(){if(this.dirOk)return;let r=this.app.vault.adapter,e=this.baseDir.split("/").filter(Boolean),t="";for(let i of e)t=t?`${t}/${i}`:i,await r.exists(t)||await r.mkdir(t);this.dirOk=!0}append(r){let e=r.author??this.getAuthor()??"",t={ts:new Date().toISOString(),...r};e?t.author=e:delete t.author;let i=JSON.stringify(t)+` -`;return this.writeChain=this.writeChain.then(async()=>{try{await this.ensureDir();let s=this.app.vault.adapter;if(typeof s.append=="function")await s.append(this.logPath,i);else{let n=await s.exists(this.logPath)?await s.read(this.logPath):"";await s.write(this.logPath,n+i)}}catch(s){console.warn("Stashpad: log append failed",s)}}),Promise.resolve()}async readState(){let r=this.app.vault.adapter;if(!await r.exists(this.statePath))return{};try{return JSON.parse(await r.read(this.statePath))}catch{return{}}}async writeState(r){await this.ensureDir(),await this.app.vault.adapter.write(this.statePath,JSON.stringify(r,null,2))}};le();var vn=class{constructor(){this.undoStack=[];this.redoStack=[];this.cap=30}push(r){for(this.undoStack.push(r),this.redoStack.length=0;this.undoStack.length>this.cap;)this.undoStack.shift()}async undo(){let r=this.undoStack.pop();if(!r)return null;try{await r.undo()}catch(e){throw console.error("Stashpad: undo failed",e),e}return this.redoStack.push(r),r}async redo(){let r=this.redoStack.pop();if(!r)return null;if(r.redo)try{await r.redo()}catch(e){throw console.error("Stashpad: redo failed",e),e}return this.undoStack.push(r),r}peekUndoLabel(){return this.undoStack[this.undoStack.length-1]?.label??null}peekRedoLabel(){return this.redoStack[this.redoStack.length-1]?.label??null}canUndo(){return this.undoStack.length>0}canRedo(){return this.redoStack.length>0}clear(){this.undoStack=[],this.redoStack=[]}};var Fn=class{constructor(r,e){this.app=r;this.data={version:1,authors:{}};this.loaded=!1;this.dirOk=!1;this.writeChain=Promise.resolve();this.path=`${e.replace(/\/+$/,"")}/authors.json`}getPath(){return this.path}async load(){if(!this.loaded){this.loaded=!0;try{let r=this.app.vault.adapter;if(await r.exists(this.path)){let e=JSON.parse(await r.read(this.path));e&&typeof e=="object"&&e.authors&&(this.data={version:typeof e.version=="number"?e.version:1,authors:e.authors})}}catch(r){console.warn("[Stashpad] author registry load failed; starting empty",r),this.data={version:1,authors:{}}}}}all(){return Object.values(this.data.authors).sort((r,e)=>(e.lastSeen??"").localeCompare(r.lastSeen??""))}get(r){return this.data.authors[r]??null}record(r){let e=(r.id??"").trim();if(!e)return!1;let t=r.at??new Date().toISOString(),i=(r.name??"").trim(),s=this.data.authors[e],n=!1;if(!s)this.data.authors[e]={id:e,name:i,role:r.role?.trim()||void 0,department:r.department?.trim()||void 0,firstSeen:t,lastSeen:t,renames:[]},n=!0;else{if(i&&i!==s.name&&(s.renames.push({from:s.name,to:i,at:t}),s.name=i,n=!0),r.role!==void 0){let o=r.role.trim()||void 0;o!==s.role&&(s.role=o,n=!0)}if(r.department!==void 0){let o=r.department.trim()||void 0;o!==s.department&&(s.department=o,n=!0)}s.lastSeen=t}return n&&this.save(),n}replaceAll(r,e){let t=e??new Date().toISOString(),i={};for(let s of r){let n=(s.id??"").trim();if(!n)continue;let o=this.data.authors[n],a=(s.name??"").trim()||o?.name||"",l=o?.renames?[...o.renames]:[];o&&a&&a!==o.name&&l.push({from:o.name,to:a,at:t}),i[n]={id:n,name:a,role:s.role?.trim()||o?.role||void 0,department:s.department?.trim()||o?.department||void 0,firstSeen:o?.firstSeen??t,lastSeen:t,renames:l}}this.data={version:1,authors:i},this.save()}async ensureDir(){if(this.dirOk)return;let r=this.app.vault.adapter,t=this.path.slice(0,this.path.lastIndexOf("/")).split("/").filter(Boolean),i="";for(let s of t)i=i?`${i}/${s}`:s,await r.exists(i)||await r.mkdir(i);this.dirOk=!0}save(){return this.writeChain=this.writeChain.then(async()=>{try{await this.ensureDir(),await this.app.vault.adapter.write(this.path,JSON.stringify(this.data,null,2))}catch(r){console.warn("[Stashpad] author registry save failed",r)}}),this.writeChain}};var ee=require("obsidian");le();qe();si();us();Be();var kn=new Set(["_attachments","_authors","_exports","_imports","_processed","_archive",".archive"]),pl="_archive",gl=new Set([nt,"edtz","stashenc","stashmeta"]),yi=class yi{constructor(r){this.plugin=r;this.pending=new Map;this.pendingFolders=new Map;this.suppressed=new Set;this.armed=!1;this.drainTimer=null;this.draining=!1;this.pendingEncryptedStashes=new Set;this.encryptedNotifySnoozeUntil=0}setArmed(r){this.armed=r}isArmed(){return this.armed}get app(){return this.plugin.app}enqueue(r){this.armed&&this.plugin.settings.autoImport&&this.isEligiblePath(r)&&(this.pending.set(r.path,Date.now()),this.arm())}enqueueFolder(r){if(!this.armed||!this.plugin.settings.autoImport||this.suppressed.has(r.path))return;let e=r.parent?.path?.replace(/\/+$/,"")??"";this.plugin.discoverStashpadFolders().includes(e)&&(kn.has(r.name)||(this.pendingFolders.set(r.path,Date.now()),this.arm()))}arm(){this.drainTimer!=null&&window.clearTimeout(this.drainTimer),this.drainTimer=window.setTimeout(()=>void this.drain(),yi.DEBOUNCE_MS)}suppress(r,e=4e3){this.suppressed.add(r),window.setTimeout(()=>this.suppressed.delete(r),e)}async createNote(r,e){this.suppress(r,6e4),await this.app.vault.create(r,e)}isEligiblePath(r){if(this.suppressed.has(r.path)||gl.has(r.extension))return!1;let e=r.parent?.path?.replace(/\/+$/,"")??"",t=e.split("/").pop()??"";return kn.has(t)?!1:this.plugin.discoverStashpadFolders().includes(e)}async isStillImportable(r){if(!this.isEligiblePath(r))return!1;if(r.extension==="md")try{let{fm:e}=he(await this.app.vault.read(r));if(e&&typeof e.id=="string"&&e.id)return!1}catch{}return!0}async drain(){if(this.drainTimer=null,!this.draining){this.draining=!0;try{let r=[...this.pending.keys()];this.pending.clear();let e=r.map(h=>this.app.vault.getAbstractFileByPath(h)).filter(h=>h instanceof ee.TFile),t=[];for(let h of e)await this.isStillImportable(h)&&t.push(h);let i=[...this.pendingFolders.keys()];this.pendingFolders.clear();let s=i.map(h=>this.app.vault.getAbstractFileByPath(h)).filter(h=>h instanceof ee.TFolder).map(h=>({folder:h,files:this.filesUnder(h.path)})).filter(h=>h.files.length>0),n=s.reduce((h,g)=>h+g.files.length,0),o=t.length+n;if(o===0)return;await this.plugin.importLog.load();let a=t,l=[],c=t.filter(h=>this.plugin.importLog.findDuplicate(h.name,h.stat?.size??null));if(c.length>0){let h=c.slice(0,5).map(p=>`\u2022 ${p.name}`).join(` +${Ar.replace(/^> /gm,"")} +`)}),i}async function xl(A){let{zipFiles:r}=await Promise.resolve().then(()=>(mn(),Xa));return r(A.map(e=>({name:e.name,data:e.data})))}function vi(A,r){return A.toString(8).padStart(r-1,"0")+"\0"}function bd(A,r){let e=new Uint8Array(512),t=(s,n,o)=>{let a=Rn(s);e.set(a.subarray(0,o),n)};t(A.slice(0,100),0,100),t(vi(420,8),100,8),t(vi(0,8),108,8),t(vi(0,8),116,8),t(vi(r,12),124,12),t(vi(0,12),136,12),t(" ",148,8),e[156]=48,t("ustar\0",257,6),t("00",263,2);let i=0;for(let s=0;s<512;s++)i+=e[s];return t(vi(i,8),148,8),e}function Dd(A){let r=[],e=0;for(let s of A){let n=bd(s.name,s.data.length);r.push(n,s.data),e+=512+s.data.length;let o=(512-s.data.length%512)%512;o&&(r.push(new Uint8Array(o)),e+=o)}r.push(new Uint8Array(1024)),e+=1024;let t=new Uint8Array(e),i=0;for(let s of r)t.set(s,i),i+=s.length;return t}async function xd(A){let r=new CompressionStream("gzip"),e=new Response(A).body.pipeThrough(r);return new Uint8Array(await new Response(e).arrayBuffer())}async function Nl(A){return xd(Dd(A))}var Un=class{constructor(r,e,t){this.app=r;this.dirOk=!1;this.writeChain=Promise.resolve();this.baseDir=e.replace(/\/+$/,""),this.logPath=`${this.baseDir}/log.jsonl`,this.statePath=`${this.baseDir}/state.json`,this.getAuthor=t??(()=>"")}getLogPath(){return this.logPath}getDir(){return this.baseDir}async ensureDir(){if(this.dirOk)return;let r=this.app.vault.adapter,e=this.baseDir.split("/").filter(Boolean),t="";for(let i of e)t=t?`${t}/${i}`:i,await r.exists(t)||await r.mkdir(t);this.dirOk=!0}append(r){let e=r.author??this.getAuthor()??"",t={ts:new Date().toISOString(),...r};e?t.author=e:delete t.author;let i=JSON.stringify(t)+` +`;return this.writeChain=this.writeChain.then(async()=>{try{await this.ensureDir();let s=this.app.vault.adapter;if(typeof s.append=="function")await s.append(this.logPath,i);else{let n=await s.exists(this.logPath)?await s.read(this.logPath):"";await s.write(this.logPath,n+i)}}catch(s){console.warn("Stashpad: log append failed",s)}}),Promise.resolve()}async readState(){let r=this.app.vault.adapter;if(!await r.exists(this.statePath))return{};try{return JSON.parse(await r.read(this.statePath))}catch{return{}}}async writeState(r){await this.ensureDir(),await this.app.vault.adapter.write(this.statePath,JSON.stringify(r,null,2))}};le();var Gn=class{constructor(){this.undoStack=[];this.redoStack=[];this.cap=30}push(r){for(this.undoStack.push(r),this.redoStack.length=0;this.undoStack.length>this.cap;)this.undoStack.shift()}async undo(){let r=this.undoStack.pop();if(!r)return null;try{await r.undo()}catch(e){throw this.undoStack.push(r),console.error("Stashpad: undo failed",e),e}return this.redoStack.push(r),r}async redo(){let r=this.redoStack.pop();if(!r)return null;if(r.redo)try{await r.redo()}catch(e){throw this.redoStack.push(r),console.error("Stashpad: redo failed",e),e}return this.undoStack.push(r),r}peekUndoLabel(){return this.undoStack[this.undoStack.length-1]?.label??null}peekRedoLabel(){return this.redoStack[this.redoStack.length-1]?.label??null}canUndo(){return this.undoStack.length>0}canRedo(){return this.redoStack.length>0}clear(){this.undoStack=[],this.redoStack=[]}};var Ln=class{constructor(r,e){this.app=r;this.data={version:1,authors:{}};this.loaded=!1;this.dirOk=!1;this.writeChain=Promise.resolve();this.path=`${e.replace(/\/+$/,"")}/authors.json`}getPath(){return this.path}async load(){if(!this.loaded){this.loaded=!0;try{let r=this.app.vault.adapter;if(await r.exists(this.path)){let e=JSON.parse(await r.read(this.path));e&&typeof e=="object"&&e.authors&&(this.data={version:typeof e.version=="number"?e.version:1,authors:e.authors})}}catch(r){console.warn("[Stashpad] author registry load failed; starting empty",r),this.data={version:1,authors:{}}}}}all(){return Object.values(this.data.authors).sort((r,e)=>(e.lastSeen??"").localeCompare(r.lastSeen??""))}get(r){return this.data.authors[r]??null}record(r){let e=(r.id??"").trim();if(!e)return!1;let t=r.at??new Date().toISOString(),i=(r.name??"").trim(),s=this.data.authors[e],n=!1;if(!s)this.data.authors[e]={id:e,name:i,role:r.role?.trim()||void 0,department:r.department?.trim()||void 0,firstSeen:t,lastSeen:t,renames:[]},n=!0;else{if(i&&i!==s.name&&(s.renames.push({from:s.name,to:i,at:t}),s.name=i,n=!0),r.role!==void 0){let o=r.role.trim()||void 0;o!==s.role&&(s.role=o,n=!0)}if(r.department!==void 0){let o=r.department.trim()||void 0;o!==s.department&&(s.department=o,n=!0)}s.lastSeen=t}return n&&this.save(),n}replaceAll(r,e){let t=e??new Date().toISOString(),i={};for(let s of r){let n=(s.id??"").trim();if(!n)continue;let o=this.data.authors[n],a=(s.name??"").trim()||o?.name||"",l=o?.renames?[...o.renames]:[];o&&a&&a!==o.name&&l.push({from:o.name,to:a,at:t}),i[n]={id:n,name:a,role:s.role?.trim()||o?.role||void 0,department:s.department?.trim()||o?.department||void 0,firstSeen:o?.firstSeen??t,lastSeen:t,renames:l}}this.data={version:1,authors:i},this.save()}async ensureDir(){if(this.dirOk)return;let r=this.app.vault.adapter,t=this.path.slice(0,this.path.lastIndexOf("/")).split("/").filter(Boolean),i="";for(let s of t)i=i?`${i}/${s}`:s,await r.exists(i)||await r.mkdir(i);this.dirOk=!0}save(){return this.writeChain=this.writeChain.then(async()=>{try{await this.ensureDir(),await this.app.vault.adapter.write(this.path,JSON.stringify(this.data,null,2))}catch(r){console.warn("[Stashpad] author registry save failed",r)}}),this.writeChain}};var ee=require("obsidian");le();Le();ri();vs();ye();var On=new Set(["_attachments","_authors","_exports","_imports","_processed","_archive",".archive","archive","trash"]),Ml=new Set([ot,"edtz","stashenc","stashmeta"]),Fi=class Fi{constructor(r){this.plugin=r;this.pending=new Map;this.pendingFolders=new Map;this.suppressed=new Set;this.armed=!1;this.drainTimer=null;this.draining=!1;this.pendingEncryptedStashes=new Set;this.encryptedNotifySnoozeUntil=0}setArmed(r){this.armed=r}isArmed(){return this.armed}get app(){return this.plugin.app}importedOriginalsDir(r){let t=Bt(Date.now(),this.plugin.settings).replace(/[/\\:]+/g,"-").replace(/\s+/g," ").trim()||"undated";return`${r}/archive/_imported/${t}`}enqueue(r){this.armed&&this.plugin.settings.autoImport&&this.isEligiblePath(r)&&(this.pending.set(r.path,Date.now()),this.arm())}enqueueFolder(r){if(!this.armed||!this.plugin.settings.autoImport||this.suppressed.has(r.path))return;let e=r.parent?.path?.replace(/\/+$/,"")??"";this.plugin.discoverStashpadFolders().includes(e)&&(On.has(r.name)||(this.pendingFolders.set(r.path,Date.now()),this.arm()))}arm(){this.drainTimer!=null&&window.clearTimeout(this.drainTimer),this.drainTimer=window.setTimeout(()=>void this.drain(),Fi.DEBOUNCE_MS)}suppress(r,e=4e3){this.suppressed.add(r),window.setTimeout(()=>this.suppressed.delete(r),e)}async createNote(r,e){this.suppress(r,6e4),await this.app.vault.create(r,e)}isEligiblePath(r){if(this.suppressed.has(r.path)||Ml.has(r.extension))return!1;let e=r.parent?.path?.replace(/\/+$/,"")??"",t=e.split("/").pop()??"";return On.has(t)?!1:this.plugin.discoverStashpadFolders().includes(e)}async isStillImportable(r){if(!this.isEligiblePath(r))return!1;if(r.extension==="md")try{let{fm:e}=he(await this.app.vault.read(r));if(e&&typeof e.id=="string"&&e.id)return!1}catch{}return!0}async drain(){if(this.drainTimer=null,!this.draining){this.draining=!0;try{let r=[...this.pending.keys()];this.pending.clear();let e=r.map(h=>this.app.vault.getAbstractFileByPath(h)).filter(h=>h instanceof ee.TFile),t=[];for(let h of e)await this.isStillImportable(h)&&t.push(h);let i=[...this.pendingFolders.keys()];this.pendingFolders.clear();let s=i.map(h=>this.app.vault.getAbstractFileByPath(h)).filter(h=>h instanceof ee.TFolder).map(h=>({folder:h,files:this.filesUnder(h.path)})).filter(h=>h.files.length>0),n=s.reduce((h,g)=>h+g.files.length,0),o=t.length+n;if(o===0)return;await this.plugin.importLog.load();let a=t,l=[],c=t.filter(h=>this.plugin.importLog.findDuplicate(h.name,h.stat?.size??null));if(c.length>0){let h=c.slice(0,5).map(p=>`\u2022 ${p.name}`).join(` `),g=c.length>5?` -\u2026and ${c.length-5} more`:"",u=await this.dupChoice(`${c.length} of these look like files you've imported before: +\u2026and ${c.length-5} more`:"",f=await this.dupChoice(`${c.length} of these look like files you've imported before: ${h}${g} -Replace the existing notes, skip the duplicates, or import them anyway as new notes?`);if(u==="skip"){let p=new Set(c.map(f=>f.path));if(a=t.filter(f=>!p.has(f.path)),a.length===0&&s.length===0){new ee.Notice(`Skipped ${c.length} duplicate file(s) \u2014 nothing new to import.`);return}}else if(u==="replace")for(let p of c){let f=this.plugin.importLog.findDuplicate(p.name,p.stat?.size??null);for(let I of f?.notePaths??[]){let m=this.app.vault.getAbstractFileByPath(I);if(m instanceof ee.TFile)try{l.push({path:I,content:await this.app.vault.read(m)}),await this.app.fileManager.trashFile(m)}catch(C){console.warn("[Stashpad] replace: couldn't remove prior note",I,C)}}}}if(o=a.length+n,o>=yi.BATCH_CONFIRM_AT){let h=s.length?` (incl. ${s.length} folder${s.length===1?"":"s"})`:"";if(!await this.confirm("Import many files?",`You're about to auto-import ${o} files into Stashpad${h}. Proceed? -Markdown becomes notes (originals archived); other files move to _attachments with a linking note. Dropped folders move to _archive.`,"Import all")){new ee.Notice(`Import cancelled \u2014 ${o} file(s) left as-is.`);return}}let d=[];for(let h of a)try{let g=h.stat?.size??null,u=h.extension==="md"?await this.importMarkdown(h):await this.importOtherFile(h);u&&(d.push(u),this.plugin.importLog.append({ts:new Date().toISOString(),folder:u.folder,kind:u.kind,originalName:h.name,size:g,sourcePath:h.path,notePaths:u.kind==="folder"?u.notePaths:[u.notePath]}))}catch(g){console.warn("[Stashpad] import failed",h.path,g)}for(let{folder:h}of s)try{let g=await this.importFolder(h);g&&g.kind==="folder"&&(d.push(g),this.plugin.importLog.append({ts:new Date().toISOString(),folder:g.folder,kind:"folder",originalName:g.originalName,size:null,sourcePath:h.path,notePaths:g.notePaths}))}catch(g){console.warn("[Stashpad] folder import failed",h.path,g)}(d.length>0||l.length>0)&&this.announce(d,l)}finally{this.draining=!1}}}async importMarkdown(r){let e=r.parent.path.replace(/\/+$/,""),t=await this.app.vault.read(r),{fm:i,body:s}=he(t),n=`${e}/${pl}`;await this.ensureFolder(n);let o=await this.uniquePath(n,r.name);if(await this.app.fileManager.renameFile(r,o),!(this.app.vault.getAbstractFileByPath(o)instanceof ee.TFile))throw new Error(`archive move failed for ${r.path} \u2014 import aborted to avoid data loss`);let a={};for(let[h,g]of Object.entries(i))Ct.includes(h)||(a[h]=g);a.id=Z(),a.parent=k;let l=this.preservedTimes(i,r);a.created=l.created,l.modified&&(a.modified=l.modified),a.attachments=Array.isArray(i.attachments)?i.attachments:[],i.author!==void 0&&(a.author=i.author),i.contributors!==void 0&&(a.contributors=i.contributors),a.imported=!0;let c=be(s)||r.basename,d=await this.uniquePath(e,De(c,a.id));return await this.createNote(d,St(a,s)),{kind:"md",folder:e,archivePath:o,notePath:d,originalName:r.name}}async importOtherFile(r){let e=r.parent.path.replace(/\/+$/,""),t=`${e}/_attachments`;await this.ensureFolder(t);let i=await this.uniquePath(t,r.name);await this.app.fileManager.renameFile(r,i);let s=r.basename,n=Z(),o={id:n,parent:k,created:new Date().toISOString(),attachments:[We(i)],imported:!0},a=`${s} +Replace the existing notes, skip the duplicates, or import them anyway as new notes?`);if(f==="skip"){let p=new Set(c.map(u=>u.path));if(a=t.filter(u=>!p.has(u.path)),a.length===0&&s.length===0){new ee.Notice(`Skipped ${c.length} duplicate file(s) \u2014 nothing new to import.`);return}}else if(f==="replace")for(let p of c){let u=this.plugin.importLog.findDuplicate(p.name,p.stat?.size??null);for(let I of u?.notePaths??[]){let m=this.app.vault.getAbstractFileByPath(I);if(m instanceof ee.TFile)try{l.push({path:I,content:await this.app.vault.read(m)}),await this.app.fileManager.trashFile(m)}catch(C){console.warn("[Stashpad] replace: couldn't remove prior note",I,C)}}}}if(o=a.length+n,o>=Fi.BATCH_CONFIRM_AT){let h=s.length?` (incl. ${s.length} folder${s.length===1?"":"s"})`:"";if(!await this.confirm("Import many files?",`You're about to auto-import ${o} files into Stashpad${h}. Proceed? +Markdown becomes notes (originals archived); other files move to _attachments with a linking note. Dropped folders move to _archive.`,"Import all")){new ee.Notice(`Import cancelled \u2014 ${o} file(s) left as-is.`);return}}let d=[];for(let h of a)try{let g=h.stat?.size??null,f=h.extension==="md"?await this.importMarkdown(h):await this.importOtherFile(h);f&&(d.push(f),this.plugin.importLog.append({ts:new Date().toISOString(),folder:f.folder,kind:f.kind,originalName:h.name,size:g,sourcePath:h.path,notePaths:f.kind==="folder"?f.notePaths:[f.notePath]}))}catch(g){console.warn("[Stashpad] import failed",h.path,g)}for(let{folder:h}of s)try{let g=await this.importFolder(h);g&&g.kind==="folder"&&(d.push(g),this.plugin.importLog.append({ts:new Date().toISOString(),folder:g.folder,kind:"folder",originalName:g.originalName,size:null,sourcePath:h.path,notePaths:g.notePaths}))}catch(g){console.warn("[Stashpad] folder import failed",h.path,g)}(d.length>0||l.length>0)&&this.announce(d,l)}finally{this.draining=!1}}}async importMarkdown(r){let e=r.parent.path.replace(/\/+$/,""),t=await this.app.vault.read(r),{fm:i,body:s}=he(t),n=this.importedOriginalsDir(e);await this.ensureFolder(n);let o=await this.uniquePath(n,r.name);if(await this.app.fileManager.renameFile(r,o),!(this.app.vault.getAbstractFileByPath(o)instanceof ee.TFile))throw new Error(`archive move failed for ${r.path} \u2014 import aborted to avoid data loss`);let a={};for(let[h,g]of Object.entries(i))Ve.includes(h)||(a[h]=g);a.id=Z(),a.parent=F;let l=this.preservedTimes(i,r);a.created=l.created,l.modified&&(a.modified=l.modified),a.attachments=Array.isArray(i.attachments)?i.attachments:[],i.author!==void 0&&(a.author=i.author),i.contributors!==void 0&&(a.contributors=i.contributors),a.imported=!0;let c=De(s)||r.basename,d=await this.uniquePath(e,xe(c,a.id));return await this.createNote(d,Dt(a,s)),{kind:"md",folder:e,archivePath:o,notePath:d,originalName:r.name}}async importOtherFile(r){let e=r.parent.path.replace(/\/+$/,""),t=`${e}/_attachments`;await this.ensureFolder(t);let i=await this.uniquePath(t,r.name);await this.app.fileManager.renameFile(r,i);let s=r.basename,n=Z(),o={id:n,parent:F,created:new Date().toISOString(),attachments:[We(i)],imported:!0},a=`${s} ![[${i}]] -`,l=be(s)||s,c=await this.uniquePath(e,De(l,n));return await this.createNote(c,St(o,a)),{kind:"file",folder:e,attachmentPath:i,notePath:c,originalName:r.name}}preservedTimes(r,e){let t=n=>typeof n=="string"&&n.trim().length>0&&!Number.isNaN(Date.parse(n)),i=t(r.created)?r.created:e.stat?.ctime?new Date(e.stat.ctime).toISOString():new Date().toISOString(),s=t(r.modified)?r.modified:e.stat?.mtime?new Date(e.stat.mtime).toISOString():null;return{created:i,modified:s}}filesUnder(r){let e=r.replace(/\/+$/,"")+"/";return this.app.vault.getFiles().filter(t=>t.path.startsWith(e))}async importFolder(r){let e=r.parent.path.replace(/\/+$/,""),t=r.name,i=`${e}/${pl}`;await this.ensureFolder(i);let s=await this.uniquePath(i,t);await this.app.fileManager.renameFile(r,s);let n=this.app.vault.getAbstractFileByPath(s),o=new Set;for(let d of this.app.vault.getMarkdownFiles()){if((d.parent?.path?.replace(/\/+$/,"")??"")!==e)continue;let h=this.app.metadataCache.getFileCache(d)?.frontmatter?.id;typeof h=="string"&&h&&o.add(h)}let a=[],l=[],c=await this.createFolderNote(e,t,k,l,o);return n instanceof ee.TFolder&&await this.buildNotesFromArchive(n,e,c,l,o,a),a.length&&new ee.Notice(`Stashpad: ${a.length} attachment name collision(s) renamed on import \u2014 check ![[links]] in the imported notes.`),{kind:"folder",folder:e,archivePath:s,notePaths:l,attachmentPaths:[],originalName:t}}async createFolderNote(r,e,t,i,s){let n=Z();s.add(n);let o={id:n,parent:t,created:new Date().toISOString(),attachments:[]},a=be(e)||e,l=await this.uniquePath(r,De(a,n));return await this.createNote(l,St(o,`${e} -`)),i.push(l),n}async mergeReservedFolder(r,e,t){await this.ensureFolder(e);for(let i of[...r.children])if(i instanceof ee.TFolder)await this.mergeReservedFolder(i,`${e}/${i.name}`,t);else if(i instanceof ee.TFile){let s=`${e}/${i.name}`;await this.app.vault.adapter.exists(s)&&(s=await this.uniquePath(e,i.name),t.push(`${i.name} \u2192 ${s.split("/").pop()}`)),await this.app.fileManager.renameFile(i,s)}}async buildNotesFromArchive(r,e,t,i,s,n){for(let o of[...r.children])if(o instanceof ee.TFolder)if(kn.has(o.name))await this.mergeReservedFolder(o,`${e}/${o.name}`,n);else{let a=await this.createFolderNote(e,o.name,t,i,s);await this.buildNotesFromArchive(o,e,a,i,s,n)}else if(o instanceof ee.TFile)if(o.extension==="md"){let a=await this.app.vault.read(o),{fm:l,body:c}=he(a),d={};for(let[I,m]of Object.entries(l))Ct.includes(I)||(d[I]=m);let h=typeof l.id=="string"&&l.id?l.id:null,g=h&&!s.has(h)?h:Z();s.add(g),d.id=g,d.parent=t;let u=this.preservedTimes(l,o);d.created=u.created,u.modified&&(d.modified=u.modified),d.attachments=Array.isArray(l.attachments)?l.attachments:[];let p=be(c)||o.basename,f=await this.uniquePath(e,De(p,d.id));await this.createNote(f,St(d,c)),i.push(f)}else{if(gl.has(o.extension))continue;{let a=Z(),l={id:a,parent:t,created:new Date().toISOString(),attachments:[We(o.path)]},c=`${o.basename} +`,l=De(s)||s,c=await this.uniquePath(e,xe(l,n));return await this.createNote(c,Dt(o,a)),{kind:"file",folder:e,attachmentPath:i,notePath:c,originalName:r.name}}preservedTimes(r,e){let t=n=>typeof n=="string"&&n.trim().length>0&&!Number.isNaN(Date.parse(n)),i=t(r.created)?r.created:e.stat?.ctime?new Date(e.stat.ctime).toISOString():new Date().toISOString(),s=t(r.modified)?r.modified:e.stat?.mtime?new Date(e.stat.mtime).toISOString():null;return{created:i,modified:s}}filesUnder(r){let e=r.replace(/\/+$/,"")+"/";return this.app.vault.getFiles().filter(t=>t.path.startsWith(e))}async importFolder(r){let e=r.parent.path.replace(/\/+$/,""),t=r.name,i=this.importedOriginalsDir(e);await this.ensureFolder(i);let s=await this.uniquePath(i,t);await this.app.fileManager.renameFile(r,s);let n=this.app.vault.getAbstractFileByPath(s),o=new Set;for(let d of this.app.vault.getMarkdownFiles()){if((d.parent?.path?.replace(/\/+$/,"")??"")!==e)continue;let h=this.app.metadataCache.getFileCache(d)?.frontmatter?.id;typeof h=="string"&&h&&o.add(h)}let a=[],l=[],c=await this.createFolderNote(e,t,F,l,o);return n instanceof ee.TFolder&&await this.buildNotesFromArchive(n,e,c,l,o,a),a.length&&new ee.Notice(`Stashpad: ${a.length} attachment name collision(s) renamed on import \u2014 check ![[links]] in the imported notes.`),{kind:"folder",folder:e,archivePath:s,notePaths:l,attachmentPaths:[],originalName:t}}async createFolderNote(r,e,t,i,s){let n=Z();s.add(n);let o={id:n,parent:t,created:new Date().toISOString(),attachments:[]},a=De(e)||e,l=await this.uniquePath(r,xe(a,n));return await this.createNote(l,Dt(o,`${e} +`)),i.push(l),n}async mergeReservedFolder(r,e,t){await this.ensureFolder(e);for(let i of[...r.children])if(i instanceof ee.TFolder)await this.mergeReservedFolder(i,`${e}/${i.name}`,t);else if(i instanceof ee.TFile){let s=`${e}/${i.name}`;await this.app.vault.adapter.exists(s)&&(s=await this.uniquePath(e,i.name),t.push(`${i.name} \u2192 ${s.split("/").pop()}`)),await this.app.fileManager.renameFile(i,s)}}async buildNotesFromArchive(r,e,t,i,s,n){for(let o of[...r.children])if(o instanceof ee.TFolder)if(On.has(o.name))await this.mergeReservedFolder(o,`${e}/${o.name}`,n);else{let a=await this.createFolderNote(e,o.name,t,i,s);await this.buildNotesFromArchive(o,e,a,i,s,n)}else if(o instanceof ee.TFile)if(o.extension==="md"){let a=await this.app.vault.read(o),{fm:l,body:c}=he(a),d={};for(let[I,m]of Object.entries(l))Ve.includes(I)||(d[I]=m);let h=typeof l.id=="string"&&l.id?l.id:null,g=h&&!s.has(h)?h:Z();s.add(g),d.id=g,d.parent=t;let f=this.preservedTimes(l,o);d.created=f.created,f.modified&&(d.modified=f.modified),d.attachments=Array.isArray(l.attachments)?l.attachments:[];let p=De(c)||o.basename,u=await this.uniquePath(e,xe(p,d.id));await this.createNote(u,Dt(d,c)),i.push(u)}else{if(Ml.has(o.extension))continue;{let a=Z(),l={id:a,parent:t,created:new Date().toISOString(),attachments:[We(o.path)]},c=`${o.basename} ![[${o.path}]] -`,d=be(o.basename)||o.basename,h=await this.uniquePath(e,De(d,a));await this.createNote(h,St(l,c)),i.push(h)}}}recordNotePaths(r){return r.kind==="folder"?r.notePaths:[r.notePath]}announce(r,e=[]){let t=r[0]?.folder??"",i=r.length,s=e.length?`, replaced ${e.length}`:"",n=!1,o=async()=>{n||(n=!0,await this.undo(r,e))};this.plugin.notifications.show({message:`Imported ${i} file${i===1?"":"s"}${s} into \`${t.split("/").pop()}\`.`,kind:"success",category:"import",duration:0,folder:t,affectedPaths:r.flatMap(a=>this.recordNotePaths(a)),actions:[{label:"Undo import",onClick:()=>void o()}]}),t&&this.plugin.getUndoStack(t).push({label:`Import ${i} file${i===1?"":"s"}`,undo:async()=>{await o()},redo:async()=>{}})}async undo(r,e=[]){for(let t of r)try{if(t.kind==="md"){let i=this.app.vault.getAbstractFileByPath(t.notePath);i instanceof ee.TFile&&await this.app.fileManager.trashFile(i);let s=this.app.vault.getAbstractFileByPath(t.archivePath);if(s instanceof ee.TFile){let n=await this.uniquePath(t.folder,t.originalName);this.suppress(n),await this.app.fileManager.renameFile(s,n)}}else if(t.kind==="file"){let i=this.app.vault.getAbstractFileByPath(t.notePath);i instanceof ee.TFile&&await this.app.fileManager.trashFile(i);let s=this.app.vault.getAbstractFileByPath(t.attachmentPath);if(s instanceof ee.TFile){let n=await this.uniquePath(t.folder,t.originalName);this.suppress(n),await this.app.fileManager.renameFile(s,n)}}else{for(let s of t.notePaths){let n=this.app.vault.getAbstractFileByPath(s);n instanceof ee.TFile&&await this.app.fileManager.trashFile(n)}for(let s of t.attachmentPaths){let n=this.app.vault.getAbstractFileByPath(s);n instanceof ee.TFile&&await this.app.fileManager.trashFile(n)}let i=this.app.vault.getAbstractFileByPath(t.archivePath);if(i instanceof ee.TFolder){let s=await this.uniquePath(t.folder,t.originalName);this.suppress(s),await this.app.fileManager.renameFile(i,s)}}}catch(i){console.warn("[Stashpad] import undo failed",t,i)}for(let t of e)try{await this.app.vault.adapter.exists(t.path)||await this.app.vault.create(t.path,t.content)}catch(i){console.warn("[Stashpad] import undo: couldn't restore replaced note",t.path,i)}new ee.Notice(`Undid import of ${r.length} file(s).`)}pickFilesInto(r){let e=document.createElement("input");e.type="file",e.multiple=!0,e.setCssStyles({display:"none"}),e.onchange=async()=>{let t=Array.from(e.files??[]);if(e.remove(),t.length===0)return;let i=[];for(let n of t)try{let o=await n.arrayBuffer(),a=await this.uniquePath(r,n.name);this.suppress(a,6e4),await this.app.vault.createBinary(a,o);let l=this.app.vault.getAbstractFileByPath(a);l instanceof ee.TFile&&i.push(l)}catch(o){console.warn("[Stashpad] file-picker write failed",n.name,o)}if(i.length===0)return;await this.plugin.importLog.load();let s=[];for(let n of i)try{let o=n.stat?.size??null,a=n.extension==="md"?await this.importMarkdown(n):await this.importOtherFile(n);a&&(s.push(a),this.plugin.importLog.append({ts:new Date().toISOString(),folder:a.folder,kind:a.kind,originalName:n.name,size:o,sourcePath:n.path,notePaths:a.kind==="folder"?a.notePaths:[a.notePath]}))}catch(o){console.warn("[Stashpad] file-picker import failed",n.path,o)}s.length>0&&this.announce(s,[])},document.body.appendChild(e),e.click()}async importLooseFilesIn(r){let e=r.replace(/\/+$/,""),t=this.app.vault.getFiles().filter(n=>(n.parent?.path?.replace(/\/+$/,"")??"")===e),i=[];for(let n of t)await this.isStillImportable(n)&&i.push(n);if(i.length===0)return 0;await this.plugin.importLog.load();let s=0;for(let n of i)try{let o=n.stat?.size??null,a=n.extension==="md"?await this.importMarkdown(n):await this.importOtherFile(n);a&&(s++,this.plugin.importLog.append({ts:new Date().toISOString(),folder:a.folder,kind:a.kind,originalName:n.name,size:o,sourcePath:n.path,notePaths:a.kind==="folder"?a.notePaths:[a.notePath]}))}catch(o){console.warn("[Stashpad] loose-file import failed",n.path,o)}return s}async importLooseFoldersIn(r){let e=r.replace(/\/+$/,""),t=this.app.vault.getAbstractFileByPath(e);if(!(t instanceof ee.TFolder))return 0;let i=t.children.filter(n=>n instanceof ee.TFolder&&!kn.has(n.name));if(i.length===0)return 0;await this.plugin.importLog.load();let s=0;for(let n of i){let o=n.path;try{let a=await this.importFolder(n);a&&a.kind==="folder"&&(s++,this.plugin.importLog.append({ts:new Date().toISOString(),folder:a.folder,kind:a.kind,originalName:n.name,size:null,sourcePath:o,notePaths:a.notePaths}))}catch(a){console.warn("[Stashpad] loose-folder import failed",o,a)}}return s}pendingEncryptedPaths(){for(let r of[...this.pendingEncryptedStashes])this.app.vault.getAbstractFileByPath(r)instanceof ee.TFile||this.pendingEncryptedStashes.delete(r);return[...this.pendingEncryptedStashes]}clearPendingEncrypted(r){this.pendingEncryptedStashes.delete(r)}shouldNotifyEncrypted(){return Date.now()>=this.encryptedNotifySnoozeUntil}snoozeEncryptedNotify(r){this.encryptedNotifySnoozeUntil=r===1/0?Number.MAX_SAFE_INTEGER:Date.now()+r}parkEncrypted(r){return this.pendingEncryptedStashes.has(r)?!1:(this.pendingEncryptedStashes.add(r),this.encryptedNotifySnoozeUntil=0,!0)}async importPendingEncrypted(){let r=0,e=!1;for(let t of this.pendingEncryptedPaths()){let i=this.app.vault.getAbstractFileByPath(t);if(!(i instanceof ee.TFile))continue;let s=i.parent?.path?.replace(/\/+$/,"")??"";if(await this.importOneStash(i,s,this.liveRootIds(s),!1,{allowLater:!0,onLater:()=>{e=!0,this.snoozeEncryptedNotify(60*60*1e3)}})&&r++,e)break}return{imported:r,rescheduled:e}}liveRootIds(r){let e=new Set;for(let t of this.app.vault.getMarkdownFiles()){if((t.parent?.path?.replace(/\/+$/,"")??"")!==r)continue;let i=this.app.metadataCache.getFileCache(t)?.frontmatter?.id;typeof i=="string"&&i&&e.add(i)}return e}async importOneStash(r,e,t,i,s={}){try{let n=new Uint8Array(await this.app.vault.readBinary(r));if(i&&Ne(n))return this.pendingEncryptedStashes.add(r.path),!1;let o=await li(this.app,n,{...s,secretId:jt(r.basename)});if(!o)return!1;let a=await ot(this.app,o,e,t);if(a.colorAliases)for(let[l,c]of Object.entries(a.colorAliases))try{await this.plugin.setColorAlias(e,l,c)}catch{}try{await this.app.fileManager.trashFile(r)}catch{}this.pendingEncryptedStashes.delete(r.path);try{await this.plugin.newLog().append({type:"stash_import",id:k,payload:{from:r.path,into:e,noteCount:a.notesWritten,attachmentsWritten:a.attachmentsWritten,collisionsRenamed:a.collisionsRenamed,loose:!0}})}catch{}return!0}catch(n){return console.warn("[Stashpad] loose .stash import failed",r.path,n),!1}}async importLooseStashesIn(r,e={}){let t=r.replace(/\/+$/,""),i=this.app.vault.getFiles().filter(o=>(o.parent?.path?.replace(/\/+$/,"")??"")===t&&o.extension===nt);if(i.length===0)return 0;let s=this.liveRootIds(t),n=0;for(let o of i)await this.importOneStash(o,t,s,!!e.auto)&&n++;return n}async importLooseInto(r,e={}){let t=await this.importLooseFilesIn(r),i=await this.importLooseFoldersIn(r),s=await this.importLooseStashesIn(r,e);return{files:t,folders:i,stashes:s}}defaultDestination(){let e=this.plugin.lastActiveStashpadLeaf?.view?.noteFolder;return e&&this.plugin.discoverStashpadFolders().includes(e)?e:this.plugin.discoverStashpadFolders()[0]??null}confirm(r,e,t){return new Promise(i=>{new ie(this.app,r,e,t,i).open()})}dupChoice(r){return new Promise(e=>{new Gi(this.app,r,e).open()})}async ensureFolder(r){let e=this.app.vault.adapter,t=r.split("/").filter(Boolean),i="";for(let s of t){i=i?`${i}/${s}`:s;try{await e.exists(i)||await e.mkdir(i)}catch(n){if(!/already exists/i.test(n.message))throw n}}}async uniquePath(r,e){let t=this.app.vault.adapter,i=e.lastIndexOf("."),s=i>0?e.slice(0,i):e,n=i>0?e.slice(i):"",o=`${r}/${e}`,a=0;for(;await t.exists(o);)a+=1,o=`${r}/${s}-${a}${n}`;return o}};yi.DEBOUNCE_MS=900,yi.BATCH_CONFIRM_AT=8;var Sn=yi;var bn=class{constructor(r,e){this.app=r;this.entries=[];this.loaded=!1;this.dirOk=!1;this.writeChain=Promise.resolve();this.path=`${e.replace(/\/+$/,"")}/import-log.jsonl`}getPath(){return this.path}async load(){if(!this.loaded){this.loaded=!0;try{let r=this.app.vault.adapter;if(await r.exists(this.path)){let e=await r.read(this.path);for(let t of e.split(/\r?\n/))if(t.trim())try{this.entries.push(JSON.parse(t))}catch{}}}catch(r){console.warn("[Stashpad] import log load failed",r)}}}recent(){return[...this.entries].reverse()}findDuplicate(r,e){for(let t=this.entries.length-1;t>=0;t--){let i=this.entries[t];if(i.originalName===r&&(e==null||i.size==null||i.size===e))return i}return null}append(r){this.entries.push(r),this.writeChain=this.writeChain.then(async()=>{try{await this.ensureDir(),await this.app.vault.adapter.append(this.path,JSON.stringify(r)+` -`)}catch(e){console.warn("[Stashpad] import log append failed",e)}})}async ensureDir(){if(this.dirOk)return;let r=this.app.vault.adapter,t=this.path.slice(0,this.path.lastIndexOf("/")).split("/").filter(Boolean),i="";for(let s of t)i=i?`${i}/${s}`:s,await r.exists(i)||await r.mkdir(i);this.dirOk=!0}};var xn=class xn{constructor(r,e){this.app=r;this.map=new Map;this.loaded=!1;this.dirty=!1;this.saveTimer=null;this.dirOk=!1;this.writeChain=Promise.resolve();this.path=`${e.replace(/\/+$/,"")}/render-cache.json`}async load(){if(!this.loaded){this.loaded=!0;try{let r=this.app.vault.adapter;if(await r.exists(this.path)){let e=JSON.parse(await r.read(this.path));if(e?.schema===1&&e.entries){for(let[t,i]of Object.entries(e.entries))this.map.set(t,i);for(let t of[...this.map.keys()])this.app.vault.getAbstractFileByPath(t)||(this.map.delete(t),this.dirty=!0)}}}catch(r){console.warn("[Stashpad] render cache load failed; starting empty",r),this.map.clear()}}}evict(r){this.map.delete(r)&&(this.dirty=!0,this.save())}get(r){return this.map.get(r)}has(r){return this.map.has(r)}set(r,e){this.map.set(r,e),this.dirty=!0,this.scheduleSave()}scheduleSave(){this.saveTimer==null&&(this.saveTimer=window.setTimeout(()=>{this.saveTimer=null,this.save()},xn.SAVE_DEBOUNCE_MS))}save(){return this.saveTimer!=null&&(window.clearTimeout(this.saveTimer),this.saveTimer=null),this.dirty?(this.dirty=!1,this.writeChain=this.writeChain.then(async()=>{try{await this.ensureDir();let r={schema:1,entries:Object.fromEntries(this.map)};await this.app.vault.adapter.write(this.path,JSON.stringify(r))}catch(r){console.warn("[Stashpad] render cache save failed",r)}}),this.writeChain):this.writeChain}async ensureDir(){if(this.dirOk)return;let r=this.app.vault.adapter,t=this.path.slice(0,this.path.lastIndexOf("/")).split("/").filter(Boolean),i="";for(let s of t)i=i?`${i}/${s}`:s,await r.exists(i)||await r.mkdir(i);this.dirOk=!0}};xn.SAVE_DEBOUNCE_MS=8e3;var Dn=xn;var ta="stashpad:unghost-after-reload",ds=class ds extends T.Plugin{constructor(){super(...arguments);this.settings={...Qt};this.debugBuffer=[];this.undoStacks=new Map;this.lastActiveStashpadLeaf=null;this.rebootstrapInProgress=!1;this.pendingEncBlobs=new Set;this.suppressComposerAutofocusUntil=0;this.settingTab=null;this.stashpadSelectionListeners=new Set;this.stashpadContentListeners=new Set;this._notifications=null;this._authorRegistry=null;this._importService=null;this._importLog=null;this.noteClipboard=null;this.noteClipboardNotice=null;this._renderCacheStore=null;this.knownStashpadFolders=new Set;this.suppressedFolderDeletes=new Set;this.sweptFolders=new Set;this.notifiedBuildVersion=null;this.authorRenameInFlight=new Set;this.reHomePending=new Set;this.okfRebuildTimers=new Map;this.okfRebuildingFolders=new Set;this.archivePending=new Map;this.autoSweepInProgress=!1;this.notifPersistenceWired=!1;this.notifSaveTimer=null;this.LAST_CURSOR_LS_KEY="stashpad:last-cursor";this.LAST_SELECTION_LS_KEY="stashpad:last-selection";this.ACTIVE_VERSIONS_LS_KEY="stashpad:active-versions";this.pendingForkDeletes=new Set;this.writeChain=Promise.resolve()}trace(e,t){if(!this.settings.debugTrace)return;let i="";if(t)try{i=" "+JSON.stringify(t)}catch{i=" [unserializable]"}let s=Math.round(performance.now());this.debugBuffer.push(`+${s}ms ${e}${i}`),this.debugBuffer.length>ds.DEBUG_BUFFER_MAX&&this.debugBuffer.splice(0,this.debugBuffer.length-ds.DEBUG_BUFFER_MAX)}getDebugTrace(){return this.debugBuffer.join(` -`)}clearDebugTrace(){this.debugBuffer=[]}get notifications(){return this._notifications||(this._notifications=new Ss(this.app)),this._notifications}get authorRegistry(){return this._authorRegistry||(this._authorRegistry=new Fn(this.app,this.pluginPrivatePath())),this._authorRegistry}get importService(){return this._importService||(this._importService=new Sn(this)),this._importService}get importLog(){return this._importLog||(this._importLog=new bn(this.app,this.pluginPrivatePath())),this._importLog}clearNoteClipboard(){try{this.noteClipboardNotice?.hide()}catch{}this.noteClipboardNotice=null,this.noteClipboard=null}get renderCacheStore(){return this._renderCacheStore||(this._renderCacheStore=new Dn(this.app,this.pluginPrivatePath())),this._renderCacheStore}async onunload(){try{this.encryption?.dispose()}catch{}try{for(let e of this.archivePending.values())window.clearTimeout(e.timer);this.archivePending.clear()}catch{}try{await this._renderCacheStore?.save()}catch{}}pluginPrivatePath(e=""){let i=`${this.manifest.dir.replace(/\/+$/,"")}/.stashpad`;return e?`${i}/${e.replace(/^\/+/,"")}`:i}newLog(){return new En(this.app,this.pluginPrivatePath(),()=>(this.settings?.authorName??"").trim())}async migrateLegacyPaths(){let e=this.app.vault.adapter,t=this.pluginPrivatePath(),i=async()=>{let a=t.split("/").filter(Boolean),l="";for(let c of a)l=l?`${l}/${c}`:c,await e.exists(l)||await e.mkdir(l)},s=`${this.manifest.dir.replace(/\/+$/,"")}/data.json`,n=this.pluginPrivatePath("data.json");if(await e.exists(n))try{let a=await e.read(n);await e.write(s,a),await e.write(`${n}.bak`,a),await e.remove(n),console.debug("[Stashpad] relocated data.json \u2192 standard path (for Obsidian Sync)")}catch(a){console.warn("Stashpad: data.json relocation failed",a)}let o=".stashpad";if(await e.exists(o))try{await i();let a=await e.list(o);for(let l of a.files){let c=l.replace(/^.*\//,""),d=this.pluginPrivatePath(c);if(!await e.exists(d))try{let h=await e.read(l);await e.write(d,h),console.debug("[Stashpad] migrated",l,"\u2192",d)}catch(h){console.warn(`Stashpad: failed to migrate ${l}`,h)}}}catch(a){console.warn("Stashpad: .stashpad migration scan failed",a)}}async createNewStashpad(e){let t=e.trim().replace(/^\/+|\/+$/g,"");if(!t)throw new Error("Folder name is empty");let i=this.app.vault.adapter,s=t.split("/").filter(Boolean),n="";for(let c of s)n=n?`${n}/${c}`:c,await i.exists(n)||await i.mkdir(n);let o=`${t}/Home.md`;if(await i.exists(o)){let c=this.app.vault.getAbstractFileByPath(o);c&&await this.app.fileManager.processFrontMatter(c,d=>{(typeof d.id!="string"||!d.id)&&(d.id=k),"parent"in d||(d.parent=null),(typeof d.created!="string"||!d.created)&&(d.created=new Date().toISOString())});return}let a=new Date().toISOString(),l=["---",`id: ${k}`,"parent: null",`created: ${a}`,"---","Home"].join(` -`);await this.app.vault.create(o,l);try{await this.seedLocalAuthorStub(t)}catch{}try{await this.seedKnownAuthorsInFolder(t)}catch{}}collectColorsInFolder(e){let t=new Map,i=e.replace(/\/+$/,"");for(let n of this.app.vault.getMarkdownFiles()){let o=n.parent?.path?.replace(/\/+$/,"")??"";if(o!==i&&!o.startsWith(i+"/"))continue;let a=this.app.metadataCache.getFileCache(n)?.frontmatter,l=typeof a?.color=="string"?a.color.trim():"";if(!l||!/^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(l))continue;let c=l.toLowerCase();t.set(c,(t.get(c)??0)+1)}let s=[...t.entries()].map(([n,o])=>({hex:n,count:o}));return s.sort((n,o)=>o.count-n.count||n.hex.localeCompare(o.hex)),s}async recolorAllInFolder(e,t,i){let s=e.replace(/\/+$/,""),n=t.toLowerCase(),o=0;for(let l of this.app.vault.getMarkdownFiles()){let c=l.parent?.path?.replace(/\/+$/,"")??"";if(c!==s&&!c.startsWith(s+"/"))continue;let d=this.app.metadataCache.getFileCache(l)?.frontmatter;if((typeof d?.color=="string"?d.color.trim().toLowerCase():"")===n)try{await this.app.fileManager.processFrontMatter(l,g=>{i?g.color=i:delete g.color}),o++}catch(g){console.warn(`Stashpad: recolor failed for ${l.path}`,g)}}let a=this.settings.colorAliases?.[s];if(a){let l=a[n];l&&(delete a[n],i&&(a[i.toLowerCase()]=l),Object.keys(a).length===0&&delete this.settings.colorAliases[s],await this.saveSettings())}return o}getColorAlias(e,t){let i=e.replace(/\/+$/,""),s=this.settings.colorAliases?.[i];if(!s)return;let n=s[t.toLowerCase()];return n&&n.trim()?n:void 0}async setColorAlias(e,t,i){let s=e.replace(/\/+$/,""),n=t.toLowerCase();this.settings.colorAliases||(this.settings.colorAliases={}),this.settings.colorAliases[s]||(this.settings.colorAliases[s]={});let o=this.settings.colorAliases[s],a=i.trim();a?o[n]=a:delete o[n],Object.keys(o).length===0&&delete this.settings.colorAliases[s],await this.saveSettings()}async waitForStashpadFolder(e,t=2e3){let i=e.trim().replace(/^\/+|\/+$/g,"");if(!i)return;let s=Date.now();for(;Date.now()-s<t;){if(this.discoverStashpadFolders().includes(i))return;await new Promise(n=>setTimeout(n,80))}}importExcludePrefixList(){return(this.settings.importExcludePrefixes??"_").split(",").map(e=>e.trim()).filter(Boolean)}pathHasExcludedSegment(e){let t=this.importExcludePrefixList();return t.length?e.replace(/\/+$/,"").split("/").some(i=>t.some(s=>i.startsWith(s))):!1}discoverStashpadFolders(){let e=new Set;for(let i of this.app.vault.getMarkdownFiles()){let s=this.app.metadataCache.getFileCache(i)?.frontmatter;if(typeof s?.id!="string"||!s.id.trim()||!s||!("parent"in s))continue;let n=i.parent?.path?.replace(/\/+$/,"")??"";n&&!this.pathHasExcludedSegment(n)&&e.add(n)}let t=[...e].sort();return this.knownStashpadFolders=new Set(t),t}closeStashpadTabsFor(e){let t=0;for(let i of this.app.workspace.getLeavesOfType(z)){let s=i.view?.noteFolder??"";s||(s=((i.getViewState?.()?.state??{}).folderOverride??"")||this.settings.folder||"Stashpad"),s=(s||"").replace(/\/+$/,""),(s===e||s.startsWith(e+"/"))&&(i.detach(),t++)}return t}async prunePlacementFor(e){let t=this.settings,i=o=>(o??[]).filter(a=>a!==e&&!a.startsWith(e+"/")),s=(t.folderPanelPinned?.length??0)+(t.folderPanelDownranked?.length??0)+(t.folderPanelHidden?.length??0);t.folderPanelPinned=i(t.folderPanelPinned),t.folderPanelDownranked=i(t.folderPanelDownranked),t.folderPanelHidden=i(t.folderPanelHidden),t.folderPanelPinned.length+t.folderPanelDownranked.length+t.folderPanelHidden.length!==s&&await this.saveSettings()}async deleteStashpadFolderWithUndo(e){let t=e.path.replace(/\/+$/,""),i=e.name,s=this.app.vault.adapter,n=".trash";try{await s.exists(n)||await s.mkdir(n)}catch(c){console.warn("[Stashpad] couldn't ensure .trash",c)}let o=`${n}/${i}`;for(let c=1;await s.exists(o);c++)o=`${n}/${i} (${c})`;this.suppressedFolderDeletes.add(t),window.setTimeout(()=>this.suppressedFolderDeletes.delete(t),5e3);let a=this.closeStashpadTabsFor(t);await this.prunePlacementFor(t),this.knownStashpadFolders.delete(t);try{await s.rename(t,o)}catch(c){console.warn("[Stashpad] folder delete failed",c),this.suppressedFolderDeletes.delete(t),new T.Notice("Delete failed (see console).");return}let l=a>0?`Deleted \u201C${i}\u201D \u2014 closed ${a} open tab${a===1?"":"s"}.`:`Deleted \u201C${i}\u201D.`;this.notifications.show({message:l,kind:"warning",category:"delete",duration:0,folder:t,actions:[{label:"Undo",onClick:async()=>{try{if(await s.exists(t)){new T.Notice(`Can't undo \u2014 \u201C${i}\u201D already exists.`);return}this.suppressedFolderDeletes.add(t),window.setTimeout(()=>this.suppressedFolderDeletes.delete(t),5e3),await s.rename(o,t),new T.Notice(`Restored \u201C${i}\u201D.`),this.activateViewForFolder(t)}catch(c){console.warn("[Stashpad] folder undo failed",c),new T.Notice("Undo failed (see console).")}}}]})}async handleStashpadFolderDeleted(e){let t=e.replace(/\/+$/,"");if(!t||this.suppressedFolderDeletes.has(t))return;let i=this.closeStashpadTabsFor(t);await this.prunePlacementFor(t),this.knownStashpadFolders.delete(t);let s=t.split("/").pop()||t;this.notifications.show({message:i>0?`Stashpad \u201C${s}\u201D was deleted \u2014 closed ${i} open tab${i===1?"":"s"}.`:`Stashpad \u201C${s}\u201D was deleted.`,kind:"warning",category:"delete",folder:t})}searchableFolders(e){let t=new Set(this.settings.searchIncludedFolders),i=new Set(this.settings.searchExcludedFolders),s=new Set((this.settings.archiveFolders??[]).map(c=>(c??"").replace(/\/+$/,""))),o=this.discoverStashpadFolders().filter(c=>s.has(c)?!1:t.size>0?t.has(c):!i.has(c)),a=(e||"").trim().replace(/\/+$/,""),l=o.filter(c=>c!==a);return a&&l.unshift(a),l}async maybeSweepFolder(e){let t=(e||"").trim().replace(/\/+$/,"");!t||this.sweptFolders.has(t)||(this.sweptFolders.add(t),setTimeout(()=>{this.runSweep(t,{silent:!0})},3e3))}async runIntegrityCheckOnFolder(e){let t=(e||"").trim().replace(/\/+$/,"");t&&await this.runSweep(t,{silent:!1})}async runSweep(e,t){try{let i=this.newLog(),s={},n=this.app.vault.getMarkdownFiles().filter(c=>c.path===e||c.path.startsWith(e+"/"));for(let c of n){let d=this.app.metadataCache.getFileCache(c)?.frontmatter,h=typeof d?.id=="string"?d.id.trim():"";if(!h)continue;let g=d&&"parent"in d?d.parent??null:null;s[h]={parent:g,path:c.path}}let o=await i.readState(),a=c=>c===e||c.startsWith(e+"/");if(!t.silent){for(let[c,d]of Object.entries(s)){let h=o[c];h?h.parent!==d.parent?await i.append({type:"parent_change",id:c,payload:{from:h.parent,to:d.parent}}):h.path!==d.path&&await i.append({type:"rename",id:c,payload:{from:h.path,to:d.path}}):await i.append({type:"create",id:c,payload:{path:d.path,parent:d.parent}})}for(let[c,d]of Object.entries(o))!s[c]&&a(d.path)&&await i.append({type:"missing",id:c,payload:{lastPath:d.path}})}let l={};for(let[c,d]of Object.entries(o))a(d.path)||(l[c]=d);for(let[c,d]of Object.entries(s))l[c]=d;await i.writeState(l)}catch(i){console.warn("Stashpad: integrity sweep failed",i)}}getUndoStack(e){let t=this.undoStacks.get(e);return t||(t=new vn,this.undoStacks.set(e,t)),t}async onload(){await this.migrateLegacyPaths(),await this.loadSettings(),ke.enabled=!!this.settings.enablePerfProfiling,this.encryption=new Ps(this.app,()=>({...ji(),...this.settings.encryption??{}}),async p=>{this.settings.encryption=p,await this.saveSettings()},()=>this.settings.encryptionIdleLockMinutes??0),this.encryption.init().then(()=>this.encryption.tryAutoUnlock()),this.app.workspace.onLayoutReady(()=>{this.resumeRotations()}),this.settingTab=new Gs(this.app,this),this.addSettingTab(this.settingTab),await this.renderCacheStore.load(),this.registerEvent(this.app.vault.on("delete",p=>this.renderCacheStore.evict(p.path))),this.registerEvent(this.app.vault.on("rename",(p,f)=>this.renderCacheStore.evict(f)));let e=(0,T.debounce)(()=>void this.flushForkSiblingPrune(),250,!0);this.registerEvent(this.app.vault.on("delete",p=>{p instanceof T.TFile&&p.extension==="md"&&(this.pendingForkDeletes.add(p.basename),e())})),this.registerEvent(this.app.vault.on("create",p=>this.onOkfFileEvent(p.path))),this.registerEvent(this.app.vault.on("delete",p=>this.onOkfFileEvent(p.path))),this.registerEvent(this.app.vault.on("rename",(p,f)=>{this.onOkfFileEvent(p.path),this.onOkfFileEvent(f)})),await this.authorRegistry.load();{let p=(this.settings.authorId??"").trim();p&&this.authorRegistry.record({id:p,name:this.settings.authorName,role:this.settings.authorRole,department:this.settings.authorDepartment})}this.registerEvent(this.app.vault.on("create",p=>{p.path.endsWith(".stashenc")&&this.pendingEncBlobs.delete(p.path)})),this.registerEvent(this.app.vault.on("delete",p=>{p.path.endsWith(".stashenc")&&this.pendingEncBlobs.delete(p.path)})),this.app.workspace.onLayoutReady(()=>{this.reconcileLockedRegistry(),window.setTimeout(()=>void this.checkDueReminders(),6e3),this.registerInterval(window.setInterval(()=>void this.checkDueReminders(),5*60*1e3)),window.setTimeout(()=>{this.seedLocalAuthorStubsEverywhere()},4e3),window.setTimeout(()=>this.syncObsidianExcludedArchives(),4500),window.setTimeout(()=>this.importService.setArmed(!0),2500),window.setTimeout(()=>void this.runAutoImportSweep(),5e3),this.registerInterval(window.setInterval(()=>void this.runAutoImportSweep(),5*60*1e3)),window.setTimeout(()=>void this.migratePinnedNotesToFrontmatter(),3e3),window.setTimeout(()=>void this.unghostStashpadTabsIfFlagged(),1200)}),this.registerView(z,p=>new gn(p,this)),this.registerView(xt,p=>new qs(p,this)),this.registerView(It,p=>new Bs(p,this)),this.registerView($t,p=>new mn(p,this)),this.registerView(zt,p=>new Cn(p,this)),this.registerView(mt,p=>new yn(p,this)),this.registerEvent(this.app.workspace.on("active-leaf-change",p=>{p&&p.view.getViewType()===z&&(this.lastActiveStashpadLeaf=p,this.settings.autoOpenDetailPanel&&this.app.workspace.getLeavesOfType(xt).length===0&&setTimeout(()=>{Ks(this.app)},0),this.notifyStashpadSelectionChanged())}));let t=()=>{let p=ht(),f=!!p&&this.app.workspace.activeLeaf&&this.app.workspace.activeLeaf.view===p,I=!!f&&this.settings.hideMobileToolbarInStashpad;document.body.classList.toggle("stashpad-hide-mobile-toolbar",I),document.body.classList.toggle("stashpad-active",!!f)};this.register(er(t)),this.registerEvent(this.app.workspace.on("active-leaf-change",t));let i=p=>{try{let f=Array.from(document.querySelectorAll("style")).filter(I=>(I.textContent??"").includes(".stashpad-"));for(let I of f){let m=I.id||"",C=m?`style[data-stashpad-source="${m}"]`:null;if(C&&p.head.querySelector(C))continue;let B=p.createElement("style");m?B.setAttribute("data-stashpad-source",m):B.setAttribute("data-stashpad-source","anon"),B.textContent=I.textContent??"",p.head.appendChild(B)}}catch(f){console.warn("[Stashpad] inject popout styles failed",f)}};this.registerEvent(this.app.workspace.on("window-open",p=>{let f=p?.doc??p?.win?.document??null;f&&i(f)})),this.registerEvent(this.app.workspace.on("file-menu",(p,f)=>{if(!(f instanceof T.TFolder))return;let I=f.path.replace(/\/+$/,"");p.addItem(C=>C.setTitle("\u{1F512} Encrypt with Stashpad").setIcon("lock").onClick(()=>void this.encryptFolderFromExplorer(I))),f.children?.some(C=>C instanceof T.TFile&&C.extension==="stashenc")&&!this.folderHasStashpadNotes(I)&&p.addItem(C=>C.setTitle("\u{1F513} Decrypt with Stashpad").setIcon("unlock").onClick(()=>void this.decryptFolderFromExplorer(I))),this.discoverStashpadFolders().includes(I)&&p.addItem(C=>{C.setTitle("Open folder in Stashpad").setIcon("layout-list").onClick(()=>void this.openFolderInStashpad(I))})})),setTimeout(()=>{try{let p=this.app.workspace;typeof p.iterateAllLeaves=="function"&&p.iterateAllLeaves(f=>{let I=f?.view?.containerEl?.ownerDocument;I&&I!==document&&i(I)})}catch{}},200),t(),this.register(()=>document.body.classList.remove("stashpad-hide-mobile-toolbar","stashpad-active"));let s=window.visualViewport,n=()=>{let p=document.querySelector(".mobile-toolbar, .mobile-toolbar-container"),f=p&&p.isConnected?p.offsetHeight:0;document.body.style.setProperty("--stashpad-toolbar-h",`${f}px`);let I=0;s&&(I=Math.max(0,window.innerHeight-s.height-s.offsetTop)),document.body.style.setProperty("--stashpad-vv-bottom-gap",`${I}px`),document.body.classList.toggle("stashpad-keyboard-open",I>100)};n(),s&&(s.addEventListener("resize",n),s.addEventListener("scroll",n),this.register(()=>{s.removeEventListener("resize",n),s.removeEventListener("scroll",n)})),window.addEventListener("resize",n),this.register(()=>window.removeEventListener("resize",n)),requestAnimationFrame(n),setTimeout(n,250),setTimeout(n,1e3),this.addRibbonIcon("list-tree","Open Stashpad",()=>{if(this.discoverStashpadFolders().length===0){this.activateView({reveal:!0});return}this.openFolderPicker()}).addEventListener("contextmenu",p=>{p.preventDefault(),this.openFolderPicker()}),this.addRibbonIcon("panel-left","Open Stashpad panels (sidebar)",()=>{ys(this.app)}),this.addRibbonIcon("folders","Open Stashpad folder panel (sidebar)",()=>{wn(this.app)}),this.addRibbonIcon("panel-right","Open Stashpad detail panel (right sidebar)",()=>{Ks(this.app)}),this.addCommand({id:"stashpad-open",name:"Open Stashpad in new tab",callback:()=>{if(this.discoverStashpadFolders().length>=2){this.openFolderPicker();return}this.activateView({reveal:!1})}}),this.addCommand({id:"stashpad-reveal",name:"Reveal or open Stashpad",callback:()=>void this.activateView({reveal:!0})}),this.addCommand({id:"stashpad-focus-last-tab",name:"Focus last Stashpad tab",callback:()=>void this.focusLastStashpadTab()}),this.addCommand({id:"stashpad-focus-folder-panel",name:"Focus folder panel",callback:()=>void this.focusFolderPanel()}),this.addCommand({id:"stashpad-lock-encryption",name:"Lock encryption (forget password)",callback:()=>{if(!this.encryption.isConfigured()){new T.Notice("Encryption isn't set up.");return}this.encryption.lock(),new T.Notice("Encryption locked.")}});let a=p=>{let f=ht();f&&typeof f[p]=="function"&&f[p]()};this.addCommand({id:"stashpad-toggle-split",name:"Toggle split-on-newlines",callback:()=>a("toggleSplit")}),this.addCommand({id:"stashpad-fork-version",name:"Fork as a version (alternate draft in this sheet)",callback:()=>a("cmdForkVersion")}),this.addCommand({id:"stashpad-mark-version-final",name:"Mark version as final (sheet)",callback:()=>a("cmdMarkVersionFinal")}),this.addCommand({id:"stashpad-command-palette",name:"Command palette (Stashpad only)",callback:()=>a("openStashpadCommandPalette")}),this.addCommand({id:"stashpad-lock-selection",name:"Encrypt (lock) selection (notes + children)",callback:()=>a("cmdLockSelection")}),this.addCommand({id:"stashpad-unlock-all",name:"Decrypt (unlock) locked notes in view",callback:()=>a("cmdUnlockAll")}),this.addCommand({id:"stashpad-unlock-all-vault",name:"Decrypt (unlock) ALL locked notes in the vault",callback:()=>void this.unlockAllInVault()}),this.addCommand({id:"stashpad-copy-notes",name:"Copy notes (note clipboard \u2014 paste to duplicate)",callback:()=>a("cmdCopyNotes")}),this.addCommand({id:"stashpad-cut-notes",name:"Cut notes (paste in list to move, in composer to extract text)",callback:()=>a("cmdCutNotes")}),this.addCommand({id:"stashpad-paste-notes",name:"Paste notes (from the note clipboard)",callback:()=>a("cmdPasteNotes")}),this.addCommand({id:"stashpad-move-to-archive",name:"Move selection to archive (encrypt)",callback:()=>a("cmdMoveToArchive")}),this.addCommand({id:"stashpad-encrypt-delete",name:"Encrypt & delete selection (to encrypted trash)",callback:()=>a("cmdEncryptDelete")}),this.addCommand({id:"stashpad-restore-trash",name:"Open encrypted trash (restore deleted)\u2026",callback:()=>this.openEncryptedTrash()}),this.addCommand({id:"stashpad-open-all-encrypted",name:"Open \u201CAll encrypted\u201D (every locked note, by folder)",callback:()=>void Bn(this,"encrypted")}),this.addCommand({id:"stashpad-open-all-archived",name:"Open \u201CAll archived\u201D (every archive folder)",callback:()=>void Bn(this,"archived")}),this.addCommand({id:"stashpad-open-all-tasks",name:"Open \u201CAll tasks\u201D (every task across folders)",callback:()=>void Bn(this,"tasks")}),this.addCommand({id:"stashpad-encrypt-existing-trash",name:"Encrypt existing Obsidian trash (backfill .trash into encrypted trash)",callback:()=>void this.encryptExistingTrash()}),this.addCommand({id:"stashpad-close-duplicate-tabs",name:"Close duplicate & orphaned Stashpad tabs (tidy up)",callback:()=>void this.closeDuplicateStashpadTabs()}),this.addCommand({id:"stashpad-claim-selected-author",name:"Claim authorship of selected notes",callback:()=>a("claimSelectedAsAuthor")}),this.addCommand({id:"stashpad-claim-folder-author",name:"Claim authorship of all unauthored notes in this folder",callback:()=>a("claimFolderAsAuthor")}),this.addCommand({id:"stashpad-claim-selected-contributor",name:"Claim selected notes (author if unowned, else add me as contributor)",callback:()=>a("claimSelectedWithContributor")}),this.addCommand({id:"stashpad-claim-folder-contributor",name:"Claim all notes in this folder (author if unowned, else add me as contributor)",callback:()=>a("claimFolderWithContributor")}),this.addCommand({id:"stashpad-pick-destination",name:"Pick destination for next note",callback:()=>a("openDestinationPicker")}),this.addCommand({id:"stashpad-search",name:"Search Stashpad notes",callback:()=>a("openSearchModal")}),this.addCommand({id:"stashpad-search-in-parent",name:"Search in current parent",callback:()=>a("openSearchInParentModal")}),this.addCommand({id:"stashpad-move-picker",name:"Move selection (picker)",callback:()=>a("cmdMovePicker")}),this.addCommand({id:"stashpad-merge",name:"Merge selection",callback:()=>a("cmdMerge")}),this.addCommand({id:"stashpad-copy",name:"Copy selection",callback:()=>a("cmdCopy")}),this.addCommand({id:"stashpad-copy-tree",name:"Copy focused subtree",callback:()=>a("cmdCopyTree")}),this.addCommand({id:"stashpad-copy-outline",name:"Copy as outline (nested embeds)",callback:()=>a("cmdCopyOutline")}),this.addCommand({id:"stashpad-split",name:"Split note\u2026",callback:()=>a("cmdSplit")}),this.addCommand({id:"stashpad-edit-note",name:"Edit note in new tab (selection)",callback:()=>a("cmdOpenInEditor")}),this.addCommand({id:"stashpad-edit-parent",name:"Edit parent note in new tab",callback:()=>a("cmdOpenParentInEditor")}),this.addCommand({id:"stashpad-delete",name:"Delete selection",callback:()=>a("cmdDelete")}),this.addCommand({id:"stashpad-move-up",name:"Move note up",callback:()=>a("cmdMoveUp")}),this.addCommand({id:"stashpad-move-down",name:"Move note down",callback:()=>a("cmdMoveDown")}),this.addCommand({id:"stashpad-move-to-top",name:"Move note to top",callback:()=>a("cmdMoveToTop")}),this.addCommand({id:"stashpad-move-to-bottom",name:"Move note to bottom",callback:()=>a("cmdMoveToBottom")}),this.addCommand({id:"stashpad-outdent",name:"Outdent (move to grandparent)",callback:()=>a("cmdOutdent")}),this.addCommand({id:"stashpad-set-color",name:"Set note color\u2026",callback:()=>a("cmdSetColor")}),this.addCommand({id:"stashpad-clone",name:"Clone selection (duplicate / copy notes)",callback:()=>a("cmdClone")}),this.addCommand({id:"stashpad-fork-note",name:"Fork into a separate note (copy under a chosen parent)",callback:()=>a("cmdForkNote")}),this.addCommand({id:"stashpad-insert-template",name:"Insert template (clone an existing note)",callback:()=>a("cmdInsertTemplate")}),this.addCommand({id:"stashpad-toggle-expand",name:"Show more / show less (expand toggle)",callback:()=>a("cmdToggleExpand")}),this.addCommand({id:"stashpad-expand-all",name:"Expand all (show every note's full body)",callback:()=>a("cmdExpandAll")}),this.addCommand({id:"stashpad-collapse-all",name:"Collapse all (clamp every note's body)",callback:()=>a("cmdCollapseAll")}),this.addCommand({id:"stashpad-pick-move",name:"Move (in-list, arrow + Enter)",callback:()=>a("cmdInListPicker")}),this.addCommand({id:"stashpad-open-in-new-tab",name:"Open in new Stashpad tab",callback:()=>a("cmdOpenInNewStashpadTab")}),this.addCommand({id:"stashpad-toggle-complete",name:"Toggle complete (strikethrough)",callback:()=>a("cmdToggleComplete")}),this.addCommand({id:"stashpad-toggle-task",name:"Toggle task (todo)",callback:()=>a("cmdToggleTask")}),this.addCommand({id:"stashpad-set-due",name:"Set due date\u2026",callback:()=>a("cmdSetDue")}),this.addCommand({id:"stashpad-dump-perf",name:"Dump performance profile (copy to clipboard)",callback:async()=>{if(!this.settings.enablePerfProfiling){new T.Notice("Enable \u201CPerformance profiling\u201D in Stashpad settings first, then use the app and run this again.");return}let p=ke.report();console.log(p);try{await navigator.clipboard.writeText(p)}catch{}new T.Notice("Performance profile copied to clipboard (also in the console).")}}),this.addCommand({id:"stashpad-reset-perf",name:"Reset performance profile",callback:()=>{ke.reset(),new T.Notice("Performance profile reset.")}}),this.addCommand({id:"stashpad-jump-to-top",name:"Jump to top of list",callback:()=>a("jumpToTop")}),this.addCommand({id:"stashpad-jump-to-bottom",name:"Jump to bottom of list",callback:()=>a("jumpToBottom")}),this.addCommand({id:"stashpad-assign",name:"Assign task to\u2026",callback:()=>a("cmdAssign")}),this.addCommand({id:"stashpad-open-import-log",name:"Open import log",callback:async()=>{await this.importLog.load();let{ImportLogModal:p}=await Promise.resolve().then(()=>(Be(),qi));new p(this.app,this.importLog.recent()).open()}}),this.addCommand({id:"stashpad-import-files",name:"Import file(s) into Stashpad\u2026",callback:()=>this.openImportPicker()}),this.addCommand({id:"stashpad-import-loose-files",name:"Import loose files & folders in this folder (scan for moved-in / unprocessed items)",checkCallback:p=>{let f=this.importService.defaultDestination();return p?!!f:(f&&this.runImportLooseFiles(f),!0)}}),this.addCommand({id:"stashpad-rerun-slug-pass",name:"Re-run filename (slug) pass on this folder",checkCallback:p=>{let f=this.importService.defaultDestination();return p?!!f:(f&&this.runFolderSlugPass(f),!0)}}),this.addCommand({id:"stashpad-rerun-frontmatter-backfill",name:"Re-run frontmatter backfill (recovery links) on this folder",checkCallback:p=>{let f=this.importService.defaultDestination();return p?!!f:(f&&this.runFolderFrontmatterBackfill(f),!0)}}),this.addCommand({id:"stashpad-select-all",name:"Select all visible notes",callback:()=>a("cmdSelectAll")}),this.addCommand({id:"stashpad-copy-codeblock",name:"Copy code from codeblock",callback:()=>a("cmdCopyCodeBlock")}),this.addCommand({id:"stashpad-open-panels",name:"Open Stashpad panels (sidebar)",callback:()=>void ys(this.app)}),this.addCommand({id:"stashpad-open-folder-panel",name:"Open folder panel (sidebar)",callback:()=>void wn(this.app)}),this.addCommand({id:"stashpad-open-detail",name:"Open Stashpad detail panel (right sidebar)",callback:()=>void Ks(this.app)}),this.addCommand({id:"stashpad-reveal-active-note",name:"Open this note in Stashpad",checkCallback:p=>{let f=this.app.workspace.getActiveFile(),I=!!f&&f.extension==="md"&&this.isStashpadNoteFile(f);return p||I&&f&&this.revealNoteInStashpad(f),I}});let l=Object.keys(Ji);for(let p of l){let f=Ji[p];this.addCommand({id:`stashpad-open-panels-${p}`,name:`Open Stashpad panel: ${f.label}`,callback:async()=>{await ys(this.app),this.app.workspace.getLeavesOfType(It)[0]?.view?.setActivePanel?.(p)}})}this.addCommand({id:"stashpad-task-review",name:"Open daily task review",callback:()=>new ii(this.app,this).open()}),this.addCommand({id:"stashpad-swap-with-parent",name:"Swap with parent (ouroboros)",callback:()=>a("cmdSwapWithParent")}),this.addCommand({id:"stashpad-toggle-pin",name:"Pin / unpin selected note (sidebar)",callback:()=>a("cmdTogglePin")}),this.addCommand({id:"stashpad-list-pin",name:"Pin / unpin to top of list",callback:()=>a("cmdToggleListPin")}),this.addCommand({id:"stashpad-open-tiny",name:"Open Stashpad in tiny window",callback:()=>void this.openTinyWindow()}),this.addCommand({id:"stashpad-clone-tab",name:"Clone (duplicate / copy) this Stashpad tab",callback:()=>a("cmdCloneStashpadTab")}),this.addCommand({id:"stashpad-undo",name:"Undo last Stashpad action",callback:()=>a("cmdUndo")}),this.addCommand({id:"stashpad-redo",name:"Redo last undone Stashpad action",callback:()=>a("cmdRedo")}),this.addCommand({id:"stashpad-export-stash",name:"Export selection to .stash",callback:()=>a("cmdExportStash")}),this.addCommand({id:"stashpad-export-okf",name:"Export selection as OKF bundle (.zip / .tar.gz / .stash)",callback:()=>a("cmdExportOkf")}),this.addCommand({id:"stashpad-import-stash",name:"Import .stash file\u2026",callback:()=>a("cmdImportStash")}),this.addCommand({id:"stashpad-pick-folder",name:"Stashpad: open / switch / create folder\u2026",callback:()=>this.openFolderPicker()}),this.addCommand({id:"stashpad-run-integrity-check",name:"Run integrity check on active Stashpad folder",checkCallback:p=>{let f=ht(),I=f&&f.noteFolder;return I?(p||(new T.Notice(`Running integrity check on "${I}"\u2026`),this.runIntegrityCheckOnFolder(I).then(()=>{new T.Notice("Integrity check complete \u2014 see Stashpad log.")})),!0):!1}}),this.addCommand({id:"stashpad-fix-orphans",name:"Set missing parents to Home (orphan fix)",callback:()=>void this.fixOrphanParents()}),this.addCommand({id:"stashpad-rebuild-author-registry",name:"Rebuild author registry (scan authors + note frontmatter)",callback:async()=>{new T.Notice("Stashpad: rebuilding author registry\u2026");try{let p=await this.rebuildAuthorRegistry();this.notifications.show({message:`Author registry rebuilt: ${p.total} author(s) \u2014 ${p.fromStubs} from stubs, ${p.fromNotes} from note links.`,kind:"success",category:"system"})}catch(p){new T.Notice(`Author registry rebuild failed: ${p.message}`)}}}),this.addCommand({id:"stashpad-restore-author-stubs",name:"Restore missing author stubs (from registry)",callback:async()=>{new T.Notice("Stashpad: restoring author stubs\u2026");try{let p=await this.restoreMissingAuthorStubs();this.notifications.show({message:p.created>0?`Restored ${p.created} author stub(s) across ${p.folders} folder(s).`:`No missing author stubs \u2014 all present across ${p.folders} folder(s).`,kind:"success",category:"system"})}catch(p){new T.Notice(`Restore author stubs failed: ${p.message}`)}}}),this.addCommand({id:"stashpad-sync-authors",name:"Sync authors across all folders (multiplayer)",callback:()=>void this.syncAuthorsAcrossFolders()}),this.addCommand({id:"stashpad-rebootstrap-all",name:"Rebootstrap all Stashpad folders (backfill metadata + rename stale titles)",callback:()=>{this.runRebootstrapWithUI().catch(()=>{})}}),this.addCommand({id:"stashpad-adopt-note",name:"Adopt active note into Stashpad (fill missing frontmatter)",checkCallback:p=>{let f=this.app.workspace.getActiveFile();return!f||f.extension!=="md"?!1:(p||this.adoptNote(f),!0)}}),this.addCommand({id:"stashpad-open-notification-history",name:"Open notification history",callback:()=>{Promise.resolve().then(()=>(Be(),qi)).then(({NotificationHistoryModal:p,LogModal:f})=>{new p(this.app,this.notifications,async()=>{let I=this.app.vault.adapter,m=this.pluginPrivatePath("log.jsonl");if(!await I.exists(m)){new T.Notice("No log yet \u2014 make some changes first.");return}let C=await I.read(m);new f(this.app,C,m).open()},this.settings.authorId||null,I=>this.lookupNoteAuthorIds(I)).open()})}});let c=[{key:"prefixTimestampsOnCopy",label:"Prefix timestamps when copying"},{key:"useTemplatesFormat",label:"Use Templates plugin date/time formats"},{key:"autoNavOnMoveIn",label:"Auto-navigate into parent on move IN"},{key:"autoNavOnMoveOut",label:"Auto-navigate to destination on move OUT"},{key:"confirmCrossParentDrag",label:"Confirm cross-parent drag-and-drop"},{key:"confirmBulkDelete",label:"Confirm bulk deletes"},{key:"confirmAttachmentDelete",label:"Offer to delete attachments with note"},{key:"autofocusComposerAfterSend",label:"Autofocus composer after sending"},{key:"popoutDuplicates",label:"Open in new window \u2014 duplicate tab"},{key:"autoExpandCursorRow",label:"Expand the cursor row's body automatically"},{key:"autoOpenDetailPanel",label:"Auto-open the detail panel"},{key:"doubleClickToFocus",label:"Double-click a note to open it"}];for(let p of c){let f=`stashpad-toggle-${String(p.key).replace(/[A-Z]/g,I=>`-${I.toLowerCase()}`)}`;this.addCommand({id:f,name:`Toggle: ${p.label}`,callback:async()=>{let I=!this.settings[p.key];this.settings[p.key]=I,await this.saveSettings(),new T.Notice(`${p.label}: ${I?"ON":"OFF"}`)}})}this.addCommand({id:"stashpad-open-settings",name:"Open Stashpad settings",callback:()=>{let p=this.app.setting;!p?.open||!p?.openTabById||(p.open(),p.openTabById(this.manifest.id))}}),this.addCommand({id:"stashpad-search-settings",name:"Search Stashpad settings\u2026",callback:()=>{let p=this.app.setting;!p?.open||!p?.openTabById||(p.open(),p.openTabById(this.manifest.id),setTimeout(()=>{p?.modalEl?.querySelector?.("input[type='search']")?.focus()},0))}});for(let p of Rs)this.addCommand({id:`stashpad-open-settings-${p.id}`,name:`Open Stashpad settings: ${p.label}`,callback:()=>{let f=this.app.setting;!f?.open||!f?.openTabById||(f.open(),f.openTabById(this.manifest.id))}});let d=()=>{let p=this.app.setting;!p?.open||!p?.openTabById||(p.open(),p.openTabById(this.manifest.id),setTimeout(()=>{document.getElementById("stashpad-jd-index-section")?.scrollIntoView({behavior:"smooth",block:"start"})},100))};this.addCommand({id:"stashpad-preview-jd-index",name:"Preview JD index (overwrites home note body)",callback:async()=>{try{let{buildJdIndexPreview:p}=await Promise.resolve().then(()=>(Yi(),vs)),f=await p(this.app,this,this.settings);if(f.error==="no-dest"){new T.Notice("Set a Designated Stashpad folder for Index in settings first.",6e3),d();return}if(f.error==="no-home"){new T.Notice(`"${this.settings.jdIndexStashpadFolder}" has no Stashpad home note. Open the folder in Stashpad first to create one.`,7e3);return}let{buildJdPreviewNotice:I}=await Promise.resolve().then(()=>(Yi(),vs));I(this.app,f)}catch(p){console.error("[stashpad] preview failed",p),new T.Notice(`Preview failed: ${p?.message??p}`,8e3)}}}),this.addCommand({id:"stashpad-decrypt-folder-bundle",name:"Decrypt a folder bundle (encrypted non-Stashpad folder)\u2026",callback:async()=>{if(!this.encryption.isConfigured()){new T.Notice("Stashpad encryption isn't set up.");return}let p=await Zr(this.app);if(!p.length){new T.Notice("No encrypted folder bundles found in this vault.");return}new sa(this.app,p,f=>void this.decryptFolderFromExplorer(f.folder)).open()}}),this.addCommand({id:"stashpad-build-jd-index",name:"Build JD index notes (creates Stashpad-note hierarchy)",callback:async()=>{try{let{buildJdIndexNotes:p,scanForJdNotes:f,JdBuildConfirmModal:I}=await Promise.resolve().then(()=>(Yi(),vs));if(!(this.settings.jdIndexStashpadFolder??"").trim().replace(/^\/+|\/+$/g,"")){new T.Notice("Set a Designated Stashpad folder for Index in settings first.",6e3),d();return}let C=f(this.app,this,this.settings);new I(this.app,this,this.settings,C.indexed.length,async()=>{try{let w=await p(this.app,this,this.settings);if(w.error==="no-dest"){new T.Notice("Set a Designated Stashpad folder for Index in settings first.",6e3),d();return}if(w.error==="dest-not-stashpad"){new T.Notice(`"${w.destFolder}" isn't a known Stashpad folder. Pick a real Stashpad folder in settings.`,7e3),d();return}this.settings.jdIndexHasBuilt=!0,await this.saveSettings(),new T.Notice(`Index built: ${w.created} created, ${w.updated} updated, ${w.skipped} skipped \u2192 ${w.destFolder}`,6e3)}catch(w){console.error("[stashpad] build failed",w),new T.Notice(`Build failed: ${w?.message??w}`,8e3)}}).open()}catch(p){console.error("[stashpad] build failed",p),new T.Notice(`Build failed: ${p?.message??p}`,8e3)}}});let h=p=>{if(p.extension!==nt)return;let f=(this.settings.importDropFolder||"").trim().replace(/^\/+|\/+$/g,""),I=(this.settings.exportFolder||"").trim().replace(/^\/+|\/+$/g,""),m=p.parent?.path||"",C=m.split("/").pop()??"";if(f&&C===f){if(I&&m.endsWith(`/${I}`))return;let B=m.slice(0,m.length-f.length).replace(/\/+$/,"")||this.settings.folder;this.autoImportStash(p,B);return}this.settings.autoImport&&this.importService.isArmed()&&(Wt(p.path)||this.discoverStashpadFolders().includes(m)&&this.autoImportStash(p,m))};this.registerEvent(this.app.vault.on("create",p=>{p instanceof T.TFile&&h(p)})),this.registerEvent(this.app.vault.on("rename",p=>{p instanceof T.TFile&&h(p)}));let g=p=>{if(p.extension!=="md")return;let f=p.parent?.path?.replace(/\/+$/,"")??"";this.discoverStashpadFolders().includes(f)&&setTimeout(()=>{this.fixOrphanParentForFile(p)},800)},u=p=>{if(p.extension!=="md")return;let f=(this.settings.importDropFolder||"").trim().replace(/^\/+|\/+$/g,"");if(!f)return;let I=p.parent?.path?.replace(/\/+$/,"")??"";if((I.split("/").pop()??"")!==f)return;let C=I.slice(0,I.length-f.length).replace(/\/+$/,"");!C||!this.discoverStashpadFolders().includes(C)||setTimeout(()=>{this.adoptMarkdownDrop(p,C)},200)};this.registerEvent(this.app.vault.on("create",p=>{p instanceof T.TFile&&(g(p),u(p))})),this.registerEvent(this.app.vault.on("rename",p=>{p instanceof T.TFile&&(g(p),u(p))})),this.registerEvent(this.app.vault.on("delete",p=>{if(!(p instanceof T.TFolder))return;let f=p.path.replace(/\/+$/,"");this.knownStashpadFolders.has(f)&&this.handleStashpadFolderDeleted(f)})),this.registerEvent(this.app.vault.on("rename",(p,f)=>{p instanceof T.TFile&&this.maybeReHomeOnCrossFolderMove(p,f)})),this.registerEvent(this.app.vault.on("rename",(p,f)=>{p instanceof T.TFile&&this.maybeArchiveOnMoveIn(p,f)})),this.registerEvent(this.app.vault.on("create",p=>{p instanceof T.TFile?this.importService.enqueue(p):p instanceof T.TFolder&&this.importService.enqueueFolder(p)})),this.registerEvent(this.app.vault.on("rename",p=>{p instanceof T.TFile?this.importService.enqueue(p):p instanceof T.TFolder&&this.importService.enqueueFolder(p)})),this.registerEvent(this.app.vault.on("rename",(p,f)=>{p instanceof T.TFile&&this.maybeAdoptAuthorRename(p,f)})),this.registerDomEvent(window,"focus",()=>void this.checkForSyncedBuild()),setTimeout(()=>void this.checkForSyncedBuild(),5e3),this.registerInterval(window.setInterval(()=>void this.checkForSyncedBuild(),45e3))}async checkForSyncedBuild(){try{let e=this.manifest.dir;if(!e)return;let t=`${e.replace(/\/+$/,"")}/manifest.json`,i=this.app.vault.adapter;if(!await i.exists(t))return;let s=JSON.parse(await i.read(t))?.version,n=this.manifest.version;if(typeof s!="string"||!s||s===n||!this.isSemverGreater(s,n)||this.notifiedBuildVersion===s)return;this.notifiedBuildVersion=s,this.notifications.show({message:`A newer Stashpad build synced in (\`${n}\` \u2192 \`${s}\`). Reload the app to apply it.`,kind:"info",category:"system",duration:0,actions:[{label:"Reload app",onClick:()=>this.reloadAppForUpdate()}]})}catch(e){console.debug("[Stashpad] synced-build check failed",e)}}reloadAppForUpdate(){try{window.localStorage?.setItem(ta,"1")}catch{}try{if(this.app.commands?.executeCommandById?.("app:reload"))return}catch(e){console.warn("[Stashpad] app:reload command failed",e)}try{window.location.reload()}catch{new T.Notice("Reload Obsidian (close + reopen) to apply the Stashpad update.")}}async unghostStashpadTabsIfFlagged(){let e=!1;try{e=window.localStorage?.getItem(ta)==="1"}catch{}if(e){try{window.localStorage?.removeItem(ta)}catch{}for(let t of this.app.workspace.getLeavesOfType(z))try{let i=t;i.isDeferred&&typeof i.loadIfDeferred=="function"&&await i.loadIfDeferred()}catch(i){console.warn("[Stashpad] un-ghost leaf failed",i)}}}isSemverGreater(e,t){let i=e.split(".").map(o=>parseInt(o,10)||0),s=t.split(".").map(o=>parseInt(o,10)||0),n=Math.max(i.length,s.length);for(let o=0;o<n;o++){let a=i[o]??0,l=s[o]??0;if(a!==l)return a>l}return!1}parseAuthorFilePath(e){let t=e.match(/\/_authors\/([^/]+?)-([a-z0-9]{4,12})\.md$/i);return t?{name:t[1].replace(/-/g," "),id:t[2]}:null}authorNameToSafe(e){return e.replace(/[^\w\- ]+/g,"").trim().replace(/\s+/g,"-")||"author"}async syncAuthorFilesToName(){let e=(this.settings.authorId??"").trim(),t=(this.settings.authorName??"").trim();if(!e||!t)return;let i=this.authorNameToSafe(t);for(let s of this.discoverStashpadFolders()){let n=`${s}/_authors`;if(await this.app.vault.adapter.exists(n))for(let o of this.app.vault.getMarkdownFiles()){if(!o.path.startsWith(n+"/"))continue;let a=this.parseAuthorFilePath(o.path);if(!a||a.id!==e)continue;let l=`${n}/${i}-${e}.md`,c=o;if(o.path!==l)try{this.authorRenameInFlight.add(o.path),this.authorRenameInFlight.add(l),await this.app.fileManager.renameFile(o,l);let d=this.app.vault.getAbstractFileByPath(l);d&&(c=d)}catch(d){console.warn("[Stashpad] author file rename failed",d);continue}try{await this.refreshAuthorStub(c)}catch{}}}}async refreshAuthorStub(e){let t=(this.settings.authorName??"").trim(),i=(this.settings.authorRole??"").trim(),s=(this.settings.authorDepartment??"").trim();if(t)try{let n=await this.app.vault.read(e),o=n.replace(/^# .*$/m,`# ${t}`);o!==n&&await this.app.vault.modify(e,o),await this.app.fileManager.processFrontMatter(e,a=>{a.aliases=[t],delete a.name,i?a.role=i:delete a.role,s?a.department=s:delete a.department})}catch(n){console.warn("[Stashpad] refreshAuthorStub failed",n)}}async maybeAdoptAuthorRename(e,t){if(this.authorRenameInFlight.delete(e.path)||this.authorRenameInFlight.delete(t))return;let i=this.parseAuthorFilePath(e.path);if(!i)return;let s=(this.settings.authorId??"").trim();if(!s||i.id!==s)return;let n=i.name.trim();!n||n===(this.settings.authorName??"").trim()||(this.settings.authorName=n,await this.saveSettings(),await this.syncAuthorFilesToName())}async adoptMarkdownDrop(e,t){try{let i=this.app.vault.adapter,s=e.name,n=s.lastIndexOf("."),o=n>0?s.slice(0,n):s,a=n>0?s.slice(n):"",l=0;for(;await i.exists(`${t}/${s}`);)l+=1,s=`${o}-${l}${a}`;let c=`${t}/${s}`;await this.app.fileManager.renameFile(e,c);let d=this.app.vault.getAbstractFileByPath(c);d instanceof T.TFile&&setTimeout(()=>{this.fixOrphanParentForFile(d)},500),this.notifications.show({message:`Imported \`${e.name}\` \u2192 \`${t}\``,kind:"success",category:"import",folder:t,affectedPaths:[c]})}catch(i){console.warn("Stashpad: markdown drop adoption failed",i),this.notifications.show({message:`Couldn't import \`${e.name}\`: ${i.message}`,kind:"error",category:"import"})}}async fixOrphanParentForFile(e){try{let t=this.app.metadataCache.getFileCache(e)?.frontmatter,i=typeof t?.id=="string"?t.id.trim():"",s=t?.parent,n=typeof s=="string"?s.trim()!=="":s!=null,o=typeof t?.created=="string"&&t.created.trim()!=="",a=!i,l=!n,c=!o;if(!a&&!l&&!c)return;let{newId:d}=await Promise.resolve().then(()=>(qe(),fs)),h,g=!1,u=!1;if(await this.app.fileManager.processFrontMatter(e,I=>{if(a&&(typeof I.id=="string"&&I.id.trim()||(h=d(),I.id=h)),l){let m=I.parent;(typeof m=="string"?m.trim()!=="":m!=null)||(I.parent=k,g=!0)}c&&(typeof I.created=="string"&&I.created.trim()||(I.created=new Date(e.stat.ctime).toISOString(),u=!0))}),!h&&!g&&!u)return;let p=this.newLog(),f=h||i;f&&await p.append({type:"parent_change",id:f,payload:{from:null,to:k,reason:"orphan_auto_fix",path:e.path,addedId:!!h,addedParent:g,addedCreated:u}}),new T.Notice(`Adopted ${e.basename} \u2192 Home`)}catch(t){console.warn("Stashpad: orphan auto-fix failed",t)}}maybeReHomeOnCrossFolderMove(e,t){if(e.extension!=="md")return;let i=e.parent?.path?.replace(/\/+$/,"")??"",s=t.lastIndexOf("/"),n=(s>=0?t.slice(0,s):"").replace(/\/+$/,"");i!==n&&this.discoverStashpadFolders().includes(i)&&(this.reHomePending.has(e.path)||(this.reHomePending.add(e.path),setTimeout(()=>{this.reHomePending.delete(e.path),this.reHomeDanglingParent(e,i)},900)))}async reHomeDanglingParent(e,t){try{let i=this.app.metadataCache.getFileCache(e)?.frontmatter,s=typeof i?.id=="string"?i.id.trim():"",n=typeof i?.parent=="string"?i.parent.trim():"";if(!s||!n||n===k||this.app.vault.getMarkdownFiles().some(a=>(a.parent?.path?.replace(/\/+$/,"")??"")===t&&this.app.metadataCache.getFileCache(a)?.frontmatter?.id===n))return;await this.app.fileManager.processFrontMatter(e,a=>{let l=typeof a.parent=="string"?a.parent.trim():"";l&&l!==k&&(a.parent=k)}),await this.newLog().append({type:"parent_change",id:s,payload:{from:n,to:k,reason:"rehome_cross_folder_move",path:e.path}}),new T.Notice(`Re-homed ${e.basename} \u2192 Home (its parent isn't in this folder)`)}catch(i){console.warn("[Stashpad] re-home on cross-folder move failed",i)}}async autoImportStash(e,t){try{let i=new Uint8Array(await this.app.vault.readBinary(e));if(Ne(i)){this.importService.parkEncrypted(e.path),this.notifyPendingEncrypted();return}let s=await li(this.app,i);if(!s)return;let n=ht(),o=new Set;if(n&&typeof n.collectExistingIds=="function"&&n.noteFolder===t)for(let c of n.collectExistingIds())o.add(c);else for(let c of this.app.vault.getMarkdownFiles()){if(!c.path.startsWith(t+"/"))continue;let d=this.app.metadataCache.getFileCache(c)?.frontmatter?.id;typeof d=="string"&&o.add(d)}let a=await ot(this.app,s,t,o);try{await this.newLog().append({type:"stash_import",id:k,payload:{from:e.path,into:t,noteCount:a.notesWritten,attachmentsWritten:a.attachmentsWritten,collisionsRenamed:a.collisionsRenamed,auto:!0}})}catch{}try{await this.app.fileManager.trashFile(e)}catch{}let l=[`Auto-imported ${a.notesWritten} note${a.notesWritten===1?"":"s"} from ${e.name}`];a.attachmentsWritten&&l.push(`+ ${a.attachmentsWritten} attachment${a.attachmentsWritten===1?"":"s"}`),a.collisionsRenamed&&l.push(`(${a.collisionsRenamed} renamed)`),this.notifications.show({message:l.join(" "),kind:"success",category:"import",folder:t}),n&&typeof n.debouncedRender=="function"&&n.debouncedRender()}catch(i){this.notifications.show({message:`Stashpad: auto-import failed +`,d=De(o.basename)||o.basename,h=await this.uniquePath(e,xe(d,a));await this.createNote(h,Dt(l,c)),i.push(h)}}}recordNotePaths(r){return r.kind==="folder"?r.notePaths:[r.notePath]}announce(r,e=[]){let t=r[0]?.folder??"",i=r.length,s=e.length?`, replaced ${e.length}`:"",n=!1,o=async()=>{n||(n=!0,await this.undo(r,e))};this.plugin.notifications.show({message:`Imported ${i} file${i===1?"":"s"}${s} into \`${t.split("/").pop()}\`.`,kind:"success",category:"import",duration:0,folder:t,affectedPaths:r.flatMap(a=>this.recordNotePaths(a)),actions:[{label:"Undo import",onClick:()=>void o()}]}),t&&this.plugin.getUndoStack(t).push({label:`Import ${i} file${i===1?"":"s"}`,undo:async()=>{await o()},redo:async()=>{}})}async undo(r,e=[]){for(let t of r)try{if(t.kind==="md"){let i=this.app.vault.getAbstractFileByPath(t.notePath);i instanceof ee.TFile&&await this.app.fileManager.trashFile(i);let s=this.app.vault.getAbstractFileByPath(t.archivePath);if(s instanceof ee.TFile){let n=await this.uniquePath(t.folder,t.originalName);this.suppress(n),await this.app.fileManager.renameFile(s,n)}}else if(t.kind==="file"){let i=this.app.vault.getAbstractFileByPath(t.notePath);i instanceof ee.TFile&&await this.app.fileManager.trashFile(i);let s=this.app.vault.getAbstractFileByPath(t.attachmentPath);if(s instanceof ee.TFile){let n=await this.uniquePath(t.folder,t.originalName);this.suppress(n),await this.app.fileManager.renameFile(s,n)}}else{for(let s of t.notePaths){let n=this.app.vault.getAbstractFileByPath(s);n instanceof ee.TFile&&await this.app.fileManager.trashFile(n)}for(let s of t.attachmentPaths){let n=this.app.vault.getAbstractFileByPath(s);n instanceof ee.TFile&&await this.app.fileManager.trashFile(n)}let i=this.app.vault.getAbstractFileByPath(t.archivePath);if(i instanceof ee.TFolder){let s=await this.uniquePath(t.folder,t.originalName);this.suppress(s),await this.app.fileManager.renameFile(i,s)}}}catch(i){console.warn("[Stashpad] import undo failed",t,i)}for(let t of e)try{await this.app.vault.adapter.exists(t.path)||await this.app.vault.create(t.path,t.content)}catch(i){console.warn("[Stashpad] import undo: couldn't restore replaced note",t.path,i)}new ee.Notice(`Undid import of ${r.length} file(s).`)}pickFilesInto(r){let e=document.createElement("input");e.type="file",e.multiple=!0,e.setCssStyles({display:"none"}),e.onchange=async()=>{let t=Array.from(e.files??[]);if(e.remove(),t.length===0)return;let i=[];for(let n of t)try{let o=await n.arrayBuffer(),a=await this.uniquePath(r,n.name);this.suppress(a,6e4),await this.app.vault.createBinary(a,o);let l=this.app.vault.getAbstractFileByPath(a);l instanceof ee.TFile&&i.push(l)}catch(o){console.warn("[Stashpad] file-picker write failed",n.name,o)}if(i.length===0)return;await this.plugin.importLog.load();let s=[];for(let n of i)try{let o=n.stat?.size??null,a=n.extension==="md"?await this.importMarkdown(n):await this.importOtherFile(n);a&&(s.push(a),this.plugin.importLog.append({ts:new Date().toISOString(),folder:a.folder,kind:a.kind,originalName:n.name,size:o,sourcePath:n.path,notePaths:a.kind==="folder"?a.notePaths:[a.notePath]}))}catch(o){console.warn("[Stashpad] file-picker import failed",n.path,o)}s.length>0&&this.announce(s,[])},document.body.appendChild(e),e.click()}async importLooseFilesIn(r){let e=r.replace(/\/+$/,""),t=this.app.vault.getFiles().filter(n=>(n.parent?.path?.replace(/\/+$/,"")??"")===e),i=[];for(let n of t)await this.isStillImportable(n)&&i.push(n);if(i.length===0)return 0;await this.plugin.importLog.load();let s=0;for(let n of i)try{let o=n.stat?.size??null,a=n.extension==="md"?await this.importMarkdown(n):await this.importOtherFile(n);a&&(s++,this.plugin.importLog.append({ts:new Date().toISOString(),folder:a.folder,kind:a.kind,originalName:n.name,size:o,sourcePath:n.path,notePaths:a.kind==="folder"?a.notePaths:[a.notePath]}))}catch(o){console.warn("[Stashpad] loose-file import failed",n.path,o)}return s}async importLooseFoldersIn(r){let e=r.replace(/\/+$/,""),t=this.app.vault.getAbstractFileByPath(e);if(!(t instanceof ee.TFolder))return 0;let i=t.children.filter(n=>n instanceof ee.TFolder&&!On.has(n.name));if(i.length===0)return 0;await this.plugin.importLog.load();let s=0;for(let n of i){let o=n.path;try{let a=await this.importFolder(n);a&&a.kind==="folder"&&(s++,this.plugin.importLog.append({ts:new Date().toISOString(),folder:a.folder,kind:a.kind,originalName:n.name,size:null,sourcePath:o,notePaths:a.notePaths}))}catch(a){console.warn("[Stashpad] loose-folder import failed",o,a)}}return s}pendingEncryptedPaths(){for(let r of[...this.pendingEncryptedStashes])this.app.vault.getAbstractFileByPath(r)instanceof ee.TFile||this.pendingEncryptedStashes.delete(r);return[...this.pendingEncryptedStashes]}clearPendingEncrypted(r){this.pendingEncryptedStashes.delete(r)}shouldNotifyEncrypted(){return Date.now()>=this.encryptedNotifySnoozeUntil}snoozeEncryptedNotify(r){this.encryptedNotifySnoozeUntil=r===1/0?Number.MAX_SAFE_INTEGER:Date.now()+r}parkEncrypted(r){return this.pendingEncryptedStashes.has(r)?!1:(this.pendingEncryptedStashes.add(r),this.encryptedNotifySnoozeUntil=0,!0)}async importPendingEncrypted(){let r=0,e=!1;for(let t of this.pendingEncryptedPaths()){let i=this.app.vault.getAbstractFileByPath(t);if(!(i instanceof ee.TFile))continue;let s=i.parent?.path?.replace(/\/+$/,"")??"";if(await this.importOneStash(i,s,this.liveRootIds(s),!1,{allowLater:!0,onLater:()=>{e=!0,this.snoozeEncryptedNotify(60*60*1e3)}})&&r++,e)break}return{imported:r,rescheduled:e}}liveRootIds(r){let e=new Set;for(let t of this.app.vault.getMarkdownFiles()){if((t.parent?.path?.replace(/\/+$/,"")??"")!==r)continue;let i=this.app.metadataCache.getFileCache(t)?.frontmatter?.id;typeof i=="string"&&i&&e.add(i)}return e}async importOneStash(r,e,t,i,s={}){try{let n=new Uint8Array(await this.app.vault.readBinary(r));if(i&&Me(n))return this.pendingEncryptedStashes.add(r.path),!1;let o=await Ai(this.app,n,{...s,secretId:ti(r.basename)});if(!o)return!1;let a=await rt(this.app,o,e,t,{stripReserved:!0});if(a.colorAliases)for(let[l,c]of Object.entries(a.colorAliases))try{await this.plugin.setColorAlias(e,l,c)}catch{}try{await this.app.fileManager.trashFile(r)}catch{}this.pendingEncryptedStashes.delete(r.path);try{await this.plugin.newLog().append({type:"stash_import",id:F,payload:{from:r.path,into:e,noteCount:a.notesWritten,attachmentsWritten:a.attachmentsWritten,collisionsRenamed:a.collisionsRenamed,loose:!0}})}catch{}return!0}catch(n){return console.warn("[Stashpad] loose .stash import failed",r.path,n),!1}}async importLooseStashesIn(r,e={}){let t=r.replace(/\/+$/,""),i=this.app.vault.getFiles().filter(o=>(o.parent?.path?.replace(/\/+$/,"")??"")===t&&o.extension===ot);if(i.length===0)return 0;let s=this.liveRootIds(t),n=0;for(let o of i)await this.importOneStash(o,t,s,!!e.auto)&&n++;return n}async importLooseInto(r,e={}){let t=await this.importLooseFilesIn(r),i=await this.importLooseFoldersIn(r),s=await this.importLooseStashesIn(r,e);return{files:t,folders:i,stashes:s}}defaultDestination(){let e=this.plugin.lastActiveStashpadLeaf?.view?.noteFolder;return e&&this.plugin.discoverStashpadFolders().includes(e)?e:this.plugin.discoverStashpadFolders()[0]??null}confirm(r,e,t){return new Promise(i=>{new ie(this.app,r,e,t,i).open()})}dupChoice(r){return new Promise(e=>{new Ki(this.app,r,e).open()})}async ensureFolder(r){let e=this.app.vault.adapter,t=r.split("/").filter(Boolean),i="";for(let s of t){i=i?`${i}/${s}`:s;try{await e.exists(i)||await e.mkdir(i)}catch(n){if(!/already exists/i.test(n.message))throw n}}}async uniquePath(r,e){let t=this.app.vault.adapter,i=e.lastIndexOf("."),s=i>0?e.slice(0,i):e,n=i>0?e.slice(i):"",o=`${r}/${e}`,a=0;for(;await t.exists(o);)a+=1,o=`${r}/${s}-${a}${n}`;return o}};Fi.DEBOUNCE_MS=900,Fi.BATCH_CONFIRM_AT=8;var qn=Fi;var ms=class ms{constructor(r,e){this.app=r;this.entries=[];this.loaded=!1;this.dirOk=!1;this.writeChain=Promise.resolve();this.path=`${e.replace(/\/+$/,"")}/import-log.jsonl`}getPath(){return this.path}async load(){if(!this.loaded){this.loaded=!0;try{let r=this.app.vault.adapter;if(await r.exists(this.path)){let e=await r.read(this.path);for(let t of e.split(/\r?\n/))if(t.trim())try{this.entries.push(JSON.parse(t))}catch{}}}catch(r){console.warn("[Stashpad] import log load failed",r)}}}recent(){return[...this.entries].reverse()}findDuplicate(r,e){for(let t=this.entries.length-1;t>=0;t--){let i=this.entries[t];if(i.originalName===r&&(e==null||i.size==null||i.size===e))return i}return null}append(r){this.entries.push(r);let e=this.entries.length>ms.MAX_ENTRIES;e&&(this.entries=this.entries.slice(-ms.MAX_ENTRIES)),this.writeChain=this.writeChain.then(async()=>{try{if(await this.ensureDir(),e){let t=this.entries.map(i=>JSON.stringify(i)).join(` +`);await this.app.vault.adapter.write(this.path,t+` +`)}else await this.app.vault.adapter.append(this.path,JSON.stringify(r)+` +`)}catch(t){console.warn("[Stashpad] import log append failed",t)}})}async ensureDir(){if(this.dirOk)return;let r=this.app.vault.adapter,t=this.path.slice(0,this.path.lastIndexOf("/")).split("/").filter(Boolean),i="";for(let s of t)i=i?`${i}/${s}`:s,await r.exists(i)||await r.mkdir(i);this.dirOk=!0}};ms.MAX_ENTRIES=5e3;var Kn=ms;var $n=class $n{constructor(r,e){this.app=r;this.map=new Map;this.loaded=!1;this.dirty=!1;this.saveTimer=null;this.dirOk=!1;this.writeChain=Promise.resolve();this.path=`${e.replace(/\/+$/,"")}/render-cache.json`}async load(){if(!this.loaded){this.loaded=!0;try{let r=this.app.vault.adapter;if(await r.exists(this.path)){let e=JSON.parse(await r.read(this.path));if(e?.schema===1&&e.entries){for(let[t,i]of Object.entries(e.entries))this.map.set(t,i);for(let t of[...this.map.keys()])this.app.vault.getAbstractFileByPath(t)||(this.map.delete(t),this.dirty=!0)}}}catch(r){console.warn("[Stashpad] render cache load failed; starting empty",r),this.map.clear()}}}evict(r){this.map.delete(r)&&(this.dirty=!0,this.save())}get(r){return this.map.get(r)}has(r){return this.map.has(r)}set(r,e){this.map.set(r,e),this.dirty=!0,this.scheduleSave()}scheduleSave(){this.saveTimer==null&&(this.saveTimer=window.setTimeout(()=>{this.saveTimer=null,this.save()},$n.SAVE_DEBOUNCE_MS))}save(){return this.saveTimer!=null&&(window.clearTimeout(this.saveTimer),this.saveTimer=null),this.dirty?(this.dirty=!1,this.writeChain=this.writeChain.then(async()=>{try{await this.ensureDir();let r={schema:1,entries:Object.fromEntries(this.map)};await this.app.vault.adapter.write(this.path,JSON.stringify(r))}catch(r){this.dirty=!0,console.warn("[Stashpad] render cache save failed",r)}}),this.writeChain):this.writeChain}async ensureDir(){if(this.dirOk)return;let r=this.app.vault.adapter,t=this.path.slice(0,this.path.lastIndexOf("/")).split("/").filter(Boolean),i="";for(let s of t)i=i?`${i}/${s}`:s,await r.exists(i)||await r.mkdir(i);this.dirOk=!0}};$n.SAVE_DEBOUNCE_MS=8e3;var Jn=$n;var gr="stashpad:unghost-after-reload",Mt=class Mt extends M.Plugin{constructor(){super(...arguments);this.settings={...Qt};this.reEncryptScheduler=null;this.debugBuffer=[];this.undoStacks=new Map;this.lastActiveStashpadLeaf=null;this.rebootstrapInProgress=!1;this.pendingEncBlobs=new Set;this.suppressComposerAutofocusUntil=0;this.settingTab=null;this.stashpadSelectionListeners=new Set;this.stashpadContentListeners=new Set;this._notifications=null;this._authorRegistry=null;this._importService=null;this._importLog=null;this.noteClipboard=null;this.noteClipboardNotice=null;this._renderCacheStore=null;this.knownStashpadFolders=new Set;this.suppressedFolderDeletes=new Set;this.sweptFolders=new Set;this.notifiedBuildVersion=null;this.authorRenameInFlight=new Set;this.reHomePending=new Set;this.archiveMigrationInFlight=!1;this.okfRebuildTimers=new Map;this.okfRebuildingFolders=new Set;this.archivePending=new Map;this.autoSweepInProgress=!1;this.notifPersistenceWired=!1;this.notifSaveTimer=null;this.LAST_CURSOR_LS_KEY="stashpad:last-cursor";this.LAST_SELECTION_LS_KEY="stashpad:last-selection";this.ACTIVE_VERSIONS_LS_KEY="stashpad:active-versions";this.pendingForkDeletes=new Set;this.writeChain=Promise.resolve();this.lastSeenSettingsRev=0;this.settingsBaseline={}}trace(e,t){if(!this.settings.debugTrace)return;let i="";if(t)try{i=" "+JSON.stringify(t)}catch{i=" [unserializable]"}let s=Math.round(performance.now());this.debugBuffer.push(`+${s}ms ${e}${i}`),this.debugBuffer.length>Mt.DEBUG_BUFFER_MAX&&this.debugBuffer.splice(0,this.debugBuffer.length-Mt.DEBUG_BUFFER_MAX)}getDebugTrace(){return this.debugBuffer.join(` +`)}clearDebugTrace(){this.debugBuffer=[]}get notifications(){return this._notifications||(this._notifications=new Us(this.app)),this._notifications}get authorRegistry(){return this._authorRegistry||(this._authorRegistry=new Ln(this.app,this.pluginPrivatePath())),this._authorRegistry}get importService(){return this._importService||(this._importService=new qn(this)),this._importService}get importLog(){return this._importLog||(this._importLog=new Kn(this.app,this.pluginPrivatePath())),this._importLog}clearNoteClipboard(){try{this.noteClipboardNotice?.hide()}catch{}this.noteClipboardNotice=null,this.noteClipboard=null}get renderCacheStore(){return this._renderCacheStore||(this._renderCacheStore=new Jn(this.app,this.pluginPrivatePath())),this._renderCacheStore}async onunload(){try{this.encryption?.dispose()}catch{}try{for(let e of this.archivePending.values())window.clearTimeout(e.timer);this.archivePending.clear()}catch{}try{this.reEncryptScheduler?.dispose()}catch{}try{await this._renderCacheStore?.save()}catch{}}pluginPrivatePath(e=""){let i=`${this.manifest.dir.replace(/\/+$/,"")}/.stashpad`;return e?`${i}/${e.replace(/^\/+/,"")}`:i}newLog(){return new Un(this.app,this.pluginPrivatePath(),()=>(this.settings?.authorName??"").trim())}async migrateLegacyPaths(){let e=this.app.vault.adapter,t=this.pluginPrivatePath(),i=async()=>{let a=t.split("/").filter(Boolean),l="";for(let c of a)l=l?`${l}/${c}`:c,await e.exists(l)||await e.mkdir(l)},s=`${this.manifest.dir.replace(/\/+$/,"")}/data.json`,n=this.pluginPrivatePath("data.json");if(await e.exists(n))try{let a=await e.read(n);await e.write(s,a),await e.write(`${n}.bak`,a),await e.remove(n),console.debug("[Stashpad] relocated data.json \u2192 standard path (for Obsidian Sync)")}catch(a){console.warn("Stashpad: data.json relocation failed",a)}let o=".stashpad";if(await e.exists(o))try{await i();let a=await e.list(o);for(let l of a.files){let c=l.replace(/^.*\//,""),d=this.pluginPrivatePath(c);if(!await e.exists(d))try{let h=await e.read(l);await e.write(d,h),console.debug("[Stashpad] migrated",l,"\u2192",d)}catch(h){console.warn(`Stashpad: failed to migrate ${l}`,h)}}}catch(a){console.warn("Stashpad: .stashpad migration scan failed",a)}}async createNewStashpad(e){let t=e.trim().replace(/^\/+|\/+$/g,"");if(!t)throw new Error("Folder name is empty");let i=this.app.vault.adapter,s=t.split("/").filter(Boolean),n="";for(let c of s)n=n?`${n}/${c}`:c,await i.exists(n)||await i.mkdir(n);let o=`${t}/Home.md`;if(await i.exists(o)){let c=this.app.vault.getAbstractFileByPath(o);c&&await this.app.fileManager.processFrontMatter(c,d=>{(typeof d.id!="string"||!d.id)&&(d.id=F),"parent"in d||(d.parent=null),(typeof d.created!="string"||!d.created)&&(d.created=new Date().toISOString())});return}let a=new Date().toISOString(),l=["---",`id: ${F}`,"parent: null",`created: ${a}`,"---","Home"].join(` +`);await this.app.vault.create(o,l);try{await this.seedLocalAuthorStub(t)}catch{}try{await this.seedKnownAuthorsInFolder(t)}catch{}}collectColorsInFolder(e){let t=new Map,i=e.replace(/\/+$/,"");for(let n of this.app.vault.getMarkdownFiles()){let o=n.parent?.path?.replace(/\/+$/,"")??"";if(o!==i&&!o.startsWith(i+"/"))continue;let a=this.app.metadataCache.getFileCache(n)?.frontmatter,l=typeof a?.color=="string"?a.color.trim():"";if(!l||!/^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(l))continue;let c=l.toLowerCase();t.set(c,(t.get(c)??0)+1)}let s=[...t.entries()].map(([n,o])=>({hex:n,count:o}));return s.sort((n,o)=>o.count-n.count||n.hex.localeCompare(o.hex)),s}async recolorAllInFolder(e,t,i){let s=e.replace(/\/+$/,""),n=t.toLowerCase(),o=0;for(let l of this.app.vault.getMarkdownFiles()){let c=l.parent?.path?.replace(/\/+$/,"")??"";if(c!==s&&!c.startsWith(s+"/"))continue;let d=this.app.metadataCache.getFileCache(l)?.frontmatter;if((typeof d?.color=="string"?d.color.trim().toLowerCase():"")===n)try{await this.app.fileManager.processFrontMatter(l,g=>{i?g.color=i:delete g.color}),o++}catch(g){console.warn(`Stashpad: recolor failed for ${l.path}`,g)}}let a=this.settings.colorAliases?.[s];if(a){let l=a[n];l&&(delete a[n],i&&(a[i.toLowerCase()]=l),Object.keys(a).length===0&&delete this.settings.colorAliases[s],await this.saveSettings())}return o}getColorAlias(e,t){let i=e.replace(/\/+$/,""),s=this.settings.colorAliases?.[i];if(!s)return;let n=s[t.toLowerCase()];return n&&n.trim()?n:void 0}async setColorAlias(e,t,i){let s=e.replace(/\/+$/,""),n=t.toLowerCase();this.settings.colorAliases||(this.settings.colorAliases={}),this.settings.colorAliases[s]||(this.settings.colorAliases[s]={});let o=this.settings.colorAliases[s],a=i.trim();a?o[n]=a:delete o[n],Object.keys(o).length===0&&delete this.settings.colorAliases[s],await this.saveSettings()}async waitForStashpadFolder(e,t=2e3){let i=e.trim().replace(/^\/+|\/+$/g,"");if(!i)return;let s=Date.now();for(;Date.now()-s<t;){if(this.discoverStashpadFolders().includes(i))return;await new Promise(n=>setTimeout(n,80))}}importExcludePrefixList(){return(this.settings.importExcludePrefixes??"_").split(",").map(e=>e.trim()).filter(Boolean)}pathHasExcludedSegment(e){let t=this.importExcludePrefixList();return t.length?e.replace(/\/+$/,"").split("/").some(i=>t.some(s=>i.startsWith(s))):!1}discoverStashpadFolders(){let e=new Set;for(let i of this.app.vault.getMarkdownFiles()){let s=this.app.metadataCache.getFileCache(i)?.frontmatter;if(typeof s?.id!="string"||!s.id.trim()||!s||!("parent"in s))continue;let n=i.parent?.path?.replace(/\/+$/,"")??"";n&&!this.pathHasExcludedSegment(n)&&!Ht(n)&&e.add(n)}let t=[...e].sort();return this.knownStashpadFolders=new Set(t),t}closeStashpadTabsFor(e){let t=0;for(let i of this.app.workspace.getLeavesOfType(z)){let s=i.view?.noteFolder??"";s||(s=((i.getViewState?.()?.state??{}).folderOverride??"")||this.settings.folder||"Stashpad"),s=(s||"").replace(/\/+$/,""),(s===e||s.startsWith(e+"/"))&&(i.detach(),t++)}return t}async remapFolderPathInSettings(e,t){let i=e.replace(/\/+$/,""),s=t.replace(/\/+$/,"");if(!i||!s||i===s)return;let n=this.settings,o=c=>c===i?s:c.startsWith(i+"/")?s+c.slice(i.length):c,a=c=>c&&c.map(o),l=c=>{if(!c)return c;let d={};for(let[h,g]of Object.entries(c))d[o(h)]=g;return d};n.folderPanelPinned=a(n.folderPanelPinned)??n.folderPanelPinned,n.folderPanelDownranked=a(n.folderPanelDownranked)??n.folderPanelDownranked,n.folderPanelHidden=a(n.folderPanelHidden)??n.folderPanelHidden,n.archiveFolders=a(n.archiveFolders)??n.archiveFolders,n.searchIncludedFolders=a(n.searchIncludedFolders)??n.searchIncludedFolders,n.searchExcludedFolders=a(n.searchExcludedFolders)??n.searchExcludedFolders,n.defaultArchiveFolder&&(n.defaultArchiveFolder=o(n.defaultArchiveFolder)),((n.folder||"").replace(/\/+$/,"")===i||(n.folder||"").startsWith(i+"/"))&&(n.folder=o((n.folder||"").replace(/\/+$/,""))),n.folderEncPrefs=l(n.folderEncPrefs)??n.folderEncPrefs,n.viewModes=l(n.viewModes)??n.viewModes,n.encryptionFilter&&(n.encryptionFilter=l(n.encryptionFilter)),await this.saveSettings()}async prunePlacementFor(e){let t=this.settings,i=o=>(o??[]).filter(a=>a!==e&&!a.startsWith(e+"/")),s=(t.folderPanelPinned?.length??0)+(t.folderPanelDownranked?.length??0)+(t.folderPanelHidden?.length??0);t.folderPanelPinned=i(t.folderPanelPinned),t.folderPanelDownranked=i(t.folderPanelDownranked),t.folderPanelHidden=i(t.folderPanelHidden),t.folderPanelPinned.length+t.folderPanelDownranked.length+t.folderPanelHidden.length!==s&&await this.saveSettings()}async deleteStashpadFolderWithUndo(e){let t=e.path.replace(/\/+$/,""),i=e.name,s=this.app.vault.adapter,n=".trash";try{await s.exists(n)||await s.mkdir(n)}catch(c){console.warn("[Stashpad] couldn't ensure .trash",c)}let o=`${n}/${i}`;for(let c=1;await s.exists(o);c++)o=`${n}/${i} (${c})`;this.suppressedFolderDeletes.add(t),window.setTimeout(()=>this.suppressedFolderDeletes.delete(t),5e3);let a=this.closeStashpadTabsFor(t);await this.prunePlacementFor(t),this.knownStashpadFolders.delete(t);try{await s.rename(t,o)}catch(c){console.warn("[Stashpad] folder delete failed",c),this.suppressedFolderDeletes.delete(t),new M.Notice("Delete failed (see console).");return}let l=a>0?`Deleted \u201C${i}\u201D \u2014 closed ${a} open tab${a===1?"":"s"}.`:`Deleted \u201C${i}\u201D.`;this.notifications.show({message:l,kind:"warning",category:"delete",duration:0,folder:t,actions:[{label:"Undo",onClick:async()=>{try{if(await s.exists(t)){new M.Notice(`Can't undo \u2014 \u201C${i}\u201D already exists.`);return}this.suppressedFolderDeletes.add(t),window.setTimeout(()=>this.suppressedFolderDeletes.delete(t),5e3),await s.rename(o,t),new M.Notice(`Restored \u201C${i}\u201D.`),this.activateViewForFolder(t)}catch(c){console.warn("[Stashpad] folder undo failed",c),new M.Notice("Undo failed (see console).")}}}]})}async handleStashpadFolderDeleted(e){let t=e.replace(/\/+$/,"");if(!t||this.suppressedFolderDeletes.has(t))return;let i=this.closeStashpadTabsFor(t);await this.prunePlacementFor(t),this.knownStashpadFolders.delete(t);let s=t.split("/").pop()||t;this.notifications.show({message:i>0?`Stashpad \u201C${s}\u201D was deleted \u2014 closed ${i} open tab${i===1?"":"s"}.`:`Stashpad \u201C${s}\u201D was deleted.`,kind:"warning",category:"delete",folder:t})}searchableFolders(e){let t=new Set(this.settings.searchIncludedFolders),i=new Set(this.settings.searchExcludedFolders),s=new Set((this.settings.archiveFolders??[]).map(c=>(c??"").replace(/\/+$/,""))),o=this.discoverStashpadFolders().filter(c=>s.has(c)?!1:t.size>0?t.has(c):!i.has(c)),a=(e||"").trim().replace(/\/+$/,""),l=o.filter(c=>c!==a);return a&&l.unshift(a),l}async maybeSweepFolder(e){let t=(e||"").trim().replace(/\/+$/,"");!t||this.sweptFolders.has(t)||(this.sweptFolders.add(t),setTimeout(()=>{this.runSweep(t,{silent:!0})},3e3))}async runIntegrityCheckOnFolder(e){let t=(e||"").trim().replace(/\/+$/,"");t&&await this.runSweep(t,{silent:!1})}async runSweep(e,t){try{let i=this.newLog(),s={},n=this.app.vault.getMarkdownFiles().filter(c=>c.path===e||c.path.startsWith(e+"/"));for(let c of n){let d=this.app.metadataCache.getFileCache(c)?.frontmatter,h=typeof d?.id=="string"?d.id.trim():"";if(!h)continue;let g=d&&"parent"in d?d.parent??null:null;s[h]={parent:g,path:c.path}}let o=await i.readState(),a=c=>c===e||c.startsWith(e+"/");if(!t.silent){for(let[c,d]of Object.entries(s)){let h=o[c];h?h.parent!==d.parent?await i.append({type:"parent_change",id:c,payload:{from:h.parent,to:d.parent}}):h.path!==d.path&&await i.append({type:"rename",id:c,payload:{from:h.path,to:d.path}}):await i.append({type:"create",id:c,payload:{path:d.path,parent:d.parent}})}for(let[c,d]of Object.entries(o))!s[c]&&a(d.path)&&await i.append({type:"missing",id:c,payload:{lastPath:d.path}})}let l={};for(let[c,d]of Object.entries(o))a(d.path)||(l[c]=d);for(let[c,d]of Object.entries(s))l[c]=d;await i.writeState(l)}catch(i){console.warn("Stashpad: integrity sweep failed",i)}}getUndoStack(e){let t=this.undoStacks.get(e);return t||(t=new Gn,this.undoStacks.set(e,t)),t}async onload(){await this.migrateLegacyPaths(),await this.loadSettings(),Se.enabled=!!this.settings.enablePerfProfiling,this.encryption=new Ys(this.app,()=>({...ns(),...this.settings.encryption??{}}),async p=>{this.settings.encryption=p,await this.saveSettings()}),this.encryption.init().then(()=>this.encryption.tryAutoUnlock()),this.app.workspace.onLayoutReady(()=>{this.resumeRotations()}),this.reEncryptScheduler=new Nn(this),this.reEncryptScheduler.start(),this.settingTab=new Xs(this.app,this),this.addSettingTab(this.settingTab),await this.renderCacheStore.load(),this.registerEvent(this.app.vault.on("delete",p=>this.renderCacheStore.evict(p.path))),this.registerEvent(this.app.vault.on("rename",(p,u)=>this.renderCacheStore.evict(u)));let e=(0,M.debounce)(()=>void this.flushForkSiblingPrune(),250,!0);this.registerEvent(this.app.vault.on("delete",p=>{p instanceof M.TFile&&p.extension==="md"&&(this.pendingForkDeletes.add(p.basename),e())})),this.registerEvent(this.app.vault.on("create",p=>this.onOkfFileEvent(p.path))),this.registerEvent(this.app.vault.on("delete",p=>this.onOkfFileEvent(p.path))),this.registerEvent(this.app.vault.on("rename",(p,u)=>{this.onOkfFileEvent(p.path),this.onOkfFileEvent(u)})),await this.authorRegistry.load();{let p=(this.settings.authorId??"").trim();p&&this.authorRegistry.record({id:p,name:this.settings.authorName,role:this.settings.authorRole,department:this.settings.authorDepartment})}this.registerEvent(this.app.vault.on("create",p=>{p.path.endsWith(".stashenc")&&this.pendingEncBlobs.delete(p.path)})),this.registerEvent(this.app.vault.on("delete",p=>{p.path.endsWith(".stashenc")&&this.pendingEncBlobs.delete(p.path)})),this.app.workspace.onLayoutReady(()=>{this.reconcileLockedRegistry(),window.setTimeout(async()=>{try{await this.migrateArchiveFoldersToSubfolders()}catch(p){console.error("[Stashpad] archive migration failed",p)}try{await this.migrateDeletedToTrashSubfolders()}catch(p){console.error("[Stashpad] trash migration failed",p)}this.pruneZombieArchiveEntries()},5e3),window.setTimeout(()=>{if(!this.settings.reEncryptNudge)return;let p=this.reEncryptWatching().length;if(p===0)return;let u=new M.Notice("",0);u.noticeEl.createSpan({text:`\u{1F513} ${p} previously-encrypted note${p===1?" is":"s are"} still plaintext. `});let I=u.noticeEl.createEl("button",{text:"Review"});I.onclick=()=>{u.hide(),Qi(this,"watch")}},8e3),window.setTimeout(()=>void this.checkDueReminders(),6e3),this.registerInterval(window.setInterval(()=>void this.checkDueReminders(),5*60*1e3)),window.setTimeout(()=>{this.seedLocalAuthorStubsEverywhere()},4e3),window.setTimeout(()=>this.syncObsidianExcludedArchives(),4500),window.setTimeout(()=>this.importService.setArmed(!0),2500),window.setTimeout(()=>void this.runAutoImportSweep(),5e3),this.registerInterval(window.setInterval(()=>void this.runAutoImportSweep(),5*60*1e3)),window.setTimeout(()=>void this.migratePinnedNotesToFrontmatter(),3e3),window.setTimeout(()=>void this.unghostStashpadTabsIfFlagged(),1200)}),this.registerView(z,p=>new kn(p,this)),this.registerView(Pt,p=>new _s(p,this)),this.registerView(It,p=>new Ds(p,this)),this.registerView(Zt,p=>new xn(p,this)),this.registerView(jt,p=>new Tn(p,this)),this.registerView(mt,p=>new Pn(p,this)),this.registerEvent(this.app.workspace.on("active-leaf-change",p=>{p&&p.view.getViewType()===z&&(this.lastActiveStashpadLeaf=p,this.settings.autoOpenDetailPanel&&this.app.workspace.getLeavesOfType(Pt).length===0&&setTimeout(()=>{en(this.app)},0),this.notifyStashpadSelectionChanged())}));let t=()=>{let p=pt(),u=!!p&&this.app.workspace.activeLeaf&&this.app.workspace.activeLeaf.view===p,I=!!u&&this.settings.hideMobileToolbarInStashpad;document.body.classList.toggle("stashpad-hide-mobile-toolbar",I),document.body.classList.toggle("stashpad-active",!!u)};this.register(ga(t)),this.registerEvent(this.app.workspace.on("active-leaf-change",t));let i=p=>{try{let u=Array.from(document.querySelectorAll("style")).filter(I=>(I.textContent??"").includes(".stashpad-"));for(let I of u){let m=I.id||"",C=m?`style[data-stashpad-source="${m}"]`:null;if(C&&p.head.querySelector(C))continue;let B=p.createElement("style");m?B.setAttribute("data-stashpad-source",m):B.setAttribute("data-stashpad-source","anon"),B.textContent=I.textContent??"",p.head.appendChild(B)}}catch(u){console.warn("[Stashpad] inject popout styles failed",u)}};this.registerEvent(this.app.workspace.on("window-open",p=>{let u=p?.doc??p?.win?.document??null;u&&i(u)})),this.registerEvent(this.app.workspace.on("file-menu",(p,u)=>{if(!(u instanceof M.TFolder))return;let I=u.path.replace(/\/+$/,"");p.addItem(C=>C.setTitle("\u{1F512} Encrypt with Stashpad").setIcon("lock").onClick(()=>void this.encryptFolderFromExplorer(I))),u.children?.some(C=>C instanceof M.TFile&&C.extension==="stashenc")&&!this.folderHasStashpadNotes(I)&&p.addItem(C=>C.setTitle("\u{1F513} Decrypt with Stashpad").setIcon("unlock").onClick(()=>void this.decryptFolderFromExplorer(I))),this.discoverStashpadFolders().includes(I)&&p.addItem(C=>{C.setTitle("Open folder in Stashpad").setIcon("layout-list").onClick(()=>void this.openFolderInStashpad(I))})})),setTimeout(()=>{try{let p=this.app.workspace;typeof p.iterateAllLeaves=="function"&&p.iterateAllLeaves(u=>{let I=u?.view?.containerEl?.ownerDocument;I&&I!==document&&i(I)})}catch{}},200),t(),this.register(()=>document.body.classList.remove("stashpad-hide-mobile-toolbar","stashpad-active"));let s=window.visualViewport,n=()=>{let p=document.querySelector(".mobile-toolbar, .mobile-toolbar-container"),u=p&&p.isConnected?p.offsetHeight:0;document.body.style.setProperty("--stashpad-toolbar-h",`${u}px`);let I=0;s&&(I=Math.max(0,window.innerHeight-s.height-s.offsetTop)),document.body.style.setProperty("--stashpad-vv-bottom-gap",`${I}px`),document.body.classList.toggle("stashpad-keyboard-open",I>100)};n(),s&&(s.addEventListener("resize",n),s.addEventListener("scroll",n),this.register(()=>{s.removeEventListener("resize",n),s.removeEventListener("scroll",n)})),window.addEventListener("resize",n),this.register(()=>window.removeEventListener("resize",n)),requestAnimationFrame(n),setTimeout(n,250),setTimeout(n,1e3),this.addRibbonIcon("list-tree","Open Stashpad",()=>{if(this.discoverStashpadFolders().length===0){this.activateView({reveal:!0});return}this.openFolderPicker()}).addEventListener("contextmenu",p=>{p.preventDefault(),this.openFolderPicker()}),this.addRibbonIcon("panel-left","Open Stashpad panels (sidebar)",()=>{xs(this.app)}),this.addRibbonIcon("folders","Open Stashpad folder panel (sidebar)",()=>{Hn(this.app)}),this.addRibbonIcon("panel-right","Open Stashpad detail panel (right sidebar)",()=>{en(this.app)}),this.addCommand({id:"stashpad-open",name:"Open Stashpad in new tab",callback:()=>{if(this.discoverStashpadFolders().length>=2){this.openFolderPicker();return}this.activateView({reveal:!1})}}),this.addCommand({id:"stashpad-reveal",name:"Reveal or open Stashpad",callback:()=>void this.activateView({reveal:!0})}),this.addCommand({id:"stashpad-focus-last-tab",name:"Focus last Stashpad tab",callback:()=>void this.focusLastStashpadTab()}),this.addCommand({id:"stashpad-focus-folder-panel",name:"Focus folder panel",callback:()=>void this.focusFolderPanel()});let a=p=>{let u=pt();u&&typeof u[p]=="function"&&u[p]()};this.addCommand({id:"stashpad-toggle-split",name:"Toggle split-on-newlines",callback:()=>a("toggleSplit")}),this.addCommand({id:"stashpad-fork-version",name:"Fork as a version (alternate draft in this sheet)",callback:()=>a("cmdForkVersion")}),this.addCommand({id:"stashpad-mark-version-final",name:"Mark version as final (sheet)",callback:()=>a("cmdMarkVersionFinal")}),this.addCommand({id:"stashpad-command-palette",name:"Command palette (Stashpad only)",callback:()=>a("openStashpadCommandPalette")}),this.addCommand({id:"stashpad-lock-selection",name:"Encrypt (lock) selection (notes + children)",callback:()=>a("cmdLockSelection")}),this.addCommand({id:"stashpad-unlock-all",name:"Decrypt (unlock) locked notes in view",callback:()=>a("cmdUnlockAll")}),this.addCommand({id:"stashpad-unlock-all-vault",name:"Decrypt (unlock) ALL locked notes in the vault",callback:()=>void this.unlockAllInVault()}),this.addCommand({id:"stashpad-copy-notes",name:"Copy notes (note clipboard \u2014 paste to duplicate)",callback:()=>a("cmdCopyNotes")}),this.addCommand({id:"stashpad-cut-notes",name:"Cut notes (paste in list to move, in composer to extract text)",callback:()=>a("cmdCutNotes")}),this.addCommand({id:"stashpad-paste-notes",name:"Paste notes (from the note clipboard)",callback:()=>a("cmdPasteNotes")}),this.addCommand({id:"stashpad-move-to-archive",name:"Move selection to archive",callback:()=>a("cmdMoveToArchive")}),this.addCommand({id:"stashpad-encrypt-delete",name:"Encrypt & delete selection (to encrypted trash)",callback:()=>a("cmdEncryptDelete")}),this.addCommand({id:"stashpad-restore-trash",name:"Open aggregated Trash view",callback:()=>this.openEncryptedTrash()}),this.addCommand({id:"stashpad-open-all-encrypted",name:"Open aggregated Encrypted notes view",callback:()=>void Qi(this,"encrypted")}),this.addCommand({id:"stashpad-open-all-archived",name:"Open aggregated Archived view",callback:()=>void Qi(this,"archived")}),this.addCommand({id:"stashpad-open-all-tasks",name:"Open aggregated Tasks view",callback:()=>void Qi(this,"tasks")}),this.addCommand({id:"stashpad-open-watchlist",name:"Open aggregated Previously encrypted view",callback:()=>void Qi(this,"watch")}),this.addCommand({id:"stashpad-encrypt-applicable",name:"Encrypt everything applicable (re-encrypt sweep)",callback:()=>void this.encryptEverythingApplicable()}),this.addCommand({id:"stashpad-encrypt-existing-trash",name:"Encrypt existing Obsidian trash (backfill .trash into encrypted trash)",callback:()=>void this.encryptExistingTrash()}),this.addCommand({id:"stashpad-close-duplicate-tabs",name:"Close duplicate & orphaned Stashpad tabs (tidy up)",callback:()=>void this.closeDuplicateStashpadTabs()}),this.addCommand({id:"stashpad-claim-selected-author",name:"Claim authorship of selected notes",callback:()=>a("claimSelectedAsAuthor")}),this.addCommand({id:"stashpad-claim-folder-author",name:"Claim authorship of all unauthored notes in this folder",callback:()=>a("claimFolderAsAuthor")}),this.addCommand({id:"stashpad-claim-selected-contributor",name:"Claim selected notes (author if unowned, else add me as contributor)",callback:()=>a("claimSelectedWithContributor")}),this.addCommand({id:"stashpad-claim-folder-contributor",name:"Claim all notes in this folder (author if unowned, else add me as contributor)",callback:()=>a("claimFolderWithContributor")}),this.addCommand({id:"stashpad-pick-destination",name:"Pick destination for next note",callback:()=>a("openDestinationPicker")}),this.addCommand({id:"stashpad-search",name:"Search Stashpad notes",callback:()=>a("openSearchModal")}),this.addCommand({id:"stashpad-search-in-parent",name:"Search in current parent",callback:()=>a("openSearchInParentModal")}),this.addCommand({id:"stashpad-move-picker",name:"Move selection (picker)",callback:()=>a("cmdMovePicker")}),this.addCommand({id:"stashpad-merge",name:"Merge selection",callback:()=>a("cmdMerge")}),this.addCommand({id:"stashpad-copy",name:"Copy selection",callback:()=>a("cmdCopy")}),this.addCommand({id:"stashpad-copy-tree",name:"Copy focused subtree",callback:()=>a("cmdCopyTree")}),this.addCommand({id:"stashpad-copy-outline",name:"Copy as outline (nested embeds)",callback:()=>a("cmdCopyOutline")}),this.addCommand({id:"stashpad-split",name:"Split note\u2026",callback:()=>a("cmdSplit")}),this.addCommand({id:"stashpad-edit-note",name:"Edit note in new tab (selection)",callback:()=>a("cmdOpenInEditor")}),this.addCommand({id:"stashpad-edit-parent",name:"Edit parent note in new tab",callback:()=>a("cmdOpenParentInEditor")}),this.addCommand({id:"stashpad-delete",name:"Delete selection",callback:()=>a("cmdDelete")}),this.addCommand({id:"stashpad-move-up",name:"Move note up",callback:()=>a("cmdMoveUp")}),this.addCommand({id:"stashpad-move-down",name:"Move note down",callback:()=>a("cmdMoveDown")}),this.addCommand({id:"stashpad-move-to-top",name:"Move note to top",callback:()=>a("cmdMoveToTop")}),this.addCommand({id:"stashpad-move-to-bottom",name:"Move note to bottom",callback:()=>a("cmdMoveToBottom")}),this.addCommand({id:"stashpad-outdent",name:"Outdent (move to grandparent)",callback:()=>a("cmdOutdent")}),this.addCommand({id:"stashpad-set-color",name:"Set note color\u2026",callback:()=>a("cmdSetColor")}),this.addCommand({id:"stashpad-clone",name:"Clone selection (duplicate / copy notes)",callback:()=>a("cmdClone")}),this.addCommand({id:"stashpad-fork-note",name:"Fork into a separate note (copy under a chosen parent)",callback:()=>a("cmdForkNote")}),this.addCommand({id:"stashpad-insert-template",name:"Insert template (clone an existing note)",callback:()=>a("cmdInsertTemplate")}),this.addCommand({id:"stashpad-toggle-expand",name:"Show more / show less (expand toggle)",callback:()=>a("cmdToggleExpand")}),this.addCommand({id:"stashpad-expand-all",name:"Expand all (show every note's full body)",callback:()=>a("cmdExpandAll")}),this.addCommand({id:"stashpad-collapse-all",name:"Collapse all (clamp every note's body)",callback:()=>a("cmdCollapseAll")}),this.addCommand({id:"stashpad-pick-move",name:"Move (in-list, arrow + Enter)",callback:()=>a("cmdInListPicker")}),this.addCommand({id:"stashpad-open-in-new-tab",name:"Open in new Stashpad tab",callback:()=>a("cmdOpenInNewStashpadTab")}),this.addCommand({id:"stashpad-toggle-complete",name:"Toggle complete (strikethrough)",callback:()=>a("cmdToggleComplete")}),this.addCommand({id:"stashpad-toggle-task",name:"Toggle task (todo)",callback:()=>a("cmdToggleTask")}),this.addCommand({id:"stashpad-set-due",name:"Set due date\u2026",callback:()=>a("cmdSetDue")}),this.addCommand({id:"stashpad-dump-perf",name:"Dump performance profile (copy to clipboard)",callback:async()=>{if(!this.settings.enablePerfProfiling){new M.Notice("Enable \u201CPerformance profiling\u201D in Stashpad settings first, then use the app and run this again.");return}let p=Se.report();console.log(p);try{await navigator.clipboard.writeText(p)}catch{}new M.Notice("Performance profile copied to clipboard (also in the console).")}}),this.addCommand({id:"stashpad-reset-perf",name:"Reset performance profile",callback:()=>{Se.reset(),new M.Notice("Performance profile reset.")}}),this.addCommand({id:"stashpad-jump-to-top",name:"Jump to top of list",callback:()=>a("jumpToTop")}),this.addCommand({id:"stashpad-jump-to-bottom",name:"Jump to bottom of list",callback:()=>a("jumpToBottom")}),this.addCommand({id:"stashpad-assign",name:"Assign task to\u2026",callback:()=>a("cmdAssign")}),this.addCommand({id:"stashpad-open-import-log",name:"Open import log",callback:async()=>{await this.importLog.load();let{ImportLogModal:p}=await Promise.resolve().then(()=>(ye(),Yi));new p(this.app,this.importLog.recent()).open()}}),this.addCommand({id:"stashpad-import-files",name:"Import file(s) into Stashpad\u2026",callback:()=>this.openImportPicker()}),this.addCommand({id:"stashpad-import-loose-files",name:"Import loose files & folders in this folder (scan for moved-in / unprocessed items)",checkCallback:p=>{let u=this.importService.defaultDestination();return p?!!u:(u&&this.runImportLooseFiles(u),!0)}}),this.addCommand({id:"stashpad-rerun-slug-pass",name:"Re-run filename (slug) pass on this folder",checkCallback:p=>{let u=this.importService.defaultDestination();return p?!!u:(u&&this.runFolderSlugPass(u),!0)}}),this.addCommand({id:"stashpad-rerun-frontmatter-backfill",name:"Re-run frontmatter backfill (recovery links) on this folder",checkCallback:p=>{let u=this.importService.defaultDestination();return p?!!u:(u&&this.runFolderFrontmatterBackfill(u),!0)}}),this.addCommand({id:"stashpad-select-all",name:"Select all visible notes",callback:()=>a("cmdSelectAll")}),this.addCommand({id:"stashpad-copy-codeblock",name:"Copy code from codeblock",callback:()=>a("cmdCopyCodeBlock")}),this.addCommand({id:"stashpad-open-panels",name:"Open Stashpad panels (sidebar)",callback:()=>void xs(this.app)}),this.addCommand({id:"stashpad-open-folder-panel",name:"Open folder panel (sidebar)",callback:()=>void Hn(this.app)}),this.addCommand({id:"stashpad-open-detail",name:"Open Stashpad detail panel (right sidebar)",callback:()=>void en(this.app)}),this.addCommand({id:"stashpad-reveal-active-note",name:"Open this note in Stashpad",checkCallback:p=>{let u=this.app.workspace.getActiveFile(),I=!!u&&u.extension==="md"&&this.isStashpadNoteFile(u);return p||I&&u&&this.revealNoteInStashpad(u),I}});let l=Object.keys(Vi);for(let p of l){let u=Vi[p];this.addCommand({id:`stashpad-open-panels-${p}`,name:`Open Stashpad panel: ${u.label}`,callback:async()=>{await xs(this.app),this.app.workspace.getLeavesOfType(It)[0]?.view?.setActivePanel?.(p)}})}this.addCommand({id:"stashpad-task-review",name:"Open daily task review",callback:()=>new oi(this.app,this).open()}),this.addCommand({id:"stashpad-swap-with-parent",name:"Swap with parent (ouroboros)",callback:()=>a("cmdSwapWithParent")}),this.addCommand({id:"stashpad-toggle-pin",name:"Pin / unpin selected note (sidebar)",callback:()=>a("cmdTogglePin")}),this.addCommand({id:"stashpad-list-pin",name:"Pin / unpin to top of list",callback:()=>a("cmdToggleListPin")}),this.addCommand({id:"stashpad-open-tiny",name:"Open Stashpad in tiny window",callback:()=>void this.openTinyWindow()}),this.addCommand({id:"stashpad-clone-tab",name:"Clone (duplicate / copy) this Stashpad tab",callback:()=>a("cmdCloneStashpadTab")}),this.addCommand({id:"stashpad-undo",name:"Undo last Stashpad action",callback:()=>a("cmdUndo")}),this.addCommand({id:"stashpad-redo",name:"Redo last undone Stashpad action",callback:()=>a("cmdRedo")}),this.addCommand({id:"stashpad-export-stash",name:"Export selection to .stash",callback:()=>a("cmdExportStash")}),this.addCommand({id:"stashpad-export-okf",name:"Export selection as OKF bundle (.zip / .tar.gz / .stash)",callback:()=>a("cmdExportOkf")}),this.addCommand({id:"stashpad-import-stash",name:"Import .stash file\u2026",callback:()=>a("cmdImportStash")}),this.addCommand({id:"stashpad-pick-folder",name:"Stashpad: open / switch / create folder\u2026",callback:()=>this.openFolderPicker()}),this.addCommand({id:"stashpad-run-integrity-check",name:"Run integrity check on active Stashpad folder",checkCallback:p=>{let u=pt(),I=u&&u.noteFolder;return I?(p||(new M.Notice(`Running integrity check on "${I}"\u2026`),this.runIntegrityCheckOnFolder(I).then(()=>{new M.Notice("Integrity check complete \u2014 see Stashpad log.")})),!0):!1}}),this.addCommand({id:"stashpad-fix-orphans",name:"Set missing parents to Home (orphan fix)",callback:()=>void this.fixOrphanParents()}),this.addCommand({id:"stashpad-rebuild-author-registry",name:"Rebuild author registry (scan authors + note frontmatter)",callback:async()=>{new M.Notice("Stashpad: rebuilding author registry\u2026");try{let p=await this.rebuildAuthorRegistry();this.notifications.show({message:`Author registry rebuilt: ${p.total} author(s) \u2014 ${p.fromStubs} from stubs, ${p.fromNotes} from note links.`,kind:"success",category:"system"})}catch(p){new M.Notice(`Author registry rebuild failed: ${p.message}`)}}}),this.addCommand({id:"stashpad-restore-author-stubs",name:"Restore missing author stubs (from registry)",callback:async()=>{new M.Notice("Stashpad: restoring author stubs\u2026");try{let p=await this.restoreMissingAuthorStubs();this.notifications.show({message:p.created>0?`Restored ${p.created} author stub(s) across ${p.folders} folder(s).`:`No missing author stubs \u2014 all present across ${p.folders} folder(s).`,kind:"success",category:"system"})}catch(p){new M.Notice(`Restore author stubs failed: ${p.message}`)}}}),this.addCommand({id:"stashpad-sync-authors",name:"Sync authors across all folders (multiplayer)",callback:()=>void this.syncAuthorsAcrossFolders()}),this.addCommand({id:"stashpad-rebootstrap-all",name:"Rebootstrap all Stashpad folders (backfill metadata + rename stale titles)",callback:()=>{this.runRebootstrapWithUI().catch(()=>{})}}),this.addCommand({id:"stashpad-adopt-note",name:"Adopt active note into Stashpad (fill missing frontmatter)",checkCallback:p=>{let u=this.app.workspace.getActiveFile();return!u||u.extension!=="md"?!1:(p||this.adoptNote(u),!0)}}),this.addCommand({id:"stashpad-open-notification-history",name:"Open notification history",callback:()=>{Promise.resolve().then(()=>(ye(),Yi)).then(({NotificationHistoryModal:p,LogModal:u})=>{new p(this.app,this.notifications,async()=>{let I=this.app.vault.adapter,m=this.pluginPrivatePath("log.jsonl");if(!await I.exists(m)){new M.Notice("No log yet \u2014 make some changes first.");return}let C=await I.read(m);new u(this.app,C,m).open()},this.settings.authorId||null,I=>this.lookupNoteAuthorIds(I)).open()})}});let c=[{key:"prefixTimestampsOnCopy",label:"Prefix timestamps when copying"},{key:"useTemplatesFormat",label:"Use Templates plugin date/time formats"},{key:"autoNavOnMoveIn",label:"Auto-navigate into parent on move IN"},{key:"autoNavOnMoveOut",label:"Auto-navigate to destination on move OUT"},{key:"confirmCrossParentDrag",label:"Confirm cross-parent drag-and-drop"},{key:"confirmBulkDelete",label:"Confirm bulk deletes"},{key:"confirmAttachmentDelete",label:"Offer to delete attachments with note"},{key:"autofocusComposerAfterSend",label:"Autofocus composer after sending"},{key:"popoutDuplicates",label:"Open in new window \u2014 duplicate tab"},{key:"autoExpandCursorRow",label:"Expand the cursor row's body automatically"},{key:"autoOpenDetailPanel",label:"Auto-open the detail panel"},{key:"doubleClickToFocus",label:"Double-click a note to open it"}];for(let p of c){let u=`stashpad-toggle-${String(p.key).replace(/[A-Z]/g,I=>`-${I.toLowerCase()}`)}`;this.addCommand({id:u,name:`Toggle: ${p.label}`,callback:async()=>{let I=!this.settings[p.key];this.settings[p.key]=I,await this.saveSettings(),new M.Notice(`${p.label}: ${I?"ON":"OFF"}`)}})}this.addCommand({id:"stashpad-open-settings",name:"Open Stashpad settings",callback:()=>{let p=this.app.setting;!p?.open||!p?.openTabById||(p.open(),p.openTabById(this.manifest.id))}}),this.addCommand({id:"stashpad-search-settings",name:"Search Stashpad settings\u2026",callback:()=>{let p=this.app.setting;!p?.open||!p?.openTabById||(p.open(),p.openTabById(this.manifest.id),setTimeout(()=>{p?.modalEl?.querySelector?.("input[type='search']")?.focus()},0))}});for(let p of Ws)this.addCommand({id:`stashpad-open-settings-${p.id}`,name:`Open Stashpad settings: ${p.label}`,callback:()=>{let u=this.app.setting;!u?.open||!u?.openTabById||(u.open(),u.openTabById(this.manifest.id))}});let d=()=>{let p=this.app.setting;!p?.open||!p?.openTabById||(p.open(),p.openTabById(this.manifest.id),setTimeout(()=>{document.getElementById("stashpad-jd-index-section")?.scrollIntoView({behavior:"smooth",block:"start"})},100))};this.addCommand({id:"stashpad-preview-jd-index",name:"Preview JD index (overwrites home note body)",callback:async()=>{try{let{buildJdIndexPreview:p}=await Promise.resolve().then(()=>(Xi(),Ps)),u=await p(this.app,this,this.settings);if(u.error==="no-dest"){new M.Notice("Set a Designated Stashpad folder for Index in settings first.",6e3),d();return}if(u.error==="no-home"){new M.Notice(`"${this.settings.jdIndexStashpadFolder}" has no Stashpad home note. Open the folder in Stashpad first to create one.`,7e3);return}let{buildJdPreviewNotice:I}=await Promise.resolve().then(()=>(Xi(),Ps));I(this.app,u)}catch(p){console.error("[stashpad] preview failed",p),new M.Notice(`Preview failed: ${p?.message??p}`,8e3)}}}),this.addCommand({id:"stashpad-decrypt-folder-bundle",name:"Decrypt a folder bundle (encrypted non-Stashpad folder)\u2026",callback:async()=>{if(!this.encryption.isConfigured()){new M.Notice("Stashpad encryption isn't set up.");return}let p=await Il(this.app);if(!p.length){new M.Notice("No encrypted folder bundles found in this vault.");return}new fr(this.app,p,u=>void this.decryptFolderFromExplorer(u.folder)).open()}}),this.addCommand({id:"stashpad-build-jd-index",name:"Build JD index notes (creates Stashpad-note hierarchy)",callback:async()=>{try{let{buildJdIndexNotes:p,scanForJdNotes:u,JdBuildConfirmModal:I}=await Promise.resolve().then(()=>(Xi(),Ps));if(!(this.settings.jdIndexStashpadFolder??"").trim().replace(/^\/+|\/+$/g,"")){new M.Notice("Set a Designated Stashpad folder for Index in settings first.",6e3),d();return}let C=u(this.app,this,this.settings);new I(this.app,this,this.settings,C.indexed.length,async()=>{try{let w=await p(this.app,this,this.settings);if(w.error==="no-dest"){new M.Notice("Set a Designated Stashpad folder for Index in settings first.",6e3),d();return}if(w.error==="dest-not-stashpad"){new M.Notice(`"${w.destFolder}" isn't a known Stashpad folder. Pick a real Stashpad folder in settings.`,7e3),d();return}this.settings.jdIndexHasBuilt=!0,await this.saveSettings(),new M.Notice(`Index built: ${w.created} created, ${w.updated} updated, ${w.skipped} skipped \u2192 ${w.destFolder}`,6e3)}catch(w){console.error("[stashpad] build failed",w),new M.Notice(`Build failed: ${w?.message??w}`,8e3)}}).open()}catch(p){console.error("[stashpad] build failed",p),new M.Notice(`Build failed: ${p?.message??p}`,8e3)}}});let h=p=>{if(p.extension!==ot)return;let u=(this.settings.importDropFolder||"").trim().replace(/^\/+|\/+$/g,""),I=(this.settings.exportFolder||"").trim().replace(/^\/+|\/+$/g,""),m=p.parent?.path||"",C=m.split("/").pop()??"";if(u&&C===u){if(I&&m.endsWith(`/${I}`))return;let B=m.slice(0,m.length-u.length).replace(/\/+$/,"")||this.settings.folder;this.autoImportStash(p,B);return}this.settings.autoImport&&this.importService.isArmed()&&(Ht(p.path)||this.discoverStashpadFolders().includes(m)&&this.autoImportStash(p,m))};this.registerEvent(this.app.vault.on("create",p=>{p instanceof M.TFile&&h(p)})),this.registerEvent(this.app.vault.on("rename",p=>{p instanceof M.TFile&&h(p)}));let g=p=>{if(p.extension!=="md")return;let u=p.parent?.path?.replace(/\/+$/,"")??"";this.discoverStashpadFolders().includes(u)&&setTimeout(()=>{this.fixOrphanParentForFile(p)},800)},f=p=>{if(p.extension!=="md")return;let u=(this.settings.importDropFolder||"").trim().replace(/^\/+|\/+$/g,"");if(!u)return;let I=p.parent?.path?.replace(/\/+$/,"")??"";if((I.split("/").pop()??"")!==u)return;let C=I.slice(0,I.length-u.length).replace(/\/+$/,"");!C||!this.discoverStashpadFolders().includes(C)||setTimeout(()=>{this.adoptMarkdownDrop(p,C)},200)};this.registerEvent(this.app.vault.on("create",p=>{p instanceof M.TFile&&(g(p),f(p))})),this.registerEvent(this.app.vault.on("rename",p=>{p instanceof M.TFile&&(g(p),f(p))})),this.registerEvent(this.app.vault.on("delete",p=>{if(!(p instanceof M.TFolder))return;let u=p.path.replace(/\/+$/,"");this.knownStashpadFolders.has(u)&&this.handleStashpadFolderDeleted(u)})),this.registerEvent(this.app.vault.on("rename",(p,u)=>{p instanceof M.TFile&&this.maybeReHomeOnCrossFolderMove(p,u)})),this.registerEvent(this.app.vault.on("rename",(p,u)=>{p instanceof M.TFile&&this.maybeArchiveOnMoveIn(p,u)})),this.registerEvent(this.app.vault.on("create",p=>{p instanceof M.TFile?this.importService.enqueue(p):p instanceof M.TFolder&&this.importService.enqueueFolder(p)})),this.registerEvent(this.app.vault.on("rename",p=>{p instanceof M.TFile?this.importService.enqueue(p):p instanceof M.TFolder&&this.importService.enqueueFolder(p)})),this.registerEvent(this.app.vault.on("rename",(p,u)=>{p instanceof M.TFile&&this.maybeAdoptAuthorRename(p,u)})),this.registerDomEvent(window,"focus",()=>void this.checkForSyncedBuild()),setTimeout(()=>void this.checkForSyncedBuild(),5e3),this.registerInterval(window.setInterval(()=>void this.checkForSyncedBuild(),45e3))}async checkForSyncedBuild(){try{let e=this.manifest.dir;if(!e)return;let t=`${e.replace(/\/+$/,"")}/manifest.json`,i=this.app.vault.adapter;if(!await i.exists(t))return;let s=JSON.parse(await i.read(t))?.version,n=this.manifest.version;if(typeof s!="string"||!s||s===n||!this.isSemverGreater(s,n)||this.notifiedBuildVersion===s)return;this.notifiedBuildVersion=s,this.notifications.show({message:`A newer Stashpad build synced in (\`${n}\` \u2192 \`${s}\`). Reload the app to apply it.`,kind:"info",category:"system",duration:0,actions:[{label:"Reload app",onClick:()=>this.reloadAppForUpdate()}]})}catch(e){console.debug("[Stashpad] synced-build check failed",e)}}reloadAppForUpdate(){try{window.localStorage?.setItem(gr,"1")}catch{}try{if(this.app.commands?.executeCommandById?.("app:reload"))return}catch(e){console.warn("[Stashpad] app:reload command failed",e)}try{window.location.reload()}catch{new M.Notice("Reload Obsidian (close + reopen) to apply the Stashpad update.")}}async unghostStashpadTabsIfFlagged(){let e=!1;try{e=window.localStorage?.getItem(gr)==="1"}catch{}if(e){try{window.localStorage?.removeItem(gr)}catch{}for(let t of this.app.workspace.getLeavesOfType(z))try{let i=t;i.isDeferred&&typeof i.loadIfDeferred=="function"&&await i.loadIfDeferred()}catch(i){console.warn("[Stashpad] un-ghost leaf failed",i)}}}isSemverGreater(e,t){let i=e.split(".").map(o=>parseInt(o,10)||0),s=t.split(".").map(o=>parseInt(o,10)||0),n=Math.max(i.length,s.length);for(let o=0;o<n;o++){let a=i[o]??0,l=s[o]??0;if(a!==l)return a>l}return!1}parseAuthorFilePath(e){let t=e.match(/\/_authors\/([^/]+?)-([a-z0-9]{4,12})\.md$/i);return t?{name:t[1].replace(/-/g," "),id:t[2]}:null}authorNameToSafe(e){return e.replace(/[^\w\- ]+/g,"").trim().replace(/\s+/g,"-")||"author"}async syncAuthorFilesToName(){let e=(this.settings.authorId??"").trim(),t=(this.settings.authorName??"").trim();if(!e||!t)return;let i=this.authorNameToSafe(t);for(let s of this.discoverStashpadFolders()){let n=`${s}/_authors`;if(await this.app.vault.adapter.exists(n))for(let o of this.app.vault.getMarkdownFiles()){if(!o.path.startsWith(n+"/"))continue;let a=this.parseAuthorFilePath(o.path);if(!a||a.id!==e)continue;let l=`${n}/${i}-${e}.md`,c=o;if(o.path!==l)try{this.authorRenameInFlight.add(o.path),this.authorRenameInFlight.add(l),await this.app.fileManager.renameFile(o,l);let d=this.app.vault.getAbstractFileByPath(l);d&&(c=d)}catch(d){this.authorRenameInFlight.delete(o.path),this.authorRenameInFlight.delete(l),console.warn("[Stashpad] author file rename failed",d);continue}try{await this.refreshAuthorStub(c)}catch{}}}}async refreshAuthorStub(e){let t=(this.settings.authorName??"").trim(),i=(this.settings.authorRole??"").trim(),s=(this.settings.authorDepartment??"").trim();if(t)try{let n=await this.app.vault.read(e),o=n.replace(/^# .*$/m,`# ${t}`);o!==n&&await this.app.vault.modify(e,o),await this.app.fileManager.processFrontMatter(e,a=>{a.aliases=[t],delete a.name,i?a.role=i:delete a.role,s?a.department=s:delete a.department})}catch(n){console.warn("[Stashpad] refreshAuthorStub failed",n)}}async maybeAdoptAuthorRename(e,t){let i=this.authorRenameInFlight.delete(e.path),s=this.authorRenameInFlight.delete(t);if(i||s)return;let n=this.parseAuthorFilePath(e.path);if(!n)return;let o=(this.settings.authorId??"").trim();if(!o||n.id!==o)return;let a=n.name.trim();!a||a===(this.settings.authorName??"").trim()||(this.settings.authorName=a,await this.saveSettings(),await this.syncAuthorFilesToName())}async adoptMarkdownDrop(e,t){try{let i=this.app.vault.adapter,s=e.name,n=s.lastIndexOf("."),o=n>0?s.slice(0,n):s,a=n>0?s.slice(n):"",l=0;for(;await i.exists(`${t}/${s}`);)l+=1,s=`${o}-${l}${a}`;let c=`${t}/${s}`;await this.app.fileManager.renameFile(e,c);let d=this.app.vault.getAbstractFileByPath(c);d instanceof M.TFile&&setTimeout(()=>{this.fixOrphanParentForFile(d)},500),this.notifications.show({message:`Imported \`${e.name}\` \u2192 \`${t}\``,kind:"success",category:"import",folder:t,affectedPaths:[c]})}catch(i){console.warn("Stashpad: markdown drop adoption failed",i),this.notifications.show({message:`Couldn't import \`${e.name}\`: ${i.message}`,kind:"error",category:"import"})}}async fixOrphanParentForFile(e){try{let t=this.app.metadataCache.getFileCache(e)?.frontmatter,i=typeof t?.id=="string"?t.id.trim():"",s=t?.parent,n=typeof s=="string"?s.trim()!=="":s!=null,o=typeof t?.created=="string"&&t.created.trim()!=="",a=!i,l=!n,c=!o;if(!a&&!l&&!c)return;let{newId:d}=await Promise.resolve().then(()=>(Le(),Fs)),h,g=!1,f=!1;if(await this.app.fileManager.processFrontMatter(e,I=>{if(a&&(typeof I.id=="string"&&I.id.trim()||(h=d(),I.id=h)),l){let m=I.parent;(typeof m=="string"?m.trim()!=="":m!=null)||(I.parent=F,g=!0)}c&&(typeof I.created=="string"&&I.created.trim()||(I.created=new Date(e.stat.ctime).toISOString(),f=!0))}),!h&&!g&&!f)return;let p=this.newLog(),u=h||i;u&&await p.append({type:"parent_change",id:u,payload:{from:null,to:F,reason:"orphan_auto_fix",path:e.path,addedId:!!h,addedParent:g,addedCreated:f}}),new M.Notice(`Adopted ${e.basename} \u2192 Home`)}catch(t){console.warn("Stashpad: orphan auto-fix failed",t)}}maybeReHomeOnCrossFolderMove(e,t){if(e.extension!=="md")return;let i=e.parent?.path?.replace(/\/+$/,"")??"",s=t.lastIndexOf("/"),n=(s>=0?t.slice(0,s):"").replace(/\/+$/,"");i!==n&&this.discoverStashpadFolders().includes(i)&&(this.reHomePending.has(e.path)||(this.reHomePending.add(e.path),setTimeout(()=>{this.reHomePending.delete(e.path),this.reHomeDanglingParent(e,i)},900)))}async reHomeDanglingParent(e,t){try{let i=this.app.metadataCache.getFileCache(e)?.frontmatter,s=typeof i?.id=="string"?i.id.trim():"",n=typeof i?.parent=="string"?i.parent.trim():"";if(!s||!n||n===F||this.app.vault.getMarkdownFiles().some(a=>(a.parent?.path?.replace(/\/+$/,"")??"")===t&&this.app.metadataCache.getFileCache(a)?.frontmatter?.id===n))return;await this.app.fileManager.processFrontMatter(e,a=>{let l=typeof a.parent=="string"?a.parent.trim():"";l&&l!==F&&(a.parent=F)}),await this.newLog().append({type:"parent_change",id:s,payload:{from:n,to:F,reason:"rehome_cross_folder_move",path:e.path}}),new M.Notice(`Re-homed ${e.basename} \u2192 Home (its parent isn't in this folder)`)}catch(i){console.warn("[Stashpad] re-home on cross-folder move failed",i)}}async autoImportStash(e,t){try{let i=new Uint8Array(await this.app.vault.readBinary(e));if(Me(i)){this.importService.parkEncrypted(e.path),this.notifyPendingEncrypted();return}let s=await Ai(this.app,i);if(!s)return;let n=pt(),o=new Set;if(n&&typeof n.collectExistingIds=="function"&&n.noteFolder===t)for(let c of n.collectExistingIds())o.add(c);else for(let c of this.app.vault.getMarkdownFiles()){if(!c.path.startsWith(t+"/"))continue;let d=this.app.metadataCache.getFileCache(c)?.frontmatter?.id;typeof d=="string"&&o.add(d)}let a=await rt(this.app,s,t,o,{stripReserved:!0});try{await this.newLog().append({type:"stash_import",id:F,payload:{from:e.path,into:t,noteCount:a.notesWritten,attachmentsWritten:a.attachmentsWritten,collisionsRenamed:a.collisionsRenamed,auto:!0}})}catch{}try{await this.app.fileManager.trashFile(e)}catch{}let l=[`Auto-imported ${a.notesWritten} note${a.notesWritten===1?"":"s"} from ${e.name}`];a.attachmentsWritten&&l.push(`+ ${a.attachmentsWritten} attachment${a.attachmentsWritten===1?"":"s"}`),a.collisionsRenamed&&l.push(`(${a.collisionsRenamed} renamed)`),this.notifications.show({message:l.join(" "),kind:"success",category:"import",folder:t}),n&&typeof n.debouncedRender=="function"&&n.debouncedRender()}catch(i){this.notifications.show({message:`Stashpad: auto-import failed File: \`${e.name}\` Error: ${i.message} -Inspect with the buttons below \u2014 rename to .zip to crack it open in an archive tool.`,kind:"error",category:"import",affectedPaths:[e.path],actions:bs(this.app,e.path,T.Platform.isMobile)}),console.error(i)}}lookupNoteAuthorIds(e){let t=new Set,i=s=>{if(typeof s!="string")return null;let n=s.match(/-([a-z0-9]{4,12})(?:\.md)?(?:\||\]\])/i);return n?n[1]:null};for(let s of this.app.vault.getMarkdownFiles()){let n=this.app.metadataCache.getFileCache(s)?.frontmatter;if(n?.id!==e)continue;let o=i(n?.author);o&&t.add(o);let a=n?.contributors;if(Array.isArray(a))for(let l of a){let c=i(l);c&&t.add(c)}break}return Array.from(t)}lookupNoteAuthorId(e){return this.lookupNoteAuthorIds(e)[0]??null}async runRebootstrapWithUI(){let t=new T.Notice("",0).noticeEl;t.empty(),t.addClass("stashpad-progress-notice"),t.createDiv({cls:"stashpad-progress-title",text:"Rebootstrapping Stashpad folders\u2026"});let s=t.createDiv({cls:"stashpad-progress-bar"}).createDiv({cls:"stashpad-progress-fill"}),n=t.createDiv({cls:"stashpad-progress-sub",text:"Counting notes\u2026"}),o=(a,l,c)=>{let d=l>0?Math.min(100,Math.round(a/l*100)):0;s.style.width=`${d}%`,n.setText(c?`${c} \xB7 ${d}%`:`${d}%`)};try{let a=await this.rebootstrapAllFolders(o),l=a.imported+a.attachmentsLinked+a.attachmentsRenamed+a.fmWritten+a.slugsRenamed,c=[];return c.push(`Checked ${a.touched.length} folder${a.touched.length===1?"":"s"} (${a.fmChecked} note${a.fmChecked===1?"":"s"})`),l===0?c.push("everything already in sync \u2014 no changes needed"):(a.fmWritten>0&&c.push(`updated frontmatter on ${a.fmWritten} note${a.fmWritten===1?"":"s"}`),a.slugsRenamed>0&&c.push(`renamed ${a.slugsRenamed} note${a.slugsRenamed===1?"":"s"} to match body`),a.imported>0&&c.push(`imported ${a.imported} loose file${a.imported===1?"":"s"}`),a.attachmentsLinked>0&&c.push(`linked attachments on ${a.attachmentsLinked} note${a.attachmentsLinked===1?"":"s"}`),a.attachmentsRenamed>0&&c.push(`renamed ${a.attachmentsRenamed} attachment${a.attachmentsRenamed===1?"":"s"}`)),a.authors>0&&c.push(`${a.authors} author${a.authors===1?"":"s"} in registry`),t.empty(),t.removeClass("stashpad-progress-notice"),t.addClass("stashpad-progress-done"),t.createDiv({cls:"stashpad-progress-title",text:"\u2713 Rebootstrap complete"}),t.createDiv({cls:"stashpad-progress-sub",text:`${c.join("; ")}.`}),a.attachmentsSkipped>0&&new T.Notice(`Stashpad: ${a.attachmentsSkipped} attachment${a.attachmentsSkipped===1?"":"s"} need renaming, but skipped to protect links. Enable Settings \u2192 Files & Links \u2192 \u201CAutomatically update internal links\u201D, then rebootstrap again.`,12e3),a}catch(a){throw t.empty(),t.removeClass("stashpad-progress-notice"),t.addClass("stashpad-progress-error"),t.createDiv({cls:"stashpad-progress-title",text:"Rebootstrap failed"}),t.createDiv({cls:"stashpad-progress-sub",text:a.message}),a}}async rebootstrapAllFolders(e){this.rebootstrapInProgress=!0;try{return await this.rebootstrapAllFoldersInner(e)}finally{window.setTimeout(()=>{this.rebootstrapInProgress=!1;for(let t of this.app.workspace.getLeavesOfType(z))t.view?.forceReconcileRender?.()},2500)}}async rebootstrapAllFoldersInner(e){let t="__root__",i=new Set;for(let y of this.app.vault.getMarkdownFiles()){if(this.app.metadataCache.getFileCache(y)?.frontmatter?.id!==t)continue;let v=y.parent?.path;v&&i.add(v)}let s=new Map;for(let y of this.app.vault.getMarkdownFiles()){let E=y.parent?.path;E&&i.has(E)&&s.set(E,(s.get(E)??0)+1)}let n=Math.max(1,[...s.values()].reduce((y,E)=>y+E,0)),o=0;e?.(0,n,"Starting\u2026");let a=(this.settings.importDropFolder||"").trim().replace(/^\/+|\/+$/g,""),l=(this.settings.exportFolder||"").trim().replace(/^\/+|\/+$/g,""),c=[],d=async y=>{if(y&&!await this.app.vault.adapter.exists(y))try{await this.app.vault.createFolder(y)}catch(E){let v=E?.message??"";if(!/already exists/i.test(v))throw E}},h=0,g=0,u=0,p=0,f=0,I=0,m=!!this.app.vault.getConfig?.("alwaysUpdateLinks"),C=new Set(this.okfActiveFolders());for(let y of i){let E=s.get(y)??1;e?.(o,n,`Processing \u201C${y.split("/").pop()||y}\u201D (${E} note${E===1?"":"s"})`);try{if(a&&await d(`${y}/${a}`),l&&await d(`${y}/${l}`),this.settings.autoImport)try{let S=await this.importService.importLooseInto(y);p+=S.files+S.folders+S.stashes}catch(S){console.warn("Stashpad: loose sweep failed",y,S)}let v=await gi(this.app,y);h+=v.checked,g+=v.written,u+=await this.rebootstrapFolderSlugs(y);let Q=await this.rebootstrapFolderAttachments(y,m);if(f+=Q.renamed,I+=Q.skipped,C.has(y.replace(/\/+$/,"")))try{await _o(this.app,y)}catch(S){console.warn("Stashpad: OKF rebuild during rebootstrap failed",y,S)}c.push(y)}catch(v){console.warn(`Stashpad: rebootstrap skipped ${y}`,v)}finally{o+=E,e?.(o,n,`Processing \u201C${y.split("/").pop()||y}\u201D`)}}e?.(n,n,"Finalizing\u2026");let B=0;try{B=(await this.rebuildAuthorRegistry()).total}catch(y){console.warn("Stashpad: rebootstrap author-registry rebuild failed",y)}let w=0;try{w=await this.convertAttachmentsToLinks()}catch(y){console.warn("Stashpad: attachment-link conversion failed",y)}return{touched:c,fmChecked:h,fmWritten:g,slugsRenamed:u,authors:B,imported:p,attachmentsLinked:w,attachmentsRenamed:f,attachmentsSkipped:I}}async rebootstrapFolderSlugs(e){let t="__root__",i=e.replace(/\/+$/,""),s=this.settings.slugStopWords??Ze,n=0,o=this.app.vault.getMarkdownFiles().filter(a=>(a.parent?.path?.replace(/\/+$/,"")??"")===i);for(let a of o){let l=yt(a.basename);if(!(!l||l===t||this.app.metadataCache.getFileCache(a)?.frontmatter?.id!==l))try{let d=await this.app.vault.cachedRead(a),h=d.startsWith("---")?d.slice(d.indexOf(` ----`,3)+4).replace(/^\r?\n/,""):d,g=be(h,s),u=De(g,l);if(a.name===u)continue;let p=a.parent?`${a.parent.path}/${u}`:u;if(this.app.vault.getAbstractFileByPath(p))continue;await this.app.fileManager.renameFile(a,p),n+=1}catch(d){console.warn(`Stashpad: slug rebootstrap skipped ${a.path}`,d)}}return n}async rebootstrapFolderAttachments(e,t){let i=`${e.replace(/\/+$/,"")}/_attachments`,s=this.app.vault.getAbstractFileByPath(i);if(!(s instanceof T.TFolder))return{renamed:0,skipped:0};let n=s.children.filter(l=>l instanceof T.TFile),o=0,a=0;for(let l of n){let c=ka(l.name);if(!c)continue;let d=ws(c.rest,c.stamp);if(l.name===d)continue;if(!t){a+=1;continue}let h=`${i}/${d}`;if(!this.app.vault.getAbstractFileByPath(h))try{await this.app.fileManager.renameFile(l,h),o+=1,await new Promise(g=>setTimeout(g,30))}catch(g){console.warn(`Stashpad: attachment rebootstrap skipped ${l.path}`,g)}}return{renamed:o,skipped:a}}onStashpadSelectionChange(e){return this.stashpadSelectionListeners.add(e),()=>this.stashpadSelectionListeners.delete(e)}notifyStashpadSelectionChanged(){for(let e of this.stashpadSelectionListeners)try{e()}catch(t){console.warn("[Stashpad] selection listener failed",t)}}onStashpadContentChange(e){return this.stashpadContentListeners.add(e),()=>this.stashpadContentListeners.delete(e)}notifyStashpadContentChanged(){for(let e of this.stashpadContentListeners)try{e()}catch(t){console.warn("[Stashpad] content listener failed",t)}}getActiveStashpadSelection(){let t=this.lastActiveStashpadLeaf?.view;if(!t||t.getViewType?.()!==z)return null;let i=t.noteFolder??"";if(!i)return null;let s=t.currentChildren??[],n;if(typeof t.cursorIdx=="number"&&t.cursorIdx>=0&&(n=s[t.cursorIdx]),!n&&t.selection?.size>0){let o=t.firstSelectedId??[...t.selection][0];n=s.find(a=>a.id===o)}return n?.file?{folder:i,id:n.id,file:n.file}:null}fileForPin(e,t){let i=e.replace(/\/+$/,"");for(let s of this.app.vault.getMarkdownFiles())if((s.parent?.path?.replace(/\/+$/,"")??"")===i&&this.app.metadataCache.getFileCache(s)?.frontmatter?.id===t)return s;return null}async pinNote(e){let t=this.fileForPin(e.folder,e.id);t&&this.app.metadataCache.getFileCache(t)?.frontmatter?.pinned!==!0&&(await this.app.fileManager.processFrontMatter(t,i=>{i.pinned=!0,i.pinnedAt=Date.now()}),this.refreshPanelsView())}async unpinNote(e){let t=this.fileForPin(e.folder,e.id);t&&this.app.metadataCache.getFileCache(t)?.frontmatter?.pinned===!0&&(await this.app.fileManager.processFrontMatter(t,i=>{delete i.pinned,delete i.pinnedAt}),this.refreshPanelsView())}isPinned(e){let t=this.fileForPin(e.folder,e.id);return t?this.app.metadataCache.getFileCache(t)?.frontmatter?.pinned===!0:!1}listPinnedNotes(){let e=new Set(this.discoverStashpadFolders()),t=[];for(let i of this.app.vault.getMarkdownFiles()){let s=i.parent?.path?.replace(/\/+$/,"")??"";if(!e.has(s))continue;let n=this.app.metadataCache.getFileCache(i)?.frontmatter;if(!n||n.pinned!==!0||typeof n.id!="string"||!n.id)continue;let o=typeof n.pinnedAt=="number"?n.pinnedAt:0;t.push({folder:s,id:n.id,pinnedAt:o,file:i})}return t.sort((i,s)=>i.pinnedAt-s.pinnedAt||i.file.path.localeCompare(s.file.path)),t}async migratePinnedNotesToFrontmatter(){let e=this.settings.pinnedNotes??[];if(e.length===0)return;let t=Date.now()-e.length*1e3;for(let i of e){let s=this.fileForPin(i.folder,i.id);if(!s){t+=1e3;continue}try{if(this.app.metadataCache.getFileCache(s)?.frontmatter?.pinned!==!0){let n=t;await this.app.fileManager.processFrontMatter(s,o=>{o.pinned=!0,o.pinnedAt=n})}}catch(n){console.warn("[Stashpad] pin migration failed for",i,n)}t+=1e3}this.settings.pinnedNotes=[],await this.saveSettings(),this.refreshPanelsView()}refreshPanelsView(){let e=this.app.workspace.getLeavesOfType(It);for(let t of e){let i=t.view;i&&typeof i.render=="function"&&i.render()}}refreshAllStashpadViews(){for(let e of this.app.workspace.getLeavesOfType(z)){let t=e.view;t&&typeof t.render=="function"&&t.render()}}refreshFolderPanels(){for(let e of this.app.workspace.getLeavesOfType(mt)){let t=e.view;t&&typeof t.refresh=="function"&&t.refresh()}}getFolderIcon(e){let t=(e||"").replace(/\/+$/,""),i=this.settings.folderIcons?.[t];return i&&i.trim()?i.trim():void 0}async setFolderIcon(e,t){let i=(e||"").replace(/\/+$/,""),s={...this.settings.folderIcons??{}};t&&t.trim()?s[i]=t.trim():delete s[i],this.settings.folderIcons=s,await this.saveSettings(),this.refreshFolderIconFor(i)}refreshFolderIconFor(e){let t=(e||"").replace(/\/+$/,"");for(let i of this.app.workspace.getLeavesOfType(z)){let s=i.view;if(s&&(s.noteFolder||"").replace(/\/+$/,"")===t){try{i.updateHeader?.()}catch{}try{s.refreshFolderSwitcherIcon?.()}catch{}}}this.refreshFolderPanels()}openFolderPicker(){let e=I=>{let C=I.getViewState().state?.folderOverride;return typeof C=="string"&&C.trim()?C.trim().replace(/^\/+|\/+$/g,""):(this.settings.folder||"Stashpad").trim().replace(/^\/+|\/+$/g,"")},t=this.app.workspace.getLeavesOfType(z),i=this.discoverStashpadFolders(),s=ht(),n=s?(s.noteFolder??"").trim().replace(/^\/+|\/+$/g,""):"",o=new Set;for(let I of this.app.vault.getAllLoadedFiles())if(I.children){let m=I.path;m&&m!=="/"&&!m.startsWith(".")&&o.add(m)}let a=I=>{let m=I.split("/").filter(Boolean).pop()??"";return m?new Set([this.settings.importDropFolder,this.settings.exportFolder,"_attachments","_processed","_authors","_exports","_imports","_archive",".archive","_deleted"].map(B=>(B??"").trim().replace(/^\/+|\/+$/g,"")).filter(Boolean)).has(m):!1},l=new Set,c=[],d=[],h=new Set;for(let I of t){let m=e(I);if(h.has(m))continue;h.add(m),l.add(m);let C=m.split("/").pop()||m;c.push({kind:"reveal",folder:m,label:`Reveal "${C}" tab`,leaf:I,icon:this.isArchiveFolder(m)?"archive":"layout-grid"}),d.push({kind:"open-anyway",folder:m,label:`Open "${C}" in another new tab`,icon:"layout-template"})}for(let I of i.filter(m=>!l.has(m))){let m=I.split("/").pop()||I;c.push({kind:"open",folder:I,label:`Open "${m}" in new tab`,icon:this.isArchiveFolder(I)?"archive":"layout-template"})}let g=I=>I.basename.replace(/-[a-z0-9]{4,12}$/,"").replace(/-/g," ").trim()||I.basename,u=this.settings.folderSwitcherIncludePinned?this.listPinnedNotes().map(I=>({kind:"pinned",folder:I.folder,file:I.file,label:g(I.file),icon:"pin"})):[],p=this,f=new class extends T.SuggestModal{getSuggestions(I){let m=I.trim().toLowerCase(),C=m?m.split(/\s+/).filter(Boolean):[],B=v=>{if(!C.length)return!0;let Q=v.toLowerCase();for(let S of C)if(!Q.includes(S))return!1;return!0},w=m?c.filter(v=>{let Q="folder"in v?v.folder:"";return B(v.label)||B(Q)}):c.slice();if(m&&s&&n)for(let v of i){if(v.toLowerCase()===n.toLowerCase())continue;let Q=v.split("/").pop()??v,S=`${v} ${Q}`;B(S)&&w.push({kind:"switch-current",folder:v,label:`Switch this tab to "${Q}"`,icon:"folder-input"})}for(let v of u)(B(v.label)||B("folder"in v?v.folder:""))&&w.push(v);let y=I.trim().replace(/^\/+|\/+$/g,"");if(y&&!a(y)){let v=Array.from(o).find(S=>S.toLowerCase()===y.toLowerCase()),Q=i.some(S=>S.toLowerCase()===y.toLowerCase());if(v&&!Q)w.push({kind:"convert",folder:v,label:`Convert \u201C${xi(v)}\u201D into a Stashpad\u2026`,icon:"folder-cog"});else if(!v){let S=xi(y);w.push({kind:"create",folder:y,label:`+ Create new Stashpad \u201C${S}\u201D`,icon:"folder-plus"})}}let E=d.filter(v=>B(v.label)||B("folder"in v?v.folder:""));return w.push(...E),p.encryption?.isConfigured?.()&&B("trash deleted encrypted")&&w.push({kind:"trash",label:"Open encrypted trash",icon:"trash-2"}),w}renderSuggestion(I,m){m.addClass("stashpad-suggest-item"),m.addClass("stashpad-ribbon-suggest-item"),I.kind==="create"&&m.addClass("stashpad-suggest-create");let C=m.createSpan({cls:"stashpad-ribbon-suggest-icon"});(0,T.setIcon)(C,I.icon);let B=m.createDiv({cls:"stashpad-ribbon-suggest-body"});B.createDiv({cls:"stashpad-suggest-title",text:I.label}),"folder"in I&&I.folder&&I.label!==I.folder&&B.createDiv({cls:"stashpad-suggest-preview",text:I.folder})}async onChooseSuggestion(I){if(I.kind==="trash"){p.openEncryptedTrash();return}if(I.kind==="pinned"){await p.revealNoteInStashpad(I.file);return}if(I.kind==="reveal"){p.app.workspace.revealLeaf(I.leaf);return}if(I.kind==="open"||I.kind==="open-anyway"){await p.activateViewForFolder(I.folder);return}if(I.kind==="switch-current"){let m=s;m&&typeof m.setFolderOverride=="function"&&(await m.setFolderOverride(I.folder),p.app.workspace.revealLeaf(m.leaf));return}if(I.kind==="create"){try{let m=xi(I.folder);await p.app.vault.adapter.exists(m)||await p.app.vault.createFolder(m),await p.activateViewForFolder(m)}catch(m){new T.Notice(`Stashpad: couldn't create folder (${m.message})`)}return}if(I.kind==="convert"){let{ConfirmModal:m}=await Promise.resolve().then(()=>(Be(),qi)),C=I.folder,B=[`\u201C${C}\u201D already exists as a regular vault folder.`,"Converting adds a Home note + _imports / _exports subfolders, and imports the existing loose files and subfolders inside it as notes.","(Files already structured as notes, and reserved subfolders, are left alone.)"];new m(p.app,"Convert into a Stashpad?",B.join(` -`),"Convert & import",async w=>{if(w)try{await p.activateViewForFolder(C),await p.runImportLooseFiles(C)}catch(y){new T.Notice(`Stashpad: couldn't convert folder (${y.message})`)}}).open();return}}}(this.app);f.setPlaceholder(s?"Open, switch this tab, or create a Stashpad folder \u2014 type to filter\u2026":"Open or create a Stashpad folder \u2014 type to filter\u2026"),f.open()}async openTinyWindow(){let e=ht(),t=e?.folderOverride??null,i=e?.focusId??"__root__",s=!!e?.compactMode,n=this.app.workspace.openPopoutLeaf?.();if(!n){new T.Notice("Stashpad: couldn't open popout window on this build.");return}await n.setViewState({type:z,active:!0,state:{folderOverride:t,focusId:i,tinyMode:!0,tinyAlwaysOnTop:!1,compactMode:s}});try{this.app.workspace.revealLeaf(n)}catch{}}async activateView(e={reveal:!0}){let{workspace:t}=this.app;if(e.reveal){let s=t.getLeavesOfType(z);if(s.length>0){t.revealLeaf(s[0]);return}}let i=t.getLeaf("tab");await i.setViewState({type:z,active:!0}),t.revealLeaf(i)}async focusLastStashpadTab(){let e=this.app.workspace,t=e.getLeavesOfType(z),i=this.lastActiveStashpadLeaf&&t.includes(this.lastActiveStashpadLeaf)?this.lastActiveStashpadLeaf:t[0]??null;if(!i){await this.activateView({reveal:!0});return}e.revealLeaf(i),e.setActiveLeaf(i,{focus:!0})}async focusFolderPanel(){await wn(this.app);let e=this.app.workspace.getLeavesOfType(mt)[0];e&&this.app.workspace.setActiveLeaf(e,{focus:!0})}async reconcileLockedRegistry(){let e=this.settings.lockedSubtrees??[],t=[];for(let n of e){let o=(n.folder??"").replace(/\/+$/,"");if(!(o==="_deleted"||o.startsWith("_deleted/")))try{await this.app.vault.adapter.exists(n.blob)&&t.push(n)}catch{t.push(n)}}let i=t.length!==e.length,s=new Set(t.map(n=>n.blob));for(let n of this.app.vault.getFiles()){if(n.extension!=="stashenc"||s.has(n.path))continue;let o=n.parent?.path?.replace(/\/+$/,"")??"";if(o==="_deleted"||o.startsWith("_deleted/"))continue;let a=await cn(this.app,n.path);a&&(t.push({folder:o,blob:n.path,parentId:a.parentId,title:a.title,count:a.count,created:a.created,rootId:a.rootId,prevSibling:a.prevSibling}),i=!0)}i&&(this.settings.lockedSubtrees=t,await this.saveSettings(),this.refreshAllStashpadViews?.())}lockedSubtreesFor(e,t){let i=e.replace(/\/+$/,""),s=[],n=new Set;for(let o of this.settings.lockedSubtrees??[])(o.folder??"").replace(/\/+$/,"")===i&&(o.parentId??k)===t&&(s.push({blob:o.blob,title:o.title??"",count:o.count??0,created:o.created??"",rootId:o.rootId,parentId:o.parentId??k,prevSibling:o.prevSibling??null}),n.add(o.blob));for(let o of this.app.vault.getFiles()){if(o.extension!=="stashenc"||n.has(o.path))continue;let a=o.parent?.path?.replace(/\/+$/,"")??"";a!==i||a==="_deleted"||a.startsWith("_deleted/")||t===k&&s.push({blob:o.path,title:o.basename,count:0,created:"",rootId:void 0,parentId:k,prevSibling:null})}return s}lockedSubtreesInFolder(e){let t=e.replace(/\/+$/,""),i=[],s=new Set;for(let n of this.settings.lockedSubtrees??[])(n.folder??"").replace(/\/+$/,"")===t&&(i.push({blob:n.blob,title:n.title??"",count:n.count??0,created:n.created??"",rootId:n.rootId,parentId:n.parentId??k}),s.add(n.blob));for(let n of this.app.vault.getFiles()){if(n.extension!=="stashenc"||s.has(n.path))continue;let o=n.parent?.path?.replace(/\/+$/,"")??"";o!==t||o==="_deleted"||o.startsWith("_deleted/")||i.push({blob:n.path,title:n.basename,count:0,created:"",rootId:void 0,parentId:k})}return i}async ensureEncryptionUnlocked(){return this.encryption.isConfigured()?this.encryption.isUnlocked()||await this.encryption.tryAutoUnlock()?!0:new Promise(e=>{new de(this.app,{mode:"unlock",offerKeychain:!0,onSubmit:async({current:t,remember:i})=>await this.encryption.unlock(t,i)?(e(!0),null):"Wrong password. Try again.",onCancel:()=>e(!1)}).open()}):(new T.Notice("Set up encryption first (Settings \u2192 Stashpad \u2192 Encryption)."),!1)}async ensureFolderUnlocked(e){if(!this.encryption.isConfigured())return new T.Notice("Set up encryption first (Settings \u2192 Stashpad \u2192 Encryption)."),null;if(this.encryption.hasFolderKey(e)){if(this.encryption.isFolderUnlocked(e))return this.encryption.getFolderKey(e);if(await this.encryption.tryAutoUnlockFolder(e))return this.encryption.getFolderKey(e);let t=e.split("/").pop()||e;return await new Promise(s=>{new de(this.app,{mode:"unlock",offerKeychain:!0,title:`Unlock \u201C${t}\u201D`,intro:"Enter this folder's password.",onSubmit:async({current:n,remember:o})=>await this.encryption.unlockFolder(e,n,o)?(s(!0),null):"Wrong password. Try again.",onCancel:()=>s(!1)}).open()})?this.encryption.getFolderKey(e):null}return await this.ensureEncryptionUnlocked()?this.encryption.getSessionKey():null}encryptionState(){let e=s=>{let n=s.replace(/\/[^/]*$/,"");return n==="_deleted"||n.startsWith("_deleted/")},t=!1,i=!1;for(let s of this.app.vault.getFiles())if(s.extension==="stashenc"&&(e(s.path)?i=!0:t=!0,t&&i))return{live:t,trash:i};for(let s of this.pendingEncBlobs)if(e(s)?i=!0:t=!0,t&&i)break;return{live:t,trash:i}}async encryptionStateStrict(){let e=this.encryptionState(),t=e.trash;if(!t)try{t=(await pn(this.app)).length>0}catch{}if(!t)try{t=(await this.app.vault.adapter.list(Te)).files.some(s=>s.endsWith(".stashenc"))}catch{}return{live:e.live,trash:t}}async lockNoteSubtree(e,t,i=null,s={}){let n=(s.blobFolder??e).replace(/\/+$/,""),o=await this.ensureFolderUnlocked(n);if(!o)return null;try{let a=!!s.blobFolder&&s.blobFolder.replace(/\/+$/,"")!==e.replace(/\/+$/,""),l=(this.settings.folderEncPrefs??{})[n]??{},c=(a?l.archiveEncryptFilenames:l.encryptFilenames)??this.settings.hideLockedTitles??!1,d=await Ur(this.app,e,t,o,i,c,s.blobFolder);this.pendingEncBlobs.add(d.blobPath);let h=(s.blobFolder??e).replace(/\/+$/,"");return this.settings.lockedSubtrees=[...(this.settings.lockedSubtrees??[]).filter(g=>g.blob!==d.blobPath),{folder:h,blob:d.blobPath,parentId:d.parentId,title:d.title,count:d.noteCount,created:d.created,rootId:d.rootId,prevSibling:i}],await this.saveSettings(),d.unpurged.length>0?new T.Notice(`\u26A0\uFE0F Locked, but ${d.unpurged.length} file${d.unpurged.length===1?" is":"s are"} still in plaintext (couldn't be removed or changed during the lock): +Inspect with the buttons below \u2014 rename to .zip to crack it open in an archive tool.`,kind:"error",category:"import",affectedPaths:[e.path],actions:Gs(this.app,e.path,M.Platform.isMobile)}),console.error(i)}}lookupNoteAuthorIds(e){let t=new Set,i=s=>{if(typeof s!="string")return null;let n=s.match(/-([a-z0-9]{4,12})(?:\.md)?(?:\||\]\])/i);return n?n[1]:null};for(let s of this.app.vault.getMarkdownFiles()){let n=this.app.metadataCache.getFileCache(s)?.frontmatter;if(n?.id!==e)continue;let o=i(n?.author);o&&t.add(o);let a=n?.contributors;if(Array.isArray(a))for(let l of a){let c=i(l);c&&t.add(c)}break}return Array.from(t)}lookupNoteAuthorId(e){return this.lookupNoteAuthorIds(e)[0]??null}async runRebootstrapWithUI(){let t=new M.Notice("",0).noticeEl;t.empty(),t.addClass("stashpad-progress-notice"),t.createDiv({cls:"stashpad-progress-title",text:"Rebootstrapping Stashpad folders\u2026"});let s=t.createDiv({cls:"stashpad-progress-bar"}).createDiv({cls:"stashpad-progress-fill"}),n=t.createDiv({cls:"stashpad-progress-sub",text:"Counting notes\u2026"}),o=(a,l,c)=>{let d=l>0?Math.min(100,Math.round(a/l*100)):0;s.style.width=`${d}%`,n.setText(c?`${c} \xB7 ${d}%`:`${d}%`)};try{let a=await this.rebootstrapAllFolders(o),l=a.imported+a.attachmentsLinked+a.attachmentsRenamed+a.fmWritten+a.slugsRenamed,c=[];return c.push(`Checked ${a.touched.length} folder${a.touched.length===1?"":"s"} (${a.fmChecked} note${a.fmChecked===1?"":"s"})`),l===0?c.push("everything already in sync \u2014 no changes needed"):(a.fmWritten>0&&c.push(`updated frontmatter on ${a.fmWritten} note${a.fmWritten===1?"":"s"}`),a.slugsRenamed>0&&c.push(`renamed ${a.slugsRenamed} note${a.slugsRenamed===1?"":"s"} to match body`),a.imported>0&&c.push(`imported ${a.imported} loose file${a.imported===1?"":"s"}`),a.attachmentsLinked>0&&c.push(`linked attachments on ${a.attachmentsLinked} note${a.attachmentsLinked===1?"":"s"}`),a.attachmentsRenamed>0&&c.push(`renamed ${a.attachmentsRenamed} attachment${a.attachmentsRenamed===1?"":"s"}`)),a.authors>0&&c.push(`${a.authors} author${a.authors===1?"":"s"} in registry`),t.empty(),t.removeClass("stashpad-progress-notice"),t.addClass("stashpad-progress-done"),t.createDiv({cls:"stashpad-progress-title",text:"\u2713 Rebootstrap complete"}),t.createDiv({cls:"stashpad-progress-sub",text:`${c.join("; ")}.`}),a.attachmentsSkipped>0&&new M.Notice(`Stashpad: ${a.attachmentsSkipped} attachment${a.attachmentsSkipped===1?"":"s"} need renaming, but skipped to protect links. Enable Settings \u2192 Files & Links \u2192 \u201CAutomatically update internal links\u201D, then rebootstrap again.`,12e3),a}catch(a){throw t.empty(),t.removeClass("stashpad-progress-notice"),t.addClass("stashpad-progress-error"),t.createDiv({cls:"stashpad-progress-title",text:"Rebootstrap failed"}),t.createDiv({cls:"stashpad-progress-sub",text:a.message}),a}}async rebootstrapAllFolders(e){this.rebootstrapInProgress=!0;try{return await this.rebootstrapAllFoldersInner(e)}finally{window.setTimeout(()=>{this.rebootstrapInProgress=!1;for(let t of this.app.workspace.getLeavesOfType(z))t.view?.forceReconcileRender?.()},2500)}}async rebootstrapAllFoldersInner(e){let t="__root__",i=new Set;for(let y of this.app.vault.getMarkdownFiles()){if(this.app.metadataCache.getFileCache(y)?.frontmatter?.id!==t)continue;let E=y.parent?.path;E&&i.add(E)}let s=new Map;for(let y of this.app.vault.getMarkdownFiles()){let v=y.parent?.path;v&&i.has(v)&&s.set(v,(s.get(v)??0)+1)}let n=Math.max(1,[...s.values()].reduce((y,v)=>y+v,0)),o=0;e?.(0,n,"Starting\u2026");let a=(this.settings.importDropFolder||"").trim().replace(/^\/+|\/+$/g,""),l=(this.settings.exportFolder||"").trim().replace(/^\/+|\/+$/g,""),c=[],d=async y=>{if(y&&!await this.app.vault.adapter.exists(y))try{await this.app.vault.createFolder(y)}catch(v){let E=v?.message??"";if(!/already exists/i.test(E))throw v}},h=0,g=0,f=0,p=0,u=0,I=0,m=!!this.app.vault.getConfig?.("alwaysUpdateLinks"),C=new Set(this.okfActiveFolders());for(let y of i){let v=s.get(y)??1;e?.(o,n,`Processing \u201C${y.split("/").pop()||y}\u201D (${v} note${v===1?"":"s"})`);try{if(a&&await d(`${y}/${a}`),l&&await d(`${y}/${l}`),this.settings.autoImport)try{let S=await this.importService.importLooseInto(y);p+=S.files+S.folders+S.stashes}catch(S){console.warn("Stashpad: loose sweep failed",y,S)}let E=await Ii(this.app,y);h+=E.checked,g+=E.written,f+=await this.rebootstrapFolderSlugs(y);let Q=await this.rebootstrapFolderAttachments(y,m);if(u+=Q.renamed,I+=Q.skipped,C.has(y.replace(/\/+$/,"")))try{await hr(this.app,y)}catch(S){console.warn("Stashpad: OKF rebuild during rebootstrap failed",y,S)}c.push(y)}catch(E){console.warn(`Stashpad: rebootstrap skipped ${y}`,E)}finally{o+=v,e?.(o,n,`Processing \u201C${y.split("/").pop()||y}\u201D`)}}e?.(n,n,"Finalizing\u2026");let B=0;try{B=(await this.rebuildAuthorRegistry()).total}catch(y){console.warn("Stashpad: rebootstrap author-registry rebuild failed",y)}let w=0;try{w=await this.convertAttachmentsToLinks()}catch(y){console.warn("Stashpad: attachment-link conversion failed",y)}return{touched:c,fmChecked:h,fmWritten:g,slugsRenamed:f,authors:B,imported:p,attachmentsLinked:w,attachmentsRenamed:u,attachmentsSkipped:I}}async rebootstrapFolderSlugs(e){let t="__root__",i=e.replace(/\/+$/,""),s=this.settings.slugStopWords??je,n=0,o=this.app.vault.getMarkdownFiles().filter(a=>(a.parent?.path?.replace(/\/+$/,"")??"")===i);for(let a of o){let l=wt(a.basename);if(!(!l||l===t||this.app.metadataCache.getFileCache(a)?.frontmatter?.id!==l))try{let d=await this.app.vault.cachedRead(a),h=d.startsWith("---")?d.slice(d.indexOf(` +---`,3)+4).replace(/^\r?\n/,""):d,g=De(h,s),f=xe(g,l);if(a.name===f)continue;let p=a.parent?`${a.parent.path}/${f}`:f;if(this.app.vault.getAbstractFileByPath(p))continue;await this.app.fileManager.renameFile(a,p),n+=1}catch(d){console.warn(`Stashpad: slug rebootstrap skipped ${a.path}`,d)}}return n}async rebootstrapFolderAttachments(e,t){let i=`${e.replace(/\/+$/,"")}/_attachments`,s=this.app.vault.getAbstractFileByPath(i);if(!(s instanceof M.TFolder))return{renamed:0,skipped:0};let n=s.children.filter(l=>l instanceof M.TFile),o=0,a=0;for(let l of n){let c=qr(l.name);if(!c)continue;let d=Ns(c.rest,c.stamp);if(l.name===d)continue;if(!t){a+=1;continue}let h=`${i}/${d}`;if(!this.app.vault.getAbstractFileByPath(h))try{await this.app.fileManager.renameFile(l,h),o+=1,await new Promise(g=>setTimeout(g,30))}catch(g){console.warn(`Stashpad: attachment rebootstrap skipped ${l.path}`,g)}}return{renamed:o,skipped:a}}onStashpadSelectionChange(e){return this.stashpadSelectionListeners.add(e),()=>this.stashpadSelectionListeners.delete(e)}notifyStashpadSelectionChanged(){for(let e of this.stashpadSelectionListeners)try{e()}catch(t){console.warn("[Stashpad] selection listener failed",t)}}onStashpadContentChange(e){return this.stashpadContentListeners.add(e),()=>this.stashpadContentListeners.delete(e)}notifyStashpadContentChanged(){for(let e of this.stashpadContentListeners)try{e()}catch(t){console.warn("[Stashpad] content listener failed",t)}}getActiveStashpadSelection(){let t=this.lastActiveStashpadLeaf?.view;if(!t||t.getViewType?.()!==z)return null;let i=t.noteFolder??"";if(!i)return null;let s=t.currentChildren??[],n;if(typeof t.cursorIdx=="number"&&t.cursorIdx>=0&&(n=s[t.cursorIdx]),!n&&t.selection?.size>0){let o=t.firstSelectedId??[...t.selection][0];n=s.find(a=>a.id===o)}return n?.file?{folder:i,id:n.id,file:n.file}:null}fileForPin(e,t){let i=e.replace(/\/+$/,"");for(let s of this.app.vault.getMarkdownFiles())if((s.parent?.path?.replace(/\/+$/,"")??"")===i&&this.app.metadataCache.getFileCache(s)?.frontmatter?.id===t)return s;return null}async pinNote(e){let t=this.fileForPin(e.folder,e.id);t&&this.app.metadataCache.getFileCache(t)?.frontmatter?.pinned!==!0&&(await this.app.fileManager.processFrontMatter(t,i=>{i.pinned=!0,i.pinnedAt=Date.now()}),this.refreshPanelsView())}async unpinNote(e){let t=this.fileForPin(e.folder,e.id);t&&this.app.metadataCache.getFileCache(t)?.frontmatter?.pinned===!0&&(await this.app.fileManager.processFrontMatter(t,i=>{delete i.pinned,delete i.pinnedAt}),this.refreshPanelsView())}isPinned(e){let t=this.fileForPin(e.folder,e.id);return t?this.app.metadataCache.getFileCache(t)?.frontmatter?.pinned===!0:!1}listPinnedNotes(){let e=new Set(this.discoverStashpadFolders()),t=[];for(let i of this.app.vault.getMarkdownFiles()){let s=i.parent?.path?.replace(/\/+$/,"")??"";if(!e.has(s))continue;let n=this.app.metadataCache.getFileCache(i)?.frontmatter;if(!n||n.pinned!==!0||typeof n.id!="string"||!n.id)continue;let o=typeof n.pinnedAt=="number"?n.pinnedAt:0;t.push({folder:s,id:n.id,pinnedAt:o,file:i})}return t.sort((i,s)=>i.pinnedAt-s.pinnedAt||i.file.path.localeCompare(s.file.path)),t}async migratePinnedNotesToFrontmatter(){let e=this.settings.pinnedNotes??[];if(e.length===0)return;let t=Date.now()-e.length*1e3;for(let i of e){let s=this.fileForPin(i.folder,i.id);if(!s){t+=1e3;continue}try{if(this.app.metadataCache.getFileCache(s)?.frontmatter?.pinned!==!0){let n=t;await this.app.fileManager.processFrontMatter(s,o=>{o.pinned=!0,o.pinnedAt=n})}}catch(n){console.warn("[Stashpad] pin migration failed for",i,n)}t+=1e3}this.settings.pinnedNotes=[],await this.saveSettings(),this.refreshPanelsView()}refreshPanelsView(){let e=this.app.workspace.getLeavesOfType(It);for(let t of e){let i=t.view;i&&typeof i.render=="function"&&i.render()}}refreshAllStashpadViews(){for(let e of this.app.workspace.getLeavesOfType(z)){let t=e.view;t&&typeof t.render=="function"&&t.render()}}refreshFolderPanels(){for(let e of this.app.workspace.getLeavesOfType(mt)){let t=e.view;t&&typeof t.refresh=="function"&&t.refresh()}}getFolderIcon(e){let t=(e||"").replace(/\/+$/,""),i=this.settings.folderIcons?.[t];return i&&i.trim()?i.trim():void 0}async setFolderIcon(e,t){let i=(e||"").replace(/\/+$/,""),s={...this.settings.folderIcons??{}};t&&t.trim()?s[i]=t.trim():delete s[i],this.settings.folderIcons=s,await this.saveSettings(),this.refreshFolderIconFor(i)}refreshFolderIconFor(e){let t=(e||"").replace(/\/+$/,"");for(let i of this.app.workspace.getLeavesOfType(z)){let s=i.view;if(s&&(s.noteFolder||"").replace(/\/+$/,"")===t){try{i.updateHeader?.()}catch{}try{s.refreshFolderSwitcherIcon?.()}catch{}}}this.refreshFolderPanels()}openFolderPicker(){let e=I=>{let C=I.getViewState().state?.folderOverride;return typeof C=="string"&&C.trim()?C.trim().replace(/^\/+|\/+$/g,""):(this.settings.folder||"Stashpad").trim().replace(/^\/+|\/+$/g,"")},t=this.app.workspace.getLeavesOfType(z),i=this.discoverStashpadFolders(),s=pt(),n=s?(s.noteFolder??"").trim().replace(/^\/+|\/+$/g,""):"",o=new Set;for(let I of this.app.vault.getAllLoadedFiles())if(I.children){let m=I.path;m&&m!=="/"&&!m.startsWith(".")&&o.add(m)}let a=I=>{let m=I.split("/").filter(Boolean).pop()??"";return m?new Set([this.settings.importDropFolder,this.settings.exportFolder,"_attachments","_processed","_authors","_exports","_imports","_archive",".archive","_deleted"].map(B=>(B??"").trim().replace(/^\/+|\/+$/g,"")).filter(Boolean)).has(m):!1},l=new Set,c=[],d=[],h=new Set;for(let I of t){let m=e(I);if(h.has(m))continue;h.add(m),l.add(m);let C=m.split("/").pop()||m;c.push({kind:"reveal",folder:m,label:`Reveal "${C}" tab`,leaf:I,icon:this.isArchiveFolder(m)?"archive":"layout-grid"}),d.push({kind:"open-anyway",folder:m,label:`Open "${C}" in another new tab`,icon:"layout-template"})}for(let I of i.filter(m=>!l.has(m))){let m=I.split("/").pop()||I;c.push({kind:"open",folder:I,label:`Open "${m}" in new tab`,icon:this.isArchiveFolder(I)?"archive":"layout-template"})}let g=I=>I.basename.replace(/-[a-z0-9]{4,12}$/,"").replace(/-/g," ").trim()||I.basename,f=this.settings.folderSwitcherIncludePinned?this.listPinnedNotes().map(I=>({kind:"pinned",folder:I.folder,file:I.file,label:g(I.file),icon:"pin"})):[],p=this,u=new class extends M.SuggestModal{getSuggestions(I){let m=I.trim().toLowerCase(),C=m?m.split(/\s+/).filter(Boolean):[],B=E=>{if(!C.length)return!0;let Q=E.toLowerCase();for(let S of C)if(!Q.includes(S))return!1;return!0},w=m?c.filter(E=>{let Q="folder"in E?E.folder:"";return B(E.label)||B(Q)}):c.slice();if(m&&s&&n)for(let E of i){if(E.toLowerCase()===n.toLowerCase())continue;let Q=E.split("/").pop()??E,S=`${E} ${Q}`;B(S)&&w.push({kind:"switch-current",folder:E,label:`Switch this tab to "${Q}"`,icon:"folder-input"})}for(let E of f)(B(E.label)||B("folder"in E?E.folder:""))&&w.push(E);let y=I.trim().replace(/^\/+|\/+$/g,"");if(y&&!a(y)){let E=Array.from(o).find(S=>S.toLowerCase()===y.toLowerCase()),Q=i.some(S=>S.toLowerCase()===y.toLowerCase());if(E&&!Q)w.push({kind:"convert",folder:E,label:`Convert \u201C${Pi(E)}\u201D into a Stashpad\u2026`,icon:"folder-cog"});else if(!E){let S=Pi(y);w.push({kind:"create",folder:y,label:`+ Create new Stashpad \u201C${S}\u201D`,icon:"folder-plus"})}}let v=d.filter(E=>B(E.label)||B("folder"in E?E.folder:""));return w.push(...v),B("trash deleted encrypted")&&w.push({kind:"trash",label:"Open Trash",icon:"trash-2"}),w}renderSuggestion(I,m){m.addClass("stashpad-suggest-item"),m.addClass("stashpad-ribbon-suggest-item"),I.kind==="create"&&m.addClass("stashpad-suggest-create");let C=m.createSpan({cls:"stashpad-ribbon-suggest-icon"});(0,M.setIcon)(C,I.icon);let B=m.createDiv({cls:"stashpad-ribbon-suggest-body"});B.createDiv({cls:"stashpad-suggest-title",text:I.label}),"folder"in I&&I.folder&&I.label!==I.folder&&B.createDiv({cls:"stashpad-suggest-preview",text:I.folder})}async onChooseSuggestion(I){if(I.kind==="trash"){p.openEncryptedTrash();return}if(I.kind==="pinned"){await p.revealNoteInStashpad(I.file);return}if(I.kind==="reveal"){p.app.workspace.revealLeaf(I.leaf);return}if(I.kind==="open"||I.kind==="open-anyway"){await p.activateViewForFolder(I.folder);return}if(I.kind==="switch-current"){let m=s;m&&typeof m.setFolderOverride=="function"&&(await m.setFolderOverride(I.folder),p.app.workspace.revealLeaf(m.leaf));return}if(I.kind==="create"){try{let m=Pi(I.folder);await p.app.vault.adapter.exists(m)||await p.app.vault.createFolder(m),await p.activateViewForFolder(m)}catch(m){new M.Notice(`Stashpad: couldn't create folder (${m.message})`)}return}if(I.kind==="convert"){let{ConfirmModal:m}=await Promise.resolve().then(()=>(ye(),Yi)),C=I.folder,B=[`\u201C${C}\u201D already exists as a regular vault folder.`,"Converting adds a Home note + _imports / _exports subfolders, and imports the existing loose files and subfolders inside it as notes.","(Files already structured as notes, and reserved subfolders, are left alone.)"];new m(p.app,"Convert into a Stashpad?",B.join(` +`),"Convert & import",async w=>{if(w)try{await p.activateViewForFolder(C),await p.runImportLooseFiles(C)}catch(y){new M.Notice(`Stashpad: couldn't convert folder (${y.message})`)}}).open();return}}}(this.app);u.setPlaceholder(s?"Open, switch this tab, or create a Stashpad folder \u2014 type to filter\u2026":"Open or create a Stashpad folder \u2014 type to filter\u2026"),u.open()}async openTinyWindow(){let e=pt(),t=e?.folderOverride??null,i=e?.focusId??"__root__",s=!!e?.compactMode,n=this.app.workspace.openPopoutLeaf?.();if(!n){new M.Notice("Stashpad: couldn't open popout window on this build.");return}await n.setViewState({type:z,active:!0,state:{folderOverride:t,focusId:i,tinyMode:!0,tinyAlwaysOnTop:!1,compactMode:s}});try{this.app.workspace.revealLeaf(n)}catch{}}async activateView(e={reveal:!0}){let{workspace:t}=this.app;if(e.reveal){let s=t.getLeavesOfType(z);if(s.length>0){t.revealLeaf(s[0]);return}}let i=t.getLeaf("tab");await i.setViewState({type:z,active:!0}),t.revealLeaf(i)}async focusLastStashpadTab(){let e=this.app.workspace,t=e.getLeavesOfType(z),i=this.lastActiveStashpadLeaf&&t.includes(this.lastActiveStashpadLeaf)?this.lastActiveStashpadLeaf:t[0]??null;if(!i){await this.activateView({reveal:!0});return}e.revealLeaf(i),e.setActiveLeaf(i,{focus:!0})}async focusFolderPanel(){await Hn(this.app);let e=this.app.workspace.getLeavesOfType(mt)[0];e&&this.app.workspace.setActiveLeaf(e,{focus:!0})}async reconcileLockedRegistry(){let e=this.settings.lockedSubtrees??[],t=[];for(let n of e){let o=(n.folder??"").replace(/\/+$/,"");if(!(o==="_deleted"||o.startsWith("_deleted/")))try{await this.app.vault.adapter.exists(n.blob)&&t.push(n)}catch{t.push(n)}}let i=t.length!==e.length,s=new Set(t.map(n=>n.blob));for(let n of this.app.vault.getFiles()){if(n.extension!=="stashenc"||s.has(n.path))continue;let o=n.parent?.path?.replace(/\/+$/,"")??"";if(o==="_deleted"||o.startsWith("_deleted/"))continue;let a=await wn(this.app,n.path);a&&(t.push({folder:o,blob:n.path,parentId:a.parentId,title:a.title,count:a.count,created:a.created,rootId:a.rootId,prevSibling:a.prevSibling}),i=!0)}i&&(this.settings.lockedSubtrees=t,await this.saveSettings(),this.refreshAllStashpadViews?.())}lockedSubtreesFor(e,t){let i=e.replace(/\/+$/,""),s=[],n=new Set;for(let o of this.settings.lockedSubtrees??[])(o.folder??"").replace(/\/+$/,"")===i&&(o.parentId??F)===t&&(s.push({blob:o.blob,title:o.title??"",count:o.count??0,created:o.created??"",rootId:o.rootId,parentId:o.parentId??F,prevSibling:o.prevSibling??null}),n.add(o.blob));for(let o of this.app.vault.getFiles()){if(o.extension!=="stashenc"||n.has(o.path))continue;let a=o.parent?.path?.replace(/\/+$/,"")??"";a!==i||a==="_deleted"||a.startsWith("_deleted/")||t===F&&s.push({blob:o.path,title:o.basename,count:0,created:"",rootId:void 0,parentId:F,prevSibling:null})}return s}lockedSubtreesInFolder(e){let t=e.replace(/\/+$/,""),i=[],s=new Set;for(let n of this.settings.lockedSubtrees??[])(n.folder??"").replace(/\/+$/,"")===t&&(i.push({blob:n.blob,title:n.title??"",count:n.count??0,created:n.created??"",rootId:n.rootId,parentId:n.parentId??F}),s.add(n.blob));for(let n of this.app.vault.getFiles()){if(n.extension!=="stashenc"||s.has(n.path))continue;let o=n.parent?.path?.replace(/\/+$/,"")??"";o!==t||o==="_deleted"||o.startsWith("_deleted/")||i.push({blob:n.path,title:n.basename,count:0,created:"",rootId:void 0,parentId:F})}return i}async ensureEncryptionUnlocked(){return this.encryption.isConfigured()?this.encryption.isUnlocked()||await this.encryption.tryAutoUnlock()?!0:new Promise(e=>{new de(this.app,{mode:"unlock",offerKeychain:!0,onSubmit:async({current:t,remember:i})=>await this.encryption.unlock(t,i)?(e(!0),null):"Wrong password. Try again.",onCancel:()=>e(!1)}).open()}):(new M.Notice("Set up encryption first (Settings \u2192 Stashpad \u2192 Encryption)."),!1)}async ensureFolderUnlocked(e){if(!this.encryption.isConfigured())return new M.Notice("Set up encryption first (Settings \u2192 Stashpad \u2192 Encryption)."),null;if(this.encryption.hasFolderKey(e)){if(this.encryption.isFolderUnlocked(e))return this.encryption.getFolderKey(e);if(await this.encryption.tryAutoUnlockFolder(e))return this.encryption.getFolderKey(e);let t=e.split("/").pop()||e;return await new Promise(s=>{new de(this.app,{mode:"unlock",offerKeychain:!0,title:`Unlock \u201C${t}\u201D`,intro:"Enter this folder's password.",onSubmit:async({current:n,remember:o})=>await this.encryption.unlockFolder(e,n,o)?(s(!0),null):"Wrong password. Try again.",onCancel:()=>s(!1)}).open()})?this.encryption.getFolderKey(e):null}return await this.ensureEncryptionUnlocked()?this.encryption.getSessionKey():null}isTrashBlobPath(e){let t=e.replace(/\/[^/]*$/,"").replace(/\/+$/,"");return t==="_deleted"||t.startsWith("_deleted/")||t==="trash"||t.endsWith("/trash")}encryptionState(){let e=s=>this.isTrashBlobPath(s),t=!1,i=!1;for(let s of this.app.vault.getFiles())if(s.extension==="stashenc"&&(e(s.path)?i=!0:t=!0,t&&i))return{live:t,trash:i};for(let s of this.pendingEncBlobs)if(e(s)?i=!0:t=!0,t&&i)break;return{live:t,trash:i}}async encryptionStateStrict(){let e=this.encryptionState(),t=e.trash;if(!t)try{t=(await As(this.app,this.trashSubfolderDirs())).length>0}catch{}if(!t)try{t=(await this.app.vault.adapter.list(Pe)).files.some(s=>s.endsWith(".stashenc"))}catch{}return{live:e.live,trash:t}}watchReEncrypt(e){let t=e.folder.replace(/\/+$/,""),i=(this.settings.reEncryptWatch??[]).filter(s=>!(s.folder===t&&s.rootId===e.rootId));this.settings.reEncryptWatch=[...i,{folder:t,rootId:e.rootId,title:e.title,count:e.count,via:e.via,unlockedAt:new Date().toISOString()}]}unwatchReEncrypt(e,t){let i=e.replace(/\/+$/,""),s=this.settings.reEncryptWatch??[],n=s.filter(o=>!(o.folder===i&&o.rootId===t));n.length!==s.length&&(this.settings.reEncryptWatch=n)}reEncryptWatching(){return(this.settings.reEncryptWatch??[]).filter(e=>!e.removed)}pruneReEncryptWatch(){let e=this.settings.reEncryptWatch??[];if(e.length===0)return!1;let t=n=>{let o=new Set;for(let a of this.app.vault.getMarkdownFiles()){if((a.parent?.path?.replace(/\/+$/,"")??"")!==n)continue;let l=this.app.metadataCache.getFileCache(a)?.frontmatter?.id;typeof l=="string"&&o.add(l)}return o},i=new Map,s=e.filter(n=>{let o=n.folder.replace(/\/+$/,"");return i.has(o)||i.set(o,t(o)),i.get(o).has(n.rootId)});return s.length===e.length?!1:(this.settings.reEncryptWatch=s,!0)}async encryptEverythingApplicable(){this.pruneReEncryptWatch()&&await this.saveSettings();let e=[];for(let i of this.reEncryptWatching())e.push({label:i.title||"(untitled)",detail:`${i.folder} \u2014 ${i.via==="restore"?"restored from trash":"unlocked"} (was encrypted)`,run:async()=>!!await this.lockNoteSubtree(i.folder,i.rootId,null,{silent:!0})});let t=new Set((this.settings.lockedSubtrees??[]).map(i=>i.rootId));for(let[i,s]of Object.entries(this.settings.folderEncPrefs??{})){if(!s?.encryptContent)continue;let n=this.app.vault.getMarkdownFiles().filter(o=>{if((o.parent?.path?.replace(/\/+$/,"")??"")!==i)return!1;let a=this.app.metadataCache.getFileCache(o)?.frontmatter?.id;return typeof a=="string"&&a!==F&&!t.has(a)}).length;n>0&&e.push({label:`Everything in \u201C${i.split("/").pop()||i}\u201D (${n} plaintext note${n===1?"":"s"})`,detail:`${i} \u2014 the folder is set to encrypt its notes`,run:async()=>(await this.lockFolder(i),!0)})}for(let[i,s]of Object.entries(this.settings.folderEncPrefs??{})){if(!s?.archiveEncryptContent)continue;let n=this.archivedPlainNotesIn(Ct(i)).length;n>0&&e.push({label:`${n} archived note${n===1?"":"s"} in \u201C${i.split("/").pop()||i}/archive\u201D`,detail:`${i} \u2014 the folder encrypts its archive`,run:async()=>(await this.encryptExistingArchiveNotes(i),!0)})}if(e.length===0){new M.Notice("Nothing needs re-encrypting \u2014 everything applicable is already locked.");return}new $i(this.app,e.map(({label:i,detail:s})=>({label:i,detail:s})),async i=>{let s=0,n=0,o=i.length>2?new M.Notice("",0):null;for(let a=0;a<i.length;a++){o?.setMessage(`\u{1F512} Re-encrypting ${a+1}/${i.length}\u2026`);try{await e[i[a]].run()?s++:n++}catch(l){n++,console.warn("[Stashpad] sweep item failed",e[i[a]].label,l)}}o?.hide(),this.notifications.show({message:`Re-encrypt sweep: ${s} done${n?`, ${n} FAILED (see console)`:""}, ${e.length-i.length} skipped.`,kind:n?"warning":"success",category:"system",folder:""})}).open()}async lockNoteSubtree(e,t,i=null,s={}){let n=(s.blobFolder??e).replace(/\/+$/,""),o=await this.ensureFolderUnlocked(n);if(!o)return null;try{let a=!!s.blobFolder&&s.blobFolder.replace(/\/+$/,"")!==e.replace(/\/+$/,""),l=(this.settings.folderEncPrefs??{})[n]??{},c=(a?l.archiveEncryptFilenames:l.encryptFilenames)??!1,d=await sl(this.app,e,t,o,i,c,s.blobFolder);this.pendingEncBlobs.add(d.blobPath);let h=(s.blobFolder??e).replace(/\/+$/,"");return this.settings.lockedSubtrees=[...(this.settings.lockedSubtrees??[]).filter(g=>g.blob!==d.blobPath),{folder:h,blob:d.blobPath,parentId:d.parentId,title:d.title,count:d.noteCount,created:d.created,rootId:d.rootId,prevSibling:i}],this.unwatchReEncrypt(e,d.rootId),this.unwatchReEncrypt(h,d.rootId),await this.saveSettings(),d.unpurged.length>0?new M.Notice(`\u26A0\uFE0F Locked, but ${d.unpurged.length} file${d.unpurged.length===1?" is":"s are"} still in plaintext (couldn't be removed or changed during the lock): ${d.unpurged.join(` -`)}`,0):s.silent||this.notifications.show({message:`Locked ${d.title?`\u201C${d.title}\u201D`:"a note"} (${d.noteCount} note${d.noteCount===1?"":"s"}).`,kind:"success",category:"system",folder:e}),d}catch(a){return console.warn("[Stashpad] lock failed",a),new T.Notice(`Couldn't lock: ${a.message}`),null}}async unlockBundleCore(e,t,i){let s=(i??e.replace(/\/[^/]*$/,"")).replace(/\/+$/,""),n=new Set;for(let o of this.app.vault.getMarkdownFiles())if((o.parent?.path?.replace(/\/+$/,"")??"")===s)try{let a=he(await this.app.vault.read(o)).fm.id;typeof a=="string"&&n.add(a)}catch{}return Rr(this.app,e,t,n,i)}async unlockBundleAt(e,t={}){let i=e.replace(/\/[^/]*$/,"").replace(/\/+$/,""),s=await this.ensureFolderUnlocked(i);if(!s)return!1;let n=(t.destFolder??e.replace(/\/[^/]*$/,"")).replace(/\/+$/,"");try{let o=await this.unlockBundleCore(e,s,t.destFolder);return this.pendingEncBlobs.delete(e),this.settings.lockedSubtrees=(this.settings.lockedSubtrees??[]).filter(a=>a.blob!==e),await this.saveSettings(),t.silent||this.notifications.show({message:`Unlocked ${o.notesWritten} note${o.notesWritten===1?"":"s"}.`,kind:"success",category:"system",folder:n}),!0}catch(o){return console.warn("[Stashpad] unlock failed",o),new T.Notice(`Couldn't unlock: ${o.message}`),!1}}async lockFolder(e){let t=e.replace(/\/+$/,"");if(!await this.ensureFolderUnlocked(t))return 0;let i=[];for(let d of this.app.vault.getMarkdownFiles()){if((d.parent?.path?.replace(/\/+$/,"")??"")!==t)continue;let h;try{h=he(await this.app.vault.read(d)).fm}catch{continue}let g=h.id;typeof g!="string"||g===k||(typeof h.parent=="string"?h.parent:k)!==k||i.push(g)}let s=new Set((this.settings.lockedSubtrees??[]).map(d=>d.rootId).filter(d=>!!d)),n=i.filter(d=>!s.has(d));if(n.length===0)return new T.Notice("Nothing to lock in this folder."),0;let a=(await new Ai(this.app).load(t))[k]??[],l=n.length>3?new T.Notice("",0):null,c=0;for(let d=0;d<n.length;d++){let h=n[d];l?.setMessage(`\u{1F512} Encrypting ${d+1}/${n.length}\u2026`);let g=a.indexOf(h),u=g>0?a[g-1]:null;await this.lockNoteSubtree(t,h,u,{silent:!0})&&c++}return l?.hide(),c>0&&this.notifications.show({message:`Locked ${c} note${c===1?"":"s"} in \u201C${t.split("/").pop()}\u201D.`,kind:"success",category:"system",folder:t}),c}async unlockFolder(e){let t=e.replace(/\/+$/,"");if(!await this.ensureFolderUnlocked(t))return 0;let i=this.app.vault.getFiles().filter(o=>o.extension==="stashenc"&&(o.parent?.path?.replace(/\/+$/,"")??"")===t).map(o=>o.path);if(i.length===0)return new T.Notice("No locked notes in this folder."),0;let s=i.length>3?new T.Notice("",0):null,n=0;for(let o=0;o<i.length;o++){s?.setMessage(`\u{1F513} Decrypting ${o+1}/${i.length}\u2026`);try{await this.unlockBundleAt(i[o],{silent:!0})&&n++}catch(a){console.warn("[Stashpad] folder unlock skipped",i[o],a)}}return s?.hide(),n>0&&this.notifications.show({message:`Unlocked ${n} note${n===1?"":"s"} in \u201C${t.split("/").pop()}\u201D.`,kind:"success",category:"system",folder:t}),n}async unlockAllInVault(){if(!this.encryption.isConfigured())return new T.Notice("Set up encryption first (Settings \u2192 Stashpad \u2192 Encryption)."),0;let e=this.app.vault.getFiles().filter(d=>d.extension==="stashenc"&&(d.parent?.path?.replace(/\/+$/,"")??"")!=="_deleted").map(d=>d.path);if(e.length===0)return new T.Notice("No locked notes anywhere in the vault."),0;let t=e.length>3?new T.Notice("",0):null,i=new Map,s=!1,n=async d=>{if(i.has(d))return i.get(d);let h=null;return this.encryption.hasFolderKey(d)?(this.encryption.isFolderUnlocked(d)||await this.encryption.tryAutoUnlockFolder(d))&&(h=this.encryption.getFolderKey(d)):(s||(s=!0,await this.ensureEncryptionUnlocked()),h=this.encryption.getSessionKey()),i.set(d,h),h},o=0,a=[],l=new Set;for(let d=0;d<e.length;d++){t?.setMessage(`\u{1F513} Decrypting ${d+1}/${e.length}\u2026`);let h=e[d].replace(/\/[^/]*$/,"").replace(/\/+$/,""),g=await n(h);if(!g){l.add(h||"(vault root)");continue}try{let u=await this.unlockBundleCore(e[d],g);o+=u.notesWritten,a.push(e[d])}catch(u){console.warn("[Stashpad] vault unlock skipped",e[d],u)}}if(a.length>0){let d=new Set(a);this.settings.lockedSubtrees=(this.settings.lockedSubtrees??[]).filter(h=>!d.has(h.blob)),await this.saveSettings()}t?.hide();for(let d of i.values())try{d?.fill(0)}catch{}let c=e[0].replace(/\/[^/]*$/,"");return o>0&&this.notifications.show({message:`Unlocked ${o} note${o===1?"":"s"} across the vault.`,kind:"success",category:"system",folder:c}),l.size>0&&new T.Notice(`Skipped ${l.size} locked folder${l.size===1?"":"s"} (no key unlocked): ${[...l].map(d=>d.split("/").pop()||d).join(", ")}. Open each to unlock it, then run this again.`,0),o}exportLockedSubtree(e){return el(this,e)}folderHasStashpadNotes(e){let t=e.replace(/\/+$/,"");return this.discoverStashpadFolders().some(i=>t===i||t.startsWith(i+"/")||i.startsWith(t+"/"))}encStamp(){let e=new Date,t=i=>String(i).padStart(2,"0");return`${e.getFullYear()}${t(e.getMonth()+1)}${t(e.getDate())}-${t(e.getHours())}${t(e.getMinutes())}`}folderKeyLabelFor(e){let t=(this.settings.authorName||"").trim(),i=t?t.split(/\s+/).map(s=>s[0]).join(""):(this.settings.authorId||"anon").slice(0,4);return`${this.encStamp()} - ${e} - ${i}`}async encryptFolderFromExplorer(e){if(!this.encryption.isConfigured()){new T.Notice("Set up Stashpad vault encryption first (Settings \u2192 Stashpad \u2192 Encryption), then try again.");return}let t=e.split("/").pop()||e;if(this.folderHasStashpadNotes(e)){await this.encryptStashpadFolder(e,t);return}if(await $o(this.app,e)){new T.Notice(`\u201C${t}\u201D is already encrypted as a Stashpad bundle. Use \u201CDecrypt with Stashpad\u201D to open it.`);return}await this.encryptRawFolder(e,t)}async encryptStashpadFolder(e,t){if(this.encryption.hasOwnFolderKey(e)){await this.ensureFolderUnlocked(e)&&await this.lockFolder(e);return}let i=this.folderKeyLabelFor(t);new de(this.app,{mode:"setup",offerKeychain:!0,title:`Encrypt \u201C${t}\u201D with Stashpad`,intro:"Give this folder its own password. Any Stashpad notes inside it get encrypted under it; you'll re-enter the password to unlock.",onSubmit:async({next:s,remember:n})=>{if(!s)return"Enter a password.";try{await this.encryption.setupFolderKey(e,s,i,n)}catch(a){return a.message}let o=await this.lockFolder(e);return new T.Notice(o>0?`Encrypted ${o} note${o===1?"":"s"} in \u201C${t}\u201D.`:`\u201C${t}\u201D now has a Stashpad password \u2014 notes added here will use it.`),this.refreshFolderPanels?.(),null}}).open()}async encryptRawFolder(e,t){let i=async s=>{let n=this.encryption.folderKeyEntry(e)?.keyId;try{let o=await Wr(this.app,e,s,n,this.encStamp());o.unpurged.length?new T.Notice(`Encrypted \u201C${t}\u201D (${o.fileCount} files) \u2014 but ${o.unpurged.length} file(s) changed mid-encrypt and were left in place.`):new T.Notice(`Encrypted \u201C${t}\u201D \u2014 ${o.fileCount} file(s) bundled into one encrypted file.`)}catch(o){return o.message}return this.refreshFolderPanels?.(),null};new ie(this.app,`Encrypt \u201C${t}\u201D \u2014 non-Stashpad folder`,`This isn't a Stashpad folder, so its files can't be locked individually. +`)}`,0):s.silent||this.notifications.show({message:`Locked ${d.title?`\u201C${d.title}\u201D`:"a note"} (${d.noteCount} note${d.noteCount===1?"":"s"}).`,kind:"success",category:"system",folder:e}),d}catch(a){return console.warn("[Stashpad] lock failed",a),new M.Notice(`Couldn't lock: ${a.message}`),null}}async unlockBundleCore(e,t,i){let s=(i??e.replace(/\/[^/]*$/,"")).replace(/\/+$/,""),n=new Set;for(let o of this.app.vault.getMarkdownFiles())if((o.parent?.path?.replace(/\/+$/,"")??"")===s)try{let a=he(await this.app.vault.read(o)).fm.id;typeof a=="string"&&n.add(a)}catch{}return nl(this.app,e,t,n,i)}async unlockBundleAt(e,t={}){let i=e.replace(/\/[^/]*$/,"").replace(/\/+$/,""),s=await this.ensureFolderUnlocked(i);if(!s)return!1;let n=(t.destFolder??e.replace(/\/[^/]*$/,"")).replace(/\/+$/,"");try{let o=await this.unlockBundleCore(e,s,t.destFolder);this.pendingEncBlobs.delete(e);let a=(this.settings.lockedSubtrees??[]).find(l=>l.blob===e);return a?.rootId&&this.watchReEncrypt({folder:n,rootId:a.rootId,title:a.title??"",count:a.count??o.notesWritten,via:"unlock"}),this.settings.lockedSubtrees=(this.settings.lockedSubtrees??[]).filter(l=>l.blob!==e),await this.saveSettings(),t.silent||this.notifications.show({message:`Unlocked ${o.notesWritten} note${o.notesWritten===1?"":"s"}.`,kind:"success",category:"system",folder:n}),!0}catch(o){return console.warn("[Stashpad] unlock failed",o),new M.Notice(`Couldn't unlock: ${o.message}`),!1}}async lockFolder(e){let t=e.replace(/\/+$/,"");if(!await this.ensureFolderUnlocked(t))return 0;let i=[];for(let d of this.app.vault.getMarkdownFiles()){if((d.parent?.path?.replace(/\/+$/,"")??"")!==t)continue;let h;try{h=he(await this.app.vault.read(d)).fm}catch{continue}let g=h.id;typeof g!="string"||g===F||(typeof h.parent=="string"?h.parent:F)!==F||i.push(g)}let s=new Set((this.settings.lockedSubtrees??[]).map(d=>d.rootId).filter(d=>!!d)),n=i.filter(d=>!s.has(d));if(n.length===0)return new M.Notice("Nothing to lock in this folder."),0;let a=(await new gi(this.app).load(t))[F]??[],l=n.length>3?new M.Notice("",0):null,c=0;for(let d=0;d<n.length;d++){let h=n[d];l?.setMessage(`\u{1F512} Encrypting ${d+1}/${n.length}\u2026`);let g=a.indexOf(h),f=g>0?a[g-1]:null;await this.lockNoteSubtree(t,h,f,{silent:!0})&&c++}return l?.hide(),c>0&&this.notifications.show({message:`Locked ${c} note${c===1?"":"s"} in \u201C${t.split("/").pop()}\u201D.`,kind:"success",category:"system",folder:t}),c}async unlockFolder(e){let t=e.replace(/\/+$/,"");if(!await this.ensureFolderUnlocked(t))return 0;let i=this.app.vault.getFiles().filter(o=>o.extension==="stashenc"&&(o.parent?.path?.replace(/\/+$/,"")??"")===t).map(o=>o.path);if(i.length===0)return new M.Notice("No locked notes in this folder."),0;let s=i.length>3?new M.Notice("",0):null,n=0;for(let o=0;o<i.length;o++){s?.setMessage(`\u{1F513} Decrypting ${o+1}/${i.length}\u2026`);try{await this.unlockBundleAt(i[o],{silent:!0})&&n++}catch(a){console.warn("[Stashpad] folder unlock skipped",i[o],a)}}return s?.hide(),n>0&&this.notifications.show({message:`Unlocked ${n} note${n===1?"":"s"} in \u201C${t.split("/").pop()}\u201D.`,kind:"success",category:"system",folder:t}),n}async unlockAllInVault(){if(!this.encryption.isConfigured())return new M.Notice("Set up encryption first (Settings \u2192 Stashpad \u2192 Encryption)."),0;let e=this.app.vault.getFiles().filter(d=>d.extension==="stashenc"&&!this.isTrashBlobPath(d.path)).map(d=>d.path);if(e.length===0)return new M.Notice("No locked notes anywhere in the vault."),0;let t=e.length>3?new M.Notice("",0):null,i=new Map,s=!1,n=async d=>{if(i.has(d))return i.get(d);let h=null;return this.encryption.hasFolderKey(d)?(this.encryption.isFolderUnlocked(d)||await this.encryption.tryAutoUnlockFolder(d))&&(h=this.encryption.getFolderKey(d)):(s||(s=!0,await this.ensureEncryptionUnlocked()),h=this.encryption.getSessionKey()),i.set(d,h),h},o=0,a=[],l=new Set;for(let d=0;d<e.length;d++){t?.setMessage(`\u{1F513} Decrypting ${d+1}/${e.length}\u2026`);let h=e[d].replace(/\/[^/]*$/,"").replace(/\/+$/,""),g=await n(h);if(!g){l.add(h||"(vault root)");continue}try{let f=await this.unlockBundleCore(e[d],g);o+=f.notesWritten,a.push(e[d])}catch(f){console.warn("[Stashpad] vault unlock skipped",e[d],f)}}if(a.length>0){let d=new Set(a);this.settings.lockedSubtrees=(this.settings.lockedSubtrees??[]).filter(h=>!d.has(h.blob)),await this.saveSettings()}t?.hide();for(let d of i.values())try{d?.fill(0)}catch{}let c=e[0].replace(/\/[^/]*$/,"");return o>0&&this.notifications.show({message:`Unlocked ${o} note${o===1?"":"s"} across the vault.`,kind:"success",category:"system",folder:c}),l.size>0&&new M.Notice(`Skipped ${l.size} locked folder${l.size===1?"":"s"} (no key unlocked): ${[...l].map(d=>d.split("/").pop()||d).join(", ")}. Open each to unlock it, then run this again.`,0),o}exportLockedSubtree(e){return Bl(this,e)}folderHasStashpadNotes(e){let t=e.replace(/\/+$/,"");return this.discoverStashpadFolders().some(i=>t===i||t.startsWith(i+"/")||i.startsWith(t+"/"))}encStamp(){let e=new Date,t=i=>String(i).padStart(2,"0");return`${e.getFullYear()}${t(e.getMonth()+1)}${t(e.getDate())}-${t(e.getHours())}${t(e.getMinutes())}`}folderKeyLabelFor(e){let t=(this.settings.authorName||"").trim(),i=t?t.split(/\s+/).map(s=>s[0]).join(""):(this.settings.authorId||"anon").slice(0,4);return`${this.encStamp()} - ${e} - ${i}`}async encryptFolderFromExplorer(e){if(!this.encryption.isConfigured()){new M.Notice("Set up Stashpad vault encryption first (Settings \u2192 Stashpad \u2192 Encryption), then try again.");return}let t=e.split("/").pop()||e;if(this.folderHasStashpadNotes(e)){await this.encryptStashpadFolder(e,t);return}if(await ar(this.app,e)){new M.Notice(`\u201C${t}\u201D is already encrypted as a Stashpad bundle. Use \u201CDecrypt with Stashpad\u201D to open it.`);return}await this.encryptRawFolder(e,t)}async encryptStashpadFolder(e,t){if(this.encryption.hasOwnFolderKey(e)){await this.ensureFolderUnlocked(e)&&await this.lockFolder(e);return}let i=this.folderKeyLabelFor(t);new de(this.app,{mode:"setup",offerKeychain:!0,title:`Encrypt \u201C${t}\u201D with Stashpad`,intro:"Give this folder its own password. Any Stashpad notes inside it get encrypted under it; you'll re-enter the password to unlock.",onSubmit:async({next:s,remember:n})=>{if(!s)return"Enter a password.";try{await this.encryption.setupFolderKey(e,s,i,n)}catch(a){return a.message}let o=await this.lockFolder(e);return new M.Notice(o>0?`Encrypted ${o} note${o===1?"":"s"} in \u201C${t}\u201D.`:`\u201C${t}\u201D now has a Stashpad password \u2014 notes added here will use it.`),this.refreshFolderPanels?.(),null}}).open()}async encryptRawFolder(e,t){let i=async s=>{let n=this.encryption.folderKeyEntry(e)?.keyId;try{let o=await ul(this.app,e,s,n,this.encStamp());o.unpurged.length?new M.Notice(`Encrypted \u201C${t}\u201D (${o.fileCount} files) \u2014 but ${o.unpurged.length} file(s) changed mid-encrypt and were left in place.`):new M.Notice(`Encrypted \u201C${t}\u201D \u2014 ${o.fileCount} file(s) bundled into one encrypted file.`)}catch(o){return o.message}return this.refreshFolderPanels?.(),null};new ie(this.app,`Encrypt \u201C${t}\u201D \u2014 non-Stashpad folder`,`This isn't a Stashpad folder, so its files can't be locked individually. Stashpad will bundle EVERY file in this folder into one encrypted file and permanently delete the originals (no copy left in Obsidian's trash \u2014 that would defeat encryption). -This can't be undone with Cmd+Z; the only way back is \u201CDecrypt with Stashpad\u201D + the password. Continue?`,"Bundle & encrypt",s=>{if(!s)return;if(this.encryption.hasOwnFolderKey(e)){(async()=>{let o=await this.ensureFolderUnlocked(e);if(!o)return;let a=await i(o);a&&new T.Notice(a)})();return}let n=this.folderKeyLabelFor(t);new de(this.app,{mode:"setup",offerKeychain:!0,title:`Encrypt \u201C${t}\u201D with Stashpad`,intro:"Set a password for this bundle. You'll re-enter it to decrypt the whole folder.",onSubmit:async({next:o,remember:a})=>{if(!o)return"Enter a password.";try{await this.encryption.setupFolderKey(e,o,n,a)}catch(c){return c.message}let l=this.encryption.getFolderKey(e);return l?await i(l):"Couldn't derive the folder key."}}).open()}).open()}async decryptFolderFromExplorer(e){let t=await $o(this.app,e);if(!t){new T.Notice("No Stashpad bundle found in this folder.");return}let i=e.split("/").pop()||e,s=await this.ensureFolderUnlocked(e);if(s){try{let n=await Xr(this.app,t,s);new T.Notice(`Decrypted \u201C${i}\u201D \u2014 restored ${n.filesWritten} file(s).`)}catch(n){new T.Notice(`Couldn't decrypt: ${n.message}`)}this.refreshFolderPanels?.()}}rotationLockPath(e){let t=e.replace(/\/+$/,""),i=t.replace(/[^a-zA-Z0-9]+/g,"-").replace(/^-+|-+$/g,"").toLowerCase().slice(0,40)||"root",s=0;for(let n=0;n<t.length;n++)s=s*31+t.charCodeAt(n)|0;return`.stashpad/rotating-${i}-${(s>>>0).toString(36)}.lock`}async commitRotTemps(e){for(let t of await Vr(this.app,e))try{await An(this.app,t)}catch(i){console.warn("[Stashpad] rot-commit failed",t,i)}}async rotateFolderKey(e,t,i=!1){let s=e.replace(/\/+$/,"");if(!t)return new T.Notice("A new password is required to rotate."),-1;let n=await this.ensureFolderUnlocked(s);if(!n)return-1;let o=this.encryption.folderKeyEntry(s)?.folderPath??s,a=this.rotationLockPath(o);if(await this.app.vault.adapter.exists(a))return new T.Notice("A rotation is already in progress for this folder (or one was interrupted \u2014 reload to recover)."),-1;let l=I=>I===o||I.startsWith(o+"/"),c=this.app.vault.getFiles().filter(I=>I.extension==="stashenc"&&l(I.parent?.path?.replace(/\/+$/,"")??"")).map(I=>I.path),d=this.encryption.folderKeyEntry(o)?.keyId,h=d?await Yo(this.app,d):[],g=[...c,...h],u=crypto.getRandomValues(new Uint8Array(32)),p=[...crypto.getRandomValues(new Uint8Array(8))].map(I=>I.toString(16).padStart(2,"0")).join(""),f=g.length>0?new T.Notice("",0):null;try{await this.app.vault.adapter.write(a,JSON.stringify({folder:o,phase:"encrypting",at:new Date().toISOString()}));for(let I=0;I<g.length;I++)f?.setMessage(`\u{1F504} Re-encrypting ${I+1}/${g.length}\u2026`),await $r(this.app,g[I],n,u);await this.app.vault.adapter.write(a,JSON.stringify({folder:o,phase:"committing",rotId:p,at:new Date().toISOString()})),await this.encryption.commitFolderRotation(o,t,u,p,void 0,i),f?.setMessage("\u{1F504} Finalizing\u2026"),await this.commitRotTemps(o);for(let I of h){await An(this.app,I);try{await zr(this.app,I,n,u)}catch(m){console.warn("[Stashpad] trash sidecar rewrap failed",I,m)}}try{await this.app.vault.adapter.remove(a)}catch{}return f?.hide(),new T.Notice(`\u{1F511} Rotated the key for \u201C${o.split("/").pop()}\u201D \u2014 ${g.length} item${g.length===1?"":"s"} re-encrypted. The OLD password no longer unlocks it; share the new one with anyone who should keep access.`,0),g.length}catch(I){f?.hide();try{await hn(this.app,o)}catch{}for(let m of h)try{await this.app.vault.adapter.remove(`${m}.rot`)}catch{}try{await this.app.vault.adapter.remove(a)}catch{}return console.warn("[Stashpad] rotation failed",I),new T.Notice(`Rotation failed: ${I.message}. Nothing was changed (originals intact).`,0),-1}finally{u.fill(0)}}async resumeRotations(){let e=[];try{e=(await this.app.vault.adapter.list(".stashpad")).files.filter(t=>/\/rotating-[^/]+\.lock$/.test(t))}catch{return}if(e.length!==0){await this.encryption.refresh();for(let t of e){let i={};try{i=JSON.parse(await this.app.vault.adapter.read(t))}catch{}let s=(i.folder??"").replace(/\/+$/,"");if(!s){try{await this.app.vault.adapter.remove(t)}catch{}continue}let n=this.encryption.folderKeyEntry(s);if(!!i.rotId&&n?.rotId===i.rotId){if(await this.commitRotTemps(s),n?.keyId)for(let a of await Yo(this.app,n.keyId))try{await An(this.app,a)}catch(l){console.warn("[Stashpad] resume trash-commit failed",a,l)}new T.Notice(`Finished an interrupted key rotation for \u201C${s.split("/").pop()}\u201D.`,0)}else{try{await hn(this.app,s)}catch{}try{await hn(this.app,"_deleted")}catch{}}try{await this.app.vault.adapter.remove(t)}catch{}}}}async encryptDeleteSubtree(e,t){let i=await this.ensureFolderUnlocked(e);if(!i)return null;try{let s=new Date().toISOString(),o=(((this.settings.folderEncPrefs??{})[e.replace(/\/+$/,"")]??{}).trashEncryptFilenames??!1)||(this.settings.hideLockedTitles??!1)||(this.settings.encryptTrashFilenames??!1),a=this.encryption.folderKeyEntry(e)?.keyId,l=await qr(this.app,e,t,i,s,o,a);return this.pendingEncBlobs.add(l.blobPath),l.unpurged.length>0&&new T.Notice(`\u26A0\uFE0F Sent to encrypted trash, but ${l.unpurged.length} file${l.unpurged.length===1?" is":"s are"} still in plaintext (couldn't be removed or changed during the delete): +This can't be undone with Cmd+Z; the only way back is \u201CDecrypt with Stashpad\u201D + the password. Continue?`,"Bundle & encrypt",s=>{if(!s)return;if(this.encryption.hasOwnFolderKey(e)){(async()=>{let o=await this.ensureFolderUnlocked(e);if(!o)return;let a=await i(o);a&&new M.Notice(a)})();return}let n=this.folderKeyLabelFor(t);new de(this.app,{mode:"setup",offerKeychain:!0,title:`Encrypt \u201C${t}\u201D with Stashpad`,intro:"Set a password for this bundle. You'll re-enter it to decrypt the whole folder.",onSubmit:async({next:o,remember:a})=>{if(!o)return"Enter a password.";try{await this.encryption.setupFolderKey(e,o,n,a)}catch(c){return c.message}let l=this.encryption.getFolderKey(e);return l?await i(l):"Couldn't derive the folder key."}}).open()}).open()}async decryptFolderFromExplorer(e){let t=await ar(this.app,e);if(!t){new M.Notice("No Stashpad bundle found in this folder.");return}let i=e.split("/").pop()||e,s=await this.ensureFolderUnlocked(e);if(s){try{let n=await fl(this.app,t,s);new M.Notice(`Decrypted \u201C${i}\u201D \u2014 restored ${n.filesWritten} file(s).`)}catch(n){new M.Notice(`Couldn't decrypt: ${n.message}`)}this.refreshFolderPanels?.()}}rotationLockPath(e){let t=e.replace(/\/+$/,""),i=t.replace(/[^a-zA-Z0-9]+/g,"-").replace(/^-+|-+$/g,"").toLowerCase().slice(0,40)||"root",s=0;for(let n=0;n<t.length;n++)s=s*31+t.charCodeAt(n)|0;return`.stashpad/rotating-${i}-${(s>>>0).toString(36)}.lock`}async commitRotTemps(e){for(let t of await gl(this.app,e))try{await Qn(this.app,t)}catch(i){console.warn("[Stashpad] rot-commit failed",t,i)}}async rotateFolderKey(e,t,i=!1){let s=e.replace(/\/+$/,"");if(!t)return new M.Notice("A new password is required to rotate."),-1;let n=await this.ensureFolderUnlocked(s);if(!n)return-1;let o=this.encryption.folderKeyEntry(s)?.folderPath??s,a=this.rotationLockPath(o);if(await this.app.vault.adapter.exists(a))return new M.Notice("A rotation is already in progress for this folder (or one was interrupted \u2014 reload to recover)."),-1;let l=I=>I===o||I.startsWith(o+"/"),c=this.app.vault.getFiles().filter(I=>I.extension==="stashenc"&&l(I.parent?.path?.replace(/\/+$/,"")??"")).map(I=>I.path),d=this.encryption.folderKeyEntry(o)?.keyId,h=d?await rr(this.app,d,this.trashSubfolderDirs()):[],g=[...c,...h],f=crypto.getRandomValues(new Uint8Array(32)),p=[...crypto.getRandomValues(new Uint8Array(8))].map(I=>I.toString(16).padStart(2,"0")).join(""),u=g.length>0?new M.Notice("",0):null;try{await this.app.vault.adapter.write(a,JSON.stringify({folder:o,phase:"encrypting",at:new Date().toISOString()}));for(let I=0;I<g.length;I++)u?.setMessage(`\u{1F504} Re-encrypting ${I+1}/${g.length}\u2026`),await hl(this.app,g[I],n,f);await this.app.vault.adapter.write(a,JSON.stringify({folder:o,phase:"committing",rotId:p,at:new Date().toISOString()})),await this.encryption.commitFolderRotation(o,t,f,p,void 0,i),u?.setMessage("\u{1F504} Finalizing\u2026"),await this.commitRotTemps(o);for(let I of h){await Qn(this.app,I);try{await pl(this.app,I,n,f)}catch(m){console.warn("[Stashpad] trash sidecar rewrap failed",I,m)}}try{await this.app.vault.adapter.remove(a)}catch{}return u?.hide(),new M.Notice(`\u{1F511} Rotated the key for \u201C${o.split("/").pop()}\u201D \u2014 ${g.length} item${g.length===1?"":"s"} re-encrypted. The OLD password no longer unlocks it; share the new one with anyone who should keep access.`,0),g.length}catch(I){u?.hide();try{await vn(this.app,o)}catch{}for(let m of h)try{await this.app.vault.adapter.remove(`${m}.rot`)}catch{}try{await this.app.vault.adapter.remove(a)}catch{}return console.warn("[Stashpad] rotation failed",I),new M.Notice(`Rotation failed: ${I.message}. Nothing was changed (originals intact).`,0),-1}finally{f.fill(0)}}async resumeRotations(){let e=[];try{e=(await this.app.vault.adapter.list(".stashpad")).files.filter(t=>/\/rotating-[^/]+\.lock$/.test(t))}catch{return}if(e.length!==0){await this.encryption.refresh();for(let t of e){let i={};try{i=JSON.parse(await this.app.vault.adapter.read(t))}catch{}let s=(i.folder??"").replace(/\/+$/,"");if(!s){try{await this.app.vault.adapter.remove(t)}catch{}continue}let n=this.encryption.folderKeyEntry(s);if(!!i.rotId&&n?.rotId===i.rotId){if(await this.commitRotTemps(s),n?.keyId)for(let a of await rr(this.app,n.keyId,this.trashSubfolderDirs()))try{await Qn(this.app,a)}catch(l){console.warn("[Stashpad] resume trash-commit failed",a,l)}new M.Notice(`Finished an interrupted key rotation for \u201C${s.split("/").pop()}\u201D.`,0)}else{try{await vn(this.app,s)}catch{}try{await vn(this.app,"_deleted")}catch{}}try{await this.app.vault.adapter.remove(t)}catch{}}}}async encryptDeleteSubtree(e,t){let i=await this.ensureFolderUnlocked(e);if(!i)return null;try{let s=new Date().toISOString(),o=(((this.settings.folderEncPrefs??{})[e.replace(/\/+$/,"")]??{}).trashEncryptFilenames??!1)||(this.settings.encryptTrashFilenames??!1),a=this.encryption.folderKeyEntry(e)?.keyId,l=await al(this.app,e,t,i,s,o,a,Fn(e));return this.pendingEncBlobs.add(l.blobPath),this.unwatchReEncrypt(e,t),await this.saveSettings(),l.unpurged.length>0&&new M.Notice(`\u26A0\uFE0F Sent to encrypted trash, but ${l.unpurged.length} file${l.unpurged.length===1?" is":"s are"} still in plaintext (couldn't be removed or changed during the delete): ${l.unpurged.join(` -`)}`,0),l.blobPath}catch(s){return console.warn("[Stashpad] encrypt-delete failed",s),new T.Notice(`Couldn't encrypt-delete: ${s.message}`,0),null}}async restoreDeletedAt(e,t={}){let i=await ft(this.app,e),s;if(i?.keyId){let n=this.encryption.folderPathByKeyId(i.keyId);if(s=n?await this.ensureFolderUnlocked(n):null,!s)return t.silent||new T.Notice("Couldn't unlock the folder key this trashed note was encrypted with."),!1}else{if(!await this.ensureEncryptionUnlocked())return!1;s=this.encryption.getSessionKey()}if(!s)return!1;if(i?.kind==="rawtrash")try{let n=await Yr(this.app,e,s);return this.pendingEncBlobs.delete(e),t.silent||this.notifications.show({message:`Restored ${n.filesWritten} file${n.filesWritten===1?"":"s"} to Obsidian's trash (${Te}/).`,kind:"success",category:"system",folder:""}),!0}catch(n){return console.warn("[Stashpad] trash-backfill restore failed",n),new T.Notice(`Couldn't restore: ${n.message}`,0),!1}try{let n=await Jo(this.app,e,i,s),o=new Set;for(let l of this.app.vault.getMarkdownFiles())if((l.parent?.path?.replace(/\/+$/,"")??"")===n.replace(/\/+$/,""))try{let c=he(await this.app.vault.read(l)).fm.id;typeof c=="string"&&o.add(c)}catch{}let a=await Kr(this.app,e,s,o);return this.pendingEncBlobs.delete(e),t.silent||this.notifications.show({message:`Restored ${a.notesWritten} note${a.notesWritten===1?"":"s"} to \u201C${a.restoredTo.split("/").pop()}\u201D.`,kind:"success",category:"system",folder:a.restoredTo}),!0}catch(n){return console.warn("[Stashpad] restore-from-trash failed",n),new T.Notice(`Couldn't restore: ${n.message}`,0),!1}}async purgeDeletedAt(e){try{return await Oo(this.app,e),this.pendingEncBlobs.delete(e),!0}catch(t){return console.warn("[Stashpad] purge-from-trash failed",e,t),new T.Notice(`Couldn't delete: ${t.message}`,0),!1}}async purgeAllLockedContent(){let e=s=>{let n=s.replace(/\/[^/]*$/,"");return n==="_deleted"||n.startsWith("_deleted/")},t=new Set;for(let s of this.app.vault.getFiles())s.extension==="stashenc"&&!e(s.path)&&t.add(s.path);for(let s of this.pendingEncBlobs)e(s)||t.add(s);let i=0;for(let s of t)try{await Oo(this.app,s),this.pendingEncBlobs.delete(s),i++}catch(n){console.warn("[Stashpad] purge locked content failed",s,n)}return this.settings.lockedSubtrees=(this.settings.lockedSubtrees??[]).filter(s=>!t.has(s.blob)),await this.saveSettings(),i}async listRawTrashStashpadNotes(){let e=Te,t=[];try{if(!await this.app.vault.adapter.exists(e))return t;let i=await this.app.vault.adapter.list(e);for(let s of i.files){if(!s.endsWith(".md"))continue;let n="";try{n=await this.app.vault.adapter.read(s)}catch{continue}let o=/^---\r?\n([\s\S]*?)\r?\n---/.exec(n);if(!o)continue;let a=o[1];if(!/^id:\s*\S/m.test(a)||!/^parent:\s*/m.test(a))continue;let l=s.split("/").pop()||s,c=l.replace(/\.md$/,"").replace(/-[a-z0-9]{4,12}$/,"").replace(/-/g," ").trim()||l,d=0;try{d=(await this.app.vault.adapter.stat(s))?.mtime??0}catch{}t.push({path:s,name:l,title:c,mtime:d})}}catch{}return t}async restoreRawTrashNote(e){let t=(this.settings.folder||"Stashpad").trim().replace(/^\/+|\/+$/g,"")||"Stashpad",i=e.split("/").pop()||e;try{if(!await this.app.vault.adapter.exists(t))try{await this.app.vault.createFolder(t)}catch{}let s=`${t}/${i}`;return await this.app.vault.adapter.exists(s)&&(s=`${t}/${i.replace(/\.md$/,"")}-restored-${Date.now()}.md`),await this.app.vault.adapter.rename(e,s),this.notifications.show({message:`Restored "${i}" to ${t}/. If its parent lives in another folder, move it from there.`,kind:"success",category:"system",folder:t}),!0}catch(s){return new T.Notice(`Couldn't restore: ${s.message}`),!1}}archivedPlainNotesIn(e){let t=e.replace(/\/+$/,""),i=t+"/",s=[];for(let n of this.app.vault.getMarkdownFiles()){let o=n.parent?.path?.replace(/\/+$/,"")??"";if(!(o===t||o.startsWith(i)))continue;let a=this.app.metadataCache.getFileCache(n)?.frontmatter?.id;typeof a=="string"&&a&&s.push(n)}return s}async unarchiveNote(e){let t=(this.settings.folder||"Stashpad").trim().replace(/^\/+|\/+$/g,"")||"Stashpad";if((e.parent?.path??"")===t)return new T.Notice("Already in the default folder."),!1;try{if(!await this.app.vault.adapter.exists(t))try{await this.app.vault.createFolder(t)}catch{}let i=`${t}/${e.name}`;return await this.app.vault.adapter.exists(i)&&(i=`${t}/${e.basename}-restored-${Date.now()}.md`),await this.app.fileManager.renameFile(e,i),this.notifications.show({message:`Un-archived "${e.basename}" to ${t}/.`,kind:"success",category:"system",folder:t}),!0}catch(i){return new T.Notice(`Couldn't un-archive: ${i.message}`),!1}}async listDeletedTrash(){let e=await pn(this.app),t=[];for(let i of e)t.push({blob:i,meta:await ft(this.app,i)});return t}async restoreAllTrash(){if(!await this.ensureEncryptionUnlocked())return 0;let e=await this.listDeletedTrash();if(e.length===0)return new T.Notice("Nothing to restore."),0;let t=e.length>3?new T.Notice("",0):null,i=0;for(let s=0;s<e.length;s++){t?.setMessage(`\u{1F513} Restoring ${s+1}/${e.length}\u2026`);try{await this.restoreDeletedAt(e[s].blob,{silent:!0})&&i++}catch(n){console.warn("[Stashpad] restore-all skipped",e[s].blob,n)}}return t?.hide(),i>0&&this.notifications.show({message:`Restored ${i} note${i===1?"":"s"} from encrypted trash.`,kind:"success",category:"system",folder:""}),i}async encryptExistingTrash(){if(!await this.ensureEncryptionUnlocked())return!1;let e=this.encryption.getSessionKey();if(!e)return!1;try{let t=(this.settings.hideLockedTitles??!1)||(this.settings.encryptTrashFilenames??!1),i=await Or(this.app,e,new Date().toISOString(),t);return i?(i.unpurged.length>0?new T.Notice(`\u26A0\uFE0F Encrypted the trash, but ${i.unpurged.length} file${i.unpurged.length===1?" is":"s are"} still in plaintext (couldn't be removed): +`)}`,0),l.blobPath}catch(s){return console.warn("[Stashpad] encrypt-delete failed",s),new M.Notice(`Couldn't encrypt-delete: ${s.message}`,0),null}}async restoreDeletedAt(e,t={}){let i=await at(this.app,e),s;if(i?.keyId){let n=this.encryption.folderPathByKeyId(i.keyId);if(s=n?await this.ensureFolderUnlocked(n):null,!s)return t.silent||new M.Notice("Couldn't unlock the folder key this trashed note was encrypted with."),!1}else{if(!await this.ensureEncryptionUnlocked())return!1;s=this.encryption.getSessionKey()}if(!s)return!1;if(i?.kind==="rawtrash")try{let n=await Al(this.app,e,s);return this.pendingEncBlobs.delete(e),t.silent||this.notifications.show({message:`Restored ${n.filesWritten} file${n.filesWritten===1?"":"s"} to Obsidian's trash (${Pe}/).`,kind:"success",category:"system",folder:""}),!0}catch(n){return console.warn("[Stashpad] trash-backfill restore failed",n),new M.Notice(`Couldn't restore: ${n.message}`,0),!1}try{let n=await nr(this.app,e,i,s),o=new Set;for(let l of this.app.vault.getMarkdownFiles())if((l.parent?.path?.replace(/\/+$/,"")??"")===n.replace(/\/+$/,""))try{let c=he(await this.app.vault.read(l)).fm.id;typeof c=="string"&&o.add(c)}catch{}let a=await ll(this.app,e,s,o);return this.pendingEncBlobs.delete(e),i?.rootId&&(this.watchReEncrypt({folder:a.restoredTo,rootId:i.rootId,title:i.title||"",count:i.count??a.notesWritten,via:"restore"}),await this.saveSettings()),t.silent||this.notifications.show({message:`Restored ${a.notesWritten} note${a.notesWritten===1?"":"s"} to \u201C${a.restoredTo.split("/").pop()}\u201D.`,kind:"success",category:"system",folder:a.restoredTo}),!0}catch(n){return console.warn("[Stashpad] restore-from-trash failed",n),new M.Notice(`Couldn't restore: ${n.message}`,0),!1}}async purgeDeletedAt(e){try{return await or(this.app,e),this.pendingEncBlobs.delete(e),!0}catch(t){return console.warn("[Stashpad] purge-from-trash failed",e,t),new M.Notice(`Couldn't delete: ${t.message}`,0),!1}}async purgeAllLockedContent(){let e=s=>this.isTrashBlobPath(s),t=new Set;for(let s of this.app.vault.getFiles())s.extension==="stashenc"&&!e(s.path)&&t.add(s.path);for(let s of this.pendingEncBlobs)e(s)||t.add(s);let i=0;for(let s of t)try{await or(this.app,s),this.pendingEncBlobs.delete(s),i++}catch(n){console.warn("[Stashpad] purge locked content failed",s,n)}return this.settings.lockedSubtrees=(this.settings.lockedSubtrees??[]).filter(s=>!t.has(s.blob)),await this.saveSettings(),i}async listRawTrashStashpadNotes(){let e=Pe,t=[];try{if(!await this.app.vault.adapter.exists(e))return t;let i=await this.app.vault.adapter.list(e);for(let s of i.files){if(!s.endsWith(".md"))continue;let n="";try{n=await this.app.vault.adapter.read(s)}catch{continue}let o=/^---\r?\n([\s\S]*?)\r?\n---/.exec(n);if(!o)continue;let a=o[1];if(!/^id:\s*\S/m.test(a)||!/^parent:\s*/m.test(a))continue;let l=s.split("/").pop()||s,c=l.replace(/\.md$/,"").replace(/-[a-z0-9]{4,12}$/,"").replace(/-/g," ").trim()||l,d=0;try{d=(await this.app.vault.adapter.stat(s))?.mtime??0}catch{}t.push({path:s,name:l,title:c,mtime:d})}}catch{}return t}async restoreRawTrashNote(e){let t=(this.settings.folder||"Stashpad").trim().replace(/^\/+|\/+$/g,"")||"Stashpad",i=e.split("/").pop()||e;try{if(!await this.app.vault.adapter.exists(t))try{await this.app.vault.createFolder(t)}catch{}let s=`${t}/${i}`;return await this.app.vault.adapter.exists(s)&&(s=`${t}/${i.replace(/\.md$/,"")}-restored-${Date.now()}.md`),await this.app.vault.adapter.rename(e,s),this.notifications.show({message:`Restored "${i}" to ${t}/. If its parent lives in another folder, move it from there.`,kind:"success",category:"system",folder:t}),!0}catch(s){return new M.Notice(`Couldn't restore: ${s.message}`),!1}}archivedPlainNotesIn(e){let t=e.replace(/\/+$/,""),i=t+"/",s=[];for(let n of this.app.vault.getMarkdownFiles()){let o=n.parent?.path?.replace(/\/+$/,"")??"";if(!(o===t||o.startsWith(i)))continue;let a=this.app.metadataCache.getFileCache(n)?.frontmatter?.id;typeof a=="string"&&a&&s.push(n)}return s}async unarchiveNote(e){let i=(e.parent?.path??"").replace(/\/+$/,"").split("/"),s=i.indexOf("archive"),n=s>0?i.slice(0,s).join("/"):(this.settings.folder||"Stashpad").trim().replace(/^\/+|\/+$/g,"")||"Stashpad";if((e.parent?.path??"")===n)return new M.Notice("Already in the default folder."),!1;try{if(!await this.app.vault.adapter.exists(n))try{await this.app.vault.createFolder(n)}catch{}let o=`${n}/${e.name}`;return await this.app.vault.adapter.exists(o)&&(o=`${n}/${e.basename}-restored-${Date.now()}.md`),await this.app.fileManager.renameFile(e,o),this.notifications.show({message:`Un-archived "${e.basename}" to ${n}/.`,kind:"success",category:"system",folder:n}),!0}catch(o){return new M.Notice(`Couldn't un-archive: ${o.message}`),!1}}trashSubfolderDirs(){return this.discoverStashpadFolders().map(e=>Fn(e))}async listDeletedTrash(){let e=await As(this.app,this.trashSubfolderDirs()),t=[];for(let i of e)t.push({blob:i,meta:await at(this.app,i)});return t}async restoreAllTrash(){if(!await this.ensureEncryptionUnlocked())return 0;let e=await this.listDeletedTrash();if(e.length===0)return new M.Notice("Nothing to restore."),0;let t=e.length>3?new M.Notice("",0):null,i=0;for(let s=0;s<e.length;s++){t?.setMessage(`\u{1F513} Restoring ${s+1}/${e.length}\u2026`);try{await this.restoreDeletedAt(e[s].blob,{silent:!0})&&i++}catch(n){console.warn("[Stashpad] restore-all skipped",e[s].blob,n)}}return t?.hide(),i>0&&this.notifications.show({message:`Restored ${i} note${i===1?"":"s"} from encrypted trash.`,kind:"success",category:"system",folder:""}),i}async encryptExistingTrash(){if(!await this.ensureEncryptionUnlocked())return!1;let e=this.encryption.getSessionKey();if(!e)return!1;try{let t=this.settings.encryptTrashFilenames??!1,i=await dl(this.app,e,new Date().toISOString(),t);return i?(i.unpurged.length>0?new M.Notice(`\u26A0\uFE0F Encrypted the trash, but ${i.unpurged.length} file${i.unpurged.length===1?" is":"s are"} still in plaintext (couldn't be removed): ${i.unpurged.join(` -`)}`,0):this.notifications.show({message:`Encrypted ${i.fileCount} trash file${i.fileCount===1?"":"s"} into the encrypted trash. Restore from the trash tab puts them back in ${Te}/.`,kind:"success",category:"system",folder:""}),!0):(new T.Notice(`Obsidian's vault trash (${Te}/) is empty \u2014 nothing to encrypt. (Files in the system/OS trash can't be reached.)`,8e3),!1)}catch(t){return console.warn("[Stashpad] trash backfill failed",t),new T.Notice(`Couldn't encrypt the existing trash: ${t.message}`,0),!1}}openEncryptedTrash(e){if(!this.encryption.isConfigured()){new T.Notice("Set up encryption first (Settings \u2192 Stashpad \u2192 Encryption).");return}al(this)}async openRestoreTrashPicker(){if(!this.encryption.isConfigured()){new T.Notice("Set up encryption first (Settings \u2192 Stashpad \u2192 Encryption).");return}let e=await this.listDeletedTrash();if(e.length===0){new T.Notice("Encrypted trash is empty.");return}let t=e.map(({blob:i,meta:s})=>({blob:i,label:s?.title||i.split("/").pop()?.replace(/\.stashenc$/,"")||"Locked note",folder:s?.originalFolder||"(unknown)"}));new un(this.app,t,i=>{this.restoreDeletedAt(i)}).open()}isArchiveFolder(e){let t=e.replace(/\/+$/,"");return(this.settings.archiveFolders??[]).includes(t)?!0:!!(this.settings.folderEncPrefs??{})[t]?.archive}async ensureOkfTemplate(){let e=await ll(this.app,this.settings.okfTemplatePath||void 0);return this.settings.okfTemplatePath!==e&&(this.settings.okfTemplatePath=e,await this.saveSettings()),e}okfTemplatePathOrDefault(){return this.settings.okfTemplatePath||Zo}okfActiveFolders(){return this.settings.okfEnabled?cl(this.settings.noteTemplates,this.okfTemplatePathOrDefault()).filter(e=>!this.isArchiveFolder(e)):[]}async rebuildOkfSuppressed(e){this.okfRebuildingFolders.add(e);try{return await _o(this.app,e)}finally{window.setTimeout(()=>{this.okfRebuildingFolders.delete(e);for(let t of this.app.workspace.getLeavesOfType(z)){let i=t.view;i?.noteFolder===e&&i.forceReconcileRender?.()}},600)}}onOkfFileEvent(e){if(!this.settings.okfEnabled||!e.toLowerCase().endsWith(".md"))return;let t=e.lastIndexOf("/"),i=(t>=0?e.slice(0,t):"").replace(/\/+$/,"");(t>=0?e.slice(t+1):e)!=="index.md"&&(/(^|\/)(_imports|_exports|_attachments|_deleted|\.stashpad)(\/|$)/.test(e)||this.okfActiveFolders().includes(i)&&this.scheduleOkfRebuild(i))}scheduleOkfRebuild(e){let t=this.okfRebuildTimers.get(e);t!=null&&window.clearTimeout(t),this.okfRebuildTimers.set(e,window.setTimeout(()=>{this.okfRebuildTimers.delete(e),this.okfActiveFolders().includes(e)&&this.rebuildOkfSuppressed(e).catch(i=>console.warn("[Stashpad] OKF auto-rebuild failed",e,i))},2500))}async rebuildAllOkf(){let e=this.okfActiveFolders(),t=0,i=0;for(let s of e){let n=await this.rebuildOkfSuppressed(s);t+=n.checked,i+=n.written}return{folders:e.length,checked:t,written:i}}async exportOkf(e,t,i,s){let n=e.replace(/\/+$/,""),o=[],a=[],l=[],c=new Set;for(let I of t){let m=await bt(this.app,n,I);if(m){o.push({id:m.rootNote.id,file:m.rootNote.file}),l.push(m.rootNote.file),c.add(m.rootNote.id);for(let C of m.descendants)a.push({id:C.id,file:C.file}),l.push(C.file),c.add(C.id)}}if(!l.length)return[];let d=(i||"okf-export").replace(/[\\/:*?"<>|]+/g," ").replace(/\s+/g," ").trim().slice(0,80)||"okf-export",h=new Date().toISOString().replace(/\D/g,"").slice(0,14),g=(this.settings.exportFolder||"_exports").trim().replace(/^\/+|\/+$/g,""),u=`${n}/${g}`;for(let I of[n,u])try{await this.app.vault.adapter.exists(I)||await this.app.vault.adapter.mkdir(I)}catch{}let p=[],f=async(I,m)=>{let C=`${u}/${I}`;await this.app.vault.createBinary(C,m.buffer.slice(m.byteOffset,m.byteOffset+m.byteLength)),p.push(C)};if(s.zip||s.targz){let I=await dl(this.app,l,n,c);s.zip&&await f(`${d}-${h}.okf.zip`,await Al(I)),s.targz&&await f(`${d}-${h}.okf.tar.gz`,await hl(I))}if(s.stash){let I=await kt(this.app,{rootNotes:o,allDescendants:a,sourceFolder:n});await f(`${d}-${h}.${nt}`,I)}return p}async idsInFolder(e){let t=e.replace(/\/+$/,""),i=new Set;for(let s of this.app.vault.getMarkdownFiles())if((s.parent?.path?.replace(/\/+$/,"")??"")===t)try{let n=he(await this.app.vault.read(s)).fm.id;typeof n=="string"&&i.add(n)}catch{}return i}async crossFolderPaste(e,t,i,s,n){let o=i.replace(/\/+$/,"");if(this.isArchiveFolder(o))return new T.Notice(`"${o.split("/").pop()}" auto-encrypts notes moved in, so cross-folder paste is disabled there. Use the "Move to archive" command \u2014 it checks the encryption key first.`),null;let a=[],l=[],c=[],d=[];for(let Q of t){let S=await bt(this.app,e,Q);if(S){c.push(S.rootNote.id),a.push({id:S.rootNote.id,file:S.rootNote.file}),d.push(S.rootNote.file);for(let x of S.descendants)l.push({id:x.id,file:x.file}),d.push(x.file)}}if(!a.length)return null;let h=a.length+l.length,g=[],u=[];n==="cut"&&(g=await this.exclusiveAttachmentsOf(d),u=await this.snapshotPaths([...d.map(Q=>Q.path),...g.map(Q=>Q.path)]));let p=await kt(this.app,{rootNotes:a,allDescendants:l,sourceFolder:e}),f=await this.idsInFolder(o),I=new Set(this.filesUnder(o)),m=await ot(this.app,p,o,f,{dedupeExisting:!1,forceNewIds:n==="copy",reparentRootsTo:s}),C=this.filesUnder(o).filter(Q=>!I.has(Q)),B=await this.snapshotPaths(C),w=c.map(Q=>m.idRemap[Q]).filter(Q=>!!Q);if(n==="cut"){for(let Q of d)try{await this.app.fileManager.trashFile(Q)}catch(S){console.warn("[Stashpad] cross-folder move: couldn't trash source note",Q.path,S)}for(let Q of g)try{await this.app.fileManager.trashFile(Q)}catch(S){console.warn("[Stashpad] cross-folder move: couldn't trash source attachment",Q.path,S)}}let y=async()=>{for(let Q of[...C].reverse()){let S=this.app.vault.getAbstractFileByPath(Q);if(S)try{await this.app.fileManager.trashFile(S)}catch{}}};return{rootIds:w,noteCount:h,undo:n==="cut"?async()=>{await y(),await this.restoreSnapshot(u)}:async()=>{await y()},redo:n==="cut"?async()=>{await this.restoreSnapshot(B);for(let Q of u){let S=this.app.vault.getAbstractFileByPath(Q.path);if(S)try{await this.app.fileManager.trashFile(S)}catch{}}}:async()=>{await this.restoreSnapshot(B)}}}async trashSubtrees(e,t){let i=[];for(let o of t){let a=await bt(this.app,e,o);a&&i.push(a.rootNote.file,...a.descendants.map(l=>l.file))}if(!i.length)return[];let s=await this.exclusiveAttachmentsOf(i),n=[];for(let o of[...i,...s])try{await this.app.fileManager.trashFile(o),n.push(o)}catch(a){console.warn("[Stashpad] trashSubtrees: couldn't trash",o.path,a)}return n}async exclusiveAttachmentsOf(e){let t=new Set(e.map(n=>n.path)),i=new Map;for(let n of e)for(let o of await an(this.app,n))i.set(o.path,o);let s=this.app.metadataCache.resolvedLinks??{};for(let n of Object.keys(s))if(!t.has(n))for(let o of Object.keys(s[n]??{}))i.delete(o);return[...i.values()]}async subtreeFilePaths(e,t){let i=[];for(let n of t){let o=await bt(this.app,e,n);o&&i.push(o.rootNote.file,...o.descendants.map(a=>a.file))}if(!i.length)return[];let s=await this.exclusiveAttachmentsOf(i);return[...i.map(n=>n.path),...s.map(n=>n.path)]}async orderedSubtreeNodes(e,t){let i=[],s=new Set,n=o=>{let a=this.app.metadataCache.getFileCache(o)?.frontmatter?.position;return typeof a=="number"?a:Number.MAX_SAFE_INTEGER};for(let o of t){let a=await bt(this.app,e,o);if(!a)continue;let l=new Map;for(let d of a.descendants){if(!d.parent)continue;let h=l.get(d.parent)??[];h.push({id:d.id,file:d.file,created:d.created}),l.set(d.parent,h)}for(let d of l.values())d.sort((h,g)=>n(h.file)-n(g.file)||h.created.localeCompare(g.created));let c=(d,h)=>{if(!s.has(d.id)){s.add(d.id),i.push({file:d.file,created:d.created,depth:h});for(let g of l.get(d.id)??[])c(g,h+1)}};c({id:a.rootNote.id,file:a.rootNote.file,created:a.rootNote.created},0)}return i}filesUnder(e){let t=e.replace(/\/+$/,"")+"/";return this.app.vault.getFiles().filter(i=>i.path.startsWith(t)).map(i=>i.path)}async snapshotPaths(e){let t=[];for(let i of e){let s=this.app.vault.getAbstractFileByPath(i);if(!s)continue;let n=!i.toLowerCase().endsWith(".md");try{n?t.push({path:i,binary:n,data:await this.app.vault.readBinary(s)}):t.push({path:i,binary:n,text:await this.app.vault.read(s)})}catch(o){console.warn("[Stashpad] snapshotPaths: couldn't read",i,o)}}return t}async restoreSnapshot(e){for(let t of e){let i=t.path.split("/").slice(0,-1).join("/");await this.ensureVaultFolder(i);let s=this.app.vault.getAbstractFileByPath(t.path);try{t.binary?s?await this.app.vault.adapter.writeBinary(t.path,t.data):await this.app.vault.createBinary(t.path,t.data):s?await this.app.vault.modify(s,t.text??""):await this.app.vault.create(t.path,t.text??"")}catch(n){console.warn("[Stashpad] restoreSnapshot: couldn't write",t.path,n)}}}async ensureVaultFolder(e){if(!e)return;let t="";for(let i of e.split("/"))if(t=t?`${t}/${i}`:i,!await this.app.vault.adapter.exists(t))try{await this.app.vault.createFolder(t)}catch{}}refreshOpenViewsForFolder(e){let t=e.replace(/\/+$/,"");for(let i of this.app.workspace.getLeavesOfType(z)){let s=i.view;if((s?.noteFolder?.replace(/\/+$/,"")??"")===t)try{s.tree?.rebuild?.(e),s.render?.()}catch(n){console.warn("[Stashpad] refresh view failed",n)}}}maybeArchiveOnMoveIn(e,t){if(e.extension!=="md")return;let i=e.parent?.path?.replace(/\/+$/,"")??"",s=t.lastIndexOf("/"),n=(s>=0?t.slice(0,s):"").replace(/\/+$/,"");if(i===n||!this.isArchiveFolder(i)||!this.encryption.isConfigured())return;let o=this.archivePending.get(i);o||(o={paths:new Set,timer:0},this.archivePending.set(i,o)),o.paths.add(e.path),window.clearTimeout(o.timer),o.timer=window.setTimeout(()=>{this.archivePending.delete(i),this.archiveSweep(i,[...o.paths])},1800)}async archiveSweep(e,t){if(!this.isArchiveFolder(e))return;let i=e.replace(/\/+$/,"");if(!((this.settings.folderEncPrefs??{})[i]?.archiveEncryptContent??!0))return;let s=[];for(let c of t){let d=this.app.vault.getAbstractFileByPath(c);if(!(d instanceof T.TFile)||(d.parent?.path?.replace(/\/+$/,"")??"")!==i)continue;let h;try{h=he(await this.app.vault.read(d)).fm}catch{continue}let g=typeof h.id=="string"?h.id:null;!g||g===k||s.push({id:g,parent:typeof h.parent=="string"?h.parent:null})}if(s.length===0)return;let n=new Set(s.map(c=>c.id)),o=new Set((this.settings.lockedSubtrees??[]).map(c=>c.rootId).filter(c=>!!c)),a=s.filter(c=>!o.has(c.id)&&!(c.parent&&n.has(c.parent)));if(a.length===0)return;if(!await this.ensureEncryptionUnlocked()){new T.Notice(`\u26A0\uFE0F Archive folder "${i.split("/").pop()}": ${a.length} arriving note${a.length===1?"":"s"} NOT encrypted (vault is locked). Unlock encryption and lock them manually.`,0);return}let l=0;for(let c of a)await this.lockNoteSubtree(i,c.id,null,{silent:!0})&&l++;l>0&&this.notifications.show({message:`Archived (encrypted) ${l} note${l===1?"":"s"} moved into \u201C${i.split("/").pop()}\u201D.`,kind:"success",category:"system",folder:i})}async activateViewForFolder(e){let t=(e||"").replace(/^\/+|\/+$/g,"");if(!t)return null;let i=this.app.workspace.getLeaf("tab");return await i.setViewState({type:z,active:!0,state:{folderOverride:t}}),this.app.workspace.revealLeaf(i),i}navigateLeafTo(e,t,i){let s=e?.view;if(s?.navigateTo&&(!s.tree||s.tree.get(i))){s.navigateTo(i);return}this.navigateWhenReady(t,i)}async openFolderInStashpad(e){let t=(e||"").replace(/^\/+|\/+$/g,"");if(!t)return;let i=await this.findStashpadLeafForFolder(t);if(i){this.app.workspace.revealLeaf(i),this.app.workspace.setActiveLeaf(i,{focus:!0});return}await this.activateViewForFolder(t)}async findStashpadLeafForFolder(e){let t=e.replace(/\/+$/,""),i=this.app.workspace.getLeavesOfType(z),s=i.find(o=>!o.isDeferred&&(o.view?.noteFolder??"").replace(/\/+$/,"")===t);if(s)return s;let n=i.find(o=>o.isDeferred&&(o.getViewState()?.state?.folderOverride??"").replace(/\/+$/,"")===t);if(n){try{await n.loadIfDeferred?.()}catch{}return n}return null}isStashpadNoteFile(e){let t=e.parent?.path?.replace(/\/+$/,"")??"";if(!this.discoverStashpadFolders().includes(t))return!1;let i=this.app.metadataCache.getFileCache(e)?.frontmatter?.id;return typeof i=="string"&&i.length>0}async revealNoteInStashpad(e){let t=e.parent?.path?.replace(/\/+$/,"")??"",i=this.app.metadataCache.getFileCache(e)?.frontmatter?.id;if(!t||typeof i!="string"||!i){new T.Notice("That note isn't a Stashpad note.");return}await this.revealNoteByRef(t,i)}async revealNoteByRef(e,t){let i=e.replace(/\/+$/,""),s=await this.findStashpadLeafForFolder(i);if(s){this.app.workspace.revealLeaf(s),this.app.workspace.setActiveLeaf(s,{focus:!0}),this.navigateLeafTo(s,i,t);return}let n=await this.activateViewForFolder(i);this.navigateLeafTo(n,i,t)}async closeDuplicateStashpadTabs(){let e=this.app.workspace.getLeavesOfType(z),t=this.app.workspace.activeLeaf;for(let p of e)try{await p.loadIfDeferred?.()}catch{}let i=p=>p.getViewState()?.state??{},s=p=>{let f=i(p);return(p.isDeferred?f.folderOverride??"":p.view?.noteFolder??f.folderOverride??"").replace(/\/+$/,"")},n=p=>{let f=i(p);return(p.isDeferred?f.focusId:p.view?.focusId??f.focusId)||k},o=p=>{let f=p.view;return!!f&&f.getViewType?.()===z&&typeof f.navigateTo=="function"&&typeof f.noteFolder=="string"},a=new Map;for(let p of this.app.vault.getMarkdownFiles()){let f=(p.parent?.path??"").replace(/\/+$/,""),I=this.app.metadataCache.getFileCache(p)?.frontmatter?.id;typeof I=="string"&&I&&(a.get(f)??a.set(f,new Set).get(f)).add(I)}let l=0,c=[];for(let p of e){let f=s(p),I=n(p);if(f&&I&&I!==k){let m=!p.isDeferred&&!!p.view?.tree?.get?.(I),C=a.get(f)?.has(I)??!1;if(!m&&!C){p.detach(),l++;continue}}c.push(p)}let d=new Map;for(let p of c){let f=s(p);if(!f)continue;let I=f+" "+n(p);(d.get(I)??d.set(I,[]).get(I)).push(p)}let h=0;for(let p of d.values()){if(p.length<=1)continue;p.sort((I,m)=>{let C=B=>B===t?2:B.isDeferred?0:1;return C(m)-C(I)});let f=null;for(let I of p){try{await I.loadIfDeferred?.()}catch{}if(o(I)){f=I;break}}f||(f=p[0]);for(let I of p)I!==f&&(I.detach(),h++)}let g=this.app.workspace.getLeavesOfType(z).length,u=document.createDocumentFragment();return u.createEl("div",{text:h+l>0?"Stashpad tabs cleaned up:":"Stashpad tabs - nothing to clean up:"}),u.createEl("div",{text:`\u2022 ${h} duplicate tab${h===1?"":"s"} closed`}),u.createEl("div",{text:`\u2022 ${l} orphaned tab${l===1?"":"s"} pruned (note no longer exists)`}),u.createEl("div",{text:`\u2022 ${g} Stashpad tab${g===1?"":"s"} remaining`}),new T.Notice(u,7e3),h+l}navigateWhenReady(e,t,i=15){let s=e.replace(/\/+$/,""),n=this.app.workspace.getLeavesOfType(z).find(o=>(o.view?.noteFolder??"").replace(/\/+$/,"")===s)?.view??this.lastActiveStashpadLeaf?.view;if(n&&typeof n.navigateTo=="function"&&(!n.tree||typeof n.tree.get!="function"||!!n.tree.get(t))){n.navigateTo(t);return}i>0?window.setTimeout(()=>this.navigateWhenReady(e,t,i-1),90):n&&typeof n.navigateTo=="function"&&n.navigateTo(t)}collectAuthoredFolders(){let e=(this.settings.authorId??"").trim();if(!e)return[];let t=`-${e}`,i=this.discoverStashpadFolders(),s=new Map;for(let n of this.app.vault.getMarkdownFiles()){let o=this.app.metadataCache.getFileCache(n)?.frontmatter;if(!o)continue;let a=typeof o.author=="string"?o.author:"",l=Array.isArray(o.contributors)?o.contributors.filter(p=>typeof p=="string"):[],c=a.includes(t),d=l.some(p=>p.includes(t));if(!c&&!d)continue;let h=n.parent?.path??"",g=i.find(p=>h===p||h.startsWith(p+"/"));if(!g)continue;s.has(g)||s.set(g,{authored:0,contributed:0});let u=s.get(g);c&&u.authored++,d&&u.contributed++}return[...s.entries()].map(([n,o])=>({folder:n,...o})).sort((n,o)=>o.authored+o.contributed-(n.authored+n.contributed))}parseAuthorRef(e){return rt(e)}authorRefFor(e,t,i){let s=i.replace(/[^\w\- ]+/g,"").trim().replace(/\s+/g,"-")||"author",n=`${e.replace(/\/+$/,"")}/_authors/${s}-${t}.md`,o=i.replace(/[\[\]|]/g,"").trim()||s;return`[[${n}|${o}]]`}async ensureAuthorStubFor(e,t,i){if(!t||!i)return!1;this.authorRegistry.record({id:t,name:i});let s=`${e.replace(/\/+$/,"")}/_authors`;if(this.app.vault.getMarkdownFiles().some(c=>c.path.startsWith(s+"/")&&this.parseAuthorFilePath(c.path)?.id===t))return!1;let o=this.authorRegistry.get(t),a=this.authorNameToSafe(i),l=`${s}/${a}-${t}.md`;try{return await this.ensureFolderPath(s),await this.app.vault.adapter.exists(l)?!1:(await this.app.vault.create(l,this.buildAuthorStub({id:t,name:i,role:o?.role,department:o?.department},new Date().toISOString())),!0)}catch(c){return console.warn("[Stashpad] ensureAuthorStubFor failed",l,c),!1}}async seedKnownAuthorsInFolder(e){let t=0;for(let i of this.collectKnownAuthors())await this.ensureAuthorStubFor(e,i.id,i.name)&&t++;return t}async checkDueReminders(){let e=Date.now(),t=new Set(this.settings.notifiedDueKeys??[]),i=(this.settings.authorId??"").trim(),s=[];for(let o of this.app.vault.getMarkdownFiles()){if(o.path.includes("/_authors/"))continue;let a=this.app.metadataCache.getFileCache(o)?.frontmatter;if(!a||a.due==null)continue;let l=typeof a.id=="string"?a.id:"";if(!l)continue;let c=String(a.due),d=typeof a.due=="number"?a.due:Date.parse(c);if(!Number.isFinite(d)||d>e)continue;let h=Mt(a);if(h.length>0&&!(i&&h.some(u=>u.id===i)))continue;let g=`${l}@${c}`;t.has(g)||s.push({id:l,folder:(o.parent?.path??"").replace(/\/+$/,""),file:o,dueMs:d,key:g})}if(s.length===0)return;this.settings.notifiedDueKeys=[...this.settings.notifiedDueKeys??[],...s.map(o=>o.key)].slice(-2e3),await this.saveSettings();let n=async o=>{try{let l=he(await this.app.vault.cachedRead(o)).body.split(/\r?\n/).map(c=>c.trim()).find(c=>c.length>0);if(l)return l.replace(/^[#>\-*\s]+/,"").slice(0,60)}catch{}return o.basename.replace(/-[a-z0-9]{4,12}$/,"").replace(/-/g," ")};if(s.length<=3)for(let o of s){let a=await n(o.file);this.notifications.show({message:`\u23F0 Task due: \u201C${a}\u201D (${ct(o.dueMs,this.settings)})`,kind:"warning",category:"reminder",duration:0,folder:o.folder,affectedIds:[o.id],actions:[{label:"Open",onClick:()=>void this.revealNoteByRef(o.folder,o.id)}]})}else this.notifications.show({message:`\u23F0 ${s.length} tasks are due \u2014 open the Tasks panel to review.`,kind:"warning",category:"reminder",duration:0,folder:""})}async syncAuthorsAcrossFolders(){await this.rebuildAuthorRegistry();let e=this.collectKnownAuthors(),t=this.discoverStashpadFolders();if(!e.length||!t.length){new T.Notice("No authors or Stashpad folders to sync.");return}let i=t.length*e.length>8?new T.Notice("",0):null,s=0;for(let n of t){i?.setMessage(`Syncing authors \u2192 ${n.split("/").pop()}\u2026`);for(let o of e)await this.ensureAuthorStubFor(n,o.id,o.name)&&s++}i?.hide(),this.notifications.show({message:`Synced authors across ${t.length} folder${t.length===1?"":"s"} \u2014 ${s} new stub${s===1?"":"s"} (${e.length} author${e.length===1?"":"s"} known).`,kind:"success",category:"system",folder:""})}collectKnownAuthors(){let e=new Map,t=(this.settings.authorId??"").trim(),i=(this.settings.authorName??"").trim();t&&i&&e.set(t,i);for(let s of this.authorRegistry.all())e.has(s.id)||e.set(s.id,s.name);for(let s of this.app.vault.getMarkdownFiles()){if(!s.path.includes("/_authors/"))continue;let n=this.parseAuthorFilePath(s.path);if(!n)continue;let o=this.app.metadataCache.getFileCache(s)?.frontmatter,l=((Array.isArray(o?.aliases)?o.aliases.find(c=>typeof c=="string")??"":typeof o?.aliases=="string"?o.aliases:"")||(typeof o?.name=="string"?o.name:"")||n.name).trim();e.has(n.id)||e.set(n.id,l),this.authorRegistry.record({id:n.id,name:l})}return[...e.entries()].map(([s,n])=>({id:s,name:n}))}async rebuildAuthorRegistry(){let e=this.discoverStashpadFolders(),t=new Map,i=0;for(let n of this.app.vault.getMarkdownFiles()){let o=this.app.metadataCache.getFileCache(n)?.frontmatter;if(!o)continue;let a=[];if(typeof o.author=="string"&&a.push(o.author),Array.isArray(o.contributors))for(let l of o.contributors)typeof l=="string"&&a.push(l);for(let l of a){let c=this.parseAuthorRef(l);if(c)if(!t.has(c.id))t.set(c.id,{id:c.id,name:c.name,fromStub:!1}),i++;else{let d=t.get(c.id);!d.name&&c.name&&(d.name=c.name)}}}let s=0;for(let n of e){let o=`${n}/_authors`;for(let a of this.app.vault.getMarkdownFiles()){if(!a.path.startsWith(o+"/"))continue;let l=this.parseAuthorFilePath(a.path);if(!l)continue;let c=this.app.metadataCache.getFileCache(a)?.frontmatter,h=((Array.isArray(c?.aliases)?c.aliases.find(f=>typeof f=="string")??"":typeof c?.aliases=="string"?c.aliases:"")||(typeof c?.name=="string"?c.name:"")||l.name).trim(),g=typeof c?.role=="string"?c.role:void 0,u=typeof c?.department=="string"?c.department:void 0,p=t.get(l.id);p||s++,t.set(l.id,{id:l.id,name:h||p?.name,role:g??p?.role,department:u??p?.department,fromStub:!0})}}return await this.authorRegistry.load(),this.authorRegistry.replaceAll([...t.values()]),await this.authorRegistry.save(),{total:t.size,fromStubs:s,fromNotes:i}}buildAuthorStub(e,t){let i=a=>a.replace(/[\r\n]+/g," ").trim(),s=a=>i(a).replace(/\\/g,"\\\\").replace(/"/g,'\\"'),n=i(e.name),o=["---",`authorId: ${e.id}`,"aliases:",` - "${s(e.name)}"`];return e.role&&o.push(`role: "${s(e.role)}"`),e.department&&o.push(`department: "${s(e.department)}"`),o.push(`created: ${t}`,"---",`# ${n}`),o.join(` -`)}async restoreMissingAuthorStubs(){await this.authorRegistry.load();let e=this.authorRegistry.all().filter(n=>n.id&&n.name),t=this.discoverStashpadFolders(),i=this.app.vault.getMarkdownFiles(),s=0;for(let n of t){let o=`${n}/_authors`,a=new Set;for(let l of i){if(!l.path.startsWith(o+"/"))continue;let c=this.parseAuthorFilePath(l.path)?.id;c&&a.add(c)}for(let l of e){if(a.has(l.id))continue;let c=this.authorNameToSafe(l.name),d=`${o}/${c}-${l.id}.md`;try{if(await this.ensureFolderPath(o),await this.app.vault.adapter.exists(d))continue;await this.app.vault.create(d,this.buildAuthorStub(l,l.firstSeen??new Date().toISOString())),s++}catch(h){console.warn("[Stashpad] restore author stub failed",d,h)}}}return{created:s,folders:t.length}}async convertAttachmentsToLinks(){let e=0,t=i=>/^\[\[.*\]\]$/.test(i.trim());for(let i of this.discoverStashpadFolders()){let s=i.replace(/\/+$/,"");for(let n of this.app.vault.getMarkdownFiles()){let o=n.parent?.path?.replace(/\/+$/,"")??"";if(o!==s&&!o.startsWith(s+"/")||Wt(n.path))continue;let a=this.app.metadataCache.getFileCache(n)?.frontmatter;if(!a||typeof a.id!="string"||!a.id)continue;let l=a.attachments,c=Array.isArray(l),d=typeof l=="string"&&l.trim().length>0;if(!(!c&&!d||!(d?!t(l):l.some(g=>typeof g=="string"&&g.trim()&&!t(g)))))try{await this.app.fileManager.processFrontMatter(n,g=>{Array.isArray(g.attachments)?g.attachments=g.attachments.map(u=>typeof u=="string"&&u.trim()?We(u):u):typeof g.attachments=="string"&&g.attachments.trim()&&(g.attachments=[We(g.attachments)])}),e++}catch(g){console.warn("[Stashpad] attachment-link conversion failed",n.path,g)}}}return e}syncObsidianExcludedArchives(){try{let e=this.app.vault;if(typeof e.getConfig!="function"||typeof e.setConfig!="function")return;let t=Array.isArray(e.getConfig("userIgnoreFilters"))?e.getConfig("userIgnoreFilters"):[],i=new Set(t),s=!1;for(let n of this.discoverStashpadFolders()){let o=`${n.replace(/\/+$/,"")}/_archive/`;i.has(o)||(i.add(o),s=!0)}s&&e.setConfig("userIgnoreFilters",[...i])}catch(e){console.warn("[Stashpad] couldn't update Obsidian excluded files",e)}}openImportPicker(){let e=this.discoverStashpadFolders();if(e.length===0){new T.Notice("No Stashpad folders to import into.");return}if(e.length===1){this.importService.pickFilesInto(e[0]);return}let t=this.importService.defaultDestination()??e[0];new ia(this.app,t,e,i=>this.importService.pickFilesInto(i)).open()}async runImportLooseFiles(e){let t=e.split("/").pop()||e,i=0,s=0,n=0;try{({files:i,folders:s,stashes:n}=await this.importService.importLooseInto(e))}catch(c){this.notifications.show({message:`Stashpad: import failed in \`${t}\` +`)}`,0):this.notifications.show({message:`Encrypted ${i.fileCount} trash file${i.fileCount===1?"":"s"} into the encrypted trash. Restore from the trash tab puts them back in ${Pe}/.`,kind:"success",category:"system",folder:""}),!0):(new M.Notice(`Obsidian's vault trash (${Pe}/) is empty \u2014 nothing to encrypt. (Files in the system/OS trash can't be reached.)`,8e3),!1)}catch(t){return console.warn("[Stashpad] trash backfill failed",t),new M.Notice(`Couldn't encrypt the existing trash: ${t.message}`,0),!1}}openEncryptedTrash(e){Fl(this)}async openRestoreTrashPicker(){if(!this.encryption.isConfigured()){new M.Notice("Set up encryption first (Settings \u2192 Stashpad \u2192 Encryption).");return}let e=await this.listDeletedTrash();if(e.length===0){new M.Notice("Encrypted trash is empty.");return}let t=e.map(({blob:i,meta:s})=>({blob:i,label:s?.title||i.split("/").pop()?.replace(/\.stashenc$/,"")||"Locked note",folder:s?.originalFolder||"(unknown)"}));new Sn(this.app,t,i=>{this.restoreDeletedAt(i)}).open()}isArchiveFolder(e){let t=e.replace(/\/+$/,"");return ys(t)||(this.settings.archiveFolders??[]).includes(t)?!0:!!(this.settings.folderEncPrefs??{})[t]?.archive}archiveEncryptFor(e){let t=e.replace(/\/+$/,"");return t.endsWith("/archive")&&(t=t.slice(0,-8)),(this.settings.folderEncPrefs??{})[t]?.archiveEncryptContent??!1}async migrateArchiveFoldersToSubfolders(){if(!this.settings.migratedArchiveToSubfolders){this.archiveMigrationInFlight=!0;try{await this.migrateArchiveFoldersToSubfoldersInner()}finally{window.setTimeout(()=>{this.archiveMigrationInFlight=!1},4e3)}}}async migrateArchiveFoldersToSubfoldersInner(){let e=new Set((this.settings.archiveFolders??[]).map(l=>l.replace(/\/+$/,"")));for(let[l,c]of Object.entries(this.settings.folderEncPrefs??{}))c?.archive&&e.add(l.replace(/\/+$/,""));let t=this.newLog(),i=[],s=0,n=[],o=`${this.pluginPrivatePath()}/archive-migration-journal.json`,a=async()=>{try{await this.app.vault.adapter.write(o,JSON.stringify({ts:new Date().toISOString(),moves:n},null,1))}catch{}};for(let l of[...e].sort()){let c=this.app.vault.getAbstractFileByPath(l);if(!(c instanceof M.TFolder))continue;let d=Ct(l);try{await this.app.vault.adapter.exists(d)||await this.app.vault.createFolder(d)}catch{}let h=0,g=0;for(let f of[...c.children]){if(!(f instanceof M.TFile)||f.extension!=="md")continue;let p;try{p=he(await this.app.vault.read(f)).fm.id}catch{g++,s++;continue}if(p===F)continue;let u=`${d}/${f.name}`,I=f.path;try{for(let m=1;await this.app.vault.adapter.exists(u);m++)u=`${d}/${f.basename}-${m}.md`;if(n.push({folder:l,from:I,to:u,ok:!1}),await this.app.fileManager.renameFile(f,u),!(this.app.vault.getAbstractFileByPath(u)instanceof M.TFile))throw new Error("rename did not land");n[n.length-1].ok=!0,h++}catch(m){g++,s++,console.warn("[Stashpad] archive migration move failed",I,m)}}if(await a(),g===0){let f={...this.settings.folderEncPrefs??{}},p={...f[l]??{}};delete p.archive,f[l]=p,this.settings.folderEncPrefs=f}i.push(`"${l}": ${h} note${h===1?"":"s"} \u2192 ${d}/${g?` (${g} failed \u2014 left in place)`:""}`),t.append({type:"archive_migration",id:"",payload:{folder:l,dest:d,moved:h,failed:g}})}s===0?(this.settings.archiveFolders=[],this.settings.defaultArchiveFolder=void 0,this.settings.migratedArchiveToSubfolders=!0,await this.saveSettings()):(await this.saveSettings(),new M.Notice(`Stashpad archive update: ${s} note${s===1?"":"s"} couldn't be moved (see the developer console). Nothing was deleted \u2014 the migration will retry on the next launch. A recovery journal of every move is at ${this.pluginPrivatePath()}/archive-migration-journal.json.`,0)),i.length&&new M.Notice(`Stashpad archive update: dedicated archive folders are now regular folders \u2014 archived notes moved into each folder's own "archive/" subfolder. + +${i.join(` +`)} + +Find everything in the aggregated Archived view. (This notice stays until dismissed.)`,0)}async migrateDeletedToTrashSubfolders(){if(this.settings.migratedTrashToSubfolders)return;let e=this.app.vault.adapter,t=this.newLog(),i=0,s=0,n=0,o=[],a=`${this.pluginPrivatePath()}/trash-migration-journal.json`;try{let l=await As(this.app);for(let c of l){let d=await at(this.app,c),h=d?.kind==="deleted"&&d.originalFolder?d.originalFolder.replace(/\/+$/,""):"";if(!h||!(this.app.vault.getAbstractFileByPath(h)instanceof M.TFolder)){s++;continue}let g=Fn(h);try{await e.exists(g)||await e.mkdir(g);let f=c.split("/").pop(),p=`${g}/${f}`;for(let B=1;await e.exists(p);B++)p=`${g}/${f.replace(/\.stashenc$/,"")} (${B}).stashenc`;o.push({from:c,to:p,ok:!1});let u=new Uint8Array(await e.readBinary(c));await e.writeBinary(p,u.slice().buffer);let I=new Uint8Array(await e.readBinary(p));if(I.length!==u.length)throw new Error("copy verify failed (length)");for(let B=0;B<u.length;B++)if(I[B]!==u[B])throw new Error("copy verify failed (content)");let m=c.replace(/\.stashenc$/,".stashmeta"),C=p.replace(/\.stashenc$/,".stashmeta");try{if(await e.exists(m)){let B=await e.read(m);if(await e.write(C,B),await e.read(C)!==B)throw new Error("sidecar verify failed");await e.remove(m)}}catch(B){throw new Error(`sidecar copy failed: ${B.message}`)}await e.remove(c),o[o.length-1].ok=!0,i++}catch(f){n++,console.warn("[Stashpad] trash migration move failed",c,f)}}try{await e.write(a,JSON.stringify({ts:new Date().toISOString(),moves:o},null,1))}catch{}}catch(l){console.error("[Stashpad] trash migration failed",l);return}t.append({type:"archive_migration",id:"",payload:{what:"trash-subfolders",moved:i,left:s,failed:n}}),n===0&&(this.settings.migratedTrashToSubfolders=!0,await this.saveSettings()),(i>0||n>0)&&new M.Notice(`Stashpad trash update: ${i} encrypted-trash item${i===1?"":"s"} moved into per-folder "trash/" subfolders${s?` (${s} stayed in _deleted/ \u2014 unknown or missing origin)`:""}${n?` \u2014 ${n} failed; will retry next launch`:""}. Everything stays visible in the Trash view.`,0)}async pruneZombieArchiveEntries(){let e=o=>this.app.vault.getAbstractFileByPath(o)instanceof M.TFolder,t=this.settings.archiveFolders??[],i=t.filter(o=>e(o.replace(/\/+$/,""))),s=i.length!==t.length,n=(this.settings.defaultArchiveFolder??"").replace(/\/+$/,"");n&&!e(n)&&(this.settings.defaultArchiveFolder=void 0,s=!0),s&&(this.settings.archiveFolders=i,await this.saveSettings(),console.info(`[Stashpad] pruned ${t.length-i.length} stale archive-folder entr${t.length-i.length===1?"y":"ies"}.`))}async ensureOkfTemplate(){let e=await Sl(this.app,this.settings.okfTemplatePath||void 0);return this.settings.okfTemplatePath!==e&&(this.settings.okfTemplatePath=e,await this.saveSettings()),e}okfTemplatePathOrDefault(){return this.settings.okfTemplatePath||dr}okfActiveFolders(){return this.settings.okfEnabled?bl(this.settings.noteTemplates,this.okfTemplatePathOrDefault()).filter(e=>!this.isArchiveFolder(e)):[]}async rebuildOkfSuppressed(e){this.okfRebuildingFolders.add(e);try{return await hr(this.app,e)}finally{window.setTimeout(()=>{this.okfRebuildingFolders.delete(e);for(let t of this.app.workspace.getLeavesOfType(z)){let i=t.view;i?.noteFolder===e&&i.forceReconcileRender?.()}},600)}}onOkfFileEvent(e){if(!this.settings.okfEnabled||!e.toLowerCase().endsWith(".md"))return;let t=e.lastIndexOf("/"),i=(t>=0?e.slice(0,t):"").replace(/\/+$/,"");(t>=0?e.slice(t+1):e)!=="index.md"&&(/(^|\/)(_imports|_exports|_attachments|_deleted|\.stashpad)(\/|$)/.test(e)||this.okfActiveFolders().includes(i)&&this.scheduleOkfRebuild(i))}scheduleOkfRebuild(e){let t=this.okfRebuildTimers.get(e);t!=null&&window.clearTimeout(t),this.okfRebuildTimers.set(e,window.setTimeout(()=>{this.okfRebuildTimers.delete(e),this.okfActiveFolders().includes(e)&&this.rebuildOkfSuppressed(e).catch(i=>console.warn("[Stashpad] OKF auto-rebuild failed",e,i))},2500))}async rebuildAllOkf(){let e=this.okfActiveFolders(),t=0,i=0;for(let s of e){let n=await this.rebuildOkfSuppressed(s);t+=n.checked,i+=n.written}return{folders:e.length,checked:t,written:i}}async exportOkf(e,t,i,s){let n=e.replace(/\/+$/,""),o=[],a=[],l=[],c=new Set;for(let I of t){let m=await Nt(this.app,n,I);if(m){o.push({id:m.rootNote.id,file:m.rootNote.file}),l.push(m.rootNote.file),c.add(m.rootNote.id);for(let C of m.descendants)a.push({id:C.id,file:C.file}),l.push(C.file),c.add(C.id)}}if(!l.length)return[];let d=(i||"okf-export").replace(/[\\/:*?"<>|]+/g," ").replace(/\s+/g," ").trim().slice(0,80)||"okf-export",h=new Date().toISOString().replace(/\D/g,"").slice(0,14),g=(this.settings.exportFolder||"_exports").trim().replace(/^\/+|\/+$/g,""),f=`${n}/${g}`;for(let I of[n,f])try{await this.app.vault.adapter.exists(I)||await this.app.vault.adapter.mkdir(I)}catch{}let p=[],u=async(I,m)=>{let C=`${f}/${I}`;await this.app.vault.createBinary(C,m.buffer.slice(m.byteOffset,m.byteOffset+m.byteLength)),p.push(C)};if(s.zip||s.targz){let I=await Dl(this.app,l,n,c);s.zip&&await u(`${d}-${h}.okf.zip`,await xl(I)),s.targz&&await u(`${d}-${h}.okf.tar.gz`,await Nl(I))}if(s.stash){let I=await bt(this.app,{rootNotes:o,allDescendants:a,sourceFolder:n});await u(`${d}-${h}.${ot}`,I)}return p}async idsInFolder(e){let t=e.replace(/\/+$/,""),i=new Set;for(let s of this.app.vault.getMarkdownFiles())if((s.parent?.path?.replace(/\/+$/,"")??"")===t)try{let n=he(await this.app.vault.read(s)).fm.id;typeof n=="string"&&i.add(n)}catch{}return i}async crossFolderPaste(e,t,i,s,n){let o=i.replace(/\/+$/,"");if(this.isArchiveFolder(o))return new M.Notice(`"${o.split("/").pop()}" is an archive folder, so cross-folder paste is disabled there. Use the "Move selection to archive" command instead \u2014 it runs the proper archive flow (and encrypts, if that folder encrypts its archive).`),null;let a=[],l=[],c=[],d=[];for(let Q of t){let S=await Nt(this.app,e,Q);if(S){c.push(S.rootNote.id),a.push({id:S.rootNote.id,file:S.rootNote.file}),d.push(S.rootNote.file);for(let x of S.descendants)l.push({id:x.id,file:x.file}),d.push(x.file)}}if(!a.length)return null;let h=a.length+l.length,g=[],f=[];n==="cut"&&(g=await this.exclusiveAttachmentsOf(d),f=await this.snapshotPaths([...d.map(Q=>Q.path),...g.map(Q=>Q.path)]));let p=await bt(this.app,{rootNotes:a,allDescendants:l,sourceFolder:e}),u=await this.idsInFolder(o),I=new Set(this.filesUnder(o)),m=await rt(this.app,p,o,u,{dedupeExisting:!1,forceNewIds:n==="copy",reparentRootsTo:s}),C=this.filesUnder(o).filter(Q=>!I.has(Q)),B=await this.snapshotPaths(C),w=c.map(Q=>m.idRemap[Q]).filter(Q=>!!Q);if(n==="cut"){for(let Q of d)try{await this.app.fileManager.trashFile(Q)}catch(S){console.warn("[Stashpad] cross-folder move: couldn't trash source note",Q.path,S)}for(let Q of g)try{await this.app.fileManager.trashFile(Q)}catch(S){console.warn("[Stashpad] cross-folder move: couldn't trash source attachment",Q.path,S)}}let y=async()=>{for(let Q of[...C].reverse()){let S=this.app.vault.getAbstractFileByPath(Q);if(S)try{await this.app.fileManager.trashFile(S)}catch{}}};return{rootIds:w,noteCount:h,undo:n==="cut"?async()=>{await y(),await this.restoreSnapshot(f)}:async()=>{await y()},redo:n==="cut"?async()=>{await this.restoreSnapshot(B);for(let Q of f){let S=this.app.vault.getAbstractFileByPath(Q.path);if(S)try{await this.app.fileManager.trashFile(S)}catch{}}}:async()=>{await this.restoreSnapshot(B)}}}async trashSubtrees(e,t){let i=[];for(let o of t){let a=await Nt(this.app,e,o);a&&i.push(a.rootNote.file,...a.descendants.map(l=>l.file))}if(!i.length)return[];let s=await this.exclusiveAttachmentsOf(i),n=[];for(let o of[...i,...s])try{await this.app.fileManager.trashFile(o),n.push(o)}catch(a){console.warn("[Stashpad] trashSubtrees: couldn't trash",o.path,a)}return n}async exclusiveAttachmentsOf(e){let t=new Set(e.map(n=>n.path)),i=new Map;for(let n of e)for(let o of await wi(this.app,n))i.set(o.path,o);let s=this.app.metadataCache.resolvedLinks??{};for(let n of Object.keys(s))if(!t.has(n))for(let o of Object.keys(s[n]??{}))i.delete(o);return[...i.values()]}async subtreeFilePaths(e,t){let i=[];for(let n of t){let o=await Nt(this.app,e,n);o&&i.push(o.rootNote.file,...o.descendants.map(a=>a.file))}if(!i.length)return[];let s=await this.exclusiveAttachmentsOf(i);return[...i.map(n=>n.path),...s.map(n=>n.path)]}async orderedSubtreeNodes(e,t){let i=[],s=new Set,n=o=>{let a=this.app.metadataCache.getFileCache(o)?.frontmatter?.position;return typeof a=="number"?a:Number.MAX_SAFE_INTEGER};for(let o of t){let a=await Nt(this.app,e,o);if(!a)continue;let l=new Map;for(let d of a.descendants){if(!d.parent)continue;let h=l.get(d.parent)??[];h.push({id:d.id,file:d.file,created:d.created}),l.set(d.parent,h)}for(let d of l.values())d.sort((h,g)=>n(h.file)-n(g.file)||h.created.localeCompare(g.created));let c=(d,h)=>{if(!s.has(d.id)){s.add(d.id),i.push({file:d.file,created:d.created,depth:h});for(let g of l.get(d.id)??[])c(g,h+1)}};c({id:a.rootNote.id,file:a.rootNote.file,created:a.rootNote.created},0)}return i}filesUnder(e){let t=e.replace(/\/+$/,"")+"/";return this.app.vault.getFiles().filter(i=>i.path.startsWith(t)).map(i=>i.path)}async snapshotPaths(e){let t=[];for(let i of e){let s=this.app.vault.getAbstractFileByPath(i);if(!s)continue;let n=!i.toLowerCase().endsWith(".md");try{n?t.push({path:i,binary:n,data:await this.app.vault.readBinary(s)}):t.push({path:i,binary:n,text:await this.app.vault.read(s)})}catch(o){console.warn("[Stashpad] snapshotPaths: couldn't read",i,o)}}return t}async restoreSnapshot(e){for(let t of e){let i=t.path.split("/").slice(0,-1).join("/");await this.ensureVaultFolder(i);let s=this.app.vault.getAbstractFileByPath(t.path);try{t.binary?s?await this.app.vault.adapter.writeBinary(t.path,t.data):await this.app.vault.createBinary(t.path,t.data):s?await this.app.vault.modify(s,t.text??""):await this.app.vault.create(t.path,t.text??"")}catch(n){console.warn("[Stashpad] restoreSnapshot: couldn't write",t.path,n)}}}async ensureVaultFolder(e){if(!e)return;let t="";for(let i of e.split("/"))if(t=t?`${t}/${i}`:i,!await this.app.vault.adapter.exists(t))try{await this.app.vault.createFolder(t)}catch{}}refreshOpenViewsForFolder(e){let t=e.replace(/\/+$/,"");for(let i of this.app.workspace.getLeavesOfType(z)){let s=i.view;if((s?.noteFolder?.replace(/\/+$/,"")??"")===t)try{s.tree?.rebuild?.(e),s.render?.()}catch(n){console.warn("[Stashpad] refresh view failed",n)}}}maybeArchiveOnMoveIn(e,t){if(e.extension!=="md")return;let i=e.parent?.path?.replace(/\/+$/,"")??"",s=t.lastIndexOf("/"),n=(s>=0?t.slice(0,s):"").replace(/\/+$/,"");if(i===n||!this.isArchiveFolder(i)||!this.encryption.isConfigured())return;let o=this.archivePending.get(i);o||(o={paths:new Set,timer:0},this.archivePending.set(i,o)),o.paths.add(e.path),window.clearTimeout(o.timer),o.timer=window.setTimeout(()=>{this.archivePending.delete(i),this.archiveSweep(i,[...o.paths])},1800)}async encryptExistingArchiveNotes(e){let t=Ct(e.replace(/\/+$/,"")),i=this.archivedPlainNotesIn(t).map(s=>s.path);return i.length===0?0:(await this.archiveSweep(t,i),i.length-this.archivedPlainNotesIn(t).length)}async archiveSweep(e,t){if(this.archiveMigrationInFlight||!this.isArchiveFolder(e))return;let i=e.replace(/\/+$/,"");if(!this.archiveEncryptFor(i))return;let s=[];for(let c of t){let d=this.app.vault.getAbstractFileByPath(c);if(!(d instanceof M.TFile)||(d.parent?.path?.replace(/\/+$/,"")??"")!==i)continue;let h;try{h=he(await this.app.vault.read(d)).fm}catch{continue}let g=typeof h.id=="string"?h.id:null;!g||g===F||s.push({id:g,parent:typeof h.parent=="string"?h.parent:null})}if(s.length===0)return;let n=new Set(s.map(c=>c.id)),o=new Set((this.settings.lockedSubtrees??[]).map(c=>c.rootId).filter(c=>!!c)),a=s.filter(c=>!o.has(c.id)&&!(c.parent&&n.has(c.parent)));if(a.length===0)return;if(!await this.ensureEncryptionUnlocked()){new M.Notice(`\u26A0\uFE0F Archive folder "${i.split("/").pop()}": ${a.length} arriving note${a.length===1?"":"s"} NOT encrypted (vault is locked). Unlock encryption and lock them manually.`,0);return}let l=0;for(let c of a)await this.lockNoteSubtree(i,c.id,null,{silent:!0})&&l++;l>0&&this.notifications.show({message:`Archived (encrypted) ${l} note${l===1?"":"s"} moved into \u201C${i.split("/").pop()}\u201D.`,kind:"success",category:"system",folder:i})}async activateViewForFolder(e){let t=(e||"").replace(/^\/+|\/+$/g,"");if(!t)return null;let i=this.app.workspace.getLeaf("tab");return await i.setViewState({type:z,active:!0,state:{folderOverride:t}}),this.app.workspace.revealLeaf(i),i}navigateLeafTo(e,t,i){let s=e?.view;if(s?.navigateTo&&(!s.tree||s.tree.get(i))){s.navigateTo(i);return}this.navigateWhenReady(t,i)}async openFolderInStashpad(e){let t=(e||"").replace(/^\/+|\/+$/g,"");if(!t)return;let i=await this.findStashpadLeafForFolder(t);if(i){this.app.workspace.revealLeaf(i),this.app.workspace.setActiveLeaf(i,{focus:!0});return}await this.activateViewForFolder(t)}async findStashpadLeafForFolder(e){let t=e.replace(/\/+$/,""),i=this.app.workspace.getLeavesOfType(z),s=i.find(o=>!o.isDeferred&&(o.view?.noteFolder??"").replace(/\/+$/,"")===t);if(s)return s;let n=i.find(o=>o.isDeferred&&(o.getViewState()?.state?.folderOverride??"").replace(/\/+$/,"")===t);if(n){try{await n.loadIfDeferred?.()}catch{}return n}return null}isStashpadNoteFile(e){let t=e.parent?.path?.replace(/\/+$/,"")??"";if(!this.discoverStashpadFolders().includes(t))return!1;let i=this.app.metadataCache.getFileCache(e)?.frontmatter?.id;return typeof i=="string"&&i.length>0}async revealNoteInStashpad(e){let t=e.parent?.path?.replace(/\/+$/,"")??"",i=this.app.metadataCache.getFileCache(e)?.frontmatter?.id;if(!t||typeof i!="string"||!i){new M.Notice("That note isn't a Stashpad note.");return}await this.revealNoteByRef(t,i)}async revealNoteByRef(e,t){let i=e.replace(/\/+$/,""),s=await this.findStashpadLeafForFolder(i);if(s){this.app.workspace.revealLeaf(s),this.app.workspace.setActiveLeaf(s,{focus:!0}),this.navigateLeafTo(s,i,t);return}let n=await this.activateViewForFolder(i);this.navigateLeafTo(n,i,t)}async closeDuplicateStashpadTabs(){let e=this.app.workspace.getLeavesOfType(z),t=this.app.workspace.activeLeaf;for(let p of e)try{await p.loadIfDeferred?.()}catch{}let i=p=>p.getViewState()?.state??{},s=p=>{let u=i(p);return(p.isDeferred?u.folderOverride??"":p.view?.noteFolder??u.folderOverride??"").replace(/\/+$/,"")},n=p=>{let u=i(p);return(p.isDeferred?u.focusId:p.view?.focusId??u.focusId)||F},o=p=>{let u=p.view;return!!u&&u.getViewType?.()===z&&typeof u.navigateTo=="function"&&typeof u.noteFolder=="string"},a=new Map;for(let p of this.app.vault.getMarkdownFiles()){let u=(p.parent?.path??"").replace(/\/+$/,""),I=this.app.metadataCache.getFileCache(p)?.frontmatter?.id;typeof I=="string"&&I&&(a.get(u)??a.set(u,new Set).get(u)).add(I)}let l=0,c=[];for(let p of e){let u=s(p),I=n(p);if(u&&I&&I!==F){let m=!p.isDeferred&&!!p.view?.tree?.get?.(I),C=a.get(u)?.has(I)??!1;if(!m&&!C){p.detach(),l++;continue}}c.push(p)}let d=new Map;for(let p of c){let u=s(p);if(!u)continue;let I=u+" "+n(p);(d.get(I)??d.set(I,[]).get(I)).push(p)}let h=0;for(let p of d.values()){if(p.length<=1)continue;p.sort((I,m)=>{let C=B=>B===t?2:B.isDeferred?0:1;return C(m)-C(I)});let u=null;for(let I of p){try{await I.loadIfDeferred?.()}catch{}if(o(I)){u=I;break}}u||(u=p[0]);for(let I of p)I!==u&&(I.detach(),h++)}let g=this.app.workspace.getLeavesOfType(z).length,f=document.createDocumentFragment();return f.createEl("div",{text:h+l>0?"Stashpad tabs cleaned up:":"Stashpad tabs - nothing to clean up:"}),f.createEl("div",{text:`\u2022 ${h} duplicate tab${h===1?"":"s"} closed`}),f.createEl("div",{text:`\u2022 ${l} orphaned tab${l===1?"":"s"} pruned (note no longer exists)`}),f.createEl("div",{text:`\u2022 ${g} Stashpad tab${g===1?"":"s"} remaining`}),new M.Notice(f,7e3),h+l}navigateWhenReady(e,t,i=15){let s=e.replace(/\/+$/,""),n=this.app.workspace.getLeavesOfType(z).find(o=>(o.view?.noteFolder??"").replace(/\/+$/,"")===s)?.view??this.lastActiveStashpadLeaf?.view;if(n&&typeof n.navigateTo=="function"&&(!n.tree||typeof n.tree.get!="function"||!!n.tree.get(t))){n.navigateTo(t);return}i>0?window.setTimeout(()=>this.navigateWhenReady(e,t,i-1),90):n&&typeof n.navigateTo=="function"&&n.navigateTo(t)}collectAuthoredFolders(){let e=(this.settings.authorId??"").trim();if(!e)return[];let t=`-${e}`,i=this.discoverStashpadFolders(),s=new Map;for(let n of this.app.vault.getMarkdownFiles()){let o=this.app.metadataCache.getFileCache(n)?.frontmatter;if(!o)continue;let a=typeof o.author=="string"?o.author:"",l=Array.isArray(o.contributors)?o.contributors.filter(p=>typeof p=="string"):[],c=a.includes(t),d=l.some(p=>p.includes(t));if(!c&&!d)continue;let h=n.parent?.path??"",g=i.find(p=>h===p||h.startsWith(p+"/"));if(!g)continue;s.has(g)||s.set(g,{authored:0,contributed:0});let f=s.get(g);c&&f.authored++,d&&f.contributed++}return[...s.entries()].map(([n,o])=>({folder:n,...o})).sort((n,o)=>o.authored+o.contributed-(n.authored+n.contributed))}parseAuthorRef(e){return ct(e)}authorRefFor(e,t,i){let s=i.replace(/[^\w\- ]+/g,"").trim().replace(/\s+/g,"-")||"author",n=`${e.replace(/\/+$/,"")}/_authors/${s}-${t}.md`,o=i.replace(/[\[\]|]/g,"").trim()||s;return`[[${n}|${o}]]`}async ensureAuthorStubFor(e,t,i){if(!t||!i)return!1;this.authorRegistry.record({id:t,name:i});let s=`${e.replace(/\/+$/,"")}/_authors`;if(this.app.vault.getMarkdownFiles().some(c=>c.path.startsWith(s+"/")&&this.parseAuthorFilePath(c.path)?.id===t))return!1;let o=this.authorRegistry.get(t),a=this.authorNameToSafe(i),l=`${s}/${a}-${t}.md`;try{return await this.ensureFolderPath(s),await this.app.vault.adapter.exists(l)?!1:(await this.app.vault.create(l,this.buildAuthorStub({id:t,name:i,role:o?.role,department:o?.department},new Date().toISOString())),!0)}catch(c){return console.warn("[Stashpad] ensureAuthorStubFor failed",l,c),!1}}async seedKnownAuthorsInFolder(e){let t=0;for(let i of this.collectKnownAuthors())await this.ensureAuthorStubFor(e,i.id,i.name)&&t++;return t}async autoResolveDueTask(e,t,i){try{await this.app.fileManager.processFrontMatter(e,s=>{let n=s.due!=null?Date.parse(String(s.due)):NaN;if(Number.isFinite(n)&&n>i)return;let o=sn(s.repeat);o?(s.due=new Date(nn(o,Number.isFinite(n)?n:null,i)).toISOString(),delete s.completed):s.completed=!0})}catch(s){console.warn("[Stashpad] auto-resolve failed",e.path,s)}}async checkDueReminders(){let e=Date.now(),t=new Set(this.settings.notifiedDueKeys??[]),i=(this.settings.authorId??"").trim(),s=[],n={...this.settings.persistReminderLog??{}},o=!1,a=new Set;for(let c of this.app.vault.getMarkdownFiles()){if(c.path.includes("/_authors/"))continue;let d=this.app.metadataCache.getFileCache(c)?.frontmatter;if(!d||d.due==null)continue;let h=typeof d.id=="string"?d.id:"";if(!h)continue;let g=String(d.due),f=typeof d.due=="number"?d.due:Date.parse(g);if(!Number.isFinite(f)||f>e)continue;if(d.completed!==!0){let m=Ho(d.autoDoneAfter);if(m!=null&&e>=f+m){await this.autoResolveDueTask(c,f,e);continue}}if(d.completed===!0)continue;let p=Ut(d);if(p.length>0&&!(i&&p.some(m=>m.id===i)))continue;let u=Ho(d.remindEvery);if(u!=null){a.add(h);let m=n[h]??0;e-m>=u&&(n[h]=e,o=!0,s.push({id:h,folder:(c.parent?.path??"").replace(/\/+$/,""),file:c,dueMs:f,key:`${h}@persist`}));continue}let I=`${h}@${g}`;t.has(I)||s.push({id:h,folder:(c.parent?.path??"").replace(/\/+$/,""),file:c,dueMs:f,key:I})}for(let c of Object.keys(n))a.has(c)||(delete n[c],o=!0);if(o&&(this.settings.persistReminderLog=n,await this.saveSettings()),s.length===0)return;this.settings.notifiedDueKeys=[...this.settings.notifiedDueKeys??[],...s.filter(c=>!c.key.endsWith("@persist")).map(c=>c.key)].slice(-2e3),await this.saveSettings();let l=async c=>{try{let h=he(await this.app.vault.cachedRead(c)).body.split(/\r?\n/).map(g=>g.trim()).find(g=>g.length>0);if(h)return h.replace(/^[#>\-*\s]+/,"").slice(0,60)}catch{}return c.basename.replace(/-[a-z0-9]{4,12}$/,"").replace(/-/g," ")};if(s.length<=3)for(let c of s){let d=await l(c.file);this.notifications.show({message:`\u23F0 Task due: \u201C${d}\u201D (${Xe(c.dueMs,this.settings)})`,kind:"warning",category:"reminder",duration:0,folder:c.folder,affectedIds:[c.id],actions:[{label:"Open",onClick:()=>void this.revealNoteByRef(c.folder,c.id)}]})}else this.notifications.show({message:`\u23F0 ${s.length} tasks are due \u2014 open the Tasks panel to review.`,kind:"warning",category:"reminder",duration:0,folder:""})}async syncAuthorsAcrossFolders(){await this.rebuildAuthorRegistry();let e=this.collectKnownAuthors(),t=this.discoverStashpadFolders();if(!e.length||!t.length){new M.Notice("No authors or Stashpad folders to sync.");return}let i=t.length*e.length>8?new M.Notice("",0):null,s=0;for(let n of t){i?.setMessage(`Syncing authors \u2192 ${n.split("/").pop()}\u2026`);for(let o of e)await this.ensureAuthorStubFor(n,o.id,o.name)&&s++}i?.hide(),this.notifications.show({message:`Synced authors across ${t.length} folder${t.length===1?"":"s"} \u2014 ${s} new stub${s===1?"":"s"} (${e.length} author${e.length===1?"":"s"} known).`,kind:"success",category:"system",folder:""})}collectKnownAuthors(){let e=new Map,t=(this.settings.authorId??"").trim(),i=(this.settings.authorName??"").trim();t&&i&&e.set(t,i);for(let s of this.authorRegistry.all())e.has(s.id)||e.set(s.id,s.name);for(let s of this.app.vault.getMarkdownFiles()){if(!s.path.includes("/_authors/"))continue;let n=this.parseAuthorFilePath(s.path);if(!n)continue;let o=this.app.metadataCache.getFileCache(s)?.frontmatter,l=((Array.isArray(o?.aliases)?o.aliases.find(c=>typeof c=="string")??"":typeof o?.aliases=="string"?o.aliases:"")||(typeof o?.name=="string"?o.name:"")||n.name).trim();e.has(n.id)||e.set(n.id,l),this.authorRegistry.record({id:n.id,name:l})}return[...e.entries()].map(([s,n])=>({id:s,name:n}))}async rebuildAuthorRegistry(){let e=this.discoverStashpadFolders(),t=new Map,i=0;for(let n of this.app.vault.getMarkdownFiles()){let o=this.app.metadataCache.getFileCache(n)?.frontmatter;if(!o)continue;let a=[];if(typeof o.author=="string"&&a.push(o.author),Array.isArray(o.contributors))for(let l of o.contributors)typeof l=="string"&&a.push(l);for(let l of a){let c=this.parseAuthorRef(l);if(c)if(!t.has(c.id))t.set(c.id,{id:c.id,name:c.name,fromStub:!1}),i++;else{let d=t.get(c.id);!d.name&&c.name&&(d.name=c.name)}}}let s=0;for(let n of e){let o=`${n}/_authors`;for(let a of this.app.vault.getMarkdownFiles()){if(!a.path.startsWith(o+"/"))continue;let l=this.parseAuthorFilePath(a.path);if(!l)continue;let c=this.app.metadataCache.getFileCache(a)?.frontmatter,h=((Array.isArray(c?.aliases)?c.aliases.find(u=>typeof u=="string")??"":typeof c?.aliases=="string"?c.aliases:"")||(typeof c?.name=="string"?c.name:"")||l.name).trim(),g=typeof c?.role=="string"?c.role:void 0,f=typeof c?.department=="string"?c.department:void 0,p=t.get(l.id);p||s++,t.set(l.id,{id:l.id,name:h||p?.name,role:g??p?.role,department:f??p?.department,fromStub:!0})}}return await this.authorRegistry.load(),this.authorRegistry.replaceAll([...t.values()]),await this.authorRegistry.save(),{total:t.size,fromStubs:s,fromNotes:i}}buildAuthorStub(e,t){let i=a=>a.replace(/[\r\n]+/g," ").trim(),s=a=>i(a).replace(/\\/g,"\\\\").replace(/"/g,'\\"'),n=i(e.name),o=["---",`authorId: ${e.id}`,"aliases:",` - "${s(e.name)}"`];return e.role&&o.push(`role: "${s(e.role)}"`),e.department&&o.push(`department: "${s(e.department)}"`),o.push(`created: ${t}`,"---",`# ${n}`),o.join(` +`)}async restoreMissingAuthorStubs(){await this.authorRegistry.load();let e=this.authorRegistry.all().filter(n=>n.id&&n.name),t=this.discoverStashpadFolders(),i=this.app.vault.getMarkdownFiles(),s=0;for(let n of t){let o=`${n}/_authors`,a=new Set;for(let l of i){if(!l.path.startsWith(o+"/"))continue;let c=this.parseAuthorFilePath(l.path)?.id;c&&a.add(c)}for(let l of e){if(a.has(l.id))continue;let c=this.authorNameToSafe(l.name),d=`${o}/${c}-${l.id}.md`;try{if(await this.ensureFolderPath(o),await this.app.vault.adapter.exists(d))continue;await this.app.vault.create(d,this.buildAuthorStub(l,l.firstSeen??new Date().toISOString())),s++}catch(h){console.warn("[Stashpad] restore author stub failed",d,h)}}}return{created:s,folders:t.length}}async convertAttachmentsToLinks(){let e=0,t=i=>/^\[\[.*\]\]$/.test(i.trim());for(let i of this.discoverStashpadFolders()){let s=i.replace(/\/+$/,"");for(let n of this.app.vault.getMarkdownFiles()){let o=n.parent?.path?.replace(/\/+$/,"")??"";if(o!==s&&!o.startsWith(s+"/")||Ht(n.path))continue;let a=this.app.metadataCache.getFileCache(n)?.frontmatter;if(!a||typeof a.id!="string"||!a.id)continue;let l=a.attachments,c=Array.isArray(l),d=typeof l=="string"&&l.trim().length>0;if(!(!c&&!d||!(d?!t(l):l.some(g=>typeof g=="string"&&g.trim()&&!t(g)))))try{await this.app.fileManager.processFrontMatter(n,g=>{Array.isArray(g.attachments)?g.attachments=g.attachments.map(f=>typeof f=="string"&&f.trim()?We(f):f):typeof g.attachments=="string"&&g.attachments.trim()&&(g.attachments=[We(g.attachments)])}),e++}catch(g){console.warn("[Stashpad] attachment-link conversion failed",n.path,g)}}}return e}syncObsidianExcludedArchives(){try{let e=this.app.vault;if(typeof e.getConfig!="function"||typeof e.setConfig!="function")return;let t=Array.isArray(e.getConfig("userIgnoreFilters"))?e.getConfig("userIgnoreFilters"):[],i=new Set(t),s=!1;for(let n of this.discoverStashpadFolders()){let o=`${n.replace(/\/+$/,"")}/_archive/`;i.has(o)||(i.add(o),s=!0)}s&&e.setConfig("userIgnoreFilters",[...i])}catch(e){console.warn("[Stashpad] couldn't update Obsidian excluded files",e)}}openImportPicker(){let e=this.discoverStashpadFolders();if(e.length===0){new M.Notice("No Stashpad folders to import into.");return}if(e.length===1){this.importService.pickFilesInto(e[0]);return}let t=this.importService.defaultDestination()??e[0];new ur(this.app,t,e,i=>this.importService.pickFilesInto(i)).open()}async runImportLooseFiles(e){let t=e.split("/").pop()||e,i=0,s=0,n=0;try{({files:i,folders:s,stashes:n}=await this.importService.importLooseInto(e))}catch(c){this.notifications.show({message:`Stashpad: import failed in \`${t}\` Error: ${c.message}`,kind:"error",category:"import",folder:e}),console.error("[Stashpad] runImportLooseFiles failed",e,c);return}let o=i+s+n,a=this.lastActiveStashpadLeaf?.view;o>0&&a?.noteFolder===e&&a?.tree&&(a.tree.rebuild(e),a.render?.());let l;if(o===0)l=`Nothing to import in \`${t}\` \u2014 everything here is already a Stashpad note.`;else{let c=[];i&&c.push(`${i} loose file${i===1?"":"s"}`),s&&c.push(`${s} folder${s===1?"":"s"} (as nested notes)`),n&&c.push(`${n} .stash bundle${n===1?"":"s"}`),l=`Imported ${c.join(" + ")} in \`${t}\`.`}this.notifications.show({message:l,kind:o>0?"success":"info",category:"import",folder:e})}refreshViewIfShowing(e){let t=this.lastActiveStashpadLeaf?.view;t?.noteFolder===e&&t?.tree&&(t.tree.rebuild(e),t.render?.())}async runFolderSlugPass(e){let t=e.split("/").pop()||e,i=0;try{i=await this.rebootstrapFolderSlugs(e)}catch(s){this.notifications.show({message:`Stashpad: slug pass failed in \`${t}\` -${s.message}`,kind:"error",category:"system",folder:e}),console.error("[Stashpad] runFolderSlugPass failed",e,s);return}this.refreshViewIfShowing(e),this.notifications.show({message:i>0?`Renamed ${i} stale filename${i===1?"":"s"} in \`${t}\`.`:`No stale filenames in \`${t}\` \u2014 all slugs match their notes.`,kind:i>0?"success":"info",category:"system",folder:e})}async runFolderFrontmatterBackfill(e){let t=e.split("/").pop()||e,i=0,s=0;try{let n=await gi(this.app,e);i=n.written,s=n.checked}catch(n){this.notifications.show({message:`Stashpad: frontmatter backfill failed in \`${t}\` -${n.message}`,kind:"error",category:"system",folder:e}),console.error("[Stashpad] runFolderFrontmatterBackfill failed",e,n);return}this.notifications.show({message:i>0?`Backfilled recovery links on ${i} note${i===1?"":"s"} in \`${t}\` (${s} checked).`:`Recovery links already up to date in \`${t}\` (${s} checked).`,kind:i>0?"success":"info",category:"system",folder:e})}async runAutoImportSweep(){if(!this.settings.autoImport||!this.importService.isArmed()||this.autoSweepInProgress)return;this.autoSweepInProgress=!0;let e=0,t=0,i=0;try{for(let n of this.discoverStashpadFolders())try{let o=await this.importService.importLooseInto(n,{auto:!0});e+=o.files,t+=o.folders,i+=o.stashes}catch(o){console.warn("[Stashpad] auto-import sweep failed",n,o)}}finally{this.autoSweepInProgress=!1}if(e+t+i>0){let n=this.lastActiveStashpadLeaf?.view;n?.tree&&n?.noteFolder&&(n.tree.rebuild(n.noteFolder),n.render?.());let o=[];e&&o.push(`${e} file${e===1?"":"s"}`),t&&o.push(`${t} folder${t===1?"":"s"}`),i&&o.push(`${i} .stash bundle${i===1?"":"s"}`),this.notifications.show({message:`Auto-imported ${o.join(" + ")} (background sweep).`,kind:"success",category:"import"})}this.notifyPendingEncrypted()}notifyPendingEncrypted(){let e=this.importService.pendingEncryptedPaths();if(e.length===0||!this.importService.shouldNotifyEncrypted())return;this.importService.snoozeEncryptedNotify(60*60*1e3);let t=e.length;this.notifications.show({message:`${t} encrypted .stash bundle${t===1?"":"s"} waiting to import. Import ${t===1?"it":"them"} with the password?`,kind:"info",category:"import",duration:0,actions:[{label:"Import now",onClick:()=>void this.importPendingEncryptedNow()},{label:"Remind me later",onClick:()=>this.importService.snoozeEncryptedNotify(60*60*1e3)},{label:"Not now (until next launch)",onClick:()=>this.importService.snoozeEncryptedNotify(1/0)}]})}async importPendingEncryptedNow(){let{imported:e,rescheduled:t}=await this.importService.importPendingEncrypted();if(e>0){let i=this.lastActiveStashpadLeaf?.view;i?.tree&&i?.noteFolder&&(i.tree.rebuild(i.noteFolder),i.render?.()),this.notifications.show({message:`Imported ${e} encrypted .stash bundle${e===1?"":"s"}.`,kind:"success",category:"import"})}}async seedLocalAuthorStub(e){let t=(this.settings.authorId??"").trim(),i=(this.settings.authorName??"").trim();if(!t||!i)return!1;let s=`${e.replace(/\/+$/,"")}/_authors`;if(this.app.vault.getMarkdownFiles().some(l=>l.path.startsWith(s+"/")&&this.parseAuthorFilePath(l.path)?.id===t))return!1;let o=this.authorNameToSafe(i),a=`${s}/${o}-${t}.md`;try{return await this.ensureFolderPath(s),await this.app.vault.adapter.exists(a)?!1:(await this.app.vault.create(a,this.buildAuthorStub({id:t,name:i,role:this.settings.authorRole,department:this.settings.authorDepartment},new Date().toISOString())),this.authorRegistry.record({id:t,name:i,role:this.settings.authorRole,department:this.settings.authorDepartment}),!0)}catch(l){return console.warn("[Stashpad] seedLocalAuthorStub failed",a,l),!1}}async seedLocalAuthorStubsEverywhere(){if(!(this.settings.authorName??"").trim())return 0;let t=0;for(let i of this.discoverStashpadFolders())await this.seedLocalAuthorStub(i)&&t++;return t}async ensureFolderPath(e){let t=this.app.vault.adapter,i=e.split("/").filter(Boolean),s="";for(let n of i){s=s?`${s}/${n}`:n;try{await t.exists(s)||await t.mkdir(s)}catch(o){if(!/already exists/i.test(o.message))throw o}}}async loadSettings(){let e=await this.loadData()??{};typeof e?.confirmMultiDelete=="boolean"&&(typeof e.confirmBulkDelete!="boolean"&&(e.confirmBulkDelete=e.confirmMultiDelete),typeof e.confirmAttachmentDelete!="boolean"&&(e.confirmAttachmentDelete=e.confirmMultiDelete),delete e.confirmMultiDelete),typeof e?.jdIndexDestFolder=="string"&&typeof e?.jdIndexStashpadFolder!="string"&&(e.jdIndexStashpadFolder=e.jdIndexDestFolder),this.settings={...Qt,...e,shortcuts:{...Qt.shortcuts,...e?.shortcuts??{}},mod:{...Qt.mod,...e?.mod??{}},bindings:cd(e?.bindings,e?.shortcuts,e?.mod),customPalette:Array.isArray(e?.customPalette)?e.customPalette.filter(t=>typeof t=="string"&&/^#[0-9a-f]{6}$/i.test(t)):[],colorAliases:e?.colorAliases&&typeof e.colorAliases=="object"?e.colorAliases:{},noteTemplates:e?.noteTemplates&&typeof e.noteTemplates=="object"?e.noteTemplates:{},authorName:typeof e?.authorName=="string"?e.authorName:"",authorId:typeof e?.authorId=="string"?e.authorId:"",authorRole:typeof e?.authorRole=="string"?e.authorRole:"",authorDepartment:typeof e?.authorDepartment=="string"?e.authorDepartment:"",showAuthor:typeof e?.showAuthor=="boolean"?e.showAuthor:!0,showContributors:typeof e?.showContributors=="boolean"?e.showContributors:!0,showLastEdit:typeof e?.showLastEdit=="boolean"?e.showLastEdit:!0,viewModes:e?.viewModes&&typeof e.viewModes=="object"&&!Array.isArray(e.viewModes)?e.viewModes:{},includeAttachmentsInEverything:e?.includeAttachmentsInEverything&&typeof e.includeAttachmentsInEverything=="object"&&!Array.isArray(e.includeAttachmentsInEverything)?e.includeAttachmentsInEverything:{},hideChildlessNotes:e?.hideChildlessNotes&&typeof e.hideChildlessNotes=="object"&&!Array.isArray(e.hideChildlessNotes)?e.hideChildlessNotes:{},hideCompletedNotes:e?.hideCompletedNotes&&typeof e.hideCompletedNotes=="object"&&!Array.isArray(e.hideCompletedNotes)?e.hideCompletedNotes:{},mutedNotificationCategories:Array.isArray(e?.mutedNotificationCategories)?e.mutedNotificationCategories.filter(t=>typeof t=="string"):[],notificationHistoryLimit:typeof e?.notificationHistoryLimit=="number"&&Number.isFinite(e.notificationHistoryLimit)?e.notificationHistoryLimit:5e3,notifiedDueKeys:Array.isArray(e?.notifiedDueKeys)?e.notifiedDueKeys.filter(t=>typeof t=="string").slice(-2e3):[],drafts:dd(e?.drafts),lastSubmitted:e?.lastSubmitted&&typeof e.lastSubmitted=="object"?e.lastSubmitted:{},slugStopWords:Array.isArray(e?.slugStopWords)?e.slugStopWords:[...Ze],migratedToggleTaskG:e?.migratedToggleTaskG===!0,dueQuickAdjusts:Array.isArray(e?.dueQuickAdjusts)?e.dueQuickAdjusts.filter(t=>typeof t=="string"):["5m","15m","30m","1h","1d","1w"]},yo(this.settings),this.settings.migratedToggleTaskG||(this.settings.bindings.toggleTask?.primary==="H"&&(this.settings.bindings.toggleTask.primary="G"),this.settings.migratedToggleTaskG=!0,await this.saveSettings()),this.notifications.loadMutedFromList(this.settings.mutedNotificationCategories),this.notifications.setHistoryLimit(this.settings.notificationHistoryLimit),this.notifications.setDefaultAuthorId(this.settings.authorId),this.attachNotificationPersistence()}notificationsPath(){return this.pluginPrivatePath("notifications.json")}async attachNotificationPersistence(){if(this.notifPersistenceWired)return;this.notifPersistenceWired=!0;let e=this.app.vault.adapter,t=this.notificationsPath();try{if(await e.exists(t)){let i=await e.read(t),s=JSON.parse(i);Array.isArray(s)&&this.notifications.loadHistory(s)}}catch(i){console.warn("[Stashpad] failed to load notification history",i)}this.notifications.onChange(()=>{this.notifSaveTimer!=null&&window.clearTimeout(this.notifSaveTimer),this.notifSaveTimer=window.setTimeout(()=>{this.notifSaveTimer=null,this.persistNotificationHistory()},1e3)})}async persistNotificationHistory(){try{let e=this.notifications.recent().slice().reverse(),t=this.notificationsPath(),i=t.replace(/\/[^/]+$/,""),s=this.app.vault.adapter;i&&!await s.exists(i)&&await s.mkdir(i),await s.write(t,JSON.stringify(e))}catch(e){console.warn("[Stashpad] failed to save notification history",e)}}readLastCursorFile(){try{let e=window.localStorage.getItem(this.LAST_CURSOR_LS_KEY);if(!e)return{};let t=JSON.parse(e);return t&&typeof t=="object"?t:{}}catch{return{}}}loadLastCursor(e){let i=this.readLastCursorFile()[e]??{};return new Map(Object.entries(i))}saveLastCursor(e,t,i){try{let s=this.readLastCursorFile();s[e]||(s[e]={}),s[e][t]=i,window.localStorage.setItem(this.LAST_CURSOR_LS_KEY,JSON.stringify(s))}catch(s){console.warn("[Stashpad] failed to save last-cursor",s)}}readLastSelectionFile(){try{let e=window.localStorage.getItem(this.LAST_SELECTION_LS_KEY);if(!e)return{};let t=JSON.parse(e);return t&&typeof t=="object"?t:{}}catch{return{}}}loadLastSelection(e){let i=this.readLastSelectionFile()[e]??{},s=new Map;for(let[n,o]of Object.entries(i))Array.isArray(o)&&s.set(n,o.filter(a=>typeof a=="string"));return s}saveLastSelection(e,t,i){try{let s=this.readLastSelectionFile();s[e]||(s[e]={}),i.length?s[e][t]=i:delete s[e][t],window.localStorage.setItem(this.LAST_SELECTION_LS_KEY,JSON.stringify(s))}catch(s){console.warn("[Stashpad] failed to save last-selection",s)}}readActiveVersionsFile(){try{let e=window.localStorage.getItem(this.ACTIVE_VERSIONS_LS_KEY);if(!e)return{};let t=JSON.parse(e);return t&&typeof t=="object"?t:{}}catch{return{}}}loadActiveVersions(e){let t=this.readActiveVersionsFile();return new Map(Object.entries(t[e]??{}))}saveActiveVersion(e,t,i){try{let s=this.readActiveVersionsFile();s[e]||(s[e]={}),s[e][t]=i,window.localStorage.setItem(this.ACTIVE_VERSIONS_LS_KEY,JSON.stringify(s))}catch(s){console.warn("[Stashpad] failed to save active-version",s)}}async flushForkSiblingPrune(){let e=new Set(this.pendingForkDeletes);if(this.pendingForkDeletes.clear(),!!e.size)for(let t of this.app.vault.getMarkdownFiles()){let i=this.app.metadataCache.getFileCache(t)?.frontmatter?.[ze];if(Array.isArray(i)&&i.some(s=>e.has(As(s)??"")))try{await this.app.fileManager.processFrontMatter(t,s=>{let o=(Array.isArray(s[ze])?s[ze]:[]).filter(a=>!e.has(As(a)??""));o.length?s[ze]=o:delete s[ze]})}catch{}}}queueWrite(){let e=this.writeChain.then(()=>this.saveData(this.settings));return this.writeChain=e.catch(()=>{}),e}async saveSettings(){await this.queueWrite(),yo(this.settings),ke.enabled=!!this.settings.enablePerfProfiling;let e=(this.settings.authorId??"").trim();e&&this.authorRegistry.record({id:e,name:this.settings.authorName,role:this.settings.authorRole,department:this.settings.authorDepartment}),console.debug("[Stashpad] saveSettings",{shortcuts:this.settings.shortcuts,mod:this.settings.mod})}async persistSettingsQuiet(){await this.queueWrite()}async fixOrphanParents(){let e=new Set(this.discoverStashpadFolders());if(e.size===0){new T.Notice("No Stashpad folders found.");return}let t=new Set,i=this.app.vault.getMarkdownFiles();for(let h of i){let g=this.app.metadataCache.getFileCache(h)?.frontmatter,u=typeof g?.id=="string"?g.id.trim():"";u&&t.add(u)}let{newId:s}=await Promise.resolve().then(()=>(qe(),fs)),n=()=>{for(let h=0;h<100;h++){let g=s();if(!t.has(g))return t.add(g),g}for(let h=8;h<=16;h+=2){let g=s(h);if(!t.has(g))return t.add(g),g}throw new Error("Could not generate a unique id")},o=[];for(let h of i){let g=h.parent?.path?.replace(/\/+$/,"")??"";if(!e.has(g))continue;let u=this.app.metadataCache.getFileCache(h)?.frontmatter,p=typeof u?.id=="string"?u.id.trim():"",f=u?.parent,I=typeof f=="string"?f.trim()!=="":f!=null,m=typeof u?.created=="string"&&u.created.trim()!=="",C=!p,B=!I,w=!m;!C&&!B&&!w||o.push({file:h,addId:C,addParent:B,addCreated:w})}if(o.length===0){new T.Notice("Nothing to fix \u2014 every note in a Stashpad folder already has id + parent + created.");return}let a=0,l=0,c=this.newLog();for(let h of o)try{let g;await this.app.fileManager.processFrontMatter(h.file,f=>{if(h.addId&&(typeof f.id=="string"&&f.id.trim()||(g=n(),f.id=g)),h.addParent){let I=f.parent;(typeof I=="string"?I.trim()!=="":I!=null)||(f.parent=k)}h.addCreated&&(typeof f.created=="string"&&f.created.trim()||(f.created=new Date(h.file.stat.ctime).toISOString()))});let u=this.app.metadataCache.getFileCache(h.file)?.frontmatter,p=g??u?.id??"";p&&await c.append({type:"parent_change",id:p,payload:{from:null,to:k,reason:"orphan_fix",path:h.file.path,addedId:h.addId,addedParent:h.addParent,addedCreated:h.addCreated}}),a++}catch(g){console.warn("Stashpad: orphan fix failed for",h.file.path,g),l++}let d=l?` (${l} failed \u2014 see console)`:"";new T.Notice(`Fixed ${a} note${a===1?"":"s"} in Stashpad folders${d}.`)}async adoptNote(e){let{newId:t}=await Promise.resolve().then(()=>(qe(),fs)),i=new Set;for(let c of this.app.vault.getMarkdownFiles()){if(c.path===e.path)continue;let d=this.app.metadataCache.getFileCache(c)?.frontmatter,h=typeof d?.id=="string"?d.id.trim():"";h&&i.add(h)}let s=()=>{for(let c=0;c<50;c++){let d=t();if(!i.has(d))return d}for(let c=8;c<=16;c+=2){let d=t(c);if(!i.has(d))return d}throw new Error("Could not generate a unique id")},n=[],o=[];try{await this.app.fileManager.processFrontMatter(e,c=>{let d=typeof c.id=="string"?c.id.trim():"";!d||/\s/.test(d)||i.has(d)?(c.id=s(),n.push("id")):o.push("id"),c.parent!==void 0&&c.parent!==null&&String(c.parent).trim()!==""?o.push("parent"):(c.parent=k,n.push("parent")),typeof c.created=="string"&&c.created.trim()!==""?o.push("created"):(c.created=new Date(e.stat.ctime).toISOString(),n.push("created"))})}catch(c){new T.Notice(`Adopt failed: ${c.message}`);return}let a=!1;try{let c=this.app.metadataCache.getFileCache(e)?.frontmatter,d=typeof c?.id=="string"?c.id.trim():"";if(d&&e.basename&&!e.basename.endsWith(`-${d}`)){let h=`${e.parent?e.parent.path+"/":""}${e.basename}-${d}.md`;await this.app.vault.adapter.exists(h)||(await this.app.fileManager.renameFile(e,h),a=!0)}}catch(c){console.warn("Stashpad: adopt rename failed",c)}if(n.length===0&&!a){new T.Notice(`Already a Stashpad note (${o.join(", ")} present).`);return}let l=[];n.length&&l.push(`added: ${n.join(", ")}`),a&&l.push("renamed with id"),new T.Notice(`Adopted into Stashpad \u2014 ${l.join("; ")}.`);try{let c=this.newLog(),d=this.app.metadataCache.getFileCache(e)?.frontmatter,h=d?.id??"";h&&await c.append({type:"create",id:h,payload:{path:e.path,parent:d?.parent??k,source:"adopt",added:n}})}catch{}}};ds.DEBUG_BUFFER_MAX=300;var Nn=ds;function cd(A,r,e){let t=Bo();for(let i of ci){let s=r&&typeof r[i.id]=="string"?r[i.id]:e&&typeof e[i.id]=="string"?e[i.id]:null;s!=null&&s!==""&&(t[i.id].primary=s)}if(A&&typeof A=="object")for(let i of ci){let s=A[i.id];!s||typeof s!="object"||(t[i.id]={primary:typeof s.primary=="string"?s.primary:t[i.id].primary,secondary:typeof s.secondary=="string"?s.secondary:"",preferRight:!!s.preferRight,useBoth:!!s.useBoth})}for(let i of ci){if(!i.defaultSecondary&&!i.defaultUseBoth)continue;let s=t[i.id];s.primary===i.defaultPrimary&&!s.secondary&&!s.useBoth&&(s.secondary=i.defaultSecondary??"",s.useBoth=!!i.defaultUseBoth)}return t}function dd(A){let r={};if(!A||typeof A!="object")return r;for(let[e,t]of Object.entries(A))if(typeof t=="string")r[e]=t;else if(t&&typeof t=="object"){for(let i of Object.values(t))if(typeof i=="string"&&i.length>0){r[e]=i;break}}return r}var ia=class extends T.SuggestModal{constructor(e,t,i,s){super(e);this.def=t;this.folders=i;this.onPick=s;this.setPlaceholder("Choose a Stashpad folder to import into\u2026")}getSuggestions(e){let t=e.toLowerCase();return[this.def,...this.folders.filter(s=>s!==this.def)].filter(s=>s.toLowerCase().includes(t)).map(s=>({label:s,folder:s,current:s===this.def}))}renderSuggestion(e,t){t.createDiv({text:e.label}),e.current&&(t.createDiv({cls:"stashpad-suggest-note",text:"current"}),t.addClass("is-pinned-import-target"))}onChooseSuggestion(e){this.onPick(e.folder)}},sa=class extends T.SuggestModal{constructor(e,t,i){super(e);this.bundles=t;this.onPick=i;this.setPlaceholder("Choose an encrypted folder bundle to decrypt\u2026")}getSuggestions(e){let t=e.toLowerCase();return this.bundles.filter(i=>i.folder.toLowerCase().includes(t))}renderSuggestion(e,t){t.createDiv({text:e.folder||"(vault root)"}),t.createDiv({cls:"stashpad-suggest-note",text:e.blobPath})}onChooseSuggestion(e){this.onPick(e)}}; +${s.message}`,kind:"error",category:"system",folder:e}),console.error("[Stashpad] runFolderSlugPass failed",e,s);return}this.refreshViewIfShowing(e),this.notifications.show({message:i>0?`Renamed ${i} stale filename${i===1?"":"s"} in \`${t}\`.`:`No stale filenames in \`${t}\` \u2014 all slugs match their notes.`,kind:i>0?"success":"info",category:"system",folder:e})}async runFolderFrontmatterBackfill(e){let t=e.split("/").pop()||e,i=0,s=0;try{let n=await Ii(this.app,e);i=n.written,s=n.checked}catch(n){this.notifications.show({message:`Stashpad: frontmatter backfill failed in \`${t}\` +${n.message}`,kind:"error",category:"system",folder:e}),console.error("[Stashpad] runFolderFrontmatterBackfill failed",e,n);return}this.notifications.show({message:i>0?`Backfilled recovery links on ${i} note${i===1?"":"s"} in \`${t}\` (${s} checked).`:`Recovery links already up to date in \`${t}\` (${s} checked).`,kind:i>0?"success":"info",category:"system",folder:e})}async runAutoImportSweep(){if(!this.settings.autoImport||!this.importService.isArmed()||this.autoSweepInProgress)return;this.autoSweepInProgress=!0;let e=0,t=0,i=0;try{for(let n of this.discoverStashpadFolders())try{let o=await this.importService.importLooseInto(n,{auto:!0});e+=o.files,t+=o.folders,i+=o.stashes}catch(o){console.warn("[Stashpad] auto-import sweep failed",n,o)}}finally{this.autoSweepInProgress=!1}if(e+t+i>0){let n=this.lastActiveStashpadLeaf?.view;n?.tree&&n?.noteFolder&&(n.tree.rebuild(n.noteFolder),n.render?.());let o=[];e&&o.push(`${e} file${e===1?"":"s"}`),t&&o.push(`${t} folder${t===1?"":"s"}`),i&&o.push(`${i} .stash bundle${i===1?"":"s"}`),this.notifications.show({message:`Auto-imported ${o.join(" + ")} (background sweep).`,kind:"success",category:"import"})}this.notifyPendingEncrypted()}notifyPendingEncrypted(){let e=this.importService.pendingEncryptedPaths();if(e.length===0||!this.importService.shouldNotifyEncrypted())return;this.importService.snoozeEncryptedNotify(60*60*1e3);let t=e.length;this.notifications.show({message:`${t} encrypted .stash bundle${t===1?"":"s"} waiting to import. Import ${t===1?"it":"them"} with the password?`,kind:"info",category:"import",duration:0,actions:[{label:"Import now",onClick:()=>void this.importPendingEncryptedNow()},{label:"Remind me later",onClick:()=>this.importService.snoozeEncryptedNotify(60*60*1e3)},{label:"Not now (until next launch)",onClick:()=>this.importService.snoozeEncryptedNotify(1/0)}]})}async importPendingEncryptedNow(){let{imported:e,rescheduled:t}=await this.importService.importPendingEncrypted();if(e>0){let i=this.lastActiveStashpadLeaf?.view;i?.tree&&i?.noteFolder&&(i.tree.rebuild(i.noteFolder),i.render?.()),this.notifications.show({message:`Imported ${e} encrypted .stash bundle${e===1?"":"s"}.`,kind:"success",category:"import"})}}async seedLocalAuthorStub(e){let t=(this.settings.authorId??"").trim(),i=(this.settings.authorName??"").trim();if(!t||!i)return!1;let s=`${e.replace(/\/+$/,"")}/_authors`;if(this.app.vault.getMarkdownFiles().some(l=>l.path.startsWith(s+"/")&&this.parseAuthorFilePath(l.path)?.id===t))return!1;let o=this.authorNameToSafe(i),a=`${s}/${o}-${t}.md`;try{return await this.ensureFolderPath(s),await this.app.vault.adapter.exists(a)?!1:(await this.app.vault.create(a,this.buildAuthorStub({id:t,name:i,role:this.settings.authorRole,department:this.settings.authorDepartment},new Date().toISOString())),this.authorRegistry.record({id:t,name:i,role:this.settings.authorRole,department:this.settings.authorDepartment}),!0)}catch(l){return console.warn("[Stashpad] seedLocalAuthorStub failed",a,l),!1}}async seedLocalAuthorStubsEverywhere(){if(!(this.settings.authorName??"").trim())return 0;let t=0;for(let i of this.discoverStashpadFolders())await this.seedLocalAuthorStub(i)&&t++;return t}async ensureFolderPath(e){let t=this.app.vault.adapter,i=e.split("/").filter(Boolean),s="";for(let n of i){s=s?`${s}/${n}`:n;try{await t.exists(s)||await t.mkdir(s)}catch(o){if(!/already exists/i.test(o.message))throw o}}}async loadSettings(){let e=await this.loadData()??{};this.lastSeenSettingsRev=typeof e?.settingsRev=="number"?e.settingsRev:0,typeof e?.confirmMultiDelete=="boolean"&&(typeof e.confirmBulkDelete!="boolean"&&(e.confirmBulkDelete=e.confirmMultiDelete),typeof e.confirmAttachmentDelete!="boolean"&&(e.confirmAttachmentDelete=e.confirmMultiDelete),delete e.confirmMultiDelete),typeof e?.jdIndexDestFolder=="string"&&typeof e?.jdIndexStashpadFolder!="string"&&(e.jdIndexStashpadFolder=e.jdIndexDestFolder),this.settings={...Qt,...e,shortcuts:{...Qt.shortcuts,...e?.shortcuts??{}},mod:{...Qt.mod,...e?.mod??{}},bindings:Nd(e?.bindings,e?.shortcuts,e?.mod),customPalette:Array.isArray(e?.customPalette)?e.customPalette.filter(t=>typeof t=="string"&&/^#[0-9a-f]{6}$/i.test(t)):[],colorAliases:e?.colorAliases&&typeof e.colorAliases=="object"?e.colorAliases:{},noteTemplates:e?.noteTemplates&&typeof e.noteTemplates=="object"?e.noteTemplates:{},authorName:typeof e?.authorName=="string"?e.authorName:"",authorId:typeof e?.authorId=="string"?e.authorId:"",authorRole:typeof e?.authorRole=="string"?e.authorRole:"",authorDepartment:typeof e?.authorDepartment=="string"?e.authorDepartment:"",showAuthor:typeof e?.showAuthor=="boolean"?e.showAuthor:!0,showContributors:typeof e?.showContributors=="boolean"?e.showContributors:!0,showLastEdit:typeof e?.showLastEdit=="boolean"?e.showLastEdit:!0,viewModes:e?.viewModes&&typeof e.viewModes=="object"&&!Array.isArray(e.viewModes)?e.viewModes:{},includeAttachmentsInEverything:e?.includeAttachmentsInEverything&&typeof e.includeAttachmentsInEverything=="object"&&!Array.isArray(e.includeAttachmentsInEverything)?e.includeAttachmentsInEverything:{},hideChildlessNotes:e?.hideChildlessNotes&&typeof e.hideChildlessNotes=="object"&&!Array.isArray(e.hideChildlessNotes)?e.hideChildlessNotes:{},hideCompletedNotes:e?.hideCompletedNotes&&typeof e.hideCompletedNotes=="object"&&!Array.isArray(e.hideCompletedNotes)?e.hideCompletedNotes:{},mutedNotificationCategories:Array.isArray(e?.mutedNotificationCategories)?e.mutedNotificationCategories.filter(t=>typeof t=="string"):[],notificationHistoryLimit:typeof e?.notificationHistoryLimit=="number"&&Number.isFinite(e.notificationHistoryLimit)?e.notificationHistoryLimit:5e3,notifiedDueKeys:Array.isArray(e?.notifiedDueKeys)?e.notifiedDueKeys.filter(t=>typeof t=="string").slice(-2e3):[],drafts:Md(e?.drafts),lastSubmitted:e?.lastSubmitted&&typeof e.lastSubmitted=="object"?e.lastSubmitted:{},slugStopWords:Array.isArray(e?.slugStopWords)?e.slugStopWords:[...je],migratedToggleTaskG:e?.migratedToggleTaskG===!0,dueQuickAdjusts:Array.isArray(e?.dueQuickAdjusts)?e.dueQuickAdjusts.filter(t=>typeof t=="string"):["5m","15m","30m","1h","1d","1w"]},Zs(this.settings),this.snapshotSettingsBaseline(),this.settings.migratedToggleTaskG||(this.settings.bindings.toggleTask?.primary==="H"&&(this.settings.bindings.toggleTask.primary="G"),this.settings.migratedToggleTaskG=!0,await this.saveSettings()),this.notifications.loadMutedFromList(this.settings.mutedNotificationCategories),this.notifications.setHistoryLimit(this.settings.notificationHistoryLimit),this.notifications.setDefaultAuthorId(this.settings.authorId),this.attachNotificationPersistence()}notificationsPath(){return this.pluginPrivatePath("notifications.json")}async attachNotificationPersistence(){if(this.notifPersistenceWired)return;this.notifPersistenceWired=!0;let e=this.app.vault.adapter,t=this.notificationsPath();try{if(await e.exists(t)){let i=await e.read(t),s=JSON.parse(i);Array.isArray(s)&&this.notifications.loadHistory(s)}}catch(i){console.warn("[Stashpad] failed to load notification history",i)}this.notifications.onChange(()=>{this.notifSaveTimer!=null&&window.clearTimeout(this.notifSaveTimer),this.notifSaveTimer=window.setTimeout(()=>{this.notifSaveTimer=null,this.persistNotificationHistory()},1e3)})}async persistNotificationHistory(){try{let e=this.notifications.recent().slice().reverse(),t=this.notificationsPath(),i=t.replace(/\/[^/]+$/,""),s=this.app.vault.adapter;i&&!await s.exists(i)&&await s.mkdir(i),await s.write(t,JSON.stringify(e))}catch(e){console.warn("[Stashpad] failed to save notification history",e)}}readLastCursorFile(){try{let e=window.localStorage.getItem(this.LAST_CURSOR_LS_KEY);if(!e)return{};let t=JSON.parse(e);return t&&typeof t=="object"?t:{}}catch{return{}}}loadLastCursor(e){let i=this.readLastCursorFile()[e]??{};return new Map(Object.entries(i))}saveLastCursor(e,t,i){try{let s=this.readLastCursorFile();s[e]||(s[e]={}),s[e][t]=i,window.localStorage.setItem(this.LAST_CURSOR_LS_KEY,JSON.stringify(s))}catch(s){console.warn("[Stashpad] failed to save last-cursor",s)}}readLastSelectionFile(){try{let e=window.localStorage.getItem(this.LAST_SELECTION_LS_KEY);if(!e)return{};let t=JSON.parse(e);return t&&typeof t=="object"?t:{}}catch{return{}}}loadLastSelection(e){let i=this.readLastSelectionFile()[e]??{},s=new Map;for(let[n,o]of Object.entries(i))Array.isArray(o)&&s.set(n,o.filter(a=>typeof a=="string"));return s}saveLastSelection(e,t,i){try{let s=this.readLastSelectionFile();s[e]||(s[e]={}),i.length?s[e][t]=i:delete s[e][t],window.localStorage.setItem(this.LAST_SELECTION_LS_KEY,JSON.stringify(s))}catch(s){console.warn("[Stashpad] failed to save last-selection",s)}}readActiveVersionsFile(){try{let e=window.localStorage.getItem(this.ACTIVE_VERSIONS_LS_KEY);if(!e)return{};let t=JSON.parse(e);return t&&typeof t=="object"?t:{}}catch{return{}}}loadActiveVersions(e){let t=this.readActiveVersionsFile();return new Map(Object.entries(t[e]??{}))}saveActiveVersion(e,t,i){try{let s=this.readActiveVersionsFile();s[e]||(s[e]={}),s[e][t]=i,window.localStorage.setItem(this.ACTIVE_VERSIONS_LS_KEY,JSON.stringify(s))}catch(s){console.warn("[Stashpad] failed to save active-version",s)}}async flushForkSiblingPrune(){let e=new Set(this.pendingForkDeletes);if(this.pendingForkDeletes.clear(),!!e.size)for(let t of this.app.vault.getMarkdownFiles()){let i=this.app.metadataCache.getFileCache(t)?.frontmatter?.[Ye];if(Array.isArray(i)&&i.some(s=>e.has(Cs(s)??"")))try{await this.app.fileManager.processFrontMatter(t,s=>{let o=(Array.isArray(s[Ye])?s[Ye]:[]).filter(a=>!e.has(Cs(a)??""));o.length?s[Ye]=o:delete s[Ye]})}catch{}}}queueWrite(){let e=this.writeChain.then(()=>this.guardedSave());return this.writeChain=e.catch(()=>{}),e}snapshotSettingsBaseline(){for(let e of Mt.COLLISION_PROTECTED_KEYS)this.settingsBaseline[e]=JSON.stringify(this.settings[e])}async guardedSave(){let e=null;try{e=await this.loadData()}catch{}let t=typeof e?.settingsRev=="number"?e.settingsRev:0;if(e){let s=[];for(let n of Mt.COLLISION_PROTECTED_KEYS){let o=JSON.stringify(this.settings[n])!==this.settingsBaseline[n],a=JSON.stringify(e[n])!==this.settingsBaseline[n];!o&&a&&e[n]!==void 0&&(this.settings[n]=e[n],s.push(n))}s.length&&(console.warn(`[Stashpad] settings collision: on-disk protected keys differ from our baseline (disk rev ${t}, we knew ${this.lastSeenSettingsRev}); adopted: ${s.join(", ")}.`),new M.Notice(`Stashpad: another Obsidian instance (or a synced machine) changed this vault's settings. Merged instead of overwriting (${s.join(", ")}). If encryption behaves oddly, restart Obsidian.`,1e4),Zs(this.settings)),t>this.lastSeenSettingsRev&&(this.lastSeenSettingsRev=t)}let i=Math.max(t,this.lastSeenSettingsRev)+1;this.settings.settingsRev=i,await this.saveData(this.settings),this.lastSeenSettingsRev=i,this.snapshotSettingsBaseline()}async saveSettings(){await this.queueWrite(),Zs(this.settings),Se.enabled=!!this.settings.enablePerfProfiling;let e=(this.settings.authorId??"").trim();e&&this.authorRegistry.record({id:e,name:this.settings.authorName,role:this.settings.authorRole,department:this.settings.authorDepartment}),console.debug("[Stashpad] saveSettings",{shortcuts:this.settings.shortcuts,mod:this.settings.mod})}async persistSettingsQuiet(){await this.queueWrite()}async fixOrphanParents(){let e=new Set(this.discoverStashpadFolders());if(e.size===0){new M.Notice("No Stashpad folders found.");return}let t=new Set,i=this.app.vault.getMarkdownFiles();for(let h of i){let g=this.app.metadataCache.getFileCache(h)?.frontmatter,f=typeof g?.id=="string"?g.id.trim():"";f&&t.add(f)}let{newId:s}=await Promise.resolve().then(()=>(Le(),Fs)),n=()=>{for(let h=0;h<100;h++){let g=s();if(!t.has(g))return t.add(g),g}for(let h=8;h<=16;h+=2){let g=s(h);if(!t.has(g))return t.add(g),g}throw new Error("Could not generate a unique id")},o=[];for(let h of i){let g=h.parent?.path?.replace(/\/+$/,"")??"";if(!e.has(g))continue;let f=this.app.metadataCache.getFileCache(h)?.frontmatter,p=typeof f?.id=="string"?f.id.trim():"",u=f?.parent,I=typeof u=="string"?u.trim()!=="":u!=null,m=typeof f?.created=="string"&&f.created.trim()!=="",C=!p,B=!I,w=!m;!C&&!B&&!w||o.push({file:h,addId:C,addParent:B,addCreated:w})}if(o.length===0){new M.Notice("Nothing to fix \u2014 every note in a Stashpad folder already has id + parent + created.");return}let a=0,l=0,c=this.newLog();for(let h of o)try{let g;await this.app.fileManager.processFrontMatter(h.file,u=>{if(h.addId&&(typeof u.id=="string"&&u.id.trim()||(g=n(),u.id=g)),h.addParent){let I=u.parent;(typeof I=="string"?I.trim()!=="":I!=null)||(u.parent=F)}h.addCreated&&(typeof u.created=="string"&&u.created.trim()||(u.created=new Date(h.file.stat.ctime).toISOString()))});let f=this.app.metadataCache.getFileCache(h.file)?.frontmatter,p=g??f?.id??"";p&&await c.append({type:"parent_change",id:p,payload:{from:null,to:F,reason:"orphan_fix",path:h.file.path,addedId:h.addId,addedParent:h.addParent,addedCreated:h.addCreated}}),a++}catch(g){console.warn("Stashpad: orphan fix failed for",h.file.path,g),l++}let d=l?` (${l} failed \u2014 see console)`:"";new M.Notice(`Fixed ${a} note${a===1?"":"s"} in Stashpad folders${d}.`)}async adoptNote(e){let{newId:t}=await Promise.resolve().then(()=>(Le(),Fs)),i=new Set;for(let c of this.app.vault.getMarkdownFiles()){if(c.path===e.path)continue;let d=this.app.metadataCache.getFileCache(c)?.frontmatter,h=typeof d?.id=="string"?d.id.trim():"";h&&i.add(h)}let s=()=>{for(let c=0;c<50;c++){let d=t();if(!i.has(d))return d}for(let c=8;c<=16;c+=2){let d=t(c);if(!i.has(d))return d}throw new Error("Could not generate a unique id")},n=[],o=[];try{await this.app.fileManager.processFrontMatter(e,c=>{let d=typeof c.id=="string"?c.id.trim():"";!d||/\s/.test(d)||i.has(d)?(c.id=s(),n.push("id")):o.push("id"),c.parent!==void 0&&c.parent!==null&&String(c.parent).trim()!==""?o.push("parent"):(c.parent=F,n.push("parent")),typeof c.created=="string"&&c.created.trim()!==""?o.push("created"):(c.created=new Date(e.stat.ctime).toISOString(),n.push("created"))})}catch(c){new M.Notice(`Adopt failed: ${c.message}`);return}let a=!1;try{let c=this.app.metadataCache.getFileCache(e)?.frontmatter,d=typeof c?.id=="string"?c.id.trim():"";if(d&&e.basename&&!e.basename.endsWith(`-${d}`)){let h=`${e.parent?e.parent.path+"/":""}${e.basename}-${d}.md`;await this.app.vault.adapter.exists(h)||(await this.app.fileManager.renameFile(e,h),a=!0)}}catch(c){console.warn("Stashpad: adopt rename failed",c)}if(n.length===0&&!a){new M.Notice(`Already a Stashpad note (${o.join(", ")} present).`);return}let l=[];n.length&&l.push(`added: ${n.join(", ")}`),a&&l.push("renamed with id"),new M.Notice(`Adopted into Stashpad \u2014 ${l.join("; ")}.`);try{let c=this.newLog(),d=this.app.metadataCache.getFileCache(e)?.frontmatter,h=d?.id??"";h&&await c.append({type:"create",id:h,payload:{path:e.path,parent:d?.parent??F,source:"adopt",added:n}})}catch{}}};Mt.DEBUG_BUFFER_MAX=300,Mt.COLLISION_PROTECTED_KEYS=["encryption","lockedSubtrees","folderEncPrefs","archiveFolders"];var Yn=Mt;function Nd(A,r,e){let t=To();for(let i of hi){let s=r&&typeof r[i.id]=="string"?r[i.id]:e&&typeof e[i.id]=="string"?e[i.id]:null;s!=null&&s!==""&&(t[i.id].primary=s)}if(A&&typeof A=="object")for(let i of hi){let s=A[i.id];!s||typeof s!="object"||(t[i.id]={primary:typeof s.primary=="string"?s.primary:t[i.id].primary,secondary:typeof s.secondary=="string"?s.secondary:"",preferRight:!!s.preferRight,useBoth:!!s.useBoth})}for(let i of hi){if(!i.defaultSecondary&&!i.defaultUseBoth)continue;let s=t[i.id];s.primary===i.defaultPrimary&&!s.secondary&&!s.useBoth&&(s.secondary=i.defaultSecondary??"",s.useBoth=!!i.defaultUseBoth)}return t}function Md(A){let r={};if(!A||typeof A!="object")return r;for(let[e,t]of Object.entries(A))if(typeof t=="string")r[e]=t;else if(t&&typeof t=="object"){for(let i of Object.values(t))if(typeof i=="string"&&i.length>0){r[e]=i;break}}return r}var ur=class extends M.SuggestModal{constructor(e,t,i,s){super(e);this.def=t;this.folders=i;this.onPick=s;this.setPlaceholder("Choose a Stashpad folder to import into\u2026")}getSuggestions(e){let t=e.toLowerCase();return[this.def,...this.folders.filter(s=>s!==this.def)].filter(s=>s.toLowerCase().includes(t)).map(s=>({label:s,folder:s,current:s===this.def}))}renderSuggestion(e,t){t.createDiv({text:e.label}),e.current&&(t.createDiv({cls:"stashpad-suggest-note",text:"current"}),t.addClass("is-pinned-import-target"))}onChooseSuggestion(e){this.onPick(e.folder)}},fr=class extends M.SuggestModal{constructor(e,t,i){super(e);this.bundles=t;this.onPick=i;this.setPlaceholder("Choose an encrypted folder bundle to decrypt\u2026")}getSuggestions(e){let t=e.toLowerCase();return this.bundles.filter(i=>i.folder.toLowerCase().includes(t))}renderSuggestion(e,t){t.createDiv({text:e.folder||"(vault root)"}),t.createDiv({cls:"stashpad-suggest-note",text:e.blobPath})}onChooseSuggestion(e){this.onPick(e)}}; /*! Bundled license information: hash-wasm/dist/index.esm.js: diff --git a/manifest.json b/manifest.json index bc7e670..e580cb7 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "stashpad", "name": "Stashpad", - "version": "0.133.0", + "version": "0.140.8", "minAppVersion": "1.13.0", "description": "A chat-style, nested-notes workspace: rapid capture, outliner navigation, fast search, tasks, and per-folder templates, with one-click Open Knowledge Format (OKF) export for LLMs and agents.", "author": "Human", diff --git a/package.json b/package.json index 5b521d3..cbf9d2a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "stashpad-obsidian", - "version": "0.133.0", + "version": "0.140.8", "private": true, "scripts": { "dev": "node esbuild.config.mjs", diff --git a/release-notes/0.140.8.md b/release-notes/0.140.8.md new file mode 100644 index 0000000..e82fe82 --- /dev/null +++ b/release-notes/0.140.8.md @@ -0,0 +1,114 @@ +# 0.140.8 — Per-folder archive/trash subfolders, encryption cleanup, smart re-encrypt, recurring reminders, and a deep review pass + +A large release spanning several feature phases since 0.133.0: archives and +encrypted trash move to per-folder subfolders, the encryption model is +simplified and hardened, a "smart re-encrypt" workflow keeps previously-locked +notes protected, reminders gain recurrence, and a multi-wave correctness/security +review fixes a long tail of bugs. + +## Archive & aggregate views (0.134.x) + +- **0.134.0** — Aggregate command wording clarified; archives now default to + plaintext (opt-in encryption) and the encryption settings are aligned to match. +- **0.134.1** — Aggregate view command names simplified to "Open aggregated X + view" (no parenthetical). +- **0.134.2** — "Open aggregated Encrypted notes view" gains the noun so it's + findable by searching "notes"/"contents". +- **0.134.3** — The three separate encryption warnings are merged into one + callout, and the danger buttons are made quieter/less alarming. +- **0.134.4** — Archive bug fixes B1–B6: default-folder mismatch, a stale copy + left behind, encryption-state desync with the store, and zombie-entry pruning. + +## Encryption cleanup (0.135.x) + +- **0.135.0** — Encryption cleanup: key backups are parked (not destroyed) on + removal, the "Lock now" and idle auto-lock controls are dropped, and the + Sharing UI is hidden. +- **0.135.1** — Keychain no-overwrite policy: any existing keychain entry is + parked before a new write, so an old key is never silently clobbered. + +## Per-folder archive subfolders (0.136.0) + +- Reserve per-folder `archive/` and `trash/` subfolder names. +- "Move to archive" now targets the current folder's own `archive/` subfolder. +- The Archived view scans each folder's `archive/` subfolder; un-archiving + restores a note to its parent folder. +- Imported originals are filed under `archive/_imported/<date>/`. +- A one-time migration moves legacy archives into the new layout and removes the + old global archive settings/UI. +- Migration hardening (from live testing): the home note is preserved, the sweep + is suppressed where it shouldn't run, home-note detection reads from disk, + moves are verified, a recovery journal is kept, and failed moves are retried. + +## Per-folder encryption fixes & trash (0.136.1 – 0.137.x) + +- **0.136.1** — Per-folder toggle fixes, keychain cycle-unlock, and a loading + state on the Remove button. +- **0.136.2** — A Restore button on locked rows in the aggregated Archived view. +- **0.137.0** — Phase 2: per-folder encrypted trash (`X/trash/`) with a + conservative migration of the legacy vault-level `_deleted/`. +- **0.137.1** — Remove the global "hide locked titles" and "follow Obsidian + trash" options — the per-folder settings now cover both. +- **0.137.2** — Retro-apply: enabling "Encrypt archived notes" offers to lock + whatever is already archived. +- **0.137.3** — Multi-writer settings-collision guard for dual instances, + network drives, and sync clients. +- **0.137.4** — A collapsible "How encryption works" concepts section in the + Encryption settings. + +## Smart re-encrypt & timed peek (0.138.0 – 0.139.0) + +- **0.138.0** — Smart re-encrypt sweep: a watchlist of previously-encrypted + notes, a "Previously encrypted" view, a one-modal batch re-lock, and a nudge + to re-protect notes left in plaintext. +- **0.139.0** — Timed "peek": temporarily decrypt a note and have it + automatically re-encrypt after a set interval — opt-in, cancellable, and + safeguarded against re-locking a note that's still being edited. A follow-up + fix strips two stray NUL bytes accidentally introduced in the scheduler. + +## Recurring reminders (0.140.0) + +- Recurring and persistent reminders — the deferred major feature — land: a + reminder can repeat on a schedule and survive restarts. + +## Correctness & security review (0.140.1 – 0.140.8) + +A multi-wave review (finding, then adversarially verifying each finding) fixed a +long tail of bugs. Grouped by the batch that shipped them: + +- **0.140.1** — Fixes for the first review wave: a data-loss bug in per-folder + trash, recurrence undo, and assorted peek/reminder/crypto issues. +- **0.140.2** — Review deferrals batch 1: decompression-bomb caps on zip import, + DEK-length validation, a leaf-return listener leak, content-aware merge in the + guarded settings save, undo-on-failure retry, and peek-input coercion. +- **0.140.3** — Review wave 2: tree-index cycle guards (self-parent in the + incremental path plus a multi-node reachability sweep), a divider-drag listener + leak with proper teardown, folder-rename path-remap across all path-keyed + settings, and merge-before-write in the order/sort stores. +- **0.140.4** — Batch B: a drag-and-drop cycle guard for before/after drops (a + parent dropped adjacent to its own child could write a cycle to frontmatter), + an author-rename in-flight guard leak, an integrity-watcher sweep race, and a + cap on the import log. +- **0.140.5** — Batch C: the time picker treats a typed hour `0`/`00` as + midnight; filename id-parsing is tightened to the exact id shape; task titles + strip only the real id (no longer eating a trailing word like "review"); + numeric `due` values parse; the tasks view uses calendar-day boundaries for + "today"; the render cache re-arms its dirty flag on a failed write; and + undo/redo push the action back on failure so it stays retryable. +- **0.140.6** — Batch D: untrusted `.stash` imports now strip reserved + frontmatter (preventing authorship/pin/due/assignee spoofing); the detail + panel validates a note's color on read (blocking a `url(...)` CSS request) and + guards its child recursion against a looping tree; destructive confirmation + dialogs focus Cancel so a stray Enter can't fire them; the custom-color dialog + no longer paints its default when dismissed untouched; and the color-picker + registers its keyboard handlers once. +- **0.140.7** — Batch E (views): the Trash permanent-delete confirmation focuses + Cancel so Enter can't irreversibly purge; the shift-range selection anchor is + tracked by item identity so it survives a restore/purge; "Restore selected" + surfaces partial failures; and the aggregate view resolves an archived blob's + restore destination to the nearest `archive/` folder. +- **0.140.8** — Batch E (crypto): the mid-lock "don't destroy a file edited + since it was bundled" guard now covers attachments, not just notes; and + encrypting-to-trash rolls back and aborts (keeping the note intact) if the + trash metadata sidecar can't be written, rather than deleting the plaintext + and stranding the content. diff --git a/scripts/reset-test-folder b/scripts/reset-test-folder index d7d4d2f..8416263 100755 --- a/scripts/reset-test-folder +++ b/scripts/reset-test-folder @@ -2,12 +2,60 @@ # Reset a Claude Dev Vault TEST folder to its canonical state — fast vault reset # between tests. ONLY Alpha/Beta; NEVER the "Stashpad" folder (real data) or # "_deleted". Usage: scripts/reset-test-folder {Alpha|Beta} +# +# 2026-07-02: routes through obs-dev when the dedicated dev instance is up (it +# OWNS the Claude Dev Vault, so the obsidian-cli can't reach it), else falls back +# to the obsidian-cli. Both calls are wrapped in a hard timeout so a driver that +# can't reach the vault fails FAST instead of hanging forever (the overnight bug: +# reset-test-folder blocked >2min because `obs` targeted the main instance while +# obs-dev held the vault, with no timeout). DIR="$(cd "$(dirname "$0")" && pwd)" -OBS="$DIR/obs" F="$1" case "$F" in Alpha|Beta) ;; *) echo "usage: reset-test-folder {Alpha|Beta} (never touches Stashpad)" >&2; exit 1;; esac -"$OBS" eval code="window.__resetFolder='$F'" >/dev/null 2>&1 -"$OBS" eval-file "$DIR/reset-test-folder.js" + +# Portable 45s timeout: use timeout/gtimeout if present (Linux / brew coreutils), +# else a pure-sh shim (macOS ships neither). Returns 124 on timeout, like GNU. +TIMEOUT_SECS=45 +if command -v timeout >/dev/null 2>&1; then + run() { timeout "$TIMEOUT_SECS" "$@"; } +elif command -v gtimeout >/dev/null 2>&1; then + run() { gtimeout "$TIMEOUT_SECS" "$@"; } +else + run() { + "$@" & cmd_pid=$! + ( sleep "$TIMEOUT_SECS"; kill -TERM "$cmd_pid" 2>/dev/null ) & watch_pid=$! + wait "$cmd_pid" 2>/dev/null; rc=$? + # Discriminator: if the watcher is STILL alive (sleeping), the command + # finished first → keep its real rc and cancel the watcher. If the watcher + # is already gone, it fired the TERM → this was a timeout (124). + if kill -0 "$watch_pid" 2>/dev/null; then + kill "$watch_pid" 2>/dev/null; wait "$watch_pid" 2>/dev/null + else + rc=124 + fi + return "$rc" + } +fi + +# Which driver owns the vault? obs-dev listens on :9222 when running. Its eval +# takes JS positionally; the obsidian-cli takes it as `code=<js>`. +if curl -s --max-time 2 "http://localhost:9222/json/version" >/dev/null 2>&1; then + echo "reset-test-folder: using obs-dev (dedicated instance holds the vault)" + run "$DIR/obs-dev" eval "window.__resetFolder='$F'" >/dev/null 2>&1 + run "$DIR/obs-dev" eval-file "$DIR/reset-test-folder.js" + rc=$? +else + echo "reset-test-folder: using obsidian-cli (obs-dev not running)" + run "$DIR/obs" eval code="window.__resetFolder='$F'" >/dev/null 2>&1 + run "$DIR/obs" eval-file "$DIR/reset-test-folder.js" + rc=$? +fi + +if [ "$rc" = 124 ]; then + echo "reset-test-folder: TIMED OUT after 45s — is the right instance holding the Claude Dev Vault?" >&2 + echo " (obs-dev running? it's used automatically. Else put your main Obsidian on the Claude Dev Vault.)" >&2 +fi +exit "$rc" diff --git a/src/aggregate-view.ts b/src/aggregate-view.ts index a097cbf..c8b5d3f 100644 --- a/src/aggregate-view.ts +++ b/src/aggregate-view.ts @@ -1,6 +1,6 @@ import { ItemView, TFile, WorkspaceLeaf, moment, setIcon, type ViewStateResult } from "obsidian"; import type StashpadPlugin from "./main"; -import { STASHPAD_AGGREGATE_VIEW_TYPE } from "./types"; +import { STASHPAD_AGGREGATE_VIEW_TYPE, archiveSubfolderOf } from "./types"; import { renderTaskTriage, defaultTaskTriageState, type TaskTriageState } from "./task-render"; import { renderAggModeBar, type AggMode } from "./agg-modes"; import { returnToOriginOnClose } from "./leaf-return"; @@ -8,7 +8,7 @@ import { returnToOriginOnClose } from "./leaf-return"; // Obsidian types `moment` as a namespace (not callable); cast to a callable. const momentFn = moment as unknown as (...args: unknown[]) => { fromNow: () => string }; -export type AggregateMode = "encrypted" | "archived" | "tasks"; +export type AggregateMode = "encrypted" | "archived" | "tasks" | "watch"; interface AggregateState { mode: AggregateMode } @@ -35,12 +35,12 @@ export class StashpadAggregateView extends ItemView { constructor(leaf: WorkspaceLeaf, private plugin: StashpadPlugin) { super(leaf); } getViewType(): string { return STASHPAD_AGGREGATE_VIEW_TYPE; } - getDisplayText(): string { return this.mode === "archived" ? "All archived" : this.mode === "tasks" ? "All tasks" : "All encrypted"; } - getIcon(): string { return this.mode === "archived" ? "archive" : this.mode === "tasks" ? "check-square" : "lock"; } + getDisplayText(): string { return this.mode === "archived" ? "All archived" : this.mode === "tasks" ? "All tasks" : this.mode === "watch" ? "Previously encrypted" : "All encrypted"; } + getIcon(): string { return this.mode === "archived" ? "archive" : this.mode === "tasks" ? "check-square" : this.mode === "watch" ? "history" : "lock"; } getState(): Record<string, unknown> { return { ...super.getState(), mode: this.mode }; } async setState(state: AggregateState, result: unknown): Promise<void> { - if (state?.mode === "archived" || state?.mode === "encrypted" || state?.mode === "tasks") this.mode = state.mode; + if (state?.mode === "archived" || state?.mode === "encrypted" || state?.mode === "tasks" || state?.mode === "watch") this.mode = state.mode; await super.setState(state, result as ViewStateResult); await this.render(); } @@ -63,9 +63,15 @@ export class StashpadAggregateView extends ItemView { private cleanFolder(p: string): string { return (p || "").replace(/\/+$/, ""); } - /** All folders flagged as archive — legacy list ∪ new per-folder prefs. */ + /** 0.136.0: every folder's `archive/` subfolder that exists on disk, plus any + * LEGACY dedicated archive folders still present (pre-migration vaults). */ private archiveFolders(): string[] { - const set = new Set<string>((this.plugin.settings.archiveFolders ?? []).map((f) => this.cleanFolder(f))); + const set = new Set<string>(); + for (const f of this.plugin.discoverStashpadFolders()) { + const sub = archiveSubfolderOf(f); + if (this.plugin.app.vault.getAbstractFileByPath(sub)) set.add(sub); + } + for (const f of (this.plugin.settings.archiveFolders ?? [])) set.add(this.cleanFolder(f)); const prefs = this.plugin.settings.folderEncPrefs ?? {}; for (const [folder, p] of Object.entries(prefs)) if (p?.archive) set.add(this.cleanFolder(folder)); return [...set].sort((a, b) => a.localeCompare(b)); @@ -90,6 +96,7 @@ export class StashpadAggregateView extends ItemView { return; } if (this.mode === "archived") { this.renderArchived(root); return; } + if (this.mode === "watch") { this.renderWatch(root); return; } this.renderEncrypted(root); } @@ -232,7 +239,7 @@ export class StashpadAggregateView extends ItemView { restore.onclick = async () => { restore.disabled = true; const ok = await this.plugin.unarchiveNote(f); if (ok) void this.render(); else restore.disabled = false; }; } - /** One locked archived subtree — Export (un-archiving encrypted needs a password). */ + /** One locked archived subtree — Restore (unlock, needs the password) + Export. */ private archiveLockedRow(container: HTMLElement, e: { blob: string; title?: string; count?: number; created?: string }): void { const row = container.createDiv({ cls: "stashpad-trash-row" }); const main = row.createDiv({ cls: "stashpad-trash-row-main" }); @@ -240,6 +247,25 @@ export class StashpadAggregateView extends ItemView { const when = e.created ? `archived ${momentFn(e.created).fromNow()}` : "archived"; const count = (e.count ?? 0) > 1 ? ` · ${e.count} notes` : ""; main.createSpan({ cls: "stashpad-trash-sub", text: when + count }); + // 0.136.2: Restore on locked rows too (mirrors the trash view). Unlocks the + // bundle (prompts for the password if needed) and restores the notes to the + // archive's PARENT folder; a legacy dedicated-archive blob (no /archive/ + // segment) unlocks in place, as the placeholder's Unlock always did. + const restore = row.createEl("button", { cls: "stashpad-trash-restore", text: "Restore" }); + setIcon(restore.createSpan({ cls: "stashpad-btn-icon" }), "rotate-ccw"); + restore.setAttr("aria-label", "Unlock and restore (needs the password)"); + restore.onclick = async () => { + restore.disabled = true; + const segs = e.blob.split("/"); + // lastIndexOf, not indexOf: the archive dir holding the blob is the one + // NEAREST it. indexOf matched a higher-level folder literally named + // "archive" (an "archive" folder at the vault root, or + // Work/archive/Projects/archive/blob) → restored to the wrong parent. (0.140.7) + const archIdx = segs.lastIndexOf("archive"); + const dest = archIdx > 0 ? segs.slice(0, archIdx).join("/") : undefined; + const ok = await this.plugin.unlockBundleAt(e.blob, dest ? { destFolder: dest } : {}); + if (ok) void this.render(); else restore.disabled = false; + }; const exp = row.createEl("button", { cls: "stashpad-trash-iconbtn", text: "Export" }); exp.setAttr("aria-label", "Export to .stash (encrypted with a password you choose)"); exp.onclick = () => void this.plugin.exportLockedSubtree(e.blob); @@ -247,6 +273,109 @@ export class StashpadAggregateView extends ItemView { setIcon(lock, "lock"); } + /** 0.138.0 "Previously encrypted": subtrees that WERE encrypted and are now + * plaintext (unlocked or restored from trash). Watching + a recoverable + * "Removed from watchlist" section; per-SUBTREE rows. */ + private watchFolderFilter = ""; + private watchViaFilter: "" | "unlock" | "restore" = ""; + private watchQuery = ""; + + private renderWatch(root: HTMLElement): void { + // 0.138.0: drop ghost entries (deleted / re-locked elsewhere) before showing. + if (this.plugin.pruneReEncryptWatch()) void this.plugin.saveSettings(); + const all = this.plugin.settings.reEncryptWatch ?? []; + const watching = all.filter((w) => !w.removed); + const removed = all.filter((w) => w.removed); + + if (all.length === 0) { + root.createDiv({ cls: "stashpad-trash-empty", text: "Nothing here yet. When you unlock an encrypted note (or restore one from the trash), it shows up here so you can re-encrypt it later." }); + return; + } + + // Filter bar: folder + how-it-got-here + Sift search. + const bar = root.createDiv({ cls: "stashpad-trash-modes" }); + const folderSel = bar.createEl("select", { cls: "dropdown" }); + folderSel.createEl("option", { value: "", text: "All folders" }); + for (const f of [...new Set(watching.map((w) => w.folder))].sort()) folderSel.createEl("option", { value: f, text: f }); + folderSel.value = this.watchFolderFilter; + folderSel.onchange = () => { this.watchFolderFilter = folderSel.value; void this.render(); }; + const viaSel = bar.createEl("select", { cls: "dropdown" }); + viaSel.createEl("option", { value: "", text: "Unlocked + restored" }); + viaSel.createEl("option", { value: "unlock", text: "Unlocked" }); + viaSel.createEl("option", { value: "restore", text: "Restored from trash" }); + viaSel.value = this.watchViaFilter; + viaSel.onchange = () => { this.watchViaFilter = viaSel.value as typeof this.watchViaFilter; void this.render(); }; + const search = bar.createEl("input", { type: "text", attr: { placeholder: "Search\u2026" } }); + search.value = this.watchQuery; + search.oninput = () => { this.watchQuery = search.value; renderLists(); }; + + // Sift: all tokens, any order, case-insensitive substring. + const sift = (q: string, hay: string): boolean => + q.toLowerCase().split(/\s+/).filter(Boolean).every((t) => hay.toLowerCase().includes(t)); + const matches = (w: (typeof all)[number]): boolean => + (!this.watchFolderFilter || w.folder === this.watchFolderFilter) + && (!this.watchViaFilter || w.via === this.watchViaFilter) + && (!this.watchQuery.trim() || sift(this.watchQuery, `${w.title} ${w.folder}`)); + + const save = async () => { await this.plugin.saveSettings(); void this.render(); }; + const row = (host: HTMLElement, w: (typeof all)[number], inRemoved: boolean): void => { + const r = host.createDiv({ cls: "stashpad-trash-row" }); + const main = r.createDiv({ cls: "stashpad-trash-row-main" }); + main.createSpan({ cls: "stashpad-trash-title", text: w.title || "(untitled)" }); + const when = momentFn(w.unlockedAt).fromNow(); + const count = w.count > 1 ? ` \u00b7 ${w.count} notes` : ""; + main.createSpan({ cls: "stashpad-trash-sub", text: `${w.via === "restore" ? "restored" : "unlocked"} ${when} \u00b7 ${w.folder.split("/").pop() || w.folder}${count}` }); + if (!inRemoved) { + const relock = r.createEl("button", { cls: "stashpad-trash-restore", text: "Re-encrypt" }); + setIcon(relock.createSpan({ cls: "stashpad-btn-icon" }), "lock"); + relock.onclick = async () => { + relock.disabled = true; + const ok = await this.plugin.lockNoteSubtree(w.folder, w.rootId, null, {}); + // Success clears the entry via the lock hook; either way, repaint truth. + if (!ok) relock.disabled = false; + void this.render(); + }; + // 0.139.0 per-note peek override: minutes until auto-re-encrypt. Blank = + // inherit folder/global; 0 = off for this note. + const peek = r.createEl("input", { type: "number", cls: "stashpad-watch-peek", attr: { placeholder: "auto", title: "Auto re-encrypt after N idle minutes (blank = folder/global default, 0 = off)", min: "0" } }); + peek.value = w.peekMin == null ? "" : String(w.peekMin); + peek.onchange = () => { const s = peek.value.trim(); const n = Number(s); if (s === "") delete w.peekMin; else if (Number.isFinite(n) && n >= 0) w.peekMin = Math.floor(n); else { peek.value = w.peekMin == null ? "" : String(w.peekMin); return; } void this.plugin.saveSettings(); }; + const rm = r.createEl("button", { cls: "stashpad-trash-iconbtn" }); + setIcon(rm, "x"); + rm.setAttr("aria-label", "Remove from watchlist (recoverable below)"); + rm.onclick = () => { w.removed = true; w.removedAt = new Date().toISOString(); void save(); }; + } else { + const un = r.createEl("button", { cls: "stashpad-trash-iconbtn", text: "Un-remove" }); + un.onclick = () => { delete w.removed; delete w.removedAt; void save(); }; + } + }; + + const listsHost = root.createDiv(); + const renderLists = (): void => { + listsHost.empty(); + const shown = watching.filter(matches).sort((a, b) => (b.unlockedAt || "").localeCompare(a.unlockedAt || "")); + const head = listsHost.createDiv({ cls: "stashpad-trash-group-head" }); + setIcon(head.createSpan({ cls: "stashpad-trash-group-icon" }), "history"); + head.createSpan({ text: "Watching" }); + head.createSpan({ cls: "stashpad-trash-group-count", text: String(shown.length) }); + if (watching.length > 0) { + const sweep = head.createEl("button", { cls: "stashpad-trash-iconbtn", text: "Re-encrypt all applicable" }); + sweep.onclick = () => void this.plugin.encryptEverythingApplicable(); + } + if (shown.length === 0) listsHost.createDiv({ cls: "stashpad-trash-sub", text: watching.length ? "No matches for the current filters." : "Nothing being watched." }); + const group = listsHost.createDiv({ cls: "stashpad-trash-group" }); + for (const w of shown) row(group, w, false); + + if (removed.length > 0) { + const det = listsHost.createEl("details", { cls: "stashpad-watch-removed" }); + det.createEl("summary", { text: `Removed from watchlist (${removed.length})` }); + const rg = det.createDiv({ cls: "stashpad-trash-group" }); + for (const w of removed.filter(matches)) row(rg, w, true); + } + }; + renderLists(); + } + async onClose(): Promise<void> { this.contentEl.empty(); } } @@ -263,5 +392,5 @@ export async function openAggregateView(plugin: StashpadPlugin, mode: AggregateM const leaf = workspace.getLeaf("tab"); await leaf.setViewState({ type: STASHPAD_AGGREGATE_VIEW_TYPE, active: true, state: { mode } }); workspace.revealLeaf(leaf); - returnToOriginOnClose(workspace, leaf, originLeaf); + returnToOriginOnClose(workspace, leaf, originLeaf, (ref) => plugin.registerEvent(ref)); } diff --git a/src/commands/io-cmds.ts b/src/commands/io-cmds.ts index 0f7733c..0a82126 100644 --- a/src/commands/io-cmds.ts +++ b/src/commands/io-cmds.ts @@ -227,7 +227,7 @@ export async function processStashFile(view: StashpadView, file: TFile): Promise // 0.85.4: try a passphrase remembered for this filename first (silent). const buf = await resolveStashBytes(view.app, raw, { secretId: secretIdForStashName(file.basename) }); if (!buf) return; // user cancelled the password prompt — leave the file as-is - const summary = await importStashZip(view.app, buf, view.noteFolder, collectExistingIds(view)); + const summary = await importStashZip(view.app, buf, view.noteFolder, collectExistingIds(view), { stripReserved: true }); view.tree.rebuild(view.noteFolder); view.render(); await view.log.append({ diff --git a/src/detail-view.ts b/src/detail-view.ts index 750100c..cad592c 100644 --- a/src/detail-view.ts +++ b/src/detail-view.ts @@ -11,6 +11,13 @@ import { type StashpadId, } from "./types"; +/** A valid #RGB / #RRGGBB color. Read-path guard so untrusted frontmatter + * (shared/imported notes) can't slip a `url(...)` or other CSS value into an + * inline style. The write path already enforces #RRGGBB. */ +function isHexColor(s: string): boolean { + return /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(s); +} + /** 0.74.1: right-sidebar detail panel. Mirrors the cursored note in the * active Stashpad view — title + rendered body + metadata + children * list. Counterpart to the left-side panels view (Pinned/Shared/Tasks): @@ -193,7 +200,10 @@ export class StashpadDetailView extends ItemView { const metaRow = header.createDiv({ cls: "stashpad-detail-metarow" }); if (sel.folder) metaRow.createSpan({ cls: "stashpad-detail-meta-chip", text: sel.folder.split("/").pop() || sel.folder }); - if (typeof fm.color === "string" && fm.color) { + if (typeof fm.color === "string" && isHexColor(fm.color)) { + // Validate on READ, not just write: a shared/imported note carrying + // `color: url(http://attacker/pixel)` would otherwise trigger an external + // fetch (CSS tracking/exfil) when this panel renders it. (0.140.6 review.) const c = metaRow.createSpan({ cls: "stashpad-detail-meta-color" }); c.style.background = fm.color; c.title = fm.color; @@ -351,10 +361,14 @@ export class StashpadDetailView extends ItemView { folder: string, node: { id: string; file: TFile | null }, depth: number, + seen: Set<string> = new Set(), ): void { if (!node.file) return; + // Cycle guard: a corrupted tree whose parent chain loops would otherwise + // recurse forever and blow the stack when the row is expanded. (0.140.6) + if (seen.has(node.id)) return; const fm = (this.app.metadataCache.getFileCache(node.file)?.frontmatter ?? {}) as any; - const color = typeof fm.color === "string" ? fm.color : null; + const color = typeof fm.color === "string" && isHexColor(fm.color) ? fm.color : null; const completed = fm.completed === true; const grandchildren = tree.getChildren(node.id) as Array<{ id: string; file: TFile | null }>; const hasGrandkids = grandchildren.length > 0; @@ -457,8 +471,9 @@ export class StashpadDetailView extends ItemView { label.onclick = () => { if (view?.navigateTo) view.navigateTo(node.id); }; if (hasGrandkids && isExpanded) { + const nextSeen = new Set(seen).add(node.id); for (const grandchild of grandchildren) { - this.renderDetailChildRow(parent, view, tree, folder, grandchild, depth + 1); + this.renderDetailChildRow(parent, view, tree, folder, grandchild, depth + 1, nextSeen); } } } diff --git a/src/encryption-ops.ts b/src/encryption-ops.ts index e840264..0c0dbdf 100644 --- a/src/encryption-ops.ts +++ b/src/encryption-ops.ts @@ -165,6 +165,20 @@ async function purgeSubtreePlaintext( } for (const [path, af] of subtreeAtts) { if (sharedExternally.has(path)) continue; + // Same mid-lock-edit guard as notes: if the attachment changed since it was + // baselined (re-pasted image, sync write) its newer bytes aren't in the blob + // — keep the plaintext rather than destroy the newer copy. (0.140.8) + const baseline = mtimes?.get(path); + if (baseline != null) { + try { + const st = await app.vault.adapter.stat(path); + if (st && st.mtime !== baseline) { + console.warn("[Stashpad] attachment changed since it was bundled — keeping plaintext", path); + unpurged.push(path); + continue; + } + } catch { /* stat failed — fall through and let delete try */ } + } try { await app.vault.delete(af); } catch (e) { console.warn("[Stashpad] couldn't delete exclusive attachment", path, e); unpurged.push(path); } } @@ -188,6 +202,11 @@ export async function lockSubtree( const mtimes = new Map<string, number>(); for (const n of allNodes) { try { const st = await app.vault.adapter.stat(n.file.path); if (st) mtimes.set(n.file.path, st.mtime); } catch { /* no baseline → delete proceeds unguarded */ } + // Baseline the note's exclusive attachments too, so purge skips one edited + // mid-lock (its newer bytes aren't in the blob). (0.140.8) + for (const af of await resolveNoteAttachmentFiles(app, n.file)) { + try { const st = await app.vault.adapter.stat(af.path); if (st) mtimes.set(af.path, st.mtime); } catch { /* no baseline */ } + } } const zip = await buildStashZip(app, { @@ -304,6 +323,16 @@ export async function deletedRestoreDest(app: App, blobPath: string, meta: Delet try { origin = safeVaultFolder(new TextDecoder().decode(await decryptWithKey(b64ToBytes(meta.originalFolderEnc), dek))); } catch { origin = null; } // wrong key / tampered — fall back } + const blobDir = blobPath.replace(/\/[^/]*$/, ""); + // 0.140.1: for a PER-FOLDER trash blob (`X/trash/…`) whose sidecar origin no + // longer exists, the blob's OWN location is authoritative — the trash travels + // with its folder on rename (A→B moves A/trash into B/trash), so a stale + // sidecar `originalFolder: A` would resurrect a dead folder A and restore + // there instead of B. Prefer the blob's parent folder in that case. + const trashParent = /(^|\/)trash$/.test(blobDir) ? blobDir.replace(/\/?trash$/, "") : ""; + if (origin && trashParent && origin !== trashParent && !(await app.vault.adapter.exists(origin))) { + origin = trashParent || null; + } if (origin) { // Recreate a missing origin folder rather than falling back: for trash // blobs the fallback would be `_deleted/` itself — decrypted plaintext @@ -311,10 +340,17 @@ export async function deletedRestoreDest(app: App, blobPath: string, meta: Delet if (!(await app.vault.adapter.exists(origin))) await app.vault.adapter.mkdir(origin); return origin; } - const blobDir = blobPath.replace(/\/[^/]*$/, ""); if (blobDir === DELETED_DIR || blobDir.startsWith(`${DELETED_DIR}/`)) { throw new Error("This deleted note's origin folder is unknown (missing or tampered sidecar) — can't restore it safely. The encrypted copy was kept."); } + // 0.137.0: a per-folder trash blob with no usable sidecar restores to the + // trash's PARENT folder (that IS its origin) — never into the reserved + // trash/ dir itself, where the plaintext would be hidden from every list. + if (/(^|\/)trash$/.test(blobDir)) { + const parent = blobDir.replace(/\/?trash$/, ""); + if (parent) { if (!(await app.vault.adapter.exists(parent))) await app.vault.adapter.mkdir(parent); return parent; } + throw new Error("This deleted note's origin folder is unknown (missing or tampered sidecar) — can't restore it safely. The encrypted copy was kept."); + } return blobDir; } @@ -332,6 +368,9 @@ export async function deletedRestoreDest(app: App, blobPath: string, meta: Delet * back. `deletedAt` is passed in (callers stamp it — keeps this pure-ish). */ export async function deleteEncryptSubtree( app: App, folder: string, rootId: StashpadId, dek: Uint8Array, deletedAt: string, hideTitle = false, keyId?: string, + /** 0.137.0: where the blob lands. Defaults to the legacy vault-level + * `_deleted/`; per-folder trash passes `<folder>/trash`. */ + destDir: string = DELETED_DIR, ): Promise<{ blobPath: string; noteCount: number; rootId: StashpadId; originalFolder: string; title: string; unpurged: string[] }> { const sub = await collectSubtree(app, folder, rootId); if (!sub) throw new Error("Couldn't find that note to delete."); @@ -341,6 +380,9 @@ export async function deleteEncryptSubtree( const mtimes = new Map<string, number>(); for (const n of allNodes) { try { const st = await app.vault.adapter.stat(n.file.path); if (st) mtimes.set(n.file.path, st.mtime); } catch { /* no baseline */ } + for (const af of await resolveNoteAttachmentFiles(app, n.file)) { + try { const st = await app.vault.adapter.stat(af.path); if (st) mtimes.set(af.path, st.mtime); } catch { /* no baseline */ } + } } const zip = await buildStashZip(app, { @@ -354,13 +396,18 @@ export async function deleteEncryptSubtree( if (back.length !== zip.length) throw new Error("Encryption self-check failed (size)."); for (let i = 0; i < zip.length; i++) if (back[i] !== zip[i]) throw new Error("Encryption self-check failed (content)."); - if (!(await app.vault.adapter.exists(DELETED_DIR))) await app.vault.adapter.mkdir(DELETED_DIR); + // 0.137.0: dest may be nested (X/trash) — mkdir intermediates. + { + const parts = destDir.replace(/\/+$/, "").split("/").filter(Boolean); + let cur = ""; + for (const p of parts) { cur = cur ? `${cur}/${p}` : p; try { if (!(await app.vault.adapter.exists(cur))) await app.vault.adapter.mkdir(cur); } catch { /* race */ } } + } const cleanedFolder = folder.replace(/\/+$/, ""); const folderSlug = cleanedFolder.split("/").pop() || "vault"; // Readable name groups by folder; opaque (rootId) when hiding titles. const base = hideTitle ? safeBlobBase(rootId) : safeBlobBase(`${folderSlug} ${titleFromFile(rootNote.file)}`); - let blobPath = `${DELETED_DIR}/${base}.${STASHENC_EXT}`; - for (let n = 1; await app.vault.adapter.exists(blobPath); n++) blobPath = `${DELETED_DIR}/${base} (${n}).${STASHENC_EXT}`; + let blobPath = `${destDir}/${base}.${STASHENC_EXT}`; + for (let n = 1; await app.vault.adapter.exists(blobPath); n++) blobPath = `${destDir}/${base} (${n}).${STASHENC_EXT}`; await writeBlobVerified(app, blobPath, blob); const all = allNodes; @@ -376,7 +423,15 @@ export async function deleteEncryptSubtree( ...(keyId ? { keyId } : {}), }; try { await app.vault.adapter.write(sidecarPath(blobPath), JSON.stringify(meta)); } - catch (e) { console.warn("[Stashpad] couldn't write deleted sidecar", e); } + catch (e) { + // The sidecar carries the restore metadata (origin folder + parent). Without + // it a _deleted/ blob is UNRESTORABLE from the UI (deletedRestoreDest hard- + // throws). Purging here would strand the content. Roll back the blob and + // fail the op — the note stays intact as plaintext, nothing lost. (0.140.8) + console.warn("[Stashpad] couldn't write deleted sidecar — aborting delete, keeping plaintext", e); + try { await app.vault.adapter.remove(blobPath); } catch { /* leave orphan blob; plaintext is safe */ } + throw new Error("Couldn't write trash metadata — the note was NOT deleted (kept intact)."); + } const { unpurged } = await purgeSubtreePlaintext(app, all, mtimes); return { blobPath, noteCount: all.length, rootId, originalFolder: cleanedFolder, title: meta.title, unpurged }; @@ -564,10 +619,12 @@ export async function rewrapDeletedSidecar(app: App, blobPath: string, oldDek: U await app.vault.adapter.write(sidecarPath(blobPath), JSON.stringify(meta)); } -/** Trash blob paths in `_deleted/` whose sidecar `keyId` matches `keyId`. */ -export async function deletedBlobsForKeyId(app: App, keyId: string): Promise<string[]> { +/** Trash blob paths (legacy `_deleted/` + per-folder trash dirs) whose sidecar + * `keyId` matches. Rotation callers MUST pass their trash dirs (0.137.0) or + * per-folder trash blobs would silently keep the retired key. */ +export async function deletedBlobsForKeyId(app: App, keyId: string, extraDirs: string[] = []): Promise<string[]> { const out: string[] = []; - for (const b of await listDeletedBlobs(app)) { + for (const b of await listDeletedBlobs(app, extraDirs)) { const m = await readDeletedMeta(app, b); if (m?.keyId === keyId) out.push(b); } @@ -708,11 +765,21 @@ export async function listRawFolderBlobs(app: App): Promise<{ folder: string; bl return out; } -/** All encrypted-deleted blob paths in `_deleted/`. */ -export async function listDeletedBlobs(app: App): Promise<string[]> { - if (!(await app.vault.adapter.exists(DELETED_DIR))) return []; - try { - const listing = await app.vault.adapter.list(DELETED_DIR); - return listing.files.filter((f) => f.endsWith(`.${STASHENC_EXT}`)); - } catch { return []; } +/** 0.137.0: a Stashpad folder's own trash subfolder (per-folder trash). */ +export function trashSubfolderOf(folder: string): string { + return `${(folder || "").replace(/\/+$/, "")}/trash`; +} + +/** All encrypted-deleted blob paths: the legacy vault-level `_deleted/` UNION + * (0.137.0) every per-folder `trash/` subfolder passed in `extraDirs`. */ +export async function listDeletedBlobs(app: App, extraDirs: string[] = []): Promise<string[]> { + const out: string[] = []; + for (const dir of [DELETED_DIR, ...extraDirs]) { + try { + if (!(await app.vault.adapter.exists(dir))) continue; + const listing = await app.vault.adapter.list(dir); + out.push(...listing.files.filter((f) => f.endsWith(`.${STASHENC_EXT}`))); + } catch { /* skip unreadable dir */ } + } + return out; } diff --git a/src/encryption-service.ts b/src/encryption-service.ts index ecf2316..ade47fb 100644 --- a/src/encryption-service.ts +++ b/src/encryption-service.ts @@ -52,6 +52,17 @@ export interface EncryptionConfig { * private key, the only secret stored on this device. */ identityPrivWrapped: string | null; identityPrivKdf: StashKdf | null; + /** 0.136.0: every keychain id this device has ever written (primary, parked + * `-r<stamp>`, `-d-<slotId>`, folder keys). Obsidian's secretStorage has NO + * list API, so without this registry parked entries are unreachable — with + * it, unlock flows can cycle through every known candidate before ever + * prompting. */ + knownKeychainIds?: string[]; + /** 0.140.1: the user chose "Forget on this device" — suppress auto-unlock + * entirely (the cycle-unlock would otherwise resurrect a PARKED password and + * silently undo the forget). Cleared the next time a password is explicitly + * remembered. */ + keychainForgotten?: boolean; } export function defaultEncryptionConfig(): EncryptionConfig { @@ -85,16 +96,17 @@ export class EncryptionService { * folder path. Overlay on the vault DEK — a folder with no entry here uses the * vault `sessionKey`. Zeroed on lock()/idle alongside the vault key. */ private folderSessionKeys = new Map<string, Uint8Array>(); - private idleTimer: number | null = null; private keyfiles: KeyfileStore; /** In-memory cache of the synced keyfile (refreshed via init()/refresh()). */ private kf: VaultKeyfile | null = null; + // (0.135.0: the idle auto-lock timer is gone — armIdle/clearIdle/idleMinutes + // removed. The session key lives until Obsidian closes or encryption is + // removed/re-passworded.) constructor( private app: App, private load: () => EncryptionConfig, private save: (cfg: EncryptionConfig) => Promise<void>, - private idleMinutes: () => number = () => 0, ) { this.keyfiles = new KeyfileStore(app); } argonProbe(): Promise<boolean> { return argon2Available(); } @@ -146,31 +158,96 @@ export class EncryptionService { if (!pw) return false; try { return await this.verifyPassword(pw); } catch { return false; } } - private async remember(password: string): Promise<void> { - try { await this.secretStore()?.setSecret(this.keychainId(), password); } - catch (e) { console.warn("[Stashpad] couldn't save password to keychain", e); } - } - async forgetKeychain(): Promise<void> { + /** 0.135.1: NEVER overwrite a keychain entry — if `id` already holds a + * different secret, park the old value under `<id>-r<stamp>` first (Obsidian + * secret ids: /^[a-z0-9-]{1,64}$/, so the base is bounded to leave room). + * Universal retention at the write bottleneck — the change-password flows' + * `-d-<slotId>` parking still applies on top for nicer names. */ + private async parkThenSet(id: string, next: string): Promise<void> { const ss = this.secretStore(); if (!ss) return; + const touched: string[] = [id]; + try { + const cur = ss.getSecret(id); + if (cur && cur !== next) { + const stamp = Date.now().toString(36); // ~8 lowercase alnum chars + const parkId = `${id.slice(0, 64 - (stamp.length + 2))}-r${stamp}`; + await ss.setSecret(parkId, cur); + touched.push(parkId); + } + } catch { /* best-effort retention — never block the new write */ } + await ss.setSecret(id, next); + await this.recordKeychainIds(touched); + } + + /** 0.136.0: remember every keychain id we write (secretStorage can't list), + * so unlock flows can cycle through all candidates. */ + private async recordKeychainIds(ids: string[]): Promise<void> { + try { + const cfg = this.load(); + const known = new Set(cfg.knownKeychainIds ?? []); + let dirty = false; + for (const id of ids) if (id && !known.has(id)) { known.add(id); dirty = true; } + if (dirty) await this.save({ ...cfg, knownKeychainIds: [...known] }); + } catch { /* best-effort */ } + } + + /** Every keychain candidate this device knows about: current + legacy ids + + * everything ever written (parked, deprecated-slot, folder keys). */ + private keychainCandidateIds(): string[] { + const ids = [this.keychainId(), this.legacyAppIdKeychainId(), LEGACY_KEYCHAIN_ID, ...(this.load().knownKeychainIds ?? [])]; + return [...new Set(ids)]; + } + private async remember(password: string): Promise<void> { + try { await this.parkThenSet(this.keychainId(), password); } + catch (e) { console.warn("[Stashpad] couldn't save password to keychain", e); } + // 0.140.1: an explicit remember re-arms auto-unlock (clears a prior forget). + try { const cfg = this.load(); if (cfg.keychainForgotten) await this.save({ ...cfg, keychainForgotten: false }); } catch { /* best-effort */ } + } + async forgetKeychain(): Promise<void> { + // 0.140.1: mark forgotten so tryAutoUnlock won't resurrect a parked password + // — this is the whole point of "Forget on this device". + try { await this.save({ ...this.load(), keychainForgotten: true }); } catch { /* best-effort */ } + const ss = this.secretStore(); + if (!ss) return; + // 0.135.1: park the current value first (no-delete policy) — forgetting + // drops the ACTIVE entry but the old password stays recoverable. + try { const cur = ss.getSecret(this.keychainId()); if (cur) await this.parkThenSet(this.keychainId(), ""); return; } catch { /* fall through */ } try { if (ss.removeSecret) await ss.removeSecret(this.keychainId()); else await ss.setSecret(this.keychainId(), ""); } catch (e) { console.warn("[Stashpad] couldn't clear keychain", e); } } + /** 0.136.0: cycle EVERY known keychain candidate (current id, legacy ids, + * parked `-r`/`-d` copies, even folder-key values) before giving up — one + * stale primary entry no longer means a prompt (or a scare) when a working + * password is sitting one id over. On a hit from a non-primary id, the + * winning password is promoted to the primary id (old value parks). */ async tryAutoUnlock(): Promise<boolean> { if (!this.isConfigured() || this.isUnlocked()) return this.isUnlocked(); + // 0.140.1: honor an explicit "Forget on this device" — don't cycle parked + // passwords back into an unlock. + if (this.load().keychainForgotten) return false; // Can't auto-unlock unless this device can unlock at all: a member (its own // slot) or any device when a shared password is enabled. if (this.accessState() !== "member" && !this.hasSharedPassword()) return false; const ss = this.secretStore(); - let stored: string | null = null; - try { stored = ss?.getSecret(this.keychainId()) ?? null; } catch { stored = null; } - if (stored) return this.unlock(stored); - let legacy: string | null = null; - try { legacy = ss?.getSecret(LEGACY_KEYCHAIN_ID) ?? null; } catch { legacy = null; } - if (!legacy) return false; - const ok = await this.unlock(legacy); - if (ok) { await this.remember(legacy); try { if (ss?.removeSecret) await ss.removeSecret(LEGACY_KEYCHAIN_ID); else await ss?.setSecret(LEGACY_KEYCHAIN_ID, ""); } catch { /* */ } } - return ok; + if (!ss) return false; + const primary = this.keychainId(); + const tried = new Set<string>(); + for (const id of this.keychainCandidateIds()) { + let pw: string | null = null; + try { pw = ss.getSecret(id) ?? null; } catch { pw = null; } + if (!pw || tried.has(pw)) continue; + tried.add(pw); + // verify first (cheap KDF check) so a wrong candidate doesn't spam + // failed-unlock side effects. + let ok = false; + try { ok = await this.verifyPassword(pw); } catch { ok = false; } + if (!ok) continue; + if (!(await this.unlock(pw))) continue; + if (id !== primary) await this.remember(pw); // promote (parks the stale primary) + return true; + } + return false; } // ---- state ---- @@ -217,7 +294,6 @@ export class EncryptionService { // explicit "Forget on this device" button and remove-encryption, so a password the // user asked us to keep can't be silently zeroed by going through a later flow. if (remember) await this.remember(password); - this.armIdle(); } /** Unlock the session DEK with this device's password. Returns false on wrong @@ -240,7 +316,6 @@ export class EncryptionService { // auto-unlock calls unlock() with remember=false; force-forgetting would // wipe the very password it just used to unlock.) if (remember) await this.remember(password); - this.armIdle(); return true; } // Member (device-approval) path — unwrap my private key, then the DEK from my @@ -253,9 +328,11 @@ export class EncryptionService { try { const dek = await unwrapDekWith(slot, priv); priv.fill(0); + // 0.140.2: a tampered slot could yield a wrong-length "DEK" — reject + // rather than silently AES-key on garbage (which would corrupt/mislead). + if (dek.length !== DEK_LEN) { dek.fill(0); return false; } this.sessionKey = dek; if (remember) await this.remember(password); - this.armIdle(); return true; } catch { priv.fill(0); } } catch { /* wrong password for my key — try shared password below */ } @@ -267,9 +344,9 @@ export class EncryptionService { for (const ps of (this.kf?.passwordSlots ?? []).filter((s) => !s.label.startsWith("[deprecated]"))) { try { const dek = await decryptStash(fromB64(ps.wrapped), password); + if (dek.length !== DEK_LEN) { dek.fill(0); continue; } // 0.140.2: reject wrong-length DEK this.sessionKey = dek; if (remember) await this.remember(password); - this.armIdle(); return true; } catch { /* not this slot */ } } @@ -293,7 +370,7 @@ export class EncryptionService { const prevActive = (this.kf.passwordSlots ?? []).filter((s) => !s.label.startsWith("[deprecated]")); const oldPw = this.rememberedPassword(); if (oldPw && prevActive[0]) { - try { await this.secretStore()?.setSecret(`${this.keychainId()}-d-${prevActive[0].id}`, oldPw); } catch { /* best-effort retention */ } + try { await this.parkThenSet(`${this.keychainId()}-d-${prevActive[0].id}`, oldPw); } catch { /* best-effort retention */ } } const wrapped = await encryptStash(this.sessionKey, passphrase); const retained = (this.kf.passwordSlots ?? []).map((s) => s.label.startsWith("[deprecated]") ? s : { ...s, label: `[deprecated] ${s.label}` }); @@ -376,7 +453,6 @@ export class EncryptionService { if (!owner) return this.getSessionKey(); const k = this.folderSessionKeys.get(owner); if (!k) return null; - this.armIdle(); return k.slice(); } @@ -405,7 +481,6 @@ export class EncryptionService { await this.keyfiles.save(this.kf); this.folderSessionKeys.set(f, dek); if (remember) await this.rememberFolder(this.folderKcIdFor(entry), password); - this.armIdle(); } /** Unlock a folder's own key with `password`. False on wrong password / no entry. */ @@ -420,7 +495,6 @@ export class EncryptionService { this.folderSessionKeys.set(entry.folderPath, dek); // key the session by the OWNING folder const kcId = this.folderKcIdFor(entry); if (remember || this.isFolderRemembered(kcId)) await this.rememberFolder(kcId, password); - this.armIdle(); return true; } catch { /* try next slot */ } } @@ -431,9 +505,26 @@ export class EncryptionService { async tryAutoUnlockFolder(folder: string): Promise<boolean> { const entry = this.folderKeyEntry(folder); if (!entry || this.isFolderUnlocked(folder)) return this.isFolderUnlocked(folder); - const pw = this.rememberedFolderPassword(this.folderKcIdFor(entry)); - if (!pw) return false; - return this.unlockFolder(folder, pw); + const kcId = this.folderKcIdFor(entry); + // Primary id first, then (0.136.0) cycle every other known candidate — + // parked copies included. A hit from a non-primary id is promoted. + const pw = this.rememberedFolderPassword(kcId); + if (pw && await this.unlockFolder(folder, pw)) return true; + const ss = this.secretStore(); + if (!ss) return false; + const tried = new Set<string>(pw ? [pw] : []); + for (const id of this.keychainCandidateIds()) { + if (id === kcId) continue; + let cand: string | null = null; + try { cand = ss.getSecret(id) ?? null; } catch { cand = null; } + if (!cand || tried.has(cand)) continue; + tried.add(cand); + if (await this.unlockFolder(folder, cand)) { + await this.rememberFolder(kcId, cand); // promote (parks any stale value) + return true; + } + } + return false; } /** Cheap "Change password" for a folder: re-wrap the SAME DEK under a new @@ -459,7 +550,7 @@ export class EncryptionService { const kcId = this.folderKcIdFor(entry); const oldPw = this.rememberedFolderPassword(kcId); if (oldPw && prevActive[0]) { - try { await this.secretStore()?.setSecret(`${kcId}-d-${prevActive[0].id}`, oldPw); } catch { /* best-effort retention */ } + try { await this.parkThenSet(`${kcId}-d-${prevActive[0].id}`, oldPw); } catch { /* best-effort retention */ } } const wrapped = await encryptStash(dek, newPassword); // Relabel every prior slot as [deprecated] (retained), prepend the new active one. @@ -496,7 +587,7 @@ export class EncryptionService { const prevActive = (entry?.passwordSlots ?? []).filter((s) => !s.label.startsWith("[deprecated]")); const oldPw = oldKcId ? this.rememberedFolderPassword(oldKcId) : null; if (oldPw && prevActive[0]) { - try { await this.secretStore()?.setSecret(`${oldKcId}-d-${prevActive[0].id}`, oldPw); } catch { /* best-effort */ } + try { await this.parkThenSet(`${oldKcId}-d-${prevActive[0].id}`, oldPw); } catch { /* best-effort */ } } const wrapped = await encryptStash(newDek, newPassword); const retained = (entry?.passwordSlots ?? []).map((s) => s.label.startsWith("[deprecated]") ? s : { ...s, label: `[deprecated] ${s.label}` }); @@ -539,7 +630,7 @@ export class EncryptionService { try { return this.secretStore()?.getSecret(kcId) || null; } catch { return null; } } private async rememberFolder(kcId: string, password: string): Promise<void> { - try { await this.secretStore()?.setSecret(kcId, password); } + try { await this.parkThenSet(kcId, password); } catch (e) { console.warn("[Stashpad] couldn't save folder password to keychain", e); } } async forgetFolderKeychain(folder: string): Promise<void> { @@ -548,6 +639,8 @@ export class EncryptionService { const ss = this.secretStore(); if (!ss) return; const id = this.folderKcIdFor(entry); + // 0.135.1: park before forgetting (no-delete policy), same as forgetKeychain. + try { const cur = ss.getSecret(id); if (cur) { await this.parkThenSet(id, ""); return; } } catch { /* fall through */ } try { if (ss.removeSecret) await ss.removeSecret(id); else await ss.setSecret(id, ""); } catch (e) { console.warn("[Stashpad] couldn't clear folder keychain", e); } } @@ -581,7 +674,6 @@ export class EncryptionService { // already saved here (the old one is now invalid — replace it so auto-unlock // doesn't keep trying a stale password). Explicit forgetting lives elsewhere. if (remember || this.isRemembered()) await this.remember(newPassword); - this.armIdle(); return true; } @@ -651,7 +743,6 @@ export class EncryptionService { // the explicit Lock command should drop ALL key material from memory). for (const k of this.folderSessionKeys.values()) k.fill(0); this.folderSessionKeys.clear(); - this.clearIdle(); } /** Lock a SINGLE folder's key (leave the vault key + other folders unlocked). @@ -666,22 +757,40 @@ export class EncryptionService { private cleanFolder(p: string): string { return (p || "").replace(/\/+$/, ""); } /** Remove encryption entirely (caller gates on "no .stashenc exists"): wipe the - * session key, this device's identity, the legacy wrap, AND the synced keyfile - * + backups (no encrypted content remains, so this is safe). */ + * session key, this device's identity, the legacy wrap, and the ACTIVE keyfile. + * + * 0.135.0: the `_keys/` rolling backups are PARKED, not deleted. The old code + * wiped every backup, so one Remove-encryption run destroyed all key history — + * hostile if the user ever needs an old key back. Backups now move to + * `_keys/removed-<stamp>/`, which KeyfileStore.load() never reads (it only + * lists `_keys/` top-level files), so the vault still comes up "not encrypted" + * while the wrapped keys stay recoverable by hand. */ async clear(): Promise<void> { this.lock(); await this.forgetKeychain(); try { const a = this.app.vault.adapter; for (const p of [".stashpad/keys.json"]) { try { if (await a.exists(p)) await a.remove(p); } catch { /* */ } } - try { const list = await a.list("_keys"); for (const f of (list.files || [])) { if (/\/keys-\d+\.json$/.test(f)) { try { await a.remove(f); } catch { /* */ } } } } catch { /* */ } + try { + const list = await a.list("_keys"); + const backups = (list.files || []).filter((f) => /\/keys-\d+\.json$/.test(f)); + if (backups.length) { + const stamp = new Date().toISOString().slice(0, 19).replace(/[:T]/g, "-"); + const parkDir = `_keys/removed-${stamp}`; + try { if (!(await a.exists(parkDir))) await a.mkdir(parkDir); } catch { /* race */ } + for (const f of backups) { + const name = f.split("/").pop()!; + try { await a.write(`${parkDir}/${name}`, await a.read(f)); await a.remove(f); } + catch { /* keep the original rather than risk losing it */ } + } + } + } catch { /* */ } } catch { /* best-effort */ } this.kf = null; await this.save(defaultEncryptionConfig()); } getSessionKey(): Uint8Array | null { - if (this.sessionKey) this.armIdle(); return this.sessionKey ? this.sessionKey.slice() : null; } @@ -703,13 +812,5 @@ export class EncryptionService { return { id: id.id, label: id.label, pubKey: id.pub, addedAt: new Date().toISOString() }; } - private armIdle(): void { - this.clearIdle(); - const mins = this.idleMinutes(); - if (mins > 0) this.idleTimer = window.setTimeout(() => this.lock(), mins * 60_000); - } - private clearIdle(): void { - if (this.idleTimer != null) { window.clearTimeout(this.idleTimer); this.idleTimer = null; } - } dispose(): void { this.lock(); } } diff --git a/src/folder-panel-view.ts b/src/folder-panel-view.ts index 6383d20..b59e3a0 100644 --- a/src/folder-panel-view.ts +++ b/src/folder-panel-view.ts @@ -105,25 +105,40 @@ export class StashpadFolderPanelView extends ItemView { pending = f; pinnedSection.style.flex = `0 0 ${(f * 100).toFixed(2)}%`; }; - const onUp = (ev: PointerEvent) => { + // 0.140.3 (review): tear the document listeners down on pointerup AND + // pointercancel (mobile scroll/palm-rejection ends a gesture with cancel, + // not up), and expose the cleanup so onClose can run it if the view is + // detached mid-drag — the old code leaked the document listeners + left the + // resizing cursor class stuck when either happened. + const cleanup = () => { document.removeEventListener("pointermove", onMove); document.removeEventListener("pointerup", onUp); + document.removeEventListener("pointercancel", onUp); document.body.removeClass("stashpad-folderpanel-resizing"); + this.dividerCleanup = null; + }; + const onUp = (ev: PointerEvent) => { try { divider.releasePointerCapture(ev.pointerId); } catch { /* noop */ } - if (pending != null) { + if (ev.type === "pointerup" && pending != null) { this.plugin.settings.folderPanelPinnedFraction = pending; void this.plugin.saveSettings(); } + cleanup(); }; - divider.addEventListener("pointerdown", (ev: PointerEvent) => { + this.registerDomEvent(divider, "pointerdown", (ev: PointerEvent) => { ev.preventDefault(); document.body.addClass("stashpad-folderpanel-resizing"); try { divider.setPointerCapture(ev.pointerId); } catch { /* noop */ } document.addEventListener("pointermove", onMove); document.addEventListener("pointerup", onUp); + document.addEventListener("pointercancel", onUp); + this.dividerCleanup = cleanup; }); } + /** 0.140.3: run any in-flight divider-drag cleanup (see attachDividerDrag). */ + private dividerCleanup: (() => void) | null = null; + // ---------- pinned notes (top) ---------- /** Pin subtree expansion state (key = `folder|id`), kept across re-renders. */ @@ -497,7 +512,8 @@ export class StashpadFolderPanelView extends ItemView { // 0.118.0: a user-set per-folder icon wins; otherwise archive folders show // the archive glyph and everything else the generic folder icon. setIcon(folderIcon, this.plugin.getFolderIcon(folder) ?? (isArchive ? "archive" : "folder")); - if (isArchive) folderIcon.setAttr("aria-label", "Archive folder — notes moved in are auto-encrypted"); + // 0.134.4 (B3): archives are plaintext by default now — don't claim auto-encryption. + if (isArchive) folderIcon.setAttr("aria-label", "Archive folder — de-indexed from search"); const homeColor = this.folderHomeColor(folder); if (homeColor) folderIcon.style.color = homeColor; @@ -626,44 +642,10 @@ export class StashpadFolderPanelView extends ItemView { menu.addItem((i) => i.setTitle("Decrypt (unlock) all notes in folder").setIcon("unlock") .onClick(() => void this.plugin.unlockFolder(folder))); } - // 0.98.25 (Phase 4): archive folder toggle. Marking requires an explicit - // confirm — auto-lock permanently deletes the arriving note's plaintext. - const cleaned = folder.replace(/\/+$/, ""); - const isArchive = this.plugin.isArchiveFolder(cleaned); - menu.addItem((i) => i.setTitle(isArchive ? "Unmark archive folder" : "Mark as archive folder…").setIcon("archive") - .onClick(async () => { - if (isArchive) { - this.plugin.settings.archiveFolders = (this.plugin.settings.archiveFolders ?? []).filter((f) => f !== cleaned); - await this.plugin.saveSettings(); - new Notice(`"${cleaned.split("/").pop()}" is no longer an archive folder. Existing locked notes stay locked.`, 0); - this.render(); - return; - } - new ConfirmModal( - this.app, - `Make "${cleaned.split("/").pop()}" an archive folder?`, - [ - `An archive folder automatically LOCKS (encrypts) any note you move into it.`, - ``, - `What that means in plain terms:`, - `• "Encrypting" scrambles the note with your encryption password so its text can't be read by anyone — or any app — without that password.`, - `• The normal, readable copy is permanently removed from your vault. What's left is an unreadable, locked 🔒 placeholder.`, - `• To read or edit the note again, you unlock it with your encryption password (one click on the placeholder). Then you can re-archive it later.`, - `• If you ever lose your encryption password, the locked notes are gone for good — there is no backdoor or recovery, on purpose.`, - `• Notes ALREADY in this folder are not touched — only notes moved in from now on. (To lock the ones already here, use "Encrypt all notes in folder".)`, - ``, - `Good for folders of things you want kept private at rest: finished or sensitive material you'd rather not have readable if someone opened your vault.`, - ].join("\n"), - "Make it an archive folder", - async (ok) => { - if (!ok) return; - this.plugin.settings.archiveFolders = [...(this.plugin.settings.archiveFolders ?? []), cleaned]; - await this.plugin.saveSettings(); - new Notice(`"${cleaned.split("/").pop()}" is now an archive folder — notes moved in will be encrypted.`, 0); - this.render(); - }, - ).open(); - })); + // (0.136.0: "Mark as archive folder" is gone — every folder owns an + // `archive/` subfolder now; use "Move selection to archive" in a list, or + // the aggregated Archived view to browse. Encryption of archived notes is + // the per-folder "Encrypt archived notes" setting.) // 0.98.31: open the encrypted-trash tab (recoverable secure-deleted notes). menu.addItem((i) => i.setTitle("Open encrypted trash").setIcon("rotate-ccw") @@ -688,13 +670,14 @@ export class StashpadFolderPanelView extends ItemView { const parent = tf.parent?.path && tf.parent.path !== "/" ? `${tf.parent.path}/` : ""; const target = `${parent}${safe}`; if (this.app.vault.getAbstractFileByPath(target)) { new Notice(`"${safe}" already exists.`); return; } + // 0.140.3 (review): re-validate — the folder could've moved/been deleted + // by sync while the rename modal sat open. + if (!(this.app.vault.getAbstractFileByPath(cleaned) instanceof TFolder)) { new Notice("That folder no longer exists."); return; } try { await this.app.fileManager.renameFile(tf, target); - // Keep the configured default folder pointing at the renamed path. - if ((this.plugin.settings.folder || "").replace(/\/+$/, "") === cleaned) { - this.plugin.settings.folder = target; - await this.plugin.saveSettings(); - } + // 0.140.3: carry ALL path-keyed settings (default folder + placement + + // archive + per-folder prefs, incl. descendants) to the new path. + await this.plugin.remapFolderPathInSettings(cleaned, target); new Notice(`Renamed to "${safe}".`); } catch (err) { console.warn("[Stashpad] folder rename failed", err); @@ -725,6 +708,10 @@ export class StashpadFolderPanelView extends ItemView { }, ).open(); } + + /** 0.140.3 (review): run any in-flight divider-drag cleanup on view close so + * a mid-drag detach doesn't leak the document pointer listeners. */ + async onClose(): Promise<void> { this.dividerCleanup?.(); this.contentEl.empty(); } } /** Tiny single-input modal for renaming a folder. */ diff --git a/src/import-log.ts b/src/import-log.ts index 02a4df2..4954fb9 100644 --- a/src/import-log.ts +++ b/src/import-log.ts @@ -64,12 +64,26 @@ export class ImportLog { return null; } + /** Cap so a heavy drop-folder auto-import user can't grow the in-memory + * array (and the on-disk jsonl, re-read whole on startup) without bound. + * Newest entries win. 0.140.4 (review). */ + private static readonly MAX_ENTRIES = 5000; + append(entry: ImportLogEntry): void { this.entries.push(entry); + const overflow = this.entries.length > ImportLog.MAX_ENTRIES; + if (overflow) this.entries = this.entries.slice(-ImportLog.MAX_ENTRIES); this.writeChain = this.writeChain.then(async () => { try { await this.ensureDir(); - await this.app.vault.adapter.append(this.path, JSON.stringify(entry) + "\n"); + if (overflow) { + // Rewrite the whole trimmed log so the file tracks the capped array + // instead of appending forever. + const body = this.entries.map((e) => JSON.stringify(e)).join("\n"); + await this.app.vault.adapter.write(this.path, body + "\n"); + } else { + await this.app.vault.adapter.append(this.path, JSON.stringify(entry) + "\n"); + } } catch (e) { console.warn("[Stashpad] import log append failed", e); } diff --git a/src/import-service.ts b/src/import-service.ts index db264a0..b3e4795 100644 --- a/src/import-service.ts +++ b/src/import-service.ts @@ -2,6 +2,7 @@ import { Notice, TFile, TFolder } from "obsidian"; import type StashpadPlugin from "./main"; import { ROOT_ID, RESERVED_FRONTMATTER, toAttachmentLink } from "./types"; import { newId } from "./id-service"; +import { formatDateOnly } from "./format"; import { bodyToSlug, buildFilename } from "./slug-service"; import { splitFrontmatter, serializeNote, importStashZip, STASH_EXT } from "./stash-package"; import { resolveStashBytes, isEncryptedStash } from "./stash-crypto"; @@ -13,11 +14,8 @@ import { ConfirmModal, ImportDupChoiceModal } from "./modals"; const RESERVED_SUBFOLDERS = new Set([ "_attachments", "_authors", "_exports", "_imports", "_processed", "_archive", ".archive", // legacy (pre-0.79.10) — keep ignoring any that exist + "archive", "trash", // 0.136.0: per-folder archive/trash subfolders ]); -/** Where import originals are preserved. 0.79.10: a normal reserved folder - * (NOT dot-prefixed) — Obsidian hides/ignores dot-folders, which made - * renameFile/getAbstractFileByPath into `.archive` unreliable. */ -const ARCHIVE_DIR = "_archive"; /** Extensions that must NEVER be turned into a note/attachment by any import * path. `.stash` is handled by the dedicated stash-import path (not a note); @@ -72,6 +70,16 @@ export class ImportService { private get app() { return this.plugin.app; } + /** 0.136.0: import originals live in the folder's own archive subfolder, + * dated: `<folder>/archive/_imported/<date>/`. The date segment follows the + * user's date-format preference, sanitized for path safety (e.g. the "us" + * format's slashes become dashes). */ + private importedOriginalsDir(folder: string): string { + const raw = formatDateOnly(Date.now(), this.plugin.settings); + const seg = raw.replace(/[/\\:]+/g, "-").replace(/\s+/g, " ").trim() || "undated"; + return `${folder}/archive/_imported/${seg}`; + } + /** Called from the vault create/rename watcher. Cheap pre-filter, then * queue + (re)arm the debounced drain. */ enqueue(file: TFile): void { @@ -284,8 +292,10 @@ export class ImportService { const raw = await this.app.vault.read(file); const { fm, body } = splitFrontmatter(raw); - // Archive the original verbatim under _archive (unique name). - const archiveDir = `${folder}/${ARCHIVE_DIR}`; + // 0.136.0: archive the original verbatim under the folder's own + // archive/_imported/<date>/ (unique name). Date follows the user's + // date-format preference, path-sanitized. + const archiveDir = this.importedOriginalsDir(folder); await this.ensureFolder(archiveDir); const archivePath = await this.uniquePath(archiveDir, file.name); await this.app.fileManager.renameFile(file, archivePath); @@ -380,10 +390,10 @@ export class ImportService { private async importFolder(folder: TFolder): Promise<ImportRecord | null> { const root = folder.parent!.path.replace(/\/+$/, ""); const name = folder.name; - // Move the whole folder to _archive first; we then build notes by - // reading from its archived location, so non-md notes link to the - // final (archived) path and md content is cloned from there. - const archiveDir = `${root}/${ARCHIVE_DIR}`; + // Move the whole folder into archive/_imported/<date>/ first; we then + // build notes by reading from its archived location, so non-md notes link + // to the final (archived) path and md content is cloned from there. + const archiveDir = this.importedOriginalsDir(root); await this.ensureFolder(archiveDir); const archivePath = await this.uniquePath(archiveDir, name); await this.app.fileManager.renameFile(folder, archivePath); @@ -841,7 +851,7 @@ export class ImportService { // 0.85.4: try a passphrase remembered for this filename before prompting. const buf = await resolveStashBytes(this.app, rawBytes, { ...promptOpts, secretId: secretIdForStashName(file.basename) }); if (!buf) return false; // cancelled / "remind me later" — leave the file - const summary = await importStashZip(this.app, buf, root, existingIds); + const summary = await importStashZip(this.app, buf, root, existingIds, { stripReserved: true }); // Merge any hex→name color aliases the bundle carried (e.g. from the web // importer) into the destination folder, so the names show in the color UI. if (summary.colorAliases) { diff --git a/src/integrity-watcher.ts b/src/integrity-watcher.ts index f02eacd..ed8110e 100644 --- a/src/integrity-watcher.ts +++ b/src/integrity-watcher.ts @@ -10,7 +10,19 @@ export class IntegrityWatcher { * to entries under `folder` before diffing — otherwise switching folders * would log every note in the previously focused folder as missing. */ + /** Serialize sweeps: the read-modify-write of the shared state file must be + * atomic across a whole sweep, or two folders sweeping concurrently (e.g. + * workspace restore opening two Stashpad leaves) each read the same base + * and the second writeState clobbers the first folder's fresh snapshot. */ + private sweepChain: Promise<void> = Promise.resolve(); + async sweep(folder?: string): Promise<void> { + const next = this.sweepChain.then(() => this.doSweep(folder), () => this.doSweep(folder)); + this.sweepChain = next.catch(() => {}); + return next; + } + + private async doSweep(folder?: string): Promise<void> { const prev = await this.log.readState(); const cur = this.tree.snapshot(); diff --git a/src/leaf-return.ts b/src/leaf-return.ts index 5e8897e..2ddf95a 100644 --- a/src/leaf-return.ts +++ b/src/leaf-return.ts @@ -10,11 +10,22 @@ import type { Workspace, WorkspaceLeaf, EventRef } from "obsidian"; * * No-ops when there's no distinct origin (origin missing, or identical to the * spawned leaf) — e.g. an aggregate view opened from the command palette with - * nothing else in the main area. In that case Obsidian's default stands. */ + * nothing else in the main area. In that case Obsidian's default stands. + * + * 0.140.2 (review fixes): + * - Pass `register` (plugin.registerEvent) so the listener is OWNED by the + * plugin and cleaned on unload — a spawned tab that's never closed no longer + * leaks a live listener (nor keeps a dead-view callback alive across a + * reload). It still self-detaches on close. + * - Only re-focus the origin when the spawned tab was FOCUSED as it vanished. + * A background middle-close doesn't fire `active-leaf-change`, leaving the + * listener armed; the OLD code then hijacked the NEXT deliberate tab switch, + * yanking focus to the origin. Tracking the last-active leaf fixes that. */ export function returnToOriginOnClose( ws: Workspace, newLeaf: WorkspaceLeaf, originLeaf: WorkspaceLeaf | null, + register?: (ref: EventRef) => void, ): void { if (!originLeaf || originLeaf === newLeaf) return; const isOpen = (target: WorkspaceLeaf): boolean => { @@ -22,15 +33,19 @@ export function returnToOriginOnClose( ws.iterateAllLeaves((l) => { if (l === target) found = true; }); return found; }; + // The spawned tab was just made active by the opener, so it starts as the + // last-active leaf we've seen. + let lastActive: WorkspaceLeaf | null = newLeaf; const off: EventRef = ws.on("active-leaf-change", () => { - // Spawned tab still around (user just switched away from it) — leave it. - if (isOpen(newLeaf)) return; - // Spawned tab closed. Detach this listener and, if the origin tab is still - // open, make it active instead of whatever Obsidian picked (the right tab). + if (isOpen(newLeaf)) { lastActive = ws.activeLeaf ?? lastActive; return; } + // Spawned tab is gone. Detach either way. ws.offref(off); - if (isOpen(originLeaf)) { + // Only reclaim focus if it closed WHILE FOCUSED (foreground close) — a + // background close shouldn't hijack the tab the user just switched to. + if (lastActive === newLeaf && isOpen(originLeaf)) { ws.setActiveLeaf(originLeaf, { focus: true }); ws.revealLeaf(originLeaf); } }); + register?.(off); // plugin-owned → cleaned on unload } diff --git a/src/main.ts b/src/main.ts index 3ea674c..631ba25 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,9 +1,10 @@ import { Notice, Platform, Plugin, SuggestModal, TFile, TFolder, WorkspaceLeaf, setIcon, debounce } from "obsidian"; import { SIBLINGS_KEY, wikilinkName } from "./sheets-versions"; -import { STASHPAD_DETAIL_VIEW_TYPE, STASHPAD_FOLDER_PANEL_VIEW_TYPE, STASHPAD_PANELS_VIEW_TYPE, STASHPAD_VIEW_TYPE, parseAuthorRef, toAttachmentLink, isInReservedSubfolder, type PinnedNoteRef, type StashpadId } from "./types"; +import { STASHPAD_DETAIL_VIEW_TYPE, STASHPAD_FOLDER_PANEL_VIEW_TYPE, STASHPAD_PANELS_VIEW_TYPE, STASHPAD_VIEW_TYPE, parseAuthorRef, toAttachmentLink, isInReservedSubfolder, isArchiveSubfolderPath, archiveSubfolderOf, type PinnedNoteRef, type StashpadId } from "./types"; import { StashpadDetailView, openStashpadDetailView } from "./detail-view"; import { StashpadView, properCaseFolderPath, DeletedTrashSuggestModal } from "./view"; import { StashpadTrashView, openTrashView } from "./trash-view"; +import { ReEncryptScheduler } from "./reencrypt-scheduler"; import { StashpadAggregateView, openAggregateView } from "./aggregate-view"; import { cmdExportLockedBlob } from "./commands/io-cmds"; import { STASHPAD_TRASH_VIEW_TYPE, STASHPAD_AGGREGATE_VIEW_TYPE } from "./types"; @@ -11,8 +12,8 @@ import { StashpadPanelsView, openStashpadPanelsView, PANEL_REGISTRY, type PanelI import { TaskReviewModal } from "./task-review-modal"; import { StashpadFolderPanelView, openFolderPanelView } from "./folder-panel-view"; import { EncryptionService, defaultEncryptionConfig } from "./encryption-service"; -import { lockSubtree, unlockBundle, readLockedMeta, type LockResult, deleteEncryptSubtree, restoreDeleted, listDeletedBlobs, readDeletedMeta, deletedRestoreDest, backfillTrashEncrypt, restoreRawTrash, purgeDeletedBlob, OBSIDIAN_TRASH_DIR, type DeletedMeta, collectSubtree, writeRotatedBlob, commitRotatedBlob, cleanupRotTemps, listRotTemps, deletedBlobsForKeyId, rewrapDeletedSidecar, lockRawFolder, unlockRawFolder, rawFolderBlobIn, listRawFolderBlobs } from "./encryption-ops"; -import { EncryptionPasswordModal, ConfirmModal } from "./modals"; +import { lockSubtree, unlockBundle, readLockedMeta, type LockResult, deleteEncryptSubtree, restoreDeleted, listDeletedBlobs, readDeletedMeta, deletedRestoreDest, backfillTrashEncrypt, restoreRawTrash, purgeDeletedBlob, OBSIDIAN_TRASH_DIR, type DeletedMeta, collectSubtree, writeRotatedBlob, commitRotatedBlob, cleanupRotTemps, listRotTemps, deletedBlobsForKeyId, rewrapDeletedSidecar, trashSubfolderOf, lockRawFolder, unlockRawFolder, rawFolderBlobIn, listRawFolderBlobs } from "./encryption-ops"; +import { EncryptionPasswordModal, ConfirmModal, ReEncryptReviewModal } from "./modals"; import { DEFAULT_SETTINGS, StashpadSettings, StashpadSettingTab, setSettings, SETTINGS_TABS, buildDefaultBindings, COMMAND_META, type CommandBindingMap, @@ -26,6 +27,7 @@ import { formatDateTime } from "./format"; import { resolveStashBytes, isEncryptedStash } from "./stash-crypto"; import { StashpadLog } from "./log"; import { ROOT_ID, parseAssignees } from "./types"; +import { parseRecurrence, nextDueOnComplete, parseDuration } from "./recurrence"; import { OrderStore } from "./order-store"; import { UndoStack } from "./undo-stack"; import { rebootstrapFolderFrontmatter } from "./frontmatter-sync"; @@ -45,6 +47,7 @@ interface FileSnapshot { path: string; binary: boolean; text?: string; data?: Ar export default class StashpadPlugin extends Plugin { settings: StashpadSettings = { ...DEFAULT_SETTINGS }; + private reEncryptScheduler: ReEncryptScheduler | null = null; /** 0.108.2: in-memory debug trace ring buffer. Populated by trace() only * when settings.debugTrace is on; copied out from the Diagnostics tab. * Purely local — no network, no file writes. Capped so a long session @@ -171,6 +174,9 @@ export default class StashpadPlugin extends Plugin { // Cancel pending archive-sweep timers — firing after unload would run // lockNoteSubtree against disposed services (key just wiped). try { for (const p of this.archivePending.values()) window.clearTimeout(p.timer); this.archivePending.clear(); } catch { /* best-effort */ } + // 0.140.1: stop any in-flight peek countdown — a raw setInterval firing + // post-unload would write stale settings over the reloaded instance. + try { this.reEncryptScheduler?.dispose(); } catch { /* best-effort */ } // 0.83.2: flush any pending render-cache writes (the store's save is // debounced, so a recent change could still be in the buffer). try { await this._renderCacheStore?.save(); } catch { /* best-effort */ } @@ -457,7 +463,10 @@ export default class StashpadPlugin extends Plugin { // field isn't a Stashpad note. if (!fm || !("parent" in fm)) continue; const dir = f.parent?.path?.replace(/\/+$/, "") ?? ""; - if (dir && !this.pathHasExcludedSegment(dir)) folders.add(dir); + // 0.136.0: reserved subfolders (archive/, trash/, _archive/, …) are never + // Stashpad folders of their own — their notes surface via the aggregated + // views instead of the folder pickers. + if (dir && !this.pathHasExcludedSegment(dir) && !isInReservedSubfolder(dir)) folders.add(dir); } const sorted = [...folders].sort(); this.knownStashpadFolders = new Set(sorted); @@ -487,6 +496,38 @@ export default class StashpadPlugin extends Plugin { /** Drop a folder (and anything nested under it) from the folder-panel * placement lists. Persists only if something changed. */ + /** 0.140.3 (review): when a folder is RENAMED, carry every path-keyed setting + * (placement, archive, per-folder encryption/view prefs) from the old path to + * the new — for the folder AND its descendants — else pins/hides/archive + * semantics silently orphan. Mirrors prunePlacementFor but rewrites. */ + async remapFolderPathInSettings(oldPath: string, newPath: string): Promise<void> { + const from = oldPath.replace(/\/+$/, ""), to = newPath.replace(/\/+$/, ""); + if (!from || !to || from === to) return; + const s = this.settings; + const remapOne = (p: string): string => + p === from ? to : (p.startsWith(from + "/") ? to + p.slice(from.length) : p); + const remapArr = (arr: string[] | undefined): string[] | undefined => + arr ? arr.map(remapOne) : arr; + const remapRec = <T,>(rec: Record<string, T> | undefined): Record<string, T> | undefined => { + if (!rec) return rec; + const out: Record<string, T> = {}; + for (const [k, v] of Object.entries(rec)) out[remapOne(k)] = v; + return out; + }; + s.folderPanelPinned = remapArr(s.folderPanelPinned) ?? s.folderPanelPinned; + s.folderPanelDownranked = remapArr(s.folderPanelDownranked) ?? s.folderPanelDownranked; + s.folderPanelHidden = remapArr(s.folderPanelHidden) ?? s.folderPanelHidden; + s.archiveFolders = remapArr(s.archiveFolders) ?? s.archiveFolders; + s.searchIncludedFolders = remapArr(s.searchIncludedFolders) ?? s.searchIncludedFolders; + s.searchExcludedFolders = remapArr(s.searchExcludedFolders) ?? s.searchExcludedFolders; + if (s.defaultArchiveFolder) s.defaultArchiveFolder = remapOne(s.defaultArchiveFolder); + if ((s.folder || "").replace(/\/+$/, "") === from || (s.folder || "").startsWith(from + "/")) s.folder = remapOne((s.folder || "").replace(/\/+$/, "")); + s.folderEncPrefs = remapRec(s.folderEncPrefs) ?? s.folderEncPrefs; + s.viewModes = remapRec(s.viewModes) ?? s.viewModes; + if (s.encryptionFilter) s.encryptionFilter = remapRec(s.encryptionFilter); + await this.saveSettings(); + } + private async prunePlacementFor(cleaned: string): Promise<void> { const s = this.settings; const prune = (arr: string[] | undefined): string[] => @@ -712,7 +753,6 @@ export default class StashpadPlugin extends Plugin { // satisfies the v2 identity fields (they read as null until set up). () => ({ ...defaultEncryptionConfig(), ...(this.settings.encryption ?? {}) }), async (cfg) => { this.settings.encryption = cfg; await this.saveSettings(); }, - () => this.settings.encryptionIdleLockMinutes ?? 0, ); // Load the synced keyfile into the service's cache FIRST (isConfigured / // accessState / tryAutoUnlock all read it), then auto-unlock if a password is @@ -722,6 +762,9 @@ export default class StashpadPlugin extends Plugin { // adapter (not the file index), but deferring keeps it off the onload hot path // and consistent with the registry reconcile below. this.app.workspace.onLayoutReady(() => { void this.resumeRotations(); }); + // 0.139.0: peek auto-re-encrypt scheduler (opt-in; no-ops until a delay is set). + this.reEncryptScheduler = new ReEncryptScheduler(this); + this.reEncryptScheduler.start(); // Reconcile the locked-subtree registry from on-disk `.stashmeta` sidecars // (recovers placeholder placement after a settings desync or cross-device // sync). Deferred to onLayoutReady (below) so it runs AFTER the vault has @@ -782,6 +825,23 @@ export default class StashpadPlugin extends Plugin { // Vault is fully indexed now — safe to reconcile locked placeholders // (drop entries whose blob is truly gone, add cross-device blobs). void this.reconcileLockedRegistry(); + // 0.136.0/0.137.0: one-time migrations to per-folder archive + trash + // subfolders, then (B5) prune any zombie legacy entries. + window.setTimeout(async () => { + try { await this.migrateArchiveFoldersToSubfolders(); } catch (e) { console.error("[Stashpad] archive migration failed", e); } + try { await this.migrateDeletedToTrashSubfolders(); } catch (e) { console.error("[Stashpad] trash migration failed", e); } + void this.pruneZombieArchiveEntries(); + }, 5000); + // 0.138.0: opt-in nudge — previously-encrypted notes still plaintext. + window.setTimeout(() => { + if (!this.settings.reEncryptNudge) return; + const k = this.reEncryptWatching().length; + if (k === 0) return; + const n = new Notice("", 0); + n.noticeEl.createSpan({ text: `\u{1F513} ${k} previously-encrypted note${k === 1 ? " is" : "s are"} still plaintext. ` }); + const b = n.noticeEl.createEl("button", { text: "Review" }); + b.onclick = () => { n.hide(); void openAggregateView(this, "watch"); }; + }, 8000); // 0.99.19: fire reminders for tasks that came due while Obsidian was // closed (delay so the metadata cache is populated), then re-check on an // interval so tasks coming due while it's open also surface. @@ -1085,17 +1145,9 @@ export default class StashpadPlugin extends Plugin { name: "Focus folder panel", callback: () => void this.focusFolderPanel(), }); - // 0.97.2: forget the in-memory encryption key (re-prompt on next use). The - // explicit alternative to background auto-relock. - this.addCommand({ - id: "stashpad-lock-encryption", - name: "Lock encryption (forget password)", - callback: () => { - if (!this.encryption.isConfigured()) { new Notice("Encryption isn't set up."); return; } - this.encryption.lock(); - new Notice("Encryption locked."); - }, - }); + // (0.135.0: the "Lock encryption (forget password)" command is removed along + // with the settings "Lock now" buttons and the idle auto-lock — the session + // key now simply lives until Obsidian closes.) const call = (method: string) => { const v = getActiveView(); @@ -1155,7 +1207,7 @@ export default class StashpadPlugin extends Plugin { }); this.addCommand({ id: "stashpad-move-to-archive", - name: "Move selection to archive (encrypt)", + name: "Move selection to archive", callback: () => call("cmdMoveToArchive"), }); this.addCommand({ @@ -1165,26 +1217,37 @@ export default class StashpadPlugin extends Plugin { }); this.addCommand({ id: "stashpad-restore-trash", - name: "Open encrypted trash (restore deleted)…", + name: "Open aggregated Trash view", callback: () => this.openEncryptedTrash(), }); // Per-folder overhaul (Phase A): the on-the-fly aggregate tabs. this.addCommand({ id: "stashpad-open-all-encrypted", - name: "Open “All encrypted” (every locked note, by folder)", + name: "Open aggregated Encrypted notes view", callback: () => void openAggregateView(this, "encrypted"), }); this.addCommand({ id: "stashpad-open-all-archived", - name: "Open “All archived” (every archive folder)", + name: "Open aggregated Archived view", callback: () => void openAggregateView(this, "archived"), }); // 0.126.1: tasks as a full tab too (alongside the sidebar panel + review modal). this.addCommand({ id: "stashpad-open-all-tasks", - name: "Open “All tasks” (every task across folders)", + name: "Open aggregated Tasks view", callback: () => void openAggregateView(this, "tasks"), }); + // 0.138.0: re-encrypt sweep — the review view + the one-modal batch command. + this.addCommand({ + id: "stashpad-open-watchlist", + name: "Open aggregated Previously encrypted view", + callback: () => void openAggregateView(this, "watch"), + }); + this.addCommand({ + id: "stashpad-encrypt-applicable", + name: "Encrypt everything applicable (re-encrypt sweep)", + callback: () => void this.encryptEverythingApplicable(), + }); // v2 backfill: "Encrypt items sent to trash" only covers going-forward // deletes — this sweeps what's ALREADY sitting in plaintext in `.trash/`. this.addCommand({ @@ -2064,6 +2127,11 @@ export default class StashpadPlugin extends Plugin { const f2 = this.app.vault.getAbstractFileByPath(targetPath) as TFile | null; if (f2) target = f2; } catch (e) { + // The rename never fired, so the reverse listener won't consume + // these guard entries — drop them here or they leak forever and + // later swallow a genuine user rename to/from one of these paths. + this.authorRenameInFlight.delete(file.path); + this.authorRenameInFlight.delete(targetPath); console.warn("[Stashpad] author file rename failed", e); continue; } @@ -2113,7 +2181,12 @@ export default class StashpadPlugin extends Plugin { * update settings.authorName. The forward sync runs after to * propagate the change to author stubs in other Stashpad folders. */ private async maybeAdoptAuthorRename(file: TFile, oldPath: string): Promise<void> { - if (this.authorRenameInFlight.delete(file.path) || this.authorRenameInFlight.delete(oldPath)) return; + // Delete BOTH guard entries (syncAuthorFilesToName adds the old AND the + // target path) — a short-circuiting `||` would consume only one and leak + // the other permanently. Non-short-circuiting so both always run. + const wasSelf = this.authorRenameInFlight.delete(file.path); + const wasOld = this.authorRenameInFlight.delete(oldPath); + if (wasSelf || wasOld) return; const parsed = this.parseAuthorFilePath(file.path); if (!parsed) return; const id = (this.settings.authorId ?? "").trim(); @@ -2308,7 +2381,7 @@ export default class StashpadPlugin extends Plugin { if (typeof id === "string") existingIds.add(id); } } - const summary = await importStashZip(this.app, buf, destFolder, existingIds); + const summary = await importStashZip(this.app, buf, destFolder, existingIds, { stripReserved: true }); try { await this.newLog().append({ type: "stash_import", @@ -3055,10 +3128,11 @@ export default class StashpadPlugin extends Plugin { // tab on the same folder (e.g., main + tiny side by side). const openAnywayFiltered = openAnywayItems.filter((it) => matchesAll(it.label) || matchesAll("folder" in it ? it.folder : "")); filtered.push(...openAnywayFiltered); - // 0.98.37: encrypted-trash entry, pinned to the very bottom. Only when - // encryption is set up; matches on "trash"/"deleted"/"encrypted". - if (plugin.encryption?.isConfigured?.() && matchesAll("trash deleted encrypted")) { - filtered.push({ kind: "trash", label: "Open encrypted trash", icon: "trash-2" }); + // Trash entry, pinned to the very bottom. Unified trash (plaintext + + // encrypted) — shown regardless of encryption setup. Matches on + // "trash"/"deleted"/"encrypted". + if (matchesAll("trash deleted encrypted")) { + filtered.push({ kind: "trash", label: "Open Trash", icon: "trash-2" }); } return filtered; } @@ -3392,11 +3466,18 @@ export default class StashpadPlugin extends Plugin { * NOT dependent on `pendingEncBlobs`. The only location `getFiles()` misses is * the `.trash/` DOT-folder; for any IRREVERSIBLE decision use * `encryptionStateStrict()`, which adapter-confirms `_deleted/` + `.trash/`. */ + /** 0.140.1: is a `.stashenc` blob in a TRASH store (recoverable), not live + * locked content? Covers the legacy vault-level `_deleted/` AND every + * per-folder `<folder>/trash/` (0.137.0). CRITICAL for Remove-encryption: + * before this, per-folder trash blobs were misclassified as live-locked and + * could be irreversibly purged / restored into the reserved dir. */ + isTrashBlobPath(p: string): boolean { + const dir = p.replace(/\/[^/]*$/, "").replace(/\/+$/, ""); + return dir === "_deleted" || dir.startsWith("_deleted/") || dir === "trash" || dir.endsWith("/trash"); + } + encryptionState(): { live: boolean; trash: boolean } { - const isTrash = (p: string): boolean => { - const dir = p.replace(/\/[^/]*$/, ""); - return dir === "_deleted" || dir.startsWith("_deleted/"); - }; + const isTrash = (p: string): boolean => this.isTrashBlobPath(p); let live = false, trash = false; for (const f of this.app.vault.getFiles()) { if (f.extension !== "stashenc") continue; @@ -3421,7 +3502,7 @@ export default class StashpadPlugin extends Plugin { const s = this.encryptionState(); let trash = s.trash; if (!trash) { - try { trash = (await listDeletedBlobs(this.app)).length > 0; } catch { /* ignore */ } + try { trash = (await listDeletedBlobs(this.app, this.trashSubfolderDirs())).length > 0; } catch { /* ignore */ } } if (!trash) { try { @@ -3432,6 +3513,114 @@ export default class StashpadPlugin extends Plugin { return { live: s.live, trash }; } + // ---- 0.138.0: re-encrypt watchlist (smart sweep) ---- + // A subtree that WAS encrypted and got unlocked to plaintext goes on the + // watchlist; locking it again (any path — manual, sweep, folder batch, which + // all funnel through lockNoteSubtree) takes it off. MUTATES settings only; + // callers are responsible for the saveSettings they already perform. + + /** Upsert a watch entry (a fresh unlock reactivates a dismissed entry). */ + watchReEncrypt(e: { folder: string; rootId: StashpadId; title: string; count: number; via: "unlock" | "restore" }): void { + const folder = e.folder.replace(/\/+$/, ""); + const rest = (this.settings.reEncryptWatch ?? []).filter((w) => !(w.folder === folder && w.rootId === e.rootId)); + this.settings.reEncryptWatch = [...rest, { folder, rootId: e.rootId, title: e.title, count: e.count, via: e.via, unlockedAt: new Date().toISOString() }]; + } + + /** Drop entries for subtrees that just got locked (or securely deleted). */ + unwatchReEncrypt(folder: string, rootId: StashpadId): void { + const cleaned = folder.replace(/\/+$/, ""); + const before = this.settings.reEncryptWatch ?? []; + const after = before.filter((w) => !(w.folder === cleaned && w.rootId === rootId)); + if (after.length !== before.length) this.settings.reEncryptWatch = after; + } + + /** Active (non-dismissed) watch entries. */ + reEncryptWatching(): import("./settings").ReEncryptWatchEntry[] { + return (this.settings.reEncryptWatch ?? []).filter((w) => !w.removed); + } + + /** 0.138.0: drop watch entries whose rootId is no longer a live PLAINTEXT + * note in its folder — it was deleted, moved, or re-locked by some path that + * didn't clear it. Keeps ghost rows out of the view + sweep. Returns true if + * it changed anything (caller saves). Cheap: metadataCache id scan per entry. */ + pruneReEncryptWatch(): boolean { + const entries = this.settings.reEncryptWatch ?? []; + if (entries.length === 0) return false; + const livePlainInFolder = (folder: string): Set<string> => { + const set = new Set<string>(); + for (const f of this.app.vault.getMarkdownFiles()) { + if ((f.parent?.path?.replace(/\/+$/, "") ?? "") !== folder) continue; + const id = this.app.metadataCache.getFileCache(f)?.frontmatter?.id; + if (typeof id === "string") set.add(id); + } + return set; + }; + const byFolder = new Map<string, Set<string>>(); + const kept = entries.filter((w) => { + const folder = w.folder.replace(/\/+$/, ""); + if (!byFolder.has(folder)) byFolder.set(folder, livePlainInFolder(folder)); + return byFolder.get(folder)!.has(w.rootId); + }); + if (kept.length === entries.length) return false; + this.settings.reEncryptWatch = kept; + return true; + } + + /** 0.138.0: the sweep — gather everything that SHOULD be encrypted but is + * currently plaintext (watchlist entries ∪ prefs-derived candidates), show + * ONE review modal (per-subtree rows, pre-ticked), and lock what's confirmed. + * Nothing encrypts without this explicit confirmation. */ + async encryptEverythingApplicable(): Promise<void> { + if (this.pruneReEncryptWatch()) await this.saveSettings(); // no ghosts in the sweep + type Cand = { label: string; detail: string; run: () => Promise<boolean> }; + const cands: Cand[] = []; + // (a) watchlist: previously-encrypted subtrees now plaintext. + for (const w of this.reEncryptWatching()) { + cands.push({ + label: w.title || "(untitled)", + detail: `${w.folder} — ${w.via === "restore" ? "restored from trash" : "unlocked"} (was encrypted)`, + run: async () => !!(await this.lockNoteSubtree(w.folder, w.rootId, null, { silent: true })), + }); + } + // (b) folders set to encrypt their notes that still hold plaintext notes. + const lockedRoots = new Set((this.settings.lockedSubtrees ?? []).map((e) => e.rootId)); + for (const [folder, p] of Object.entries(this.settings.folderEncPrefs ?? {})) { + if (!p?.encryptContent) continue; + const plain = this.app.vault.getMarkdownFiles().filter((f) => { + if ((f.parent?.path?.replace(/\/+$/, "") ?? "") !== folder) return false; + const id = this.app.metadataCache.getFileCache(f)?.frontmatter?.id; + return typeof id === "string" && id !== ROOT_ID && !lockedRoots.has(id); + }).length; + if (plain > 0) cands.push({ + label: `Everything in “${folder.split("/").pop() || folder}” (${plain} plaintext note${plain === 1 ? "" : "s"})`, + detail: `${folder} — the folder is set to encrypt its notes`, + run: async () => { await this.lockFolder(folder); return true; }, + }); + } + // (c) folders that encrypt their archive but have plain archived notes. + for (const [folder, p] of Object.entries(this.settings.folderEncPrefs ?? {})) { + if (!p?.archiveEncryptContent) continue; + const n = this.archivedPlainNotesIn(archiveSubfolderOf(folder)).length; + if (n > 0) cands.push({ + label: `${n} archived note${n === 1 ? "" : "s"} in “${folder.split("/").pop() || folder}/archive”`, + detail: `${folder} — the folder encrypts its archive`, + run: async () => { await this.encryptExistingArchiveNotes(folder); return true; }, + }); + } + if (cands.length === 0) { new Notice("Nothing needs re-encrypting — everything applicable is already locked."); return; } + new ReEncryptReviewModal(this.app, cands.map(({ label, detail }) => ({ label, detail })), async (chosen) => { + let ok = 0, failed = 0; + const prog = chosen.length > 2 ? new Notice("", 0) : null; + for (let i = 0; i < chosen.length; i++) { + prog?.setMessage(`🔒 Re-encrypting ${i + 1}/${chosen.length}…`); + try { (await cands[chosen[i]].run()) ? ok++ : failed++; } + catch (e) { failed++; console.warn("[Stashpad] sweep item failed", cands[chosen[i]].label, e); } + } + prog?.hide(); + this.notifications.show({ message: `Re-encrypt sweep: ${ok} done${failed ? `, ${failed} FAILED (see console)` : ""}, ${cands.length - chosen.length} skipped.`, kind: failed ? "warning" : "success", category: "system", folder: "" }); + }).open(); + } + async lockNoteSubtree(folder: string, rootId: StashpadId, prevSibling: StashpadId | null = null, opts: { silent?: boolean; blobFolder?: string } = {}): Promise<LockResult | null> { // Encrypt under the key of the folder the BLOB will live in (archive moves put // the blob in opts.blobFolder, not the source folder), so opening that folder @@ -3445,7 +3634,7 @@ export default class StashpadPlugin extends Plugin { // else its live-notes pref — falling back to the global hide-titles setting. const isArchiveLock = !!opts.blobFolder && opts.blobFolder.replace(/\/+$/, "") !== folder.replace(/\/+$/, ""); const fp = (this.settings.folderEncPrefs ?? {})[keyFolder] ?? {}; - const hideTitle = (isArchiveLock ? fp.archiveEncryptFilenames : fp.encryptFilenames) ?? this.settings.hideLockedTitles ?? false; + const hideTitle = (isArchiveLock ? fp.archiveEncryptFilenames : fp.encryptFilenames) ?? false; // 0.137.1: per-folder only const r = await lockSubtree(this.app, folder, rootId, dek, prevSibling, hideTitle, opts.blobFolder); this.pendingEncBlobs.add(r.blobPath); // fast-state index: cover the pre-vault-index window // Record a placeholder registry entry so the list shows a 🔒 stub where @@ -3456,6 +3645,10 @@ export default class StashpadPlugin extends Plugin { ...(this.settings.lockedSubtrees ?? []).filter((e) => e.blob !== r.blobPath), { folder: blobFolder, blob: r.blobPath, parentId: r.parentId, title: r.title, count: r.noteCount, created: r.created, rootId: r.rootId, prevSibling }, ]; + // 0.138.0: locked again → off the re-encrypt watchlist (source folder AND + // blob folder — archive locks register under the destination). + this.unwatchReEncrypt(folder, r.rootId); + this.unwatchReEncrypt(blobFolder, r.rootId); await this.saveSettings(); if (r.unpurged.length > 0) { // The blob is good but readable plaintext is STILL on disk (delete @@ -3504,6 +3697,12 @@ export default class StashpadPlugin extends Plugin { // Blob is removed via raw adapter (no vault 'delete' event) — prune the // fast-index pending entry directly so it can't falsely report "locked". this.pendingEncBlobs.delete(blobPath); + // 0.138.0: this subtree WAS encrypted and is now plaintext → watchlist. + // Grab its registry entry (rootId/title/count) before filtering it out. + const wasLocked = (this.settings.lockedSubtrees ?? []).find((e) => e.blob === blobPath); + if (wasLocked?.rootId) { + this.watchReEncrypt({ folder, rootId: wasLocked.rootId, title: wasLocked.title ?? "", count: wasLocked.count ?? r.notesWritten, via: "unlock" }); + } this.settings.lockedSubtrees = (this.settings.lockedSubtrees ?? []).filter((e) => e.blob !== blobPath); await this.saveSettings(); if (!opts.silent) this.notifications.show({ message: `Unlocked ${r.notesWritten} note${r.notesWritten === 1 ? "" : "s"}.`, kind: "success", category: "system", folder }); @@ -3586,11 +3785,13 @@ export default class StashpadPlugin extends Plugin { * everything" safety valve. Each blob is independent + skip-on-error. */ async unlockAllInVault(): Promise<number> { if (!this.encryption.isConfigured()) { new Notice("Set up encryption first (Settings → Stashpad → Encryption)."); return 0; } - // Exclude the `_deleted/` trash store — those are DELETED notes, not locked - // ones; "unlocking" them would wrongly restore them into _deleted/. Use the - // trash-restore flow for those. + // Exclude ALL trash stores (`_deleted/` + per-folder `<folder>/trash/`) — + // those are DELETED notes, not locked ones; "unlocking" them would restore + // them INTO the reserved trash dir (invisible to every view). Use the + // trash-restore flow for those. 0.140.1: was `_deleted`-only — per-folder + // trash blobs leaked through and got restored into `<folder>/trash/`. const blobs = this.app.vault.getFiles() - .filter((f) => f.extension === "stashenc" && (f.parent?.path?.replace(/\/+$/, "") ?? "") !== "_deleted") + .filter((f) => f.extension === "stashenc" && !this.isTrashBlobPath(f.path)) .map((f) => f.path); if (blobs.length === 0) { new Notice("No locked notes anywhere in the vault."); return 0; } const prog = blobs.length > 3 ? new Notice("", 0) : null; @@ -3815,7 +4016,7 @@ export default class StashpadPlugin extends Plugin { // Per-folder trash keys: the folder's TRASH blobs (in _deleted/, identified by // the sidecar keyId) must rotate too, or they'd be orphaned under the old DEK. const ownerKeyId = this.encryption.folderKeyEntry(cleaned)?.keyId; - const trashBlobs = ownerKeyId ? await deletedBlobsForKeyId(this.app, ownerKeyId) : []; + const trashBlobs = ownerKeyId ? await deletedBlobsForKeyId(this.app, ownerKeyId, this.trashSubfolderDirs()) : []; const blobs = [...liveBlobs, ...trashBlobs]; const newDek = crypto.getRandomValues(new Uint8Array(32)); const rotId = [...crypto.getRandomValues(new Uint8Array(8))].map((b) => b.toString(16).padStart(2, "0")).join(""); @@ -3870,7 +4071,7 @@ export default class StashpadPlugin extends Plugin { // Trash blobs (in _deleted/) keyed to this folder — commit their .rot too // (byte copy; the keyfile already holds the new DEK). Hidden-origin sidecars // can't be re-wrapped here (the old DEK is gone) — a rare crash edge. - if (entry?.keyId) { for (const tb of await deletedBlobsForKeyId(this.app, entry.keyId)) { try { await commitRotatedBlob(this.app, tb); } catch (e) { console.warn("[Stashpad] resume trash-commit failed", tb, e); } } } + if (entry?.keyId) { for (const tb of await deletedBlobsForKeyId(this.app, entry.keyId, this.trashSubfolderDirs())) { try { await commitRotatedBlob(this.app, tb); } catch (e) { console.warn("[Stashpad] resume trash-commit failed", tb, e); } } } new Notice(`Finished an interrupted key rotation for “${folder.split("/").pop()}”.`, 0); } else { try { await cleanupRotTemps(this.app, folder); } catch { /* */ } @@ -3897,10 +4098,16 @@ export default class StashpadPlugin extends Plugin { // the general hide-locked-titles setting is off. This folder's // trashEncryptFilenames pref takes precedence over the globals. const _trFp = (this.settings.folderEncPrefs ?? {})[folder.replace(/\/+$/, "")] ?? {}; - const hideTitle = (_trFp.trashEncryptFilenames ?? false) || (this.settings.hideLockedTitles ?? false) || (this.settings.encryptTrashFilenames ?? false); + const hideTitle = (_trFp.trashEncryptFilenames ?? false) || (this.settings.encryptTrashFilenames ?? false); // 0.137.1: global hideLockedTitles removed const keyId = this.encryption.folderKeyEntry(folder)?.keyId; // undefined ⇒ vault-keyed - const r = await deleteEncryptSubtree(this.app, folder, rootId, dek, deletedAt, hideTitle, keyId); + // 0.137.0 (per-folder trash): the blob lands in THIS folder's own trash/ + // subfolder instead of the vault-level _deleted/ (which stays readable + // for legacy blobs until they're restored or migrated). + const r = await deleteEncryptSubtree(this.app, folder, rootId, dek, deletedAt, hideTitle, keyId, trashSubfolderOf(folder)); this.pendingEncBlobs.add(r.blobPath); // fast-state index + // 0.138.0: secure-deleted → no longer plaintext, so off the watchlist. + this.unwatchReEncrypt(folder, rootId); + await this.saveSettings(); if (r.unpurged.length > 0) { new Notice(`⚠️ Sent to encrypted trash, but ${r.unpurged.length} file${r.unpurged.length === 1 ? " is" : "s are"} still in plaintext (couldn't be removed or changed during the delete):\n${r.unpurged.join("\n")}`, 0); } @@ -3959,6 +4166,12 @@ export default class StashpadPlugin extends Plugin { } const r = await restoreDeleted(this.app, blobPath, dek, existing); this.pendingEncBlobs.delete(blobPath); + // 0.138.0: restore-from-trash counts as an unlock (user decision) — the + // note WAS encrypted and is plaintext again. Sidecar carries the identity. + if (meta?.rootId) { + this.watchReEncrypt({ folder: r.restoredTo, rootId: meta.rootId, title: meta.title || "", count: meta.count ?? r.notesWritten, via: "restore" }); + await this.saveSettings(); + } if (!opts.silent) this.notifications.show({ message: `Restored ${r.notesWritten} note${r.notesWritten === 1 ? "" : "s"} to “${r.restoredTo.split("/").pop()}”.`, kind: "success", category: "system", folder: r.restoredTo }); return true; } catch (e) { @@ -3987,7 +4200,10 @@ export default class StashpadPlugin extends Plugin { * Delete locked content" for when the password is lost. Excludes `_deleted/` * trash (that's handled separately). Caller MUST confirm. Returns the count. */ async purgeAllLockedContent(): Promise<number> { - const isTrash = (p: string): boolean => { const d = p.replace(/\/[^/]*$/, ""); return d === "_deleted" || d.startsWith("_deleted/"); }; + // 0.140.1: was `_deleted`-only — per-folder `<folder>/trash/` blobs slipped + // through and got IRREVERSIBLY PURGED even though the trash has its own + // decrypt-vs-discard step. Use the trash-aware predicate. + const isTrash = (p: string): boolean => this.isTrashBlobPath(p); const blobs = new Set<string>(); for (const f of this.app.vault.getFiles()) if (f.extension === "stashenc" && !isTrash(f.path)) blobs.add(f.path); for (const p of this.pendingEncBlobs) if (!isTrash(p)) blobs.add(p); @@ -4072,7 +4288,15 @@ export default class StashpadPlugin extends Plugin { * note keeps its id/parent, so the tree re-nests it under its parent if that * parent is in the default folder; else it lands at the folder root. */ async unarchiveNote(file: TFile): Promise<boolean> { - const dest = (this.settings.folder || "Stashpad").trim().replace(/^\/+|\/+$/g, "") || "Stashpad"; + // 0.136.0: a note in `X/archive/...` un-archives back to X (its own folder), + // not the global default. Legacy dedicated-archive notes (no /archive + // segment) still fall back to the default folder — their origin is unknown. + const dir = (file.parent?.path ?? "").replace(/\/+$/, ""); + const segs = dir.split("/"); + const archIdx = segs.indexOf("archive"); + const dest = archIdx > 0 + ? segs.slice(0, archIdx).join("/") + : ((this.settings.folder || "Stashpad").trim().replace(/^\/+|\/+$/g, "") || "Stashpad"); if ((file.parent?.path ?? "") === dest) { new Notice("Already in the default folder."); return false; } try { if (!(await this.app.vault.adapter.exists(dest))) { try { await this.app.vault.createFolder(dest); } catch { /* race */ } } @@ -4087,9 +4311,15 @@ export default class StashpadPlugin extends Plugin { } } + /** Every per-folder trash/ subfolder (0.137.0) — the extra dirs the + * encrypted-trash listing unions with the legacy _deleted/. */ + trashSubfolderDirs(): string[] { + return this.discoverStashpadFolders().map((f) => trashSubfolderOf(f)); + } + /** List the encrypted-trash contents (blob path + sidecar metadata). */ async listDeletedTrash(): Promise<Array<{ blob: string; meta: DeletedMeta | null }>> { - const blobs = await listDeletedBlobs(this.app); + const blobs = await listDeletedBlobs(this.app, this.trashSubfolderDirs()); const out: Array<{ blob: string; meta: DeletedMeta | null }> = []; for (const b of blobs) out.push({ blob: b, meta: await readDeletedMeta(this.app, b) }); return out; @@ -4122,7 +4352,7 @@ export default class StashpadPlugin extends Plugin { const dek = this.encryption.getSessionKey(); if (!dek) return false; try { - const hideTitle = (this.settings.hideLockedTitles ?? false) || (this.settings.encryptTrashFilenames ?? false); + const hideTitle = this.settings.encryptTrashFilenames ?? false; // 0.137.1: global hideLockedTitles removed const r = await backfillTrashEncrypt(this.app, dek, new Date().toISOString(), hideTitle); if (!r) { new Notice(`Obsidian's vault trash (${OBSIDIAN_TRASH_DIR}/) is empty — nothing to encrypt. (Files in the system/OS trash can't be reached.)`, 8000); return false; } if (r.unpurged.length > 0) { @@ -4138,10 +4368,11 @@ export default class StashpadPlugin extends Plugin { } } - /** Open the recoverable encrypted-trash TAB. (The `_` arg keeps the old + /** Open the recoverable Trash TAB. Unified across all folders — lists both + * plaintext (`.trash/`) and encrypted (`_deleted/`) deleted notes — so it no + * longer requires encryption to be set up. (The `_` arg keeps the old * per-folder call sites working; the tab groups by origin folder anyway.) */ openEncryptedTrash(_scopeFolder?: string): void { - if (!this.encryption.isConfigured()) { new Notice("Set up encryption first (Settings → Stashpad → Encryption)."); return; } void openTrashView(this); } @@ -4160,15 +4391,226 @@ export default class StashpadPlugin extends Plugin { // --- 0.98.25 (Phase 4): archive folders — auto-encrypt notes moved in --- + /** 0.136.0 (per-folder archive): "is this an archive?" now means "is this a + * folder's `archive/` subfolder (or inside one)?". The legacy dedicated + * archive-folder model (archiveFolders[] ∪ folderEncPrefs[f].archive) is + * still honored DURING the transition so un-migrated vaults keep their + * search exclusion + icons until the one-time migration has run. */ isArchiveFolder(folder: string): boolean { const cleaned = folder.replace(/\/+$/, ""); - // Single source of truth: the legacy archiveFolders[] list UNION the per-folder - // pref (folderEncPrefs[f].archive). The settings toggle keeps both in sync, but - // unioning here means any future writer of either can't make this lie. + if (isArchiveSubfolderPath(cleaned)) return true; if ((this.settings.archiveFolders ?? []).includes(cleaned)) return true; return !!(this.settings.folderEncPrefs ?? {})[cleaned]?.archive; } + /** 0.134.4 (B1): the ONE resolver for "does archiving into `folder` encrypt?". + * Both the move-in sweep and the Move-to-archive command must use this — + * they previously had opposite defaults (sweep `?? true`, command `?? false`), + * so a drag encrypted while the command moved plaintext. Default is PLAINTEXT; + * encryption is opt-in per folder via "Encrypt archived notes". */ + archiveEncryptFor(folder: string): boolean { + let cleaned = folder.replace(/\/+$/, ""); + // 0.136.0: when handed an `X/archive` subfolder path (the move-in sweep + // does), the pref lives on the PARENT folder X. + if (cleaned.endsWith("/archive")) cleaned = cleaned.slice(0, -"/archive".length); + return (this.settings.folderEncPrefs ?? {})[cleaned]?.archiveEncryptContent ?? false; + } + + /** 0.136.0: ONE-TIME migration to per-folder archive subfolders ("Option D" + * phase 1, docs/archive-trash-subfolders-plan.md, migration option b). + * Each legacy dedicated archive folder F becomes a normal folder again; its + * plain notes move into F's OWN `archive/` subfolder. Locked `.stashenc` + * blobs stay where they are (the lockedSubtrees registry paths must not + * break; they remain visible in the aggregated views). Legacy folders keep + * auto-encrypt semantics: archiveEncryptContent is stamped TRUE when unset + * (they were created under the encrypt-on-arrival regime). Persistent + * notice + a log entry per folder, then the legacy settings are cleared. */ + /** True while the one-time archive migration is moving files — gates the + * auto-encrypt sweep (its rename events must not look like new arrivals). */ + private archiveMigrationInFlight = false; + + async migrateArchiveFoldersToSubfolders(): Promise<void> { + if (this.settings.migratedArchiveToSubfolders) return; + this.archiveMigrationInFlight = true; + try { + await this.migrateArchiveFoldersToSubfoldersInner(); + } finally { + // Keep the gate up through the sweep's 1800ms settle window — rename + // events fired by the last migration move are still queued when the + // inner function returns. + window.setTimeout(() => { this.archiveMigrationInFlight = false; }, 4000); + } + } + + private async migrateArchiveFoldersToSubfoldersInner(): Promise<void> { + const legacy = new Set<string>((this.settings.archiveFolders ?? []).map((f) => f.replace(/\/+$/, ""))); + for (const [f, p] of Object.entries(this.settings.folderEncPrefs ?? {})) if (p?.archive) legacy.add(f.replace(/\/+$/, "")); + const log = this.newLog(); + const lines: string[] = []; + let totalFailed = 0; + // Recovery journal: every planned + completed move, persisted BEFORE the + // settings flip so a crash mid-migration leaves a trail to restore from. + const journal: Array<{ folder: string; from: string; to: string; ok: boolean }> = []; + const journalPath = `${this.pluginPrivatePath()}/archive-migration-journal.json`; + const writeJournal = async () => { + try { await this.app.vault.adapter.write(journalPath, JSON.stringify({ ts: new Date().toISOString(), moves: journal }, null, 1)); } catch { /* best-effort */ } + }; + for (const folder of [...legacy].sort()) { + const tf = this.app.vault.getAbstractFileByPath(folder); + if (!(tf instanceof TFolder)) continue; // zombie entry — nothing to move + const dest = archiveSubfolderOf(folder); + try { if (!(await this.app.vault.adapter.exists(dest))) await this.app.vault.createFolder(dest); } catch { /* race */ } + let moved = 0, failed = 0; + // Direct .md children only: locked blobs + reserved subfolders stay put, + // and nested subfolders are their own Stashpad folders. + for (const child of [...tf.children]) { + if (!(child instanceof TFile) || child.extension !== "md") continue; + // Keep the folder's HOME note (id: root) — archiving it would destroy + // the folder's identity as a Stashpad folder (caught in live testing). + // Read the id from DISK: at layout-ready+5s the metadataCache can still + // be cold (big vault / network drive), and a cold cache would return + // undefined for the home note and let it be moved. + let fmId: unknown; + try { fmId = splitFrontmatter(await this.app.vault.read(child)).fm.id; } + catch { failed++; totalFailed++; continue; } // unreadable → leave in place + if (fmId === ROOT_ID) continue; + let to = `${dest}/${child.name}`; + const from = child.path; + try { + for (let n = 1; await this.app.vault.adapter.exists(to); n++) to = `${dest}/${child.basename}-${n}.md`; + journal.push({ folder, from, to, ok: false }); + await this.app.fileManager.renameFile(child, to); + // Verify the move actually landed (the 0.79.21 import lesson: a + // silently-failed rename must never be counted as done). + if (!(this.app.vault.getAbstractFileByPath(to) instanceof TFile)) throw new Error("rename did not land"); + journal[journal.length - 1].ok = true; + moved++; + } catch (e) { failed++; totalFailed++; console.warn("[Stashpad] archive migration move failed", from, e); } + } + await writeJournal(); + // Do NOT stamp archiveEncryptContent — since 0.134.4 the effective + // default for these folders is already plaintext, and stamping TRUE made + // the migration's own moves trigger the auto-encrypt sweep, silently + // encrypting every previously-plaintext archived note (caught in live + // testing — a data-loss-grade surprise). Encryption stays opt-in. + // De-flag prefs.archive only when THIS folder migrated cleanly — a folder + // marked only via the pref must stay in the retry set otherwise. + if (failed === 0) { + const prefs = { ...(this.settings.folderEncPrefs ?? {}) }; + const cur = { ...(prefs[folder] ?? {}) }; + delete cur.archive; + prefs[folder] = cur; + this.settings.folderEncPrefs = prefs; + } + lines.push(`"${folder}": ${moved} note${moved === 1 ? "" : "s"} → ${dest}/${failed ? ` (${failed} failed — left in place)` : ""}`); + void log.append({ type: "archive_migration", id: "", payload: { folder, dest, moved, failed } as Record<string, unknown> }); + } + // Only finalize when EVERY move landed. On any failure the legacy settings + // stay put and the marker stays false, so the next launch retries the + // leftovers (already-moved notes are gone from the root, so a retry is a + // no-op for them). No data is ever deleted either way — a failed move + // leaves the note exactly where it was. + if (totalFailed === 0) { + this.settings.archiveFolders = []; + this.settings.defaultArchiveFolder = undefined; + this.settings.migratedArchiveToSubfolders = true; + await this.saveSettings(); + } else { + await this.saveSettings(); // persist any prefs.archive de-flags that DID complete + new Notice(`Stashpad archive update: ${totalFailed} note${totalFailed === 1 ? "" : "s"} couldn't be moved (see the developer console). Nothing was deleted — the migration will retry on the next launch. A recovery journal of every move is at ${this.pluginPrivatePath()}/archive-migration-journal.json.`, 0); + } + if (lines.length) { + new Notice(`Stashpad archive update: dedicated archive folders are now regular folders — archived notes moved into each folder's own "archive/" subfolder.\n\n${lines.join("\n")}\n\nFind everything in the aggregated Archived view. (This notice stays until dismissed.)`, 0); + } + } + + /** 0.137.0: ONE-TIME migration of the vault-level encrypted trash into + * per-folder `trash/` subfolders. Conservative by design: ONLY `deleted` + * blobs whose sidecar names a plaintext origin folder that still exists + * move to `<origin>/trash/`; everything else (rawtrash/rawfolder backfills, + * hidden-origin sidecars, unreadable sidecars, missing folders) STAYS in + * `_deleted/`, which remains fully listed/restorable forever. Copy → + * byte-verify → remove, via the adapter (no vault events, so no sweep or + * import interactions). Journal + retry like the archive migration. */ + async migrateDeletedToTrashSubfolders(): Promise<void> { + if (this.settings.migratedTrashToSubfolders) return; + const a = this.app.vault.adapter; + const log = this.newLog(); + let moved = 0, left = 0, failed = 0; + const journal: Array<{ from: string; to: string; ok: boolean }> = []; + const journalPath = `${this.pluginPrivatePath()}/trash-migration-journal.json`; + try { + const blobs = await listDeletedBlobs(this.app); // legacy _deleted/ only + for (const blob of blobs) { + const meta = await readDeletedMeta(this.app, blob); + const origin = (meta?.kind === "deleted" && meta.originalFolder) ? meta.originalFolder.replace(/\/+$/, "") : ""; + if (!origin || !(this.app.vault.getAbstractFileByPath(origin) instanceof TFolder)) { left++; continue; } + const destDir = trashSubfolderOf(origin); + try { + if (!(await a.exists(destDir))) await a.mkdir(destDir); + const name = blob.split("/").pop()!; + let to = `${destDir}/${name}`; + for (let n = 1; await a.exists(to); n++) to = `${destDir}/${name.replace(/\.stashenc$/, "")} (${n}).stashenc`; + journal.push({ from: blob, to, ok: false }); + // blob: copy → verify bytes → remove + const bytes = new Uint8Array(await a.readBinary(blob)); + await a.writeBinary(to, bytes.slice().buffer as ArrayBuffer); + // 0.140.1: FULL byte-compare before removing the only copy — a + // same-length corruption (flaky network drive) passed the old + // length-only check → AEAD-unrestorable blob, permanent loss. + const back = new Uint8Array(await a.readBinary(to)); + if (back.length !== bytes.length) throw new Error("copy verify failed (length)"); + for (let i = 0; i < bytes.length; i++) if (back[i] !== bytes[i]) throw new Error("copy verify failed (content)"); + // sidecar FIRST, and verify it too — a truncated sidecar loses keyId/ + // originalFolderEnc → folder-keyed blob unrestorable. Copy+verify the + // sidecar and remove the blob LAST (crash → harmless duplicate, never + // a sidecar-less orphan in _deleted/). + const sc = blob.replace(/\.stashenc$/, ".stashmeta"); + const scTo = to.replace(/\.stashenc$/, ".stashmeta"); + try { + if (await a.exists(sc)) { + const scBody = await a.read(sc); + await a.write(scTo, scBody); + if ((await a.read(scTo)) !== scBody) throw new Error("sidecar verify failed"); + await a.remove(sc); + } + } catch (e) { throw new Error(`sidecar copy failed: ${(e as Error).message}`); } + await a.remove(blob); + journal[journal.length - 1].ok = true; + moved++; + } catch (e) { failed++; console.warn("[Stashpad] trash migration move failed", blob, e); } + } + try { await a.write(journalPath, JSON.stringify({ ts: new Date().toISOString(), moves: journal }, null, 1)); } catch { /* best-effort */ } + } catch (e) { console.error("[Stashpad] trash migration failed", e); return; } + void log.append({ type: "archive_migration", id: "", payload: { what: "trash-subfolders", moved, left, failed } as Record<string, unknown> }); + if (failed === 0) { + this.settings.migratedTrashToSubfolders = true; + await this.saveSettings(); + } + if (moved > 0 || failed > 0) { + new Notice(`Stashpad trash update: ${moved} encrypted-trash item${moved === 1 ? "" : "s"} moved into per-folder "trash/" subfolders${left ? ` (${left} stayed in _deleted/ — unknown or missing origin)` : ""}${failed ? ` — ${failed} failed; will retry next launch` : ""}. Everything stays visible in the Trash view.`, 0); + } + } + + /** 0.134.4 (B5): drop archiveFolders[] entries (and a defaultArchiveFolder) + * pointing at folders that no longer exist. Folder deletion never cleaned + * these up, so zombies accumulated. Conservative: folderEncPrefs entries are + * left alone (inert when the folder is gone, and they may describe a folder + * that exists on another synced device). */ + async pruneZombieArchiveEntries(): Promise<void> { + const exists = (f: string) => this.app.vault.getAbstractFileByPath(f) instanceof TFolder; + const before = this.settings.archiveFolders ?? []; + const kept = before.filter((f) => exists(f.replace(/\/+$/, ""))); + let dirty = kept.length !== before.length; + const def = (this.settings.defaultArchiveFolder ?? "").replace(/\/+$/, ""); + if (def && !exists(def)) { this.settings.defaultArchiveFolder = undefined; dirty = true; } + if (dirty) { + this.settings.archiveFolders = kept; + await this.saveSettings(); + console.info(`[Stashpad] pruned ${before.length - kept.length} stale archive-folder entr${before.length - kept.length === 1 ? "y" : "ies"}.`); + } + } + /** Ensure the OKF template note exists and remember its path (called when OKF * is enabled). */ async ensureOkfTemplate(): Promise<string> { @@ -4327,7 +4769,9 @@ export default class StashpadPlugin extends Plugin { ): Promise<{ rootIds: StashpadId[]; noteCount: number; undo: () => Promise<void>; redo: () => Promise<void> } | null> { const cleanDest = destFolder.replace(/\/+$/, ""); if (this.isArchiveFolder(cleanDest)) { - new Notice(`"${cleanDest.split("/").pop()}" auto-encrypts notes moved in, so cross-folder paste is disabled there. Use the "Move to archive" command — it checks the encryption key first.`); + // 0.134.4 (B4): archives default to plaintext now — the block stays (paste + // bypasses the archive flow entirely) but the copy shouldn't claim encryption. + new Notice(`"${cleanDest.split("/").pop()}" is an archive folder, so cross-folder paste is disabled there. Use the "Move selection to archive" command instead — it runs the proper archive flow (and encrypts, if that folder encrypts its archive).`); return null; } // Gather source subtree(s) from DISK (authoritative; the source folder's view @@ -4566,6 +5010,19 @@ export default class StashpadPlugin extends Plugin { }, 1800); } + /** 0.137.2 (retro-apply): when "Encrypt archived notes" turns ON for a + * folder, offer to lock the plain notes ALREADY sitting in its archive/ + * subfolder — the pref used to apply only to future arrivals. Reuses the + * proven arrival sweep (root-resolution, key selection, loud-on-locked). */ + async encryptExistingArchiveNotes(folder: string): Promise<number> { + const sub = archiveSubfolderOf(folder.replace(/\/+$/, "")); + const plain = this.archivedPlainNotesIn(sub).map((f) => f.path); + if (plain.length === 0) return 0; + await this.archiveSweep(sub, plain); + // Report how many got locked (the sweep may be cancelled at the prompt). + return plain.length - this.archivedPlainNotesIn(sub).length; + } + /** Lock the notes that just arrived in an archive folder. Among the arrivals, * only subtree ROOTS are locked (a child whose parent also arrived rides * inside the parent's bundle). Skips the Home note, already-locked roots, and @@ -4573,12 +5030,16 @@ export default class StashpadPlugin extends Plugin { * locked and the user declines to unlock — silent failure here would mean * plaintext sitting in a folder the user believes is encrypted. */ private async archiveSweep(folder: string, arrivedPaths: string[]): Promise<void> { + // 0.136.0: the one-time subfolder migration re-shelves notes; its moves + // must NEVER count as "new arrivals to encrypt" (caught in live testing: + // the sweep mass-encrypted every migrated note). + if (this.archiveMigrationInFlight) return; if (!this.isArchiveFolder(folder)) return; // unmarked while settling const cleaned = folder.replace(/\/+$/, ""); - // Per-folder overhaul: an archive folder can be PLAINTEXT (de-indexed but not - // encrypted) when its archiveEncryptContent pref is off. Default ON to preserve - // existing auto-encrypt-on-move-in behavior for archives marked before this. - if (!((this.settings.folderEncPrefs ?? {})[cleaned]?.archiveEncryptContent ?? true)) return; + // 0.134.4 (B1): shared resolver — plaintext default, same as the + // Move-to-archive command (they used to disagree: drag encrypted, command + // didn't). Encryption is opt-in via "Encrypt archived notes" per folder. + if (!this.archiveEncryptFor(cleaned)) return; type Arr = { id: StashpadId; parent: StashpadId | null }; const arrived: Arr[] = []; for (const p of arrivedPaths) { @@ -4935,32 +5396,87 @@ export default class StashpadPlugin extends Plugin { * same task+due never re-fires — a changed due date re-keys and reminds again), * shows a PERSISTENT notification under the "reminder" category (so it lands in * the history log + respects mute), then records it. */ + /** 0.140.0: resolve an "un-failable" task that's past due by its grace. + * Repeating → roll due forward (stays active); one-off → mark complete. */ + private async autoResolveDueTask(f: TFile, _dueMs: number, now: number): Promise<void> { + try { + await this.app.fileManager.processFrontMatter(f, (fm) => { + // 0.140.1: re-read `due` INSIDE the callback — the metadataCache dueMs + // the caller passed can be stale (a concurrent sweep / another device + // may have just rolled it), which would re-roll from the wrong anchor + // and skip/duplicate a cycle. + const curDue = fm.due != null ? Date.parse(String(fm.due)) : NaN; + if (Number.isFinite(curDue) && curDue > now) return; // already rolled forward — done + const rec = parseRecurrence(fm.repeat as string | undefined); + if (rec) { fm.due = new Date(nextDueOnComplete(rec, Number.isFinite(curDue) ? curDue : null, now)).toISOString(); delete fm.completed; } + else fm.completed = true; + }); + } catch (e) { console.warn("[Stashpad] auto-resolve failed", f.path, e); } + } + async checkDueReminders(): Promise<void> { const now = Date.now(); const notified = new Set(this.settings.notifiedDueKeys ?? []); const myId = (this.settings.authorId ?? "").trim(); const due: Array<{ id: string; folder: string; file: TFile; dueMs: number; key: string }> = []; + // 0.140.0: persistent-reminder re-fire bookkeeping (id → last-notified ms). + const persistLog = { ...(this.settings.persistReminderLog ?? {}) }; + let persistDirty = false; + const activePersistIds = new Set<string>(); // 0.140.1: for pruning the log for (const f of this.app.vault.getMarkdownFiles()) { if (f.path.includes("/_authors/")) continue; - const fm = this.app.metadataCache.getFileCache(f)?.frontmatter as { id?: unknown; due?: unknown } | undefined; + const fm = this.app.metadataCache.getFileCache(f)?.frontmatter as { id?: unknown; due?: unknown; completed?: unknown; repeat?: unknown; autoDoneAfter?: unknown; remindEvery?: unknown } | undefined; if (!fm || fm.due == null) continue; const id = typeof fm.id === "string" ? fm.id : ""; if (!id) continue; const dueRaw = String(fm.due); const dueMs = typeof fm.due === "number" ? fm.due : Date.parse(dueRaw); if (!Number.isFinite(dueMs) || dueMs > now) continue; // not due yet - // Assignee scoping: a task assigned to specific people only reminds THOSE - // people (so I only get a reminder for a task assigned to me). An - // UNASSIGNED task reminds everyone (i.e. me too). + + // 0.140.0: auto-complete-after ("un-failable" tasks) — once past due by + // the configured grace, resolve WITHOUT reminding. A repeating task rolls + // forward; a one-off just gets marked complete. Runs before assignee + // scoping so it applies regardless of who'd be reminded. + if (fm.completed !== true) { + const grace = parseDuration(fm.autoDoneAfter as string | undefined); + if (grace != null && now >= dueMs + grace) { + await this.autoResolveDueTask(f, dueMs, now); + continue; + } + } + if (fm.completed === true) continue; // completed one-offs: nothing to do + const assignees = parseAssignees(fm); if (assignees.length > 0 && !(myId && assignees.some((a) => a.id === myId))) continue; + + // 0.140.0: persistent reminders re-fire every `remindEvery` until done — + // otherwise the once-per-(id@due) key suppresses repeats. + const every = parseDuration(fm.remindEvery as string | undefined); + if (every != null) { + activePersistIds.add(id); + const last = persistLog[id] ?? 0; + if (now - last >= every) { + persistLog[id] = now; persistDirty = true; + due.push({ id, folder: (f.parent?.path ?? "").replace(/\/+$/, ""), file: f, dueMs, key: `${id}@persist` }); + } + continue; // persistent tasks bypass the once-only key path + } + const key = `${id}@${dueRaw}`; if (notified.has(key)) continue; due.push({ id, folder: (f.parent?.path ?? "").replace(/\/+$/, ""), file: f, dueMs, key }); } + // 0.140.1: actually prune the persist log (the old code only wrote it back) + // — drop ids that are no longer a live persistent+due task, so it can't grow + // unbounded after tasks complete/lose remindEvery. + for (const kId of Object.keys(persistLog)) if (!activePersistIds.has(kId)) { delete persistLog[kId]; persistDirty = true; } + if (persistDirty) { this.settings.persistReminderLog = persistLog; await this.saveSettings(); } if (due.length === 0) return; // Record up front so the interval / a fast re-entry can't double-fire. - this.settings.notifiedDueKeys = [...(this.settings.notifiedDueKeys ?? []), ...due.map((d) => d.key)].slice(-2000); + // 0.140.1: EXCLUDE `@persist` keys — they're never read back (the persist + // path bypasses `notified`), so appending them just churned the 2000-cap + // ring and evicted legit `id@due` keys (→ old past-due one-offs re-notify). + this.settings.notifiedDueKeys = [...(this.settings.notifiedDueKeys ?? []), ...due.filter((d) => !d.key.endsWith("@persist")).map((d) => d.key)].slice(-2000); await this.saveSettings(); const titleOf = async (file: TFile): Promise<string> => { try { @@ -5482,6 +5998,9 @@ export default class StashpadPlugin extends Plugin { async loadSettings(): Promise<void> { const data = (await this.loadData()) ?? {}; + // 0.137.3: collision guard — remember the on-disk write generation we + // loaded from, so a save can detect that another instance wrote since. + this.lastSeenSettingsRev = typeof data?.settingsRev === "number" ? data.settingsRev : 0; // Migrate legacy `confirmMultiDelete` (split in 0.51.12 into two flags: // confirmBulkDelete + confirmAttachmentDelete). Preserve the user's // previous choice by seeding both new flags from the old value when @@ -5557,6 +6076,9 @@ export default class StashpadPlugin extends Plugin { : ["5m", "15m", "30m", "1h", "1d", "1w"], }; setSettings(this.settings); + // 0.137.3: collision guard — baseline what the protected keys looked like + // at load, so a later save can tell "we changed it" from "someone else did". + this.snapshotSettingsBaseline(); // 0.124.1: one-time migration of the "Toggle task" default H → G. Installs // persist the FULL bindings map, so changing the default alone never reaches // existing users. Flip a still-default `H` to `G` once, then mark it done so @@ -5771,11 +6293,66 @@ export default class StashpadPlugin extends Plugin { // Snapshot the settings reference at queue time. saveData itself does // a synchronous JSON.stringify, but we still chain so two in-flight // writes can't interleave their adapter.write calls. - const next = this.writeChain.then(() => this.saveData(this.settings)); + const next = this.writeChain.then(() => this.guardedSave()); this.writeChain = next.catch(() => {}); return next; } + /** 0.137.3: MULTI-WRITER COLLISION GUARD. Two Obsidian instances on the same + * vault (dual-open, network drives, sync tools) each hold settings in + * memory; last-save-wins used to let a STALE writer silently revert the + * other's changes — including the encryption identity, which manifests as + * "my password stopped working". Every save now re-reads data.json first; + * if another writer bumped the rev since we last saw it, the SECURITY- + * CRITICAL keys we haven't touched this session are adopted from disk + * instead of clobbered (keys we DID change: ours win — we're the active + * editor). Best-effort (not a lock — simultaneous writes can still race), + * but it turns the common interleaving from silent key-loss into a merge + + * a loud notice. */ + private static readonly COLLISION_PROTECTED_KEYS = ["encryption", "lockedSubtrees", "folderEncPrefs", "archiveFolders"] as const; + private lastSeenSettingsRev = 0; + private settingsBaseline: Record<string, string | undefined> = {}; + + private snapshotSettingsBaseline(): void { + for (const k of StashpadPlugin.COLLISION_PROTECTED_KEYS) { + this.settingsBaseline[k] = JSON.stringify((this.settings as unknown as Record<string, unknown>)[k]); + } + } + + private async guardedSave(): Promise<void> { + let disk: Record<string, unknown> | null = null; + try { disk = (await this.loadData()) as Record<string, unknown> | null; } catch { /* first write / unreadable */ } + const diskRev = typeof disk?.settingsRev === "number" ? (disk.settingsRev as number) : 0; + // 0.140.2: adopt on CONTENT change, not just a higher rev. Two instances + // that both loaded rev N and write near-simultaneously both stamp N+1, so a + // rev-gated check (`diskRev > lastSeen`) misses the collision and silently + // clobbers the other's protected keys. Comparing the on-disk protected-key + // content against OUR baseline catches it whenever the other write already + // landed — regardless of rev — which is the common "minutes apart" case. + if (disk) { + const adopted: string[] = []; + for (const k of StashpadPlugin.COLLISION_PROTECTED_KEYS) { + const oursChanged = JSON.stringify((this.settings as unknown as Record<string, unknown>)[k]) !== this.settingsBaseline[k]; + const diskChanged = JSON.stringify(disk[k]) !== this.settingsBaseline[k]; + if (!oursChanged && diskChanged && disk[k] !== undefined) { + (this.settings as unknown as Record<string, unknown>)[k] = disk[k]; + adopted.push(k); + } + } + if (adopted.length) { + console.warn(`[Stashpad] settings collision: on-disk protected keys differ from our baseline (disk rev ${diskRev}, we knew ${this.lastSeenSettingsRev}); adopted: ${adopted.join(", ")}.`); + new Notice(`Stashpad: another Obsidian instance (or a synced machine) changed this vault's settings. Merged instead of overwriting (${adopted.join(", ")}). If encryption behaves oddly, restart Obsidian.`, 10000); + setSettings(this.settings); + } + if (diskRev > this.lastSeenSettingsRev) this.lastSeenSettingsRev = diskRev; + } + const rev = Math.max(diskRev, this.lastSeenSettingsRev) + 1; + (this.settings as unknown as Record<string, unknown>).settingsRev = rev; + await this.saveData(this.settings); + this.lastSeenSettingsRev = rev; + this.snapshotSettingsBaseline(); + } + async saveSettings(): Promise<void> { await this.queueWrite(); setSettings(this.settings); diff --git a/src/modals.ts b/src/modals.ts index 8a1f234..88d5be3 100644 --- a/src/modals.ts +++ b/src/modals.ts @@ -7,8 +7,21 @@ import { newId } from "./id-service"; import type { ImportLogEntry } from "./import-log"; export interface AssigneeRef { id: string; name: string } -export interface DuePickResult { iso: string | null; assignees: AssigneeRef[] } +export interface DuePickResult { + iso: string | null; + assignees: AssigneeRef[]; + /** 0.140.0: recurrence + reminder rules (empty string = clear the field). + * Present only when the picker showed the "Repeat & reminders" section. */ + repeat?: string; + autoDoneAfter?: string; + remindEvery?: string; +} export interface DuePickerOptions { + /** 0.140.0: show the "Repeat & reminders" section, pre-filled from these. */ + showRecurrence?: boolean; + currentRepeat?: string; + currentAutoDoneAfter?: string; + currentRemindEvery?: string; /** Known authors to offer in the assignee picker (from the registry). */ knownAuthors?: AssigneeRef[]; /** Assignees already on the note, to pre-fill the chips. */ @@ -252,6 +265,8 @@ export class LogModal extends Modal { if (this.footerEl) this.footerEl.empty(); new Notice("Log cleared."); }, + "Cancel", + /*dangerous*/ true, ).open(); } @@ -1174,6 +1189,9 @@ export class TypeToConfirmModal extends Modal { export class CustomColorModal extends Modal { private value: string; private delivered = false; + /** Whether the user actually changed the color. A dismiss (Esc/click-out) + * without touching anything must NOT apply the seeded default. (0.140.6) */ + private touched = false; constructor( app: App, seed: string | null, @@ -1207,6 +1225,7 @@ export class CustomColorModal extends Modal { const sync = (next: string) => { const v = next.startsWith("#") ? next : "#" + next; if (!/^#[0-9a-f]{6}$/i.test(v)) return; + this.touched = true; this.value = v; preview.style.background = v; wheel.value = v; @@ -1236,9 +1255,10 @@ export class CustomColorModal extends Modal { this.onResult(this.value, opts); } onClose(): void { - if (!this.delivered) { - // Click-out / Esc → apply hastily (no palette persistence), matching - // the user's "skip adding" intent. + if (!this.delivered && this.touched) { + // Click-out / Esc AFTER changing the color → apply hastily (no palette + // persistence), matching the user's "skip adding" intent. An UNtouched + // dismiss is a cancel — don't paint the seeded default over the note. this.delivered = true; this.onResult(this.value, { addToPalette: false }); } @@ -1265,6 +1285,9 @@ export class ColorPickerModal extends Modal { /** Active grid index for keyboard nav. -1 = no focus yet. */ private focusIdx = -1; + /** Guard so re-rendering the grid (handleDelete → onOpen) doesn't stack a + * second copy of the keyboard handlers on the persistent scope. (0.140.6) */ + private scopeBound = false; /** Snapshot of selectable tiles in render order. The "+" tile is `kind: * "add"` and opens CustomColorModal rather than committing directly. */ private items: { kind: "none" | "preset" | "custom" | "add"; color: string | null; el: HTMLElement }[] = []; @@ -1332,12 +1355,17 @@ export class ColorPickerModal extends Modal { this.focusIdx = seedIdx >= 0 ? seedIdx : 0; this.refreshActive(); - // Keyboard nav: arrows move focus; Enter activates. - this.scope.register([], "ArrowRight", (e) => { e.preventDefault(); this.moveFocus(1); }); - this.scope.register([], "ArrowLeft", (e) => { e.preventDefault(); this.moveFocus(-1); }); - this.scope.register([], "ArrowDown", (e) => { e.preventDefault(); this.moveFocus(this.columns()); }); - this.scope.register([], "ArrowUp", (e) => { e.preventDefault(); this.moveFocus(-this.columns()); }); - this.scope.register([], "Enter", (e) => { e.preventDefault(); this.activate(this.focusIdx); }); + // Keyboard nav: arrows move focus; Enter activates. Registered once — the + // handlers read live this.focusIdx/this.items, so they survive a grid + // rebuild; re-registering (handleDelete → onOpen) would stack duplicates. + if (!this.scopeBound) { + this.scope.register([], "ArrowRight", (e) => { e.preventDefault(); this.moveFocus(1); }); + this.scope.register([], "ArrowLeft", (e) => { e.preventDefault(); this.moveFocus(-1); }); + this.scope.register([], "ArrowDown", (e) => { e.preventDefault(); this.moveFocus(this.columns()); }); + this.scope.register([], "ArrowUp", (e) => { e.preventDefault(); this.moveFocus(-this.columns()); }); + this.scope.register([], "Enter", (e) => { e.preventDefault(); this.activate(this.focusIdx); }); + this.scopeBound = true; + } // After paint, focus the modal so arrow keys land here, not in the // background view. @@ -1428,6 +1456,9 @@ export class ConfirmModal extends Modal { private confirmText: string, private onChoose: (confirmed: boolean) => void, private cancelText: string = "Cancel", + /** When true (destructive confirms like "Clear log"), focus Cancel instead + * of Confirm so a stray Enter doesn't fire the irreversible action. */ + private dangerous: boolean = false, ) { super(app); } onOpen(): void { this.modalEl?.addClass("stashpad-compact-modal"); // 0.76.18 @@ -1446,8 +1477,9 @@ export class ConfirmModal extends Modal { cancel.onclick = () => { this.didChoose = true; this.close(); this.onChoose(false); }; const ok = row.createEl("button", { cls: "mod-cta", text: this.confirmText }); ok.onclick = () => { this.didChoose = true; this.close(); this.onChoose(true); }; - // Focus the confirm button so Enter accepts. - requestAnimationFrame(() => ok.focus()); + // Focus the confirm button so Enter accepts — but for destructive confirms + // focus Cancel instead, so a stray Enter cancels rather than fires. (0.140.6) + requestAnimationFrame(() => (this.dangerous ? cancel : ok).focus()); } onClose(): void { this.contentEl.empty(); @@ -1746,6 +1778,32 @@ export class DueDatePickerModal extends Modal { timeInput.value = ""; dateInput.focus(); }; + // 0.140.0: optional "Repeat & reminders" section (collapsible). Three + // free-text fields; recurrence uses natural language ("every weekday", + // "every 30 days when done"). Only shown/returned when opts.showRecurrence. + let repeatIn: HTMLInputElement | null = null; + let autoIn: HTMLInputElement | null = null; + let remindIn: HTMLInputElement | null = null; + if (this.opts.showRecurrence) { + const det = wrap.createEl("details", { cls: "stashpad-due-recur" }); + if (this.opts.currentRepeat || this.opts.currentAutoDoneAfter || this.opts.currentRemindEvery) det.open = true; + det.createEl("summary", { text: "🔁 Repeat & reminders" }); + const mkRow = (label: string, ph: string, val?: string): HTMLInputElement => { + const r = det.createDiv({ cls: "stashpad-due-recur-row" }); + r.createEl("label", { text: label }); + const inp = r.createEl("input", { type: "text", attr: { placeholder: ph } }); + if (val) inp.value = val; + return inp; + }; + repeatIn = mkRow("Repeat", 'e.g. "every weekday", "every 30 days when done"', this.opts.currentRepeat); + autoIn = mkRow("Auto-complete after", 'e.g. "1d" — mark done once this overdue', this.opts.currentAutoDoneAfter); + remindIn = mkRow("Remind every", 'e.g. "2h" — re-notify until done', this.opts.currentRemindEvery); + } + const recur = (): Pick<DuePickResult, "repeat" | "autoDoneAfter" | "remindEvery"> => + this.opts.showRecurrence + ? { repeat: repeatIn!.value.trim(), autoDoneAfter: autoIn!.value.trim(), remindEvery: remindIn!.value.trim() } + : {}; + const cancel = grid.createEl("button", { cls: "stashpad-due-btn", text: "Cancel" }); cancel.onclick = () => { this.didChoose = true; this.close(); }; const ok = grid.createEl("button", { cls: "stashpad-due-btn mod-cta", text: "Set" }); @@ -1755,7 +1813,7 @@ export class DueDatePickerModal extends Modal { if (!dateInput.value) { this.didChoose = true; this.close(); - this.onPick({ iso: null, assignees: this.assignees }); + this.onPick({ iso: null, assignees: this.assignees, ...recur() }); return; } // Default time to 09:00 when only a date was chosen. @@ -1765,7 +1823,7 @@ export class DueDatePickerModal extends Modal { const due = new Date(y, m - 1, d, hh, mm, 0, 0); this.didChoose = true; this.close(); - this.onPick({ iso: due.toISOString(), assignees: this.assignees }); + this.onPick({ iso: due.toISOString(), assignees: this.assignees, ...recur() }); }; requestAnimationFrame(() => dateInput.focus()); } @@ -1975,6 +2033,8 @@ export class NotificationHistoryModal extends Modal { this.service.clearHistory(); // service.clearHistory emits — our subscriber refreshes. }, + "Cancel", + /*dangerous*/ true, ).open(); }; @@ -2256,3 +2316,43 @@ export class OkfExportModal extends Modal { } onClose(): void { this.contentEl.empty(); } } + +/** 0.138.0 (re-encrypt sweep): ONE review modal listing everything that should + * be encrypted but is plaintext — per-subtree rows, checkboxes pre-ticked. + * Confirm passes the CHECKED indices; nothing encrypts without it. */ +export class ReEncryptReviewModal extends Modal { + constructor( + app: App, + private items: Array<{ label: string; detail: string }>, + private onConfirm: (chosenIndices: number[]) => void | Promise<void>, + ) { super(app); } + + onOpen(): void { + this.titleEl.setText("Re-encrypt everything applicable?"); + const { contentEl } = this; + contentEl.createEl("p", { + cls: "setting-item-description", + text: "These notes should be encrypted (they were unlocked/restored, or their folder is set to encrypt) but are currently plaintext. Untick anything you want to leave readable.", + }); + const list = contentEl.createDiv({ cls: "stashpad-reenc-list" }); + const boxes: HTMLInputElement[] = []; + this.items.forEach((it) => { + const row = list.createEl("label", { cls: "stashpad-reenc-row" }); + const cb = row.createEl("input", { type: "checkbox" }); + cb.checked = true; + boxes.push(cb); + const body = row.createDiv({ cls: "stashpad-reenc-body" }); + body.createDiv({ cls: "stashpad-reenc-label", text: it.label }); + body.createDiv({ cls: "stashpad-reenc-detail", text: it.detail }); + }); + const btns = contentEl.createDiv({ cls: "modal-button-container" }); + const go = btns.createEl("button", { cls: "mod-cta", text: `Re-encrypt checked` }); + go.onclick = () => { + const chosen = boxes.map((b, i) => (b.checked ? i : -1)).filter((i) => i >= 0); + this.close(); + if (chosen.length) void this.onConfirm(chosen); + }; + btns.createEl("button", { text: "Cancel" }).onclick = () => this.close(); + } + onClose(): void { this.contentEl.empty(); } +} diff --git a/src/note-picker.ts b/src/note-picker.ts index 8029ac5..10ce603 100644 --- a/src/note-picker.ts +++ b/src/note-picker.ts @@ -383,12 +383,9 @@ export class StashpadSuggest extends SuggestModal<PickerItem> { // run token-order-agnostic match against title/body. const parsed = parseSearchQuery(query); const tokens = parsed.text; - // 0.64.2 diagnostic — use console.log (not debug) so it shows - // at the default DevTools log level. Always fires while we're - // debugging the filter pipeline. - console.log("[Stashpad] search query parsed", { - query, text: parsed.text, filters: parsed.filters, - }); + // 0.140.1: removed a leftover `console.log` that dumped every typed search + // query to the DevTools console on every keystroke (info-leak on a shared + // machine + production noise). Was tagged "always fires while debugging". const matchesAll = (haystack: string): boolean => { if (!tokens.length) return true; for (const t of tokens) if (!haystack.includes(t)) return false; diff --git a/src/order-store.ts b/src/order-store.ts index 2c441ac..7c69eca 100644 --- a/src/order-store.ts +++ b/src/order-store.ts @@ -83,11 +83,26 @@ export class OrderStore { private async doWrite(folder: string): Promise<void> { const map = this.cache.get(folder) ?? {}; + const path = `${folder}/${ORDER_FILE}`; + const adapter = this.app.vault.adapter; + // 0.140.3 (review): a mutation (setOrder/appendChild) that fires BEFORE this + // folder's load() has resolved seeds the cache with ONLY the touched parent, + // so writing it verbatim would clobber every other parent's saved order. + // Merge in any on-disk parents we DIDN'T touch this session (cache wins per + // key) so untouched orders survive. + try { + if (await adapter.exists(path)) { + const parsed = JSON.parse(await adapter.read(path)); + if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) { + for (const [k, v] of Object.entries(parsed)) { + if (!(k in map) && Array.isArray(v) && v.every((x) => typeof x === "string")) map[k] = v as string[]; + } + } + } + } catch { /* unreadable / absent — write what we have */ } const trimmed: Record<string, string[]> = {}; for (const [k, v] of Object.entries(map)) if (v.length > 0) trimmed[k] = v; this.cache.set(folder, trimmed); - const path = `${folder}/${ORDER_FILE}`; - const adapter = this.app.vault.adapter; try { if (Object.keys(trimmed).length === 0) { // Skip the exists() probe — remove() throws "file not found" if diff --git a/src/panels-view.ts b/src/panels-view.ts index 3527070..228112c 100644 --- a/src/panels-view.ts +++ b/src/panels-view.ts @@ -443,7 +443,8 @@ export class StashpadPanelsView extends ItemView { /** Display title from a TFile — strip the trailing "-id" suffix and * un-hyphenate. */ private titleFromFile(file: TFile): string { - return titleFromTaskFile(file); + const id = this.app.metadataCache.getFileCache(file)?.frontmatter?.id; + return titleFromTaskFile(file, typeof id === "string" ? id : null); } // ---------- Actions ---------- diff --git a/src/recurrence.ts b/src/recurrence.ts new file mode 100644 index 0000000..bb73fef --- /dev/null +++ b/src/recurrence.ts @@ -0,0 +1,135 @@ +/** 0.140.0: recurring + persistent reminders. Hand-rolled (no rrule dep) parser + * for the natural-language recurrence patterns people actually type, plus the + * duration parser the auto-complete / persistent-reminder features share. + * + * Frontmatter contract (all optional, on any task note): + * - `repeat`: a recurrence rule string (see parseRecurrence). A trailing + * "when done" anchors the next occurrence to the COMPLETION time + * instead of the due time (toothbrush-every-30-days case). + * - `autoDoneAfter`: a duration ("1h" / "2d" / "30m"). Once the task is this + * long past due, it auto-marks complete (un-failable tasks). + * - `remindEvery`: a duration. Re-notify this often until done (persistent). + */ + +const UNIT_MS: Record<string, number> = { + min: 60_000, minute: 60_000, m: 60_000, + h: 3_600_000, hr: 3_600_000, hour: 3_600_000, + d: 86_400_000, day: 86_400_000, + w: 604_800_000, wk: 604_800_000, week: 604_800_000, +}; + +/** "30m" / "2 hours" / "1d" / "90 min" → milliseconds. null if unparseable. */ +export function parseDuration(s: string | number | null | undefined): number | null { + if (typeof s === "number") return s > 0 ? s : null; + if (!s) return null; + const m = String(s).trim().toLowerCase().match(/^(\d+(?:\.\d+)?)\s*([a-z]+)?$/); + if (!m) return null; + const n = parseFloat(m[1]); + const unit = (m[2] || "min").replace(/s$/, ""); + const ms = UNIT_MS[unit]; + return ms && n > 0 ? n * ms : null; +} + +type Anchor = "due" | "completion"; +export interface Recurrence { + /** Advance `from` (ms) to the next occurrence AFTER it. */ + next: (from: number) => number; + /** "when done" → anchor the roll to the completion time, not the old due. */ + anchor: Anchor; + /** Human echo of what we parsed, for confirmation UI. */ + label: string; +} + +const WEEKDAYS = ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"]; + +function addMonths(ms: number, n: number): number { + const d = new Date(ms); + const day = d.getDate(); + d.setMonth(d.getMonth() + n); + // Clamp end-of-month overflow (Jan 31 + 1mo → Feb 28/29, not Mar 3). + if (d.getDate() < day) d.setDate(0); + return d.getTime(); +} +function addYears(ms: number, n: number): number { + const d = new Date(ms); d.setFullYear(d.getFullYear() + n); return d.getTime(); +} +function nextWeekday(from: number, targetDow: number): number { + const d = new Date(from); + do { d.setDate(d.getDate() + 1); } while (d.getDay() !== targetDow); + return d.getTime(); +} +function nextBusinessDay(from: number): number { + const d = new Date(from); + do { d.setDate(d.getDate() + 1); } while (d.getDay() === 0 || d.getDay() === 6); + return d.getTime(); +} + +/** Parse a recurrence rule. Understood forms (case-insensitive, "every" + * optional): "daily" / "every day" / "every 3 days"; "weekly" / "every week" / + * "every 2 weeks"; "every weekday"; "every monday"; "monthly" / "every month" / + * "every 2 months" / "first of the month"; "yearly" / "every year"; "every N + * hours" / "every N minutes". A trailing "when done" / "after completion" sets + * the completion anchor. Returns null if nothing matched. */ +export function parseRecurrence(rule: string | null | undefined): Recurrence | null { + if (!rule) return null; + let s = String(rule).trim().toLowerCase(); + if (!s) return null; + let anchor: Anchor = "due"; + if (/\b(when done|after completion|on completion|from completion)\b/.test(s)) { + anchor = "completion"; + s = s.replace(/\b(when done|after completion|on completion|from completion)\b/g, "").trim(); + } + s = s.replace(/^every\s+/, "").replace(/\s+/g, " ").trim(); + + const mk = (next: (from: number) => number, label: string): Recurrence => ({ next, anchor, label }); + + // first of the month + if (/^(first|1st) of (the )?month$/.test(s)) { + return mk((from) => { const d = new Date(from); d.setMonth(d.getMonth() + 1, 1); return d.getTime(); }, "first of the month"); + } + // weekday name(s) — single weekday only for v1 + const dow = WEEKDAYS.indexOf(s.replace(/s$/, "")); + if (dow >= 0) return mk((from) => nextWeekday(from, dow), `every ${WEEKDAYS[dow]}`); + if (s === "weekday" || s === "weekdays" || s === "business day" || s === "business days") + return mk(nextBusinessDay, "every weekday"); + + // bare keywords + if (s === "daily" || s === "day") return mk((f) => f + UNIT_MS.day, "every day"); + if (s === "weekly" || s === "week") return mk((f) => f + UNIT_MS.week, "every week"); + if (s === "monthly" || s === "month") return mk((f) => addMonths(f, 1), "every month"); + if (s === "yearly" || s === "annually" || s === "year") return mk((f) => addYears(f, 1), "every year"); + if (s === "hourly" || s === "hour") return mk((f) => f + UNIT_MS.h, "every hour"); + + // "N <unit>" + const m = s.match(/^(\d+)\s*([a-z]+)$/); + if (m) { + const n = parseInt(m[1], 10); + const unit = m[2].replace(/s$/, ""); + if (n > 0) { + if (unit === "day") return mk((f) => f + n * UNIT_MS.day, `every ${n} days`); + if (unit === "week" || unit === "wk") return mk((f) => f + n * UNIT_MS.week, `every ${n} weeks`); + if (unit === "month" || unit === "mo") return mk((f) => addMonths(f, n), `every ${n} months`); + if (unit === "year" || unit === "yr") return mk((f) => addYears(f, n), `every ${n} years`); + if (unit === "hour" || unit === "hr" || unit === "h") return mk((f) => f + n * UNIT_MS.h, `every ${n} hours`); + if (unit === "min" || unit === "minute" || unit === "m") return mk((f) => f + n * UNIT_MS.min, `every ${n} minutes`); + } + } + return null; +} + +/** The next due timestamp for a repeating task being completed now. Rolls from + * the completion time (`nowMs`) when anchored "when done", else from the old + * due — advancing PAST now so an overdue task doesn't reschedule into the past. */ +export function nextDueOnComplete(rec: Recurrence, oldDueMs: number | null, nowMs: number): number { + const from = rec.anchor === "completion" ? nowMs : (oldDueMs ?? nowMs); + let next = rec.next(from); + // Never schedule the next occurrence in the past (missed several cycles). + let guard = 0; + while (next <= nowMs && guard++ < 5000) next = rec.next(next); + // 0.140.1: if we STILL landed in the past (pathological overdue — e.g. an + // every-5-min task months overdue), advance from NOW so the contract "never + // returns a past timestamp" always holds and callers don't rewrite the file + // every poll trying to converge. + if (next <= nowMs) { next = rec.next(nowMs); let g = 0; while (next <= nowMs && g++ < 5000) next = rec.next(next); } + return next; +} diff --git a/src/reencrypt-scheduler.ts b/src/reencrypt-scheduler.ts new file mode 100644 index 0000000..160978b --- /dev/null +++ b/src/reencrypt-scheduler.ts @@ -0,0 +1,146 @@ +import { MarkdownView, Notice, TFile } from "obsidian"; +import type StashpadPlugin from "./main"; +import type { ReEncryptWatchEntry } from "./settings"; + +/** 0.139.0 (Q5 "peek"): after a configurable idle time, an unlocked + * previously-encrypted subtree is QUEUED to be re-encrypted — with a visible, + * cancellable countdown. This deliberately revives a form of the removed idle + * auto-lock, so it's hedged with hard safeguards: + * - OPT-IN, default off. Delay resolves per-note → per-folder → global; 0/undef = off. + * - NEVER fires while the note is OPEN in an editor or was edited very recently. + * - Always a "Re-encrypting in Ns… [Keep unlocked]" countdown the user can cancel. + * - Cancelling (or editing) re-arms; the entry stays on the watchlist either way. + * Granularity is per-subtree (rootId), matching the watchlist. */ +export class ReEncryptScheduler { + private tick: number | null = null; + /** folder → last activity ms (a modify/open in that folder re-arms its timers). */ + private lastTouch = new Map<string, number>(); + /** key `${folder} ${rootId}` currently showing a countdown — don't double-fire. */ + private counting = new Set<string>(); + private static readonly COUNTDOWN_SECS = 10; + private static readonly RECENT_EDIT_MS = 20_000; // treat a just-edited folder as active + + constructor(private plugin: StashpadPlugin) {} + + private key(folder: string, rootId: string): string { return `${folder} ${rootId}`; } + + /** Resolve the idle delay (minutes) for one entry: per-note > per-folder > + * global. 0 or undefined = feature off for that entry. */ + private delayMin(e: ReEncryptWatchEntry): number { + const perNote = e.peekMin; + if (perNote != null) return perNote; + const fp = (this.plugin.settings.folderEncPrefs ?? {})[e.folder.replace(/\/+$/, "")]; + const perFolder = fp?.reEncryptAfterMin; + if (perFolder != null) return perFolder; + return this.plugin.settings.reEncryptAfterMin ?? 0; + } + + /** Any peek delay configured anywhere? If not, the interval stays off. */ + private anyArmed(): boolean { + if ((this.plugin.settings.reEncryptAfterMin ?? 0) > 0) return true; + for (const p of Object.values(this.plugin.settings.folderEncPrefs ?? {})) if ((p?.reEncryptAfterMin ?? 0) > 0) return true; + return this.plugin.reEncryptWatching().some((e) => (e.peekMin ?? 0) > 0); + } + + /** Register vault/workspace listeners + the poll. Call once from onload. */ + start(): void { + const touch = (folder: string) => { if (folder) this.lastTouch.set(folder.replace(/\/+$/, ""), Date.now()); }; + this.plugin.registerEvent(this.plugin.app.vault.on("modify", (f) => { if (f instanceof TFile) touch(f.parent?.path ?? ""); })); + this.plugin.registerEvent(this.plugin.app.workspace.on("file-open", (f) => { if (f) touch(f.parent?.path ?? ""); })); + // Poll every 30s — cheap, and only does work when a delay is configured. + this.tick = window.setInterval(() => void this.poll(), 30_000); + this.plugin.registerInterval(this.tick); + } + + /** A markdown file for this subtree's folder is open+visible → treat as busy. + * Coarse (folder-level, not exact-subtree) on purpose — err toward NOT + * re-encrypting something the user might be looking at. */ + private folderHasOpenEditor(folder: string): boolean { + const clean = folder.replace(/\/+$/, ""); + let open = false; + this.plugin.app.workspace.iterateAllLeaves((leaf) => { + const v = leaf.view; + if (v instanceof MarkdownView && v.file && (v.file.parent?.path?.replace(/\/+$/, "") ?? "") === clean) open = true; + }); + return open; + } + + /** The re-lock key is already in memory (so re-encrypting won't PROMPT). + * Never fire a timer that would surprise the user with a password box. */ + private keyReady(folder: string): boolean { + const enc = this.plugin.encryption; + if (!enc?.isConfigured?.()) return false; + return enc.hasOwnFolderKey(folder) ? enc.isFolderUnlocked(folder) : enc.isUnlocked(); + } + + private async poll(): Promise<void> { + if (!this.anyArmed()) return; + const now = Date.now(); + for (const e of this.plugin.reEncryptWatching()) { + const mins = this.delayMin(e); + if (mins <= 0) continue; + const folder = e.folder.replace(/\/+$/, ""); + const k = this.key(folder, e.rootId); + if (this.counting.has(k)) continue; + const base = Math.max(Date.parse(e.unlockedAt) || 0, this.lastTouch.get(folder) ?? 0); + if (now < base + mins * 60_000) continue; // still within idle window + if (!this.keyReady(folder)) continue; // locked — never prompt on a timer + if (this.folderHasOpenEditor(folder)) { touchFolder(this.lastTouch, folder, now); continue; } // busy — defer + void this.countdownThenLock(e, k); + } + } + + /** Live countdown intervals, so dispose() can clear them on unload. */ + private countdownTimers = new Set<number>(); + + private countdownThenLock(e: ReEncryptWatchEntry, k: string): void { + this.counting.add(k); + const folder = e.folder.replace(/\/+$/, ""); + const title = e.title || "a note"; + const startedAt = Date.now(); + let left = ReEncryptScheduler.COUNTDOWN_SECS; + let cancelled = false; + const note = new Notice("", 0); + const finish = () => { window.clearInterval(iv); this.countdownTimers.delete(iv); note.hide(); this.counting.delete(k); }; + const abortReArm = () => { this.lastTouch.set(folder, Date.now()); finish(); }; + const render = () => { + note.noticeEl.empty(); + note.noticeEl.createSpan({ text: `🔒 Re-encrypting “${title}” in ${left}s… ` }); + note.noticeEl.createEl("button", { text: "Keep unlocked" }).onclick = () => { cancelled = true; abortReArm(); }; + }; + render(); + const iv = window.setInterval(async () => { + if (cancelled) return; + // 0.140.1: a modify/open in the folder DURING the countdown re-arms and + // aborts (the header promised "editing re-arms" — the old code ignored it). + if ((this.lastTouch.get(folder) ?? 0) > startedAt) { abortReArm(); return; } + left -= 1; + if (left > 0) { render(); return; } + finish(); + // Still applicable? (re-locked/deleted during the countdown.) + if (!this.plugin.reEncryptWatching().some((w) => w.folder === e.folder && w.rootId === e.rootId)) return; + if (this.folderHasOpenEditor(folder)) { this.lastTouch.set(folder, Date.now()); return; } // opened it mid-countdown → abort + // 0.140.1: re-check the key is STILL in memory — never pop a password + // modal from a background timer (it may have locked during the countdown). + if (!this.keyReady(folder)) { this.lastTouch.set(folder, Date.now()); return; } + const ok = await this.plugin.lockNoteSubtree(e.folder, e.rootId as unknown as import("./types").StashpadId, null, { silent: true }); + if (ok) new Notice(`Re-encrypted “${title}”.`); + }, 1000); + // 0.140.1: track so dispose() clears it — a raw setInterval survived plugin + // unload and wrote stale settings over the reloaded instance. + this.countdownTimers.add(iv); + this.plugin.registerInterval(iv); + } + + /** Called from the plugin's onunload — stop any in-flight countdown so it + * can't fire lockNoteSubtree/saveSettings on a dead instance. */ + dispose(): void { + for (const iv of this.countdownTimers) window.clearInterval(iv); + this.countdownTimers.clear(); + this.counting.clear(); + } +} + +function touchFolder(map: Map<string, number>, folder: string, when: number): void { + map.set(folder.replace(/\/+$/, ""), when); +} diff --git a/src/render-cache-store.ts b/src/render-cache-store.ts index 8fd752b..098c066 100644 --- a/src/render-cache-store.ts +++ b/src/render-cache-store.ts @@ -95,6 +95,10 @@ export class RenderCacheStore implements RenderCacheLike { const obj = { schema: CACHE_SCHEMA, entries: Object.fromEntries(this.map) }; await this.app.vault.adapter.write(this.path, JSON.stringify(obj)); } catch (e) { + // Re-arm dirty so the next set()/evict() retries the write. Losing it + // here would strand an evicted (deleted/locked) note's plaintext in the + // cache file after a transient failure — defeating secure-delete. (0.140.5) + this.dirty = true; console.warn("[Stashpad] render cache save failed", e); } }); diff --git a/src/settings.ts b/src/settings.ts index aa9d3bd..907e877 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -140,7 +140,7 @@ export const COMMAND_META: CommandMeta[] = [ { id: "commandPalette", label: "Command palette (Stashpad only)", desc: "Default: Mod+K — open a command palette listing only Stashpad's commands, with Sift search.", defaultPrimary: "Mod+K" }, { id: "lockSelection", label: "Encrypt (lock) selection", desc: "Encrypt the selected note(s) + their children into a locked .stashenc bundle in place (prompts to unlock first if needed). No default chord.", defaultPrimary: "" }, { id: "unlockAll", label: "Decrypt (unlock) locked notes in view", desc: "Decrypt every locked stash shown in the current view back into place, skipping any that can't be read. No default chord.", defaultPrimary: "" }, - { id: "moveToArchive", label: "Move selection to archive (encrypt)", desc: "Move the selected note(s) to the default archive folder, encrypted on arrival. Undoable. No default chord.", defaultPrimary: "" }, + { id: "moveToArchive", label: "Move selection to archive", desc: "Move the selected note(s) into this folder's own archive subfolder (drops out of search; plaintext unless this folder encrypts its archive). Undoable. No default chord.", defaultPrimary: "" }, { id: "encryptDelete", label: "Encrypt & delete selection", desc: "Send the selected note(s) to the encrypted trash (recoverable with your password, Ctrl/Cmd+Z undoable). No default chord.", defaultPrimary: "" }, { id: "copyNotes", label: "Copy notes (note clipboard)", desc: "Copy the selected note(s) as NOTES: paste in the list to duplicate them (new ids), or anywhere else to paste their text. Skipped when text is highlighted (normal copy wins).", defaultPrimary: "Mod+C" }, { id: "cutNotes", label: "Cut notes", desc: "Cut the selected note(s): paste in the list to MOVE them, or in the composer to extract their text and delete the originals (undoable).", defaultPrimary: "Mod+X" }, @@ -166,6 +166,14 @@ export function buildDefaultBindings(): CommandBindingMap { * true to bring the UI back. */ const SHOW_DEVICE_APPROVAL_UI = false; +/** 0.135.0: the vault-level "Sharing" section (shared-password setup UI) is + * hidden — redundant, since the vault password itself can be shared out-of-band + * (Signal, password manager) from the moment it's set up. The service code + * (setSharedPassword/removeSharedPassword/hasSharedPassword) stays DORMANT, and + * a vault that ALREADY has a shared password still gets the manage/turn-off UI + * so it isn't stranded. Flip to true to bring the setup UI back. */ +const SHOW_SHARING_UI = false; + /** Per-folder overhaul: the dedicated "Forget password on this device" buttons are * discontinued (passwords are retained, never deleted — see the no-delete policy). * The forgetKeychain service code stays for remove-encryption + possible reinstate; @@ -182,6 +190,28 @@ const SHOW_FORGET_KEYCHAIN_BUTTON = false; * The six "encrypt" flags are the 6-checkbox model: a content flag + a filename * flag for each of folder / archive / trash. A filename flag is only meaningful * when its content flag is on (the UI greys it out otherwise). */ + +/** 0.138.0: one watchlist entry — a subtree that WAS encrypted and is now + * plaintext. Per-SUBTREE (rootId) granularity: children ride with their root. */ +export interface ReEncryptWatchEntry { + folder: string; + rootId: string; + title: string; + count: number; + /** ISO timestamp of the unlock/restore that put it on the list. */ + unlockedAt: string; + /** How it became plaintext: an ad-hoc unlock, or a restore from trash. */ + via: "unlock" | "restore"; + /** Dismissed by the user — lives in the "Removed from watchlist" section + * (recoverable) and is excluded from sweeps/nudges. */ + removed?: boolean; + removedAt?: string; + /** 0.139.0 (peek): per-note override of the auto-re-encrypt idle delay + * (minutes). Wins over the per-folder + global settings. 0 = off for this + * note; undefined = fall through to folder/global. */ + peekMin?: number; +} + export interface FolderEncPrefs { /** Folder is an archive (de-indexed from cross-folder search). Supersedes the * legacy `archiveFolders[]` list over time; both honored during migration. */ @@ -195,6 +225,9 @@ export interface FolderEncPrefs { archiveEncryptFilenames?: boolean; trashEncryptContent?: boolean; trashEncryptFilenames?: boolean; + /** 0.139.0 (peek): auto-re-encrypt this folder's unlocked notes after N idle + * minutes. Overrides the global `reEncryptAfterMin`; 0/undef = use global. */ + reEncryptAfterMin?: number; } export interface StashpadSettings { @@ -296,19 +329,39 @@ export interface StashpadSettings { * encrypt OR list those, so encrypted-trash + the recoverable trash view won't * apply. The `_deleted/` store is the secure default precisely because it's the * only location Stashpad fully controls. */ - encryptTrashFollowObsidian?: boolean; - /** Drop the in-memory key after N idle minutes (0 = never). */ - encryptionIdleLockMinutes: number; - /** 0.98.14: hide the note title on locked placeholders (show a generic label - * instead) so a glance at the vault doesn't reveal what's locked. Default OFF - * (titles shown). Global for now; per-folder/trash scoping is future work. */ - hideLockedTitles: boolean; + // (0.135.0: encryptionIdleLockMinutes removed — idle auto-lock is gone. + // 0.137.1: encryptTrashFollowObsidian + hideLockedTitles removed — trash + // handling and filename-hiding are per-folder (folderEncPrefs) now. Stale + // values in an existing data.json are simply ignored.) /** 0.124.1: one-time migration marker — the default "Toggle task" hotkey * changed from H to G. Existing installs persist the full bindings map, so * the default change alone wouldn't reach them; on first load we flip a * still-default `H` to `G` once, then set this so it never re-flips (the user * can rebind to H afterwards and it sticks). */ migratedToggleTaskG: boolean; + /** 0.136.0: one-time move of legacy dedicated-archive folders' notes into + * each folder's own `archive/` subfolder (per-folder archive overhaul). */ + migratedArchiveToSubfolders: boolean; + /** 0.137.0: one-time move of _deleted/ encrypted-trash blobs into each + * origin folder's own trash/ subfolder (per-folder trash overhaul). */ + migratedTrashToSubfolders: boolean; + /** 0.138.0 (smart re-encrypt sweep): subtrees that WERE encrypted and got + * unlocked back to plaintext (ad-hoc unlock or restore-from-trash). + * Unlimited — the "Previously encrypted" review view manages it. `removed` + * entries live in that view's "Removed from watchlist" section (a + * recoverable dismissal, not a delete). */ + reEncryptWatch: ReEncryptWatchEntry[]; + /** 0.138.0: on startup, if the watchlist has active entries, show a Notice + * with a Review button that opens the review view. Default OFF. */ + reEncryptNudge: boolean; + /** 0.139.0 (peek): GLOBAL auto-re-encrypt idle delay (minutes). After a + * previously-encrypted note has been unlocked + idle this long, a cancellable + * countdown re-locks it. 0 = OFF (default). Per-folder / per-note override. */ + reEncryptAfterMin: number; + /** 0.137.3: settings write generation for the multi-writer collision guard + * (see guardedSave in main.ts). Bumped on every save; a save that sees a + * HIGHER rev on disk knows another instance/machine wrote since. */ + settingsRev?: number; /** 0.125.1: quick relative time-adjust presets shown in the due-date / snooze * picker (e.g. ["5m","15m","1h","1d"]). A +/- flip toggles add vs subtract. */ dueQuickAdjusts: string[]; @@ -506,6 +559,9 @@ export interface StashpadSettings { /** Keys (`<id>@<dueRaw>`) of task due-reminders already fired, so they don't * re-fire on every launch. Bounded; pruned when it grows. */ notifiedDueKeys: string[]; + /** 0.140.0: last-fired ms per task id for PERSISTENT reminders (`remindEvery`) + * — so a persistent task re-notifies on its interval, not once. */ + persistReminderLog?: Record<string, number>; /** 0.71.0 / 0.71.2: JD Index Builder. Two flavors: * - "Preview" → writes a single Index.md inside the designated * Stashpad folder, showing the would-be hierarchy + non-matches. @@ -577,9 +633,12 @@ export const DEFAULT_SETTINGS: StashpadSettings = { encryption: defaultEncryptionConfig(), encryptTrash: false, encryptTrashFilenames: false, - encryptionIdleLockMinutes: 0, - hideLockedTitles: false, migratedToggleTaskG: false, + migratedArchiveToSubfolders: false, + migratedTrashToSubfolders: false, + reEncryptWatch: [], + reEncryptNudge: false, + reEncryptAfterMin: 0, dueQuickAdjusts: ["5m", "15m", "30m", "1h", "1d", "1w"], archiveFolders: [], folderEncPrefs: {}, @@ -1022,12 +1081,12 @@ export class StashpadSettingTab extends PluginSettingTab { private promptSetFolderPassword(folder: string): void { new EncryptionPasswordModal(this.app, { mode: "setup", offerKeychain: true, title: `Set password for “${folder.split("/").pop()}”`, intro: "A separate password just for this folder. Share it with collaborators out-of-band (a password manager, Signal, in person). There is NO recovery if it's lost.", - onSubmit: async ({ next, remember }) => { if (!next) return "Enter a password."; try { await this.plugin.encryption.setupFolderKey(folder, next, this.folderKeyLabel(folder), remember); } catch (e) { return (e as Error).message; } new Notice("Folder password set — share it securely."); this.update?.(); return null; } }).open(); + onSubmit: async ({ next, remember }) => { if (!next) return "Enter a password."; try { await this.plugin.encryption.setupFolderKey(folder, next, this.folderKeyLabel(folder), remember); } catch (e) { return (e as Error).message; } new Notice("Folder password set — share it securely."); this.update?.(); this.pfeRerender?.(); return null; } }).open(); } private promptChangeFolderPassword(folder: string): void { new EncryptionPasswordModal(this.app, { mode: "setup", offerKeychain: true, title: `Change password for “${folder.split("/").pop()}”`, intro: "Re-wraps this folder's key under a new password. The OLD password stops working on THIS device right away (un-synced copies elsewhere keep working until they sync). To truly cut off someone who left, you'll want “Rotate key” (re-encrypts everything) — coming soon.", - onSubmit: async ({ next, remember }) => { if (!next) return "Enter a password."; try { await this.plugin.encryption.changeFolderPassword(folder, next, remember); } catch (e) { return (e as Error).message; } new Notice("Folder password changed."); this.update?.(); return null; } }).open(); + onSubmit: async ({ next, remember }) => { if (!next) return "Enter a password."; try { await this.plugin.encryption.changeFolderPassword(folder, next, remember); } catch (e) { return (e as Error).message; } new Notice("Folder password changed."); this.update?.(); this.pfeRerender?.(); return null; } }).open(); } private promptRotateFolderKey(folder: string): void { const name = folder.split("/").pop() || folder; @@ -1037,12 +1096,12 @@ export class StashpadSettingTab extends PluginSettingTab { (ok) => { if (!ok) return; new EncryptionPasswordModal(this.app, { mode: "setup", offerKeychain: true, title: `New password for “${name}”`, intro: "The new password to use after rotating. Share it only with people who should keep access.", - onSubmit: async ({ next, remember }) => { if (!next) return "Enter a password."; const n = await this.plugin.rotateFolderKey(folder, next, remember); if (n < 0) return "Rotation failed — see the notice."; this.update?.(); return null; } }).open(); + onSubmit: async ({ next, remember }) => { if (!next) return "Enter a password."; const n = await this.plugin.rotateFolderKey(folder, next, remember); if (n < 0) return "Rotation failed — see the notice."; this.update?.(); this.pfeRerender?.(); return null; } }).open(); }).open(); } private promptUnlockFolder(folder: string): void { new EncryptionPasswordModal(this.app, { mode: "unlock", offerKeychain: true, title: `Unlock “${folder.split("/").pop()}”`, intro: "Enter this folder's password.", - onSubmit: async ({ current, remember }) => { const ok = await this.plugin.encryption.unlockFolder(folder, current!, remember); if (!ok) return "Wrong password. Try again."; new Notice("Folder unlocked."); this.update?.(); return null; } }).open(); + onSubmit: async ({ current, remember }) => { const ok = await this.plugin.encryption.unlockFolder(folder, current!, remember); if (!ok) return "Wrong password. Try again."; new Notice("Folder unlocked."); this.update?.(); this.pfeRerender?.(); return null; } }).open(); } /** Gate for settings that do nothing without vault encryption: returns true if @@ -1059,7 +1118,7 @@ export class StashpadSettingTab extends PluginSettingTab { if (!next) return "Enter a password."; try { await this.plugin.encryption.setup(next, remember, this.plugin.settings.authorName || "This device"); } catch (e) { return (e as Error).message; } new Notice("Encryption set up — now switch on the setting you wanted."); - this.update?.(); + this.update?.(); this.pfeRerender?.(); return null; }, }).open(); @@ -1069,6 +1128,9 @@ export class StashpadSettingTab extends PluginSettingTab { /** The folder selected in the per-folder dropdown — kept on the tab instance so it * survives the declarative `update()` re-renders (no ghosting between folders). */ private pfeSelected: string | null = null; + /** 0.136.0: local re-render for the per-folder encryption panel (see + * renderPerFolderEncryption — update() alone left stale toggle state). */ + private pfeRerender: (() => void) | null = null; /** 0.118.6: selected folder for the (searchable) per-folder icon control in * Folders & Storage. */ private iconPickFolder: string | null = null; @@ -1079,9 +1141,8 @@ export class StashpadSettingTab extends PluginSettingTab { host.createEl("p", { cls: "setting-item-description" }).setText("Set up vault encryption above first. Per-folder passwords layer on top: a folder with its own password uses a separate key; folders without one use the vault password."); return; } - // Merged archive + trash caution (the same warnings the folder-panel modal shows - // on the action) — surfaced here at the start of the folder section. - host.createEl("p", { cls: "setting-item-description stashpad-enc-warning" }).setText("⚠️ Encryption has no recovery — if you lose a password, anything encrypted under it (notes, archived items, and encrypted-trash items) is gone for good. Locking / archiving / secure-deleting permanently removes the plaintext; the encrypted copy is the only one left. A plaintext archive only de-indexes notes from search — it does NOT encrypt them."); + // (The no-recovery caution now lives in the single merged warning callout at + // the top of the Vault Encryption section — 0.134.3.) const folders = this.plugin.discoverStashpadFolders(); if (folders.length === 0) { host.createEl("p", { cls: "setting-item-description" }).setText("No Stashpad folders found yet."); return; } // Keep a VALID selection across re-renders (default to the first folder). @@ -1089,15 +1150,22 @@ export class StashpadSettingTab extends PluginSettingTab { // One dropdown to pick the folder; the panel below shows only that folder's // options — so the section stays compact regardless of folder count. + // 0.136.0: the panel re-renders LOCALLY (pfeRerender) — `this.update?.()` + // proved unreliable for refreshing section content in place (toggles kept + // stale values: a cancelled password prompt left the switch flipped, and + // the hide-filenames sub-toggles never enabled after their parent did). + const panel = host.createDiv({ cls: "stashpad-folderenc-panel" }); + this.pfeRerender = () => { panel.empty(); this.renderFolderEncPanel(panel, this.pfeSelected!); }; new Setting(host).setName("Folder") .setDesc("Pick a folder to configure its password, archive, and trash options. Everything below applies to the selected folder only.") .addDropdown((d) => { for (const f of folders) d.addOption(f, f); d.setValue(this.pfeSelected!); - d.onChange((v) => { this.pfeSelected = v; this.update?.(); }); + d.onChange((v) => { this.pfeSelected = v; this.pfeRerender?.(); }); }); - - const panel = host.createDiv({ cls: "stashpad-folderenc-panel" }); + // (createDiv above put the panel before the dropdown in the DOM — move it + // back below so the layout reads Folder → panel.) + host.appendChild(panel); this.renderFolderEncPanel(panel, this.pfeSelected); } @@ -1119,17 +1187,15 @@ export class StashpadSettingTab extends PluginSettingTab { const head = new Setting(host).setName("Password").setDesc(status); if (inherited) { - head.addButton((b) => b.setButtonText("Manage on parent folder").onClick(() => { this.pfeSelected = owner; this.update?.(); })); + head.addButton((b) => b.setButtonText("Manage on parent folder").onClick(() => { this.pfeSelected = owner; this.update?.(); this.pfeRerender?.(); })); } else if (!hasOwn) { head.addButton((b) => b.setButtonText("Set folder password…").setCta().onClick(() => this.promptSetFolderPassword(folder))); } else if (!unlocked) { head.addButton((b) => b.setButtonText("Unlock…").setCta().onClick(() => this.promptUnlockFolder(folder))); } else { - // "Lock" = forget THIS folder's key from memory (re-prompts on next access) — - // the per-folder analog of the vault "Lock now". It must NOT call - // plugin.lockFolder (that's the batch ENCRYPT op). Encrypting/decrypting the - // folder's content is the "Encrypt this folder's notes" toggle below. - head.addButton((b) => b.setButtonText("Lock (forget password)").onClick(() => { this.plugin.encryption.lockFolder(folder); new Notice("Folder locked — you'll re-enter its password next time."); this.update?.(); })); + // (0.135.0: the per-folder "Lock (forget password)" button is gone, same + // reasoning as the vault-level "Lock now". Encrypting/decrypting the + // folder's content is the "Encrypt this folder's notes" toggle below.) head.addButton((b) => b.setButtonText("Change password…").onClick(() => this.promptChangeFolderPassword(folder))); head.addButton((b) => { b.setButtonText("Rotate key…").onClick(() => this.promptRotateFolderKey(folder)); b.buttonEl.addClass("mod-warning"); }); } @@ -1139,39 +1205,41 @@ export class StashpadSettingTab extends PluginSettingTab { await this.plugin.saveSettings(); }; - new Setting(host).setName("Archive") - .setDesc("De-indexes this folder from cross-folder search; notes moved in get auto-encrypted (when “Encrypt archived notes” is on below).") - .addToggle((t) => t.setValue(!!prefs.archive).onChange(async (v) => { - await setPref({ archive: v, ...(v && prefs.archiveEncryptContent === undefined ? { archiveEncryptContent: true } : {}) }); - const set = new Set((this.plugin.settings.archiveFolders ?? []).map((f) => f.replace(/\/+$/, ""))); - if (v) set.add(folder); else set.delete(folder); - this.plugin.settings.archiveFolders = [...set]; - if (!v && (this.plugin.settings.defaultArchiveFolder ?? "") === folder) this.plugin.settings.defaultArchiveFolder = undefined; - await this.plugin.saveSettings(); - this.plugin.refreshFolderPanels(); - this.update?.(); - })); - // (The default archive target is chosen via the "Default archive folder" dropdown - // in the global settings below — no per-folder toggle needed.) + // (0.136.0: the per-folder "Archive" mark toggle is gone — every folder + // implicitly owns an `archive/` subfolder. What remains configurable is + // whether archiving INTO it encrypts: the "Encrypt archived notes" pair + // below.) + // 0.137.1: no more "Use global default" — the global follow-Obsidian's- + // trash option is gone; unset simply means Stashpad's own trash. new Setting(host).setName("Trash handling").setDesc("Where notes deleted from this folder go.") .addDropdown((d) => d - .addOption("", "Use global default") - .addOption("stashpad", "Stashpad encrypted trash") + .addOption("", "Stashpad trash (default)") .addOption("obsidian", "Obsidian native trash") - .setValue(prefs.trashHandling ?? "") + .setValue(prefs.trashHandling === "obsidian" ? "obsidian" : "") .onChange((v) => setPref({ trashHandling: (v || undefined) as FolderEncPrefs["trashHandling"] }))); - const pair = (label: string, cKey: keyof FolderEncPrefs, fKey: keyof FolderEncPrefs, onContent?: (v: boolean) => Promise<void>) => { + // 0.136.0: `requireKey` gates turning a pair ON behind an actual unlock + // (vault key, or this folder's own key) — the archive/trash pairs used to + // flip on without ever proving a key exists. Every path ends in + // pfeRerender so the toggles + sub-toggles always show the REAL state + // (a cancelled password prompt used to leave the switch flipped, and the + // hide-filenames sub-toggles never enabled without a full reopen). + const pair = (label: string, cKey: keyof FolderEncPrefs, fKey: keyof FolderEncPrefs, onContent?: (v: boolean) => Promise<void>, requireKey = false) => { new Setting(host).setName(label) .addToggle((t) => t.setValue(!!prefs[cKey]).onChange(async (v) => { + if (v && requireKey) { + if (!this.encryptionOrOnboard()) { this.pfeRerender?.(); return; } + const dek = await this.plugin.ensureFolderUnlocked(folder); + if (!dek) { this.pfeRerender?.(); return; } // prompt cancelled/failed → revert + } const patch: Partial<FolderEncPrefs> = {}; (patch as Record<string, unknown>)[cKey] = v; if (!v) (patch as Record<string, unknown>)[fKey] = false; await setPref(patch); if (onContent) await onContent(v); - this.update?.(); + this.pfeRerender?.(); })); new Setting(host).setName(`↳ ${label} — hide filenames`).setClass("stashpad-subsetting") - .addToggle((t) => { t.setValue(!!prefs[fKey]); t.setDisabled(!prefs[cKey]); t.onChange((v) => { const p: Partial<FolderEncPrefs> = {}; (p as Record<string, unknown>)[fKey] = v; void setPref(p); }); }); + .addToggle((t) => { t.setValue(!!prefs[fKey]); t.setDisabled(!prefs[cKey]); t.onChange(async (v) => { const p: Partial<FolderEncPrefs> = {}; (p as Record<string, unknown>)[fKey] = v; await setPref(p); this.pfeRerender?.(); }); }); }; pair("Encrypt this folder's notes", "encryptContent", "encryptFilenames", async (v) => { if (v) await this.plugin.lockFolder(folder); else await this.plugin.unlockFolder(folder); @@ -1181,8 +1249,37 @@ export class StashpadSettingTab extends PluginSettingTab { const has = (this.plugin.settings.lockedSubtrees ?? []).some((e) => (e.folder || "").replace(/\/+$/, "") === folder); await setPref({ encryptContent: has, ...(has ? {} : { encryptFilenames: false }) }); }); - pair("Encrypt archived notes", "archiveEncryptContent", "archiveEncryptFilenames"); - pair("Encrypt trashed notes", "trashEncryptContent", "trashEncryptFilenames"); + pair("Encrypt archived notes", "archiveEncryptContent", "archiveEncryptFilenames", async (v) => { + // 0.137.2 (retro-apply): offer to lock what's ALREADY in the archive — + // the pref alone only affects future arrivals. + if (!v) return; + const plainCount = this.plugin.archivedPlainNotesIn(`${folder}/archive`).length; + if (plainCount === 0) return; + new ConfirmModal(this.app, + `Encrypt the ${plainCount} note${plainCount === 1 ? "" : "s"} already archived?`, + `“${folder.split("/").pop()}”'s archive already holds ${plainCount} plaintext note${plainCount === 1 ? "" : "s"}. Encrypt ${plainCount === 1 ? "it" : "them"} now too? (Skipping leaves ${plainCount === 1 ? "it" : "them"} readable; only notes archived from now on get encrypted.)`, + "Encrypt them", + async (ok) => { + if (!ok) return; + const n = await this.plugin.encryptExistingArchiveNotes(folder); + new Notice(n > 0 ? `Encrypted ${n} archived note${n === 1 ? "" : "s"}.` : "Nothing was encrypted (cancelled or already locked)."); + this.pfeRerender?.(); + }).open(); + }, true); + pair("Encrypt trashed notes", "trashEncryptContent", "trashEncryptFilenames", undefined, true); + + // 0.139.0 (peek): per-folder auto-re-encrypt idle override. Blank = use the + // global setting; a number overrides it for this folder; 0 = off here. + new Setting(host).setName("Auto re-encrypt after idle minutes") + .setDesc("Override the global auto-re-encrypt timer for this folder. Blank = use global; 0 = off for this folder. A cancellable countdown always shows first, and open notes are never touched.") + .addText((t) => { + t.setPlaceholder("global"); + t.setValue(prefs.reEncryptAfterMin == null ? "" : String(prefs.reEncryptAfterMin)); + t.onChange((v) => { + const s = v.trim(); + { const n = Number(s); if (s === "") void setPref({ reEncryptAfterMin: undefined }); else if (Number.isFinite(n) && n >= 0) void setPref({ reEncryptAfterMin: Math.floor(n) }); else { t.setValue(prefs.reEncryptAfterMin == null ? "" : String(prefs.reEncryptAfterMin)); } } + }); + }); // Subfolders: each shares this folder's key (inheritance), but you can encrypt // their notes / hide their filenames individually. @@ -1202,7 +1299,7 @@ export class StashpadSettingTab extends PluginSettingTab { if (v) await this.plugin.lockFolder(sub); else await this.plugin.unlockFolder(sub); const has = (this.plugin.settings.lockedSubtrees ?? []).some((e) => (e.folder || "").replace(/\/+$/, "") === sub); await setSubPref({ encryptContent: has, ...(has ? {} : { encryptFilenames: false }) }); - this.update?.(); + this.update?.(); this.pfeRerender?.(); })); new Setting(host).setName(`↳ ${rel} — hide filenames`).setClass("stashpad-subsetting") .addToggle((t) => { t.setValue(!!sp.encryptFilenames); t.setDisabled(!subLocked); t.onChange((v) => void setSubPref({ encryptFilenames: v })); }); @@ -1511,12 +1608,40 @@ export class StashpadSettingTab extends PluginSettingTab { const betaRow = host.createDiv({ cls: "stashpad-beta-row" }); betaRow.createEl("span", { cls: "stashpad-beta-badge", text: "BETA" }); betaRow.createEl("span", { cls: "stashpad-beta-note", text: "Encryption is in beta — keep your own backups of anything important." }); - host.createEl("div", { cls: "stashpad-ai-disclaimer" }).setText( - "⚠️ AI-built, NOT human-audited. This encryption was written by an AI assistant — not designed, reviewed, or security-audited by a human, and not tested by any security professional. It may carry real security, privacy, and DATA-LOSS risks. Treat it as a best-effort nice-to-have that might buy a little time against a casual snoop — nothing is guaranteed. Do NOT rely on it for anything sensitive, and always keep your own unencrypted backups of anything important.", + // 0.134.3: ONE merged warning callout (was three: the AI disclaimer box, + // a ⚠️ description paragraph here, and a "no recovery" callout at the top + // of the per-folder section). Same .stashpad-enc-warning callout style. + const warn = host.createEl("div", { cls: "stashpad-enc-warning" }); + warn.createEl("p").setText( + "⚠️ AI-built, NOT human-audited. This encryption was written by an AI assistant — not reviewed or security-audited by a human. Treat it as best-effort protection against a casual snoop, not a guarantee — and always keep your own unencrypted backups of anything important.", ); - host.createEl("p", { cls: "setting-item-description" }).setText( - "⚠️ Encryption protects what you lock in this vault. Each device unlocks with its own password (which never leaves the device); the vault key is shared with collaborators by approving their device — no shared password. If everyone with access loses their password, anything encrypted is gone for good. While encrypting, avoid a sync/cloud service writing the vault mid-operation — it can corrupt files.", + warn.createEl("p").setText( + "There is NO recovery. If you lose a password, anything encrypted under it (notes, archived items, encrypted-trash items) is gone for good. Locking, encrypted archiving, and secure-deleting permanently remove the plaintext — the encrypted copy is the only one left. (A plaintext archive only de-indexes notes from search; it does NOT encrypt them.)", ); + warn.createEl("p").setText( + "Each device unlocks with its own password (it never leaves the device); collaborators get access by device approval, not a shared password. If everyone with access loses their password, the content is unrecoverable. While encrypting or decrypting, avoid having a sync/cloud service write the vault mid-operation — it can corrupt files.", + ); + + // 0.137.4: ONE collapsible, toggle-free "how it works" reference block, so + // the individual setting rows below can stay one-liners instead of each + // re-explaining hide-filenames / plaintext-archive / trash / inheritance. + const info = host.createEl("details", { cls: "stashpad-enc-info" }); + info.createEl("summary", { text: "ℹ️ How Stashpad encryption works (concepts)" }); + const infoItem = (term: string, body: string): void => { + const p = info.createEl("p", { cls: "stashpad-enc-info-item" }); + p.createEl("strong", { text: `${term} — ` }); + p.appendText(body); + }; + infoItem("Vault password vs per-folder passwords", + "One vault password protects everything by default. A folder can also get its OWN password (a separate key) so you can share just that folder with collaborators; subfolders inherit their nearest keyed ancestor's password. Folders without their own key use the vault password."); + infoItem("Encrypt notes vs Hide filenames", + "“Encrypt … notes” scrambles the note body into a locked .stashenc blob (the plaintext is removed). “Hide filenames” goes further: it replaces the on-disk name AND the placeholder title with a generic label, so a glance at the vault doesn't reveal WHAT is locked. Whether a title is hidden is decided when the note is locked."); + infoItem("Archive: plaintext vs encrypted", + "Every folder has its own “archive” subfolder. Archiving is a plain move by default — the notes drop out of cross-folder search but stay readable. Turn on “Encrypt archived notes” for a folder to lock notes as they're archived instead."); + infoItem("Trash: Stashpad vs Obsidian", + "Deleting a note normally sends it to that folder's own “trash” subfolder — encrypted if the folder encrypts its trash — and it's recoverable from the Trash view. Set a folder's “Trash handling” to “Obsidian native trash” to use Obsidian's plain deleted-files behavior instead (Stashpad can't encrypt or recover those)."); + infoItem("No recovery", + "There is no backdoor and no reset. If a password is lost, everything encrypted under it is gone — so keep your own unencrypted backups of anything you can't afford to lose."); const kdfProbe = () => enc.argonProbe(); const deviceLabel = () => (this.plugin.settings.authorName?.trim() || "This device"); @@ -1621,10 +1746,11 @@ export class StashpadSettingTab extends PluginSettingTab { }, }).open(); })); - } else { - new Setting(host).setName("Lock now").setDesc("Forget the password from memory until you re-enter it.").addButton((b) => - b.setButtonText("Lock now").onClick(() => { enc.lock(); new Notice("Encryption locked."); this.update?.(); })); } + // (0.135.0: the "Lock now" forget-from-memory button is gone — it confused + // more than it protected, and it's one of the flows users blamed for + // "something emptied my keys". The session key simply lives until Obsidian + // closes. Removing encryption or changing the password still re-prompts.) if (SHOW_FORGET_KEYCHAIN_BUTTON && enc.keychainAvailable() && enc.isRemembered()) { new Setting(host).setName("Forget password on this device").setDesc("Drops ONLY the copy saved in this device's keychain — your encryption stays set up and nothing is decrypted or deleted. You'll just re-type the password next session. (To turn encryption off entirely, use “Remove encryption” below.)").addButton((b) => @@ -1753,12 +1879,31 @@ export class StashpadSettingTab extends PluginSettingTab { // Nothing encrypted at all → clean removal. proceedToConfirm("", false); }; - b.setButtonText("Remove…").onClick(() => void runRemoval()); + // 0.136.0: loading button — the state check can take a while on slow + // (network) drives, and spam-clicks used to queue several removal runs + // (each spawning its own "delete encrypted contents" modal). While the + // check runs the button dims with a loading bar and SWALLOWS clicks. + let removalBusy = false; + b.setButtonText("Remove…").onClick(async () => { + if (removalBusy) return; + removalBusy = true; + b.setDisabled(true); + b.buttonEl.addClass("stashpad-btn-loading"); + try { await runRemoval(); } + finally { + removalBusy = false; + b.setDisabled(false); + b.buttonEl.removeClass("stashpad-btn-loading"); + } + }); b.buttonEl.addClass("mod-warning"); }); } // ---- Sharing method 1: shared password (Model 1) ---- + // 0.135.0: hidden unless a shared password ALREADY exists (see + // SHOW_SHARING_UI) — no new setups, but existing ones stay manageable. + if (SHOW_SHARING_UI || enc.hasSharedPassword()) { new Setting(host).setName("Sharing").setHeading(); if (!enc.isUnlocked()) { host.createEl("p", { cls: "setting-item-description" }).setText("Unlock encryption above to manage how this vault is shared."); @@ -1778,6 +1923,7 @@ export class StashpadSettingTab extends PluginSettingTab { })); } } + } // end SHOW_SHARING_UI / existing-shared-password gate // ---- Sharing method 2: device approval (Model 3) — members + requests ---- // Hidden in the per-folder overhaul (manual password sharing is the model @@ -1834,33 +1980,29 @@ export class StashpadSettingTab extends PluginSettingTab { if (v && !this.encryptionOrOnboard()) { this.update?.(); return; } this.plugin.settings.encryptTrashFilenames = v; await this.plugin.saveSettings(); })), ["trash", "filename", "encrypt"])); - items.push(this.renderDef("Follow Obsidian's trash setting instead", "OFF (recommended): encrypted-deleted notes go to Stashpad's own “_deleted/” store — the only trash location Stashpad fully controls, so it can encrypt, list, and restore them. ON: deletes follow Obsidian's “Deleted files” setting instead (system/OS trash or permanent). ⚠️ Stashpad CANNOT encrypt or recover notes that go to the system trash — so the encrypted trash + recoverable trash view won't apply. Only turn this on if you specifically want Obsidian's native trash behavior.", (s) => - s.addToggle((t) => t.setValue(this.plugin.settings.encryptTrashFollowObsidian ?? false).onChange(async (v) => { - this.plugin.settings.encryptTrashFollowObsidian = v || undefined; await this.plugin.saveSettings(); - })), ["trash", "obsidian", "system", "delete", "encrypt"])); - items.push(this.renderDef("Auto-lock after idle minutes", "Forget the password from memory after this many idle minutes (0 = never). Re-prompts on the next encryption action.", (s) => - s.addText((t) => t.setValue(String(this.plugin.settings.encryptionIdleLockMinutes ?? 0)).onChange(async (v) => { - const n = Math.max(0, Math.floor(Number(v) || 0)); - this.plugin.settings.encryptionIdleLockMinutes = n; await this.plugin.saveSettings(); - })), ["auto-lock", "idle", "timeout", "lock"])); - items.push(this.renderDef("Hide titles of locked notes (default)", "The DEFAULT for hiding 🔒 locked-placeholder titles — used by any folder/trash that doesn't set its own “hide filenames” option in Per-Folder Passwords below (those override this). Shows a generic label instead of the note's title so a glance doesn't reveal what's locked. Default OFF.", (s) => - s.addToggle((t) => t.setValue(this.plugin.settings.hideLockedTitles ?? false).onChange(async (v) => { - if (v && !this.encryptionOrOnboard()) { this.update?.(); return; } - this.plugin.settings.hideLockedTitles = v; await this.plugin.saveSettings(); - this.plugin.refreshAllStashpadViews?.(); - })), ["title", "hide", "private", "lock", "placeholder", "visibility"])); + // 0.138.0: re-encrypt sweep nudge (default OFF). Never auto-encrypts — + // the Notice just carries a Review button into the watchlist view. + items.push(this.renderDef("Remind me about unlocked encrypted notes", "On startup, if notes that WERE encrypted are still sitting plaintext (you unlocked them, or restored them from the trash), show a notice with a Review button that opens the “Previously encrypted” view. Nothing is ever re-encrypted automatically.", (s) => + s.addToggle((t) => t.setValue(this.plugin.settings.reEncryptNudge ?? false).onChange(async (v) => { + this.plugin.settings.reEncryptNudge = v; await this.plugin.saveSettings(); + })), ["remind", "nudge", "re-encrypt", "watchlist", "unlocked", "sweep"])); + items.push(this.renderDef("Auto re-encrypt after idle minutes (global)", "0 = OFF (default). After an unlocked, previously-encrypted note sits idle this long, Stashpad shows a “Re-encrypting in 10s… [Keep unlocked]” countdown you can cancel, then re-locks it. It NEVER re-encrypts a note you have open in an editor. Set per-folder overrides under “Per-Folder Passwords”; per-note in the “Previously encrypted” view.", (s) => + s.addText((t) => t.setValue(String(this.plugin.settings.reEncryptAfterMin ?? 0)).onChange(async (v) => { + { const n = Number(v.trim()); if (v.trim() === "") this.plugin.settings.reEncryptAfterMin = 0; else if (Number.isFinite(n) && n >= 0) this.plugin.settings.reEncryptAfterMin = Math.floor(n); else { t.setValue(String(this.plugin.settings.reEncryptAfterMin ?? 0)); return; } await this.plugin.saveSettings(); } + })), ["auto", "re-encrypt", "idle", "timer", "peek", "lock"])); + // (0.135.0: "Auto-lock after idle minutes" removed — the surprise + // key-forgetting caused more confusion than protection.) + // (0.137.1: the global "Follow Obsidian's trash setting" and "Hide titles + // of locked notes" options are gone — trash handling and filename-hiding + // are per-folder now ("Per-Folder Passwords" below). Whether a title is + // hidden is decided AT LOCK TIME by the folder's hide-filenames prefs and + // stored in the placeholder, so display simply follows the stored title.) - items.push(this.headingDef("Archive", "Archive folders are de-indexed from cross-folder search; mark a folder as an archive (and set whether it encrypts) under “Per-Folder Passwords” below.")); - items.push(this.renderDef("Default archive folder", "Where the \"Move selection to archive\" command sends notes (they're auto-encrypted on arrival). Leaving this blank is fine — the command will just show you a list of your archive folders to pick from each time (or use the only one if you have a single archive). Mark a folder as an archive via the folder panel → right-click → \"Mark as archive\".", (s) => { - const archives = this.plugin.settings.archiveFolders ?? []; - s.addDropdown((d) => { - d.addOption("", archives.length ? "— pick from list each time —" : "— no archive folders yet —"); - for (const f of archives) d.addOption(f, f); - const cur = this.plugin.settings.defaultArchiveFolder ?? ""; - d.setValue(archives.includes(cur) ? cur : ""); - d.onChange(async (v) => { this.plugin.settings.defaultArchiveFolder = v || undefined; await this.plugin.saveSettings(); }); - }); - }, ["archive", "default", "move", "encrypt", "folder"])); + // (0.136.0: the global "Archive" section is gone — every folder owns an + // `archive/` subfolder now; "Move selection to archive" always targets the + // current folder's own archive. Per-folder encryption of archived notes + // lives under "Per-Folder Passwords" below.) + items.push(this.headingDef("Archive", "Every folder has its own “archive” subfolder: “Move selection to archive” tucks notes into it, archived notes drop out of search, and everything shows up in the aggregated Archived view. Whether a folder's archive is encrypted is set per folder under “Per-Folder Passwords” below (“Encrypt archived notes”).")); // ---- Per-folder passwords (per-folder overhaul) — below the global settings ---- items.push(this.sectionDef("Per-Folder Passwords", "Give a folder its own password (a separate key) so you can share just that folder with collaborators. Folders without their own password use the vault password.", (host) => { diff --git a/src/slug-service.ts b/src/slug-service.ts index 18e797b..d64e8f3 100644 --- a/src/slug-service.ts +++ b/src/slug-service.ts @@ -47,7 +47,13 @@ export function buildFilename(slug: string, id: string): string { } export function parseIdFromFilename(basename: string): string | null { - const m = basename.match(/-([a-z0-9]{4,12})$/); + // Match EXACTLY what buildFilename emits: a trailing `-<id>` where <id> is + // 6 chars from id-service's alphabet (no l/o/0/1). The old {4,12} + full + // a-z0-9 class matched ordinary trailing words — `meeting-notes` and + // `quick-notes` both parsed to the same "notes", colliding the synthetic + // node ids minted for non-Stashpad files. (0.140.5 review.) The `-N` + // rename uniquifier keeps `-<id>` last, so this still recovers the id. + const m = basename.match(/-([abcdefghijkmnpqrstuvwxyz23456789]{6})$/); return m ? m[1] : null; } diff --git a/src/sort-store.ts b/src/sort-store.ts index 407b486..3f385f2 100644 --- a/src/sort-store.ts +++ b/src/sort-store.ts @@ -113,6 +113,18 @@ export class SortStore { const map = this.cache.get(folder) ?? {}; const path = `${folder}/${SORT_FILE}`; const adapter = this.app.vault.adapter; + // 0.140.3 (review): merge on-disk parents we didn't touch this session, so a + // mutation before load() resolves can't clobber other parents' sort modes. + try { + if (await adapter.exists(path)) { + const parsed = JSON.parse(await adapter.read(path)); + if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) { + for (const [k, v] of Object.entries(parsed)) { + if (!(k in map) && typeof v === "string" && VALID_MODES.has(v as SortMode)) map[k] = v as SortMode; + } + } + } + } catch { /* unreadable / absent — write what we have */ } try { if (Object.keys(map).length === 0) { // Skip the exists() probe — remove() throws if it's missing, which diff --git a/src/stash-package.ts b/src/stash-package.ts index 204f4d7..eb913c3 100644 --- a/src/stash-package.ts +++ b/src/stash-package.ts @@ -1,7 +1,7 @@ import { App, TFile, parseYaml, stringifyYaml } from "obsidian"; import { bytesToStr, unzipFiles, zipFiles, type ZipEntry } from "./zip"; import { newId } from "./id-service"; -import { ROOT_ID, attachmentLinkPath, toAttachmentLink, type StashpadId } from "./types"; +import { ROOT_ID, RESERVED_FRONTMATTER, attachmentLinkPath, toAttachmentLink, type StashpadId } from "./types"; export const STASH_EXT = "stash"; export const SCHEMA_VERSION = 1; @@ -121,7 +121,7 @@ export async function importStashZip( buf: ArrayBuffer | Uint8Array, destFolder: string, existingIds: Set<StashpadId>, - opts: { dedupeExisting?: boolean; forceNewIds?: boolean; reparentRootsTo?: StashpadId | null } = {}, + opts: { dedupeExisting?: boolean; forceNewIds?: boolean; reparentRootsTo?: StashpadId | null; stripReserved?: boolean } = {}, ): Promise<ImportSummary> { const zip = await unzipFiles(buf); const manifestBytes = zip["manifest.json"]; @@ -144,8 +144,13 @@ export async function importStashZip( // Security: flatten to a safe single-segment name (zip-slip defense). // Fall back to the note id (or a generated name) if the entry name is // empty/traversal-only; a later collision check still de-dupes. + // Defense-in-depth: run the id fallback through safeZipEntryName too, so a + // future change to the note-entry filter can't let an attacker-controlled + // YAML `id` (e.g. "../../evil") escape the destination. Today the fallback + // is unreachable (note entries always end in .md), but don't rely on that. const safeName = safeZipEntryName(name.slice("notes/".length)) - || `${(fm.id as string) || "imported-" + newId(4)}.md`; + || safeZipEntryName(`${(fm.id as string) || "imported-" + newId(4)}.md`) + || `imported-${newId(4)}.md`; parsed.push({ originalName: safeName, fm, body }); } @@ -248,8 +253,28 @@ export async function importStashZip( // or a reused existing file when deduping). const rewrittenBody = rewriteImportedAttachmentLinks(p.body, attRoute, attachmentsFolder); + // For UNTRUSTED imports (external `.stash` bundles) strip reserved + // frontmatter the same way the markdown drop-import does, so a crafted + // bundle can't inject pin/due/assignee/position/parentLink state or forge + // structural fields. Authorship (who wrote it) and the original timestamps + // are retained; attachments are kept for the link-remap below. Trusted + // callers (unlock/restore, internal cut-paste) omit stripReserved so their + // own notes keep every field intact. (0.140.6 review — documented invariant.) + let srcFm: Record<string, any> = p.fm; + if (opts.stripReserved) { + const filtered: Record<string, any> = {}; + for (const [k, v] of Object.entries(p.fm)) { + if (!RESERVED_FRONTMATTER.includes(k)) filtered[k] = v; + } + if (p.fm.author !== undefined) filtered.author = p.fm.author; + if (p.fm.contributors !== undefined) filtered.contributors = p.fm.contributors; + if (p.fm.created !== undefined) filtered.created = p.fm.created; + if (p.fm.modified !== undefined) filtered.modified = p.fm.modified; + if (Array.isArray(p.fm.attachments)) filtered.attachments = p.fm.attachments; + srcFm = filtered; + } const newFm: Record<string, any> = { - ...p.fm, + ...srcFm, id: newIdVal, parent: newParent, import_date: importDate, diff --git a/src/task-collect.ts b/src/task-collect.ts index 3beca1d..08438ea 100644 --- a/src/task-collect.ts +++ b/src/task-collect.ts @@ -21,12 +21,19 @@ export interface TaskItem { author: { id: string; name: string } | null; } -/** Title from a note filename: drop the trailing `-id` suffix, dashes → spaces. */ -export function titleFromTaskFile(file: TFile): string { - return file.basename - .replace(/-[a-z0-9]{4,12}$/, "") - .replace(/-/g, " ") - .trim() || file.basename; +/** Title from a note filename: drop the trailing `-id` suffix, dashes → spaces. + * Pass the note's real `id` (from frontmatter) so we only strip the actual id + * and not a legit trailing word — `quarterly-budget-review` was losing "review" + * to the old blind `-[a-z0-9]{4,12}$` strip. When no id is available, fall back + * to stripping only an exact id-shaped token (id-service alphabet, 6 chars). */ +export function titleFromTaskFile(file: TFile, id?: string | null): string { + let base = file.basename; + if (id && base.endsWith(`-${id}`)) { + base = base.slice(0, -(id.length + 1)); + } else if (!id) { + base = base.replace(/-[abcdefghijkmnpqrstuvwxyz23456789]{6}$/, ""); + } + return base.replace(/-/g, " ").trim() || file.basename; } /** Scan every Stashpad folder for task-flagged notes (the `task` tag, the legacy @@ -44,7 +51,13 @@ export function collectTasks(app: App, plugin: StashpadPlugin): TaskItem[] { const task = fmHasTag(fm, "task") || fm.task === true || fm.completed !== undefined; const dueRaw = typeof fm.due === "string" || typeof fm.due === "number" ? String(fm.due) : null; let due: number | null = null; - if (dueRaw) { + if (typeof fm.due === "number") { + // A numeric `due` is a raw epoch value — Date.parse("1730000000000") is + // NaN, so parse it directly. Only accept plausible ms timestamps (≥ ~1973 + // in ms) so a bare small number like `2026` isn't read as 2 seconds past + // epoch. (0.140.5 review.) + if (Number.isFinite(fm.due) && fm.due >= 1e11) due = fm.due; + } else if (dueRaw) { const t = Date.parse(dueRaw); if (!Number.isNaN(t)) due = t; } @@ -53,7 +66,7 @@ export function collectTasks(app: App, plugin: StashpadPlugin): TaskItem[] { file: f, folder: dir, id, - title: titleFromTaskFile(f), + title: titleFromTaskFile(f, id), task, completed, due, diff --git a/src/task-render.ts b/src/task-render.ts index d53d215..9fa6897 100644 --- a/src/task-render.ts +++ b/src/task-render.ts @@ -236,7 +236,12 @@ function renderRow( if (t.author) meta.createSpan({ cls: "stashpad-review-author", text: `by ${t.author.name}` }); if (t.due != null) { const overdue = t.due < now && !t.completed; - const isToday = t.due >= now - 86_400_000 && t.due < now + 86_400_000; + // Calendar "today" (midnight→midnight), matching the bucket boundaries — + // a ±24h window mislabels yesterday-11pm / tomorrow-9am as today and then + // renders them time-only with no date. (0.140.5 review.) + const startToday = new Date(now); startToday.setHours(0, 0, 0, 0); + const startTodayMs = startToday.getTime(); + const isToday = t.due >= startTodayMs && t.due < startTodayMs + 86_400_000; const dueEl = meta.createSpan({ cls: "stashpad-review-due", text: isToday ? formatTimeOnly(t.due, plugin.settings) : formatDateOnly(t.due, plugin.settings), diff --git a/src/time-picker.ts b/src/time-picker.ts index e0d5672..4fa03c4 100644 --- a/src/time-picker.ts +++ b/src/time-picker.ts @@ -31,6 +31,7 @@ export interface BuildTimePickerOpts { /** Convert a typed (hh 1–24, period) into 24-hour hours. */ function to24(hh: number, period: "am" | "pm"): number { + if (hh === 0) return 0; // explicit midnight (24-hour entry) if (hh > 12) return hh >= 24 ? 0 : hh; // already 24-hour if (period === "am") return hh === 12 ? 0 : hh; return hh === 12 ? 12 : hh + 12; @@ -101,9 +102,13 @@ export function buildTimePickerInto(pop: HTMLElement, opts: BuildTimePickerOpts) syncAmpmEnabled(); const finalize = (): void => { - const hh = parseInt(hField.value || "12", 10) || 12; + // Distinguish a typed 0/00 (midnight) from an empty field. `|| 12` would + // coerce a real 0 to 12 and re-interpret it through the am/pm toggle + // (00:30+PM → 12:30 noon). Empty stays 12; a typed 0 stays 0. (0.140.5) + const hParsed = parseInt(hField.value, 10); + const hh = Number.isNaN(hParsed) ? 12 : hParsed; const mm = parseInt(mField.value || "0", 10) || 0; - const is24 = hh > 12; + const is24 = hh > 12 || hh === 0; opts.onFinalize({ hours24: to24(hh, period), minutes: mm, diff --git a/src/trash-view.ts b/src/trash-view.ts index 886292c..2760a6b 100644 --- a/src/trash-view.ts +++ b/src/trash-view.ts @@ -1,4 +1,4 @@ -import { ItemView, WorkspaceLeaf, moment, setIcon } from "obsidian"; +import { ItemView, Notice, WorkspaceLeaf, moment, setIcon } from "obsidian"; import type StashpadPlugin from "./main"; import { STASHPAD_TRASH_VIEW_TYPE } from "./types"; import { ConfirmModal } from "./modals"; @@ -58,7 +58,10 @@ export class StashpadTrashView extends ItemView { * flat render order (for shift-range); `anchorIdx` is the last clicked row. */ private selected = new Set<string>(); private order: string[] = []; - private anchorIdx: number | null = null; + /** Shift-range anchor stored as a BLOB PATH (not an index): a restore/purge + * rebuilds `order` shorter, so an index anchor would point at the wrong row + * afterward. We re-derive the index from this path at shift-click. (0.140.7) */ + private anchorBlob: string | null = null; /** 0.130.0: shared view mode (same set as the Archive tab). Default per-folder. */ private trashMode: AggMode = "byfolder"; @@ -67,6 +70,9 @@ export class StashpadTrashView extends ItemView { const root = this.contentEl; root.empty(); root.addClass("stashpad-trash-view-body"); + // Reset the shift-range order at the TOP so "mixed" mode (which returns + // before the old reset point) can't leave a stale list for Mod+A. (0.140.7) + this.order = []; const header = root.createDiv({ cls: "stashpad-trash-view-header" }); header.createEl("h3", { text: "Trash" }); @@ -110,7 +116,16 @@ export class StashpadTrashView extends ItemView { const bar = root.createDiv({ cls: "stashpad-trash-selbar" }); bar.createSpan({ cls: "stashpad-trash-selcount", text: `${this.selected.size} selected` }); const restoreSel = bar.createEl("button", { cls: "stashpad-trash-restore", text: "Restore selected" }); - restoreSel.onclick = async () => { restoreSel.disabled = true; for (const b of [...this.selected]) await this.plugin.restoreDeletedAt(b, { silent: true }); this.clearSelection(); await this.render(); }; + restoreSel.onclick = async () => { + restoreSel.disabled = true; + // Surface partial failures — silent:true suppresses per-item notices, + // so without this a key-locked item left behind reads as success. (0.140.7) + let failed = 0; + for (const b of [...this.selected]) { if (!(await this.plugin.restoreDeletedAt(b, { silent: true }))) failed++; } + if (failed > 0) new Notice(`${failed} item${failed === 1 ? "" : "s"} couldn't be restored (locked key?) — still in trash.`); + this.clearSelection(); + await this.render(); + }; const delSel = bar.createEl("button", { cls: "stashpad-trash-delete mod-warning", text: "Delete selected" }); delSel.onclick = () => this.confirmPurge([...this.selected]); const clear = bar.createEl("button", { cls: "stashpad-trash-iconbtn", text: "Clear" }); @@ -239,29 +254,32 @@ export class StashpadTrashView extends ItemView { } } - private clearSelection(): void { this.selected.clear(); this.anchorIdx = null; } + private clearSelection(): void { this.selected.clear(); this.anchorBlob = null; } /** Select every trash item (Mod+A). No-op when the list is empty. */ private selectAll(): void { if (this.order.length === 0) return; for (const b of this.order) this.selected.add(b); - this.anchorIdx = this.order.length - 1; + this.anchorBlob = this.order[this.order.length - 1] ?? null; void this.render(); } /** Selection click with cmd/ctrl (toggle), shift (range), plain (single). */ private onRowClick(idx: number, blob: string, e: MouseEvent): void { - if (e.shiftKey && this.anchorIdx !== null) { - const [a, b] = this.anchorIdx < idx ? [this.anchorIdx, idx] : [idx, this.anchorIdx]; + // Re-derive the anchor index from its blob path — a restore/purge may have + // shifted rows since the anchor was set. A vanished anchor → treat as none. + const anchorIdx = this.anchorBlob !== null ? this.order.indexOf(this.anchorBlob) : -1; + if (e.shiftKey && anchorIdx >= 0) { + const [a, b] = anchorIdx < idx ? [anchorIdx, idx] : [idx, anchorIdx]; this.selected.clear(); for (let i = a; i <= b; i++) this.selected.add(this.order[i]); } else if (e.metaKey || e.ctrlKey) { if (this.selected.has(blob)) this.selected.delete(blob); else this.selected.add(blob); - this.anchorIdx = idx; + this.anchorBlob = blob; } else { if (this.selected.size === 1 && this.selected.has(blob)) this.selected.clear(); else { this.selected.clear(); this.selected.add(blob); } - this.anchorIdx = idx; + this.anchorBlob = blob; } void this.render(); } @@ -278,7 +296,12 @@ export class StashpadTrashView extends ItemView { for (const b of blobs) await this.plugin.purgeDeletedAt(b); this.clearSelection(); await this.render(); - }).open(); + }, + "Cancel", + // Permanent, undecryptable delete — focus Cancel so a stray Enter can't + // purge irreversibly. (0.140.7) + /*dangerous*/ true, + ).open(); } async onClose(): Promise<void> { this.contentEl.empty(); } @@ -294,5 +317,5 @@ export async function openTrashView(plugin: StashpadPlugin): Promise<void> { const leaf = workspace.getLeaf("tab"); await leaf.setViewState({ type: STASHPAD_TRASH_VIEW_TYPE, active: true }); workspace.revealLeaf(leaf); - returnToOriginOnClose(workspace, leaf, originLeaf); + returnToOriginOnClose(workspace, leaf, originLeaf, (ref) => plugin.registerEvent(ref)); } diff --git a/src/tree-index.ts b/src/tree-index.ts index eae6690..a2598c0 100644 --- a/src/tree-index.ts +++ b/src/tree-index.ts @@ -1,5 +1,5 @@ import { TFile, TFolder, type App } from "obsidian"; -import { ROOT_ID, RESERVED_SUBFOLDER_NAMES, isInReservedSubfolder, type StashpadId, type TreeNode } from "./types"; +import { ROOT_ID, isReservedSubfolderName, isInReservedSubfolder, type StashpadId, type TreeNode } from "./types"; /** Walk a Stashpad folder's TFolder subtree and return every .md file under * it. Iterative DFS rather than recursive to avoid a deep-recursion blow-up @@ -18,10 +18,11 @@ function collectMarkdown(app: App, folderPath: string): TFile[] { if (child.extension === "md") out.push(child); } else if (child instanceof TFolder) { // 0.79.12: never descend into reserved Stashpad subfolders - // (_archive / _attachments / _authors / …) — their files aren't - // notes, and an archived original carrying a Stashpad id would - // otherwise surface as a phantom duplicate note. - if (!RESERVED_SUBFOLDER_NAMES.has(child.name)) stack.push(child); + // (_archive / _attachments / _authors / … and, 0.136.0, the per-folder + // archive/ + trash/) — their files aren't the folder's live notes, and + // an archived note carrying a Stashpad id would otherwise surface as a + // phantom duplicate. (`child` is always in subfolder position here.) + if (!isReservedSubfolderName(child.name)) stack.push(child); } } } @@ -128,6 +129,23 @@ export class TreeIndex { this.byPath.set(f.path, id); } + // 0.140.3 (review): break MULTI-node parent cycles (A.parent=B, B.parent=A — + // e.g. two devices moving notes in opposite directions before sync merges). + // A cycle member reaches neither ROOT nor a live ancestor, so its whole + // subtree would silently vanish from the UI and any recursive child-walk + // would loop forever. Walk each node's parent chain; on a revisit, reparent + // that node to ROOT so the tree stays acyclic + visible. + for (const node of this.nodes.values()) { + if (node.id === ROOT_ID) continue; + const seen = new Set<string>([node.id]); + let p = node.parent ?? ROOT_ID; + while (p !== ROOT_ID && this.nodes.has(p)) { + if (seen.has(p)) { node.parent = ROOT_ID; break; } + seen.add(p); + p = this.nodes.get(p)!.parent ?? ROOT_ID; + } + } + for (const node of this.nodes.values()) { if (node.id === ROOT_ID) continue; const parentId = node.parent ?? ROOT_ID; @@ -375,7 +393,12 @@ export class TreeIndex { return changed; } - const parentId = ((fm?.parent as string | null | undefined) ?? ROOT_ID); + let parentId = ((fm?.parent as string | null | undefined) ?? ROOT_ID); + // 0.140.3 (review): the self-parent guard existed only in rebuild() — a note + // declaring itself its own parent (hand-edited / synced) arriving via the + // incremental `changed` event would attach as its OWN child, hanging every + // recursive child-walk. Pin it to ROOT here too. + if (parentId === id) parentId = ROOT_ID; const created = (fm?.created as string) ?? ""; // Safety net: if the declared parent isn't ROOT and isn't in the diff --git a/src/types.ts b/src/types.ts index 1e43efa..60fe34a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -130,9 +130,36 @@ export const RESERVED_SUBFOLDER_NAMES: ReadonlySet<string> = new Set([ "_attachments", "_authors", "_exports", "_imports", "_processed", "_archive", ".archive", // .archive is legacy (pre-0.79.10) ]); -/** True if any path segment is a reserved Stashpad subfolder. */ + +/** 0.136.0 (per-folder archive/trash overhaul): every Stashpad folder owns an + * `archive/` (and, Phase 2, a `trash/`) subfolder. These names are reserved + * ONLY as subfolders — a TOP-LEVEL folder literally named "archive"/"trash" + * stays a normal folder. NOTE: a pre-existing user SUBfolder named + * `archive`/`trash` becomes that folder's archive/trash — its notes stay + * reachable via the aggregated views. */ +export const SUBFOLDER_ONLY_RESERVED_NAMES: ReadonlySet<string> = new Set(["archive", "trash"]); + +/** A reserved name in SUBFOLDER position (never applies to a path's first + * segment for archive/trash; always applies for the underscore names). */ +export function isReservedSubfolderName(name: string): boolean { + return RESERVED_SUBFOLDER_NAMES.has(name) || SUBFOLDER_ONLY_RESERVED_NAMES.has(name); +} + +/** 0.136.0: the archive subfolder for a Stashpad folder. */ +export function archiveSubfolderOf(folder: string): string { + return `${(folder || "").replace(/\/+$/, "")}/archive`; +} +/** 0.136.0: is `path` (a folder or file path) inside some folder's `archive/` + * subfolder? Matches any `archive` segment except a leading one (a top-level + * folder literally named "archive" is a normal folder, not a subfolder). */ +export function isArchiveSubfolderPath(path: string): boolean { + return (path || "").replace(/\/+$/, "").split("/").slice(1).includes("archive"); +} +/** True if any path segment is a reserved Stashpad subfolder. archive/trash + * count only in SUBfolder position (see SUBFOLDER_ONLY_RESERVED_NAMES). */ export function isInReservedSubfolder(path: string): boolean { - return path.split("/").some((seg) => RESERVED_SUBFOLDER_NAMES.has(seg)); + return path.split("/").some((seg, i) => + RESERVED_SUBFOLDER_NAMES.has(seg) || (i > 0 && SUBFOLDER_ONLY_RESERVED_NAMES.has(seg))); } /** True if the path lives under an archive subfolder (`_archive`/`.archive`) * — the import-originals graveyard, excluded from search + link surfaces. */ @@ -259,7 +286,10 @@ export type LogEventType = | "complete" | "uncomplete" | "stash_export" | "stash_import" | "attachment_add" | "attachment_remove" - | "palette_color_add" | "palette_color_remove"; + | "palette_color_add" | "palette_color_remove" + // 0.136.0: one-time move of legacy dedicated-archive contents into the + // folder's own archive/ subfolder (per-folder archive overhaul). + | "archive_migration"; export interface LogEvent { ts: string; diff --git a/src/undo-stack.ts b/src/undo-stack.ts index 1906eeb..65528ab 100644 --- a/src/undo-stack.ts +++ b/src/undo-stack.ts @@ -21,7 +21,10 @@ export class UndoStack { async undo(): Promise<UndoAction | null> { const a = this.undoStack.pop(); if (!a) return null; - try { await a.undo(); } catch (e) { console.error("Stashpad: undo failed", e); throw e; } + // On failure (e.g. a file transiently locked on a network vault) push the + // action BACK onto the undo stack so it stays retryable — otherwise it's + // lost from both stacks, possibly after a partial mutation. (0.140.5) + try { await a.undo(); } catch (e) { this.undoStack.push(a); console.error("Stashpad: undo failed", e); throw e; } this.redoStack.push(a); return a; } @@ -31,7 +34,7 @@ export class UndoStack { if (!a) return null; // Undo-only actions have no redo handler — moving them back to the undo // stack without re-applying is correct (and was previously a crash). - if (a.redo) { try { await a.redo(); } catch (e) { console.error("Stashpad: redo failed", e); throw e; } } + if (a.redo) { try { await a.redo(); } catch (e) { this.redoStack.push(a); console.error("Stashpad: redo failed", e); throw e; } } this.undoStack.push(a); return a; } diff --git a/src/view.ts b/src/view.ts index 505f6ce..dc6787b 100644 --- a/src/view.ts +++ b/src/view.ts @@ -5,11 +5,13 @@ import { } from "obsidian"; import { ROOT_ID, STASHPAD_VIEW_TYPE, RESERVED_FRONTMATTER, fmHasTag, fmAddTag, fmRemoveTag, parseAssignees, parseAuthorRef, attachmentLinkPath, toAttachmentLink, + archiveSubfolderOf, isArchiveSubfolderPath, type StashpadId, type TimeFilter, type TreeNode, type ViewConfigState, type ViewMode, type ScrollPolicy, } from "./types"; import { TreeIndex } from "./tree-index"; import { perf } from "./perf"; import { formatDateTime, formatDateOnly, formatTimeOnly } from "./format"; +import { parseRecurrence, nextDueOnComplete } from "./recurrence"; import { OrderStore } from "./order-store"; import { SortStore, SORT_MODE_LABELS, SORT_MODES_ORDER } from "./sort-store"; import { FrontmatterSyncQueue, rebootstrapFolderFrontmatter } from "./frontmatter-sync"; @@ -1838,7 +1840,7 @@ export class StashpadView extends ItemView { // 0.98.14: optionally hide the real title (privacy) — show a generic label. // Also fall back to generic when the on-disk title is empty (it was locked with // hide-titles on, so the real title lives only inside the blob). - const hideTitle = (this.plugin.settings.hideLockedTitles ?? false) || !lk.title; + const hideTitle = !lk.title; // 0.137.1: hidden iff hidden AT LOCK TIME (empty stored title) row.createSpan({ cls: "stashpad-locked-title", text: hideTitle ? "Locked note" : lk.title }); row.createSpan({ cls: "stashpad-locked-count", text: lk.count > 1 ? `${lk.count} notes · locked` : "locked" }); const unlockBtn = row.createEl("button", { cls: "stashpad-locked-unlock", text: "Unlock" }); @@ -6632,31 +6634,20 @@ export class StashpadView extends ItemView { } } - /** 0.98.28 (Phase 4): move the selected note(s) into an archive folder, which - * auto-encrypts them on arrival. Uses the default archive folder if set; else - * the only archive folder if there's just one; else offers a pick-list. */ + /** 0.136.0: move the selected note(s) into THIS folder's own `archive/` + * subfolder (plaintext unless the folder's "Encrypt archived notes" is on). */ async cmdMoveToArchive(): Promise<void> { const targets = this.getActionTargets(); if (targets.length === 0) return; const cur = (this.noteFolder ?? "").replace(/\/+$/, ""); - // If we're ALREADY in an archive folder, don't silently re-home to a DIFFERENT - // archive (the old bug: the current folder was filtered out, so the default was - // ignored and a different archive was picked). The note is already archived. - if (this.plugin.isArchiveFolder(cur)) { - new Notice(`These notes are already in an archive folder (“${cur.split("/").pop()}”).`, 6000); + // 0.136.0 (per-folder archive): archiving ALWAYS targets this folder's own + // `archive/` subfolder — no picker, no default-archive setting, no separate + // dedicated archive folders. + if (isArchiveSubfolderPath(cur)) { + new Notice("These notes are already in an archive.", 6000); return; } - const archives = (this.plugin.settings.archiveFolders ?? []).map((f) => f.replace(/\/+$/, "")).filter((f) => f !== cur); - if (archives.length === 0) { - new Notice("No archive folder available. Mark a folder as archive first (Settings → Stashpad → Encryption → Per-folder, or the folder panel right-click).", 8000); - return; - } - const def = (this.plugin.settings.defaultArchiveFolder ?? "").replace(/\/+$/, ""); - const dest = (def && archives.includes(def)) ? def : (archives.length === 1 ? archives[0] : null); - const go = (folder: string) => { void this.archiveSources(targets, folder); }; - if (dest) { go(dest); return; } - // Several archives, no (valid) default → pick one. - new ArchiveFolderSuggestModal(this.app, archives, go).open(); + void this.archiveSources(targets, archiveSubfolderOf(cur)); } /** 0.98.34 (Phase 4): archive the given notes into `dest` — encrypt each root's @@ -6667,12 +6658,14 @@ export class StashpadView extends ItemView { * no async hook, so undo is a clean self-contained reversal. */ private async archiveSources(sources: TreeNode[], dest: string): Promise<void> { const src = this.noteFolder; - // Archiving currently encrypts the moved notes (under the destination folder's - // key, or the vault key as fallback). If the destination is set to NOT encrypt - // its archive ("Encrypt archived notes" off = plaintext archive), that's a - // plaintext move — not wired into this command yet, so guide rather than fail. const cleanDest = dest.replace(/\/+$/, ""); - const encryptDest = (this.plugin.settings.folderEncPrefs ?? {})[cleanDest]?.archiveEncryptContent ?? true; + // 0.136.0: dest is `<parent>/archive` — the encrypt-or-not pref lives on the + // PARENT folder (folderEncPrefs[parent].archiveEncryptContent, plaintext + // default via the shared B1 resolver). + const parentFolder = cleanDest.replace(/\/archive$/, ""); + const encryptDest = this.plugin.archiveEncryptFor(parentFolder); + // The subfolder may not exist yet — create it before any move/blob write. + try { if (!(await this.app.vault.adapter.exists(cleanDest))) await this.app.vault.createFolder(cleanDest); } catch { /* race / exists */ } if (!encryptDest) { // Plaintext archive (encryption off): MOVE the subtree's files into the // destination (de-indexed via the archive flag, but not encrypted). Stashpad's @@ -6702,7 +6695,7 @@ export class StashpadView extends ItemView { return; } if (!this.plugin.encryption?.isConfigured?.()) { - new Notice(`Archiving encrypts the notes, but encryption isn't set up yet (Settings → Stashpad → Encryption). Set it up — or turn off “Encrypt archived notes” for “${cleanDest.split("/").pop()}” to make it a plaintext archive.`, 9000); + new Notice(`Archiving here encrypts the notes ("Encrypt archived notes" is on for “${parentFolder.split("/").pop()}”), but encryption isn't set up yet (Settings → Stashpad → Encryption). Set it up — or turn that toggle off for a plaintext archive.`, 9000); return; } const ids = new Set(sources.map((t) => t.id)); @@ -6729,8 +6722,16 @@ export class StashpadView extends ItemView { label: `Archive (${blobs.length})`, undo: async () => { // Restore the blobs back to the SOURCE folder (not the archive folder). - for (const b of blobs) { try { await this.plugin.unlockBundleAt(b, { silent: true, destFolder: src }); } catch (e) { console.warn("[Stashpad] archive undo failed", b, e); } } - blobs = []; + // 0.140.2: only DROP the ones that actually restored — if a restore + // failed (key prompt declined, error), keep it in `blobs` so undo can be + // retried and redo doesn't rebuild from a phantom-empty state. + const failed: string[] = []; + for (const b of blobs) { + try { if (!(await this.plugin.unlockBundleAt(b, { silent: true, destFolder: src }))) failed.push(b); } + catch (e) { failed.push(b); console.warn("[Stashpad] archive undo failed", b, e); } + } + blobs = failed; + if (failed.length) new Notice(`Undo incomplete: ${failed.length} archived item${failed.length === 1 ? "" : "s"} couldn't be restored (they're still in the archive). Try again, or restore from the Archived view.`, 8000); this.tree.rebuild(src); this.render(); }, redo: async () => { @@ -6804,8 +6805,14 @@ export class StashpadView extends ItemView { this.plugin.getUndoStack(folder).push({ label: `Secure delete (${blobs.length})`, undo: async () => { - for (const b of blobs) { try { await this.plugin.restoreDeletedAt(b, { silent: true }); } catch (e) { console.warn("[Stashpad] secure-delete undo failed", b, e); } } - blobs = []; + // 0.140.2: keep the ones that failed to restore so undo is retryable. + const failed: string[] = []; + for (const b of blobs) { + try { if (!(await this.plugin.restoreDeletedAt(b, { silent: true }))) failed.push(b); } + catch (e) { failed.push(b); console.warn("[Stashpad] secure-delete undo failed", b, e); } + } + blobs = failed; + if (failed.length) new Notice(`Undo incomplete: ${failed.length} deleted item${failed.length === 1 ? "" : "s"} couldn't be restored (still in Trash). Try again, or restore from the Trash view.`, 8000); this.tree.rebuild(folder); this.render(); }, redo: async () => { @@ -7157,7 +7164,7 @@ export class StashpadView extends ItemView { localFolder: this.noteFolder, // 0.124.0: surface encrypted/locked notes in search (placeholder when titles hidden). lockedNotes: () => this.plugin.lockedSubtreesInFolder(this.noteFolder), - hideLockedTitles: this.plugin.settings.hideLockedTitles ?? false, + hideLockedTitles: false, // 0.137.1: global option removed; picker falls back to !lk.title }); this.openSearchInstance = instance; // Wrap onClose to clear our tracked reference when the modal closes. @@ -8969,7 +8976,7 @@ export class StashpadView extends ItemView { ws.revealLeaf(leaf); // 0.57.5: when this spawned tab closes, the originating Stashpad tab // regains focus (see returnToOriginOnClose — shared by every opener). - returnToOriginOnClose(ws, leaf, originLeaf); + returnToOriginOnClose(ws, leaf, originLeaf, (ref) => this.plugin.registerEvent(ref)); } /** Open a Stashpad folder's home in a new tab (any folder, not just @@ -9006,7 +9013,7 @@ export class StashpadView extends ItemView { ws.revealLeaf(leaf); // 0.133.0: closing the search-opened tab returns to the tab you searched // from, not the tab to the right. - returnToOriginOnClose(ws, leaf, originLeaf); + returnToOriginOnClose(ws, leaf, originLeaf, (ref) => this.plugin.registerEvent(ref)); } private async openFolderInNewTab(folder: string): Promise<void> { @@ -9029,7 +9036,7 @@ export class StashpadView extends ItemView { ws.setActiveLeaf(leaf, { focus: true }); ws.revealLeaf(leaf); // When the spawned leaf closes, restore focus to the originating tab. - returnToOriginOnClose(ws, leaf, originLeaf); + returnToOriginOnClose(ws, leaf, originLeaf, (ref) => this.plugin.registerEvent(ref)); } // --- Open shortcuts --- @@ -9073,7 +9080,7 @@ export class StashpadView extends ItemView { // When the edit tab closes, reveal the originating Stashpad leaf instead // of whatever Obsidian picked (the tab to the right). - returnToOriginOnClose(ws, leaf, originLeaf); + returnToOriginOnClose(ws, leaf, originLeaf, (ref) => this.plugin.registerEvent(ref)); const view: any = leaf.view; const editor: any = view?.editor; @@ -9228,22 +9235,44 @@ export class StashpadView extends ItemView { // more useful: if any are incomplete, mark all complete). const anyIncomplete = targets.some((t) => !this.isCompleted(t)); const newState = anyIncomplete; // true means "mark complete" - const priorStates: { id: StashpadId; path: string; was: boolean }[] = []; + // 0.140.1: capture the pre-toggle due + whether it rolled, so undo/redo can + // reverse a recurrence roll (not just the `completed` flag). + const priorStates: { id: StashpadId; path: string; was: boolean; dueBefore: string | null; rolledTo: string | null }[] = []; const changedIds: StashpadId[] = []; + const rolled: Array<{ title: string; when: number }> = []; // recurring tasks rescheduled for (const t of targets) { if (!t.file) continue; const was = this.isCompleted(t); - priorStates.push({ id: t.id, path: t.file.path, was }); + const rec0 = this.app.metadataCache.getFileCache(t.file)?.frontmatter; + const dueBefore = rec0?.due != null ? String(rec0.due) : null; + const ps = { id: t.id, path: t.file.path, was, dueBefore, rolledTo: null as string | null }; + priorStates.push(ps); if (was === newState) continue; + let didRoll = false; await this.app.fileManager.processFrontMatter(t.file, (fm) => { - if (newState) fm.completed = true; + // 0.140.0: completing a REPEATING task rolls it forward (stays an active + // task with a new due) instead of just going strikethrough. + const rec = newState ? parseRecurrence(fm.repeat as string | undefined) : null; + if (rec) { + const oldDue = fm.due != null ? Date.parse(String(fm.due)) : NaN; + const next = nextDueOnComplete(rec, Number.isFinite(oldDue) ? oldDue : null, Date.now()); + fm.due = new Date(next).toISOString(); + delete fm.completed; + ps.rolledTo = fm.due as string; + rolled.push({ title: (t.file!.basename.replace(/-[a-z0-9]{4,12}$/, "").replace(/-/g, " ").trim()) || "task", when: next }); + didRoll = true; + } else if (newState) fm.completed = true; else delete fm.completed; }); - this.completedState.set(t.file.path, newState); // 0.76.11 + // A rolled task is NOT completed — reflect its real (incomplete) state. + this.completedState.set(t.file.path, didRoll ? false : newState); changedIds.push(t.id); } this.render(); + for (const r of rolled) { + this.plugin.notifications.show({ message: `🔁 Rescheduled “${r.title}” → ${formatDateTime(r.when, this.plugin.settings)}.`, kind: "success", category: "system", folder: this.noteFolder }); + } if (changedIds.length > 0) { await this.log.append({ type: newState ? "complete" : "uncomplete", @@ -9272,7 +9301,12 @@ export class StashpadView extends ItemView { const f = this.app.vault.getAbstractFileByPath(p.path) as TFile | null; if (!f) continue; await this.app.fileManager.processFrontMatter(f, (fm) => { - if (p.was) fm.completed = true; + if (p.rolledTo != null) { + // 0.140.1: reverse a recurrence roll — restore the old due, keep + // it an active (incomplete) task. + if (p.dueBefore != null) fm.due = p.dueBefore; else delete fm.due; + delete fm.completed; + } else if (p.was) fm.completed = true; else delete fm.completed; }); if (changedIds.includes(p.id)) reverted.push(p.id); @@ -9292,7 +9326,8 @@ export class StashpadView extends ItemView { const f = this.app.vault.getAbstractFileByPath(p.path) as TFile | null; if (!f) continue; await this.app.fileManager.processFrontMatter(f, (fm) => { - if (newState) fm.completed = true; + if (p.rolledTo != null) { fm.due = p.rolledTo; delete fm.completed; } // re-roll + else if (newState) fm.completed = true; else delete fm.completed; }); } @@ -9378,8 +9413,17 @@ export class StashpadView extends ItemView { const knownAuthors = this.plugin.collectKnownAuthors(); const currentAssignees = parseAssignees(curFm ?? {}); new DueDatePickerModal(this.app, current, (result) => { - void this.applyDue(targets, result.iso, result.assignees); - }, { knownAuthors, currentAssignees, quickAdjusts: this.plugin.settings.dueQuickAdjusts }).open(); + void this.applyDue(targets, result.iso, result.assignees, false, { + repeat: result.repeat, autoDoneAfter: result.autoDoneAfter, remindEvery: result.remindEvery, + }); + }, { knownAuthors, currentAssignees, quickAdjusts: this.plugin.settings.dueQuickAdjusts, + // 0.140.1: recurrence is a per-note concept — only show/write it for a + // single target, else a multi-select would clobber 2..n's rules with #1's. + showRecurrence: targets.length === 1, + currentRepeat: typeof curFm?.repeat === "string" ? curFm.repeat : "", + currentAutoDoneAfter: typeof curFm?.autoDoneAfter === "string" ? curFm.autoDoneAfter : "", + currentRemindEvery: typeof curFm?.remindEvery === "string" ? curFm.remindEvery : "", + }).open(); } /** 0.125.0: Snooze — reschedule a task's due date. Reuses the due-date picker @@ -9405,8 +9449,8 @@ export class StashpadView extends ItemView { /** Write the chosen due value (or clear it) across `targets`, with * undo. Setting a date also flips `task: true`; clearing leaves the * task flag intact (clearing a due ≠ "no longer a task"). */ - private async applyDue(targets: TreeNode[], iso: string | null, assignees: Array<{ id: string; name: string }> = [], dueOnly = false): Promise<void> { - const prior: { id: StashpadId; path: string; due: unknown; task: unknown; assignedTo: unknown; assignedBy: unknown; wasTagged: boolean }[] = []; + private async applyDue(targets: TreeNode[], iso: string | null, assignees: Array<{ id: string; name: string }> = [], dueOnly = false, recur?: { repeat?: string; autoDoneAfter?: string; remindEvery?: string }): Promise<void> { + const prior: { id: StashpadId; path: string; due: unknown; task: unknown; assignedTo: unknown; assignedBy: unknown; wasTagged: boolean; repeat: unknown; autoDoneAfter: unknown; remindEvery: unknown }[] = []; const changedIds: StashpadId[] = []; // 0.78.1: who is doing the assigning (the local user) — stamped as // assignedBy so the "assigned by me" filter works. Null if the user @@ -9422,10 +9466,21 @@ export class StashpadView extends ItemView { if (!t.file) continue; const fm = this.app.metadataCache.getFileCache(t.file)?.frontmatter as any; const wasTagged = this.isTaskTagged(t); - prior.push({ id: t.id, path: t.file.path, due: fm?.due, task: fm?.task, assignedTo: fm?.assignedTo, assignedBy: fm?.assignedBy, wasTagged }); + prior.push({ id: t.id, path: t.file.path, due: fm?.due, task: fm?.task, assignedTo: fm?.assignedTo, assignedBy: fm?.assignedBy, wasTagged, repeat: fm?.repeat, autoDoneAfter: fm?.autoDoneAfter, remindEvery: fm?.remindEvery }); await this.app.fileManager.processFrontMatter(t.file, (m) => { if (iso === null) delete m.due; else { m.due = iso; m.task = true; } + // 0.140.0: recurrence + reminder fields — an empty string clears the + // field; a non-empty one sets it (and implies task-ness). + if (recur) { + const set3 = (k: "repeat" | "autoDoneAfter" | "remindEvery", v?: string) => { + const val = (v ?? "").trim(); + if (val) { m[k] = val; m.task = true; } else delete m[k]; + }; + set3("repeat", recur.repeat); + set3("autoDoneAfter", recur.autoDoneAfter); + set3("remindEvery", recur.remindEvery); + } // 0.125.0: Snooze passes dueOnly — reschedule the due date WITHOUT // touching assignees (the plain applyDue would clear them on an empty // list). Skip all assignee writes in that mode. @@ -9473,6 +9528,10 @@ export class StashpadView extends ItemView { if (p.task === undefined) delete m.task; else m.task = p.task; if (p.assignedTo === undefined) delete m.assignedTo; else m.assignedTo = p.assignedTo; if (p.assignedBy === undefined) delete m.assignedBy; else m.assignedBy = p.assignedBy; + // 0.140.1: restore recurrence/reminder fields too (else undo left them). + if (p.repeat === undefined) delete m.repeat; else m.repeat = p.repeat; + if (p.autoDoneAfter === undefined) delete m.autoDoneAfter; else m.autoDoneAfter = p.autoDoneAfter; + if (p.remindEvery === undefined) delete m.remindEvery; else m.remindEvery = p.remindEvery; }); this.taskTaggedState.set(p.path, p.wasTagged); // 0.85.1 } @@ -9924,26 +9983,29 @@ export class StashpadView extends ItemView { }); return; } - // For nesting: prevent dropping onto a descendant of the source (would create a cycle). - if (position === "into") { - for (const src of sourceNodes) { - if (this.isDescendant(targetId, src.id)) { - this.plugin.notifications.show({ - message: `Can't nest "${this.titleForNode(src)}" under one of its own descendants — that would create a cycle.`, - kind: "warning", - category: "move", - folder: this.noteFolder, - }); - return; - } - } - } - // Decide which parent the sources will end up under. const newParentId = position === "into" ? targetId : ((targetNode.parent as StashpadId) ?? ROOT_ID); + // Cycle guard — applies to ALL positions, not just "into" (0.140.4 review). + // For "before"/"after" the destination parent is targetNode.parent, so + // dropping a parent adjacent to its own child (newParent === the source) or + // to a deeper descendant (newParent is a descendant of the source) would + // write a self-parent / cycle to frontmatter. The tree-index guard would + // reset it in-memory, but the on-disk `parent` field would still be corrupt. + for (const src of sourceNodes) { + if (newParentId === src.id || this.isDescendant(newParentId, src.id)) { + this.plugin.notifications.show({ + message: `Can't move "${this.titleForNode(src)}" under itself or one of its own descendants — that would create a cycle.`, + kind: "warning", + category: "move", + folder: this.noteFolder, + }); + return; + } + } + // Detect cross-parent sources (relative to the new destination). const isCross = sourceNodes.some((n) => (n.parent ?? ROOT_ID) !== newParentId); if (isCross) { @@ -10169,7 +10231,7 @@ export class StashpadView extends ItemView { const _delFolder = (this.noteFolder ?? "").replace(/\/+$/, ""); const _delFp = (this.plugin.settings.folderEncPrefs ?? {})[_delFolder] ?? {}; const _encryptTrash = _delFp.trashEncryptContent ?? this.plugin.settings.encryptTrash ?? false; - const _followObsidian = _delFp.trashHandling ? _delFp.trashHandling === "obsidian" : (this.plugin.settings.encryptTrashFollowObsidian ?? false); + const _followObsidian = _delFp.trashHandling === "obsidian"; // 0.137.1: global follow-Obsidian option removed if (_encryptTrash && !_followObsidian) { if (!this.plugin.encryption?.isConfigured?.()) { // Don't silently fall back to the plaintext trash the user asked to avoid. @@ -11957,23 +12019,8 @@ export class StashpadView extends ItemView { export { matchBinding } from "./view-keys"; export { properCaseFolderPath } from "./view-helpers"; -/** 0.98.28: tiny fuzzy picker over archive folders, for "Move to archive" when - * more than one archive exists and no default is set. */ -class ArchiveFolderSuggestModal extends SuggestModal<string> { - constructor(app: App, private folders: string[], private onPick: (folder: string) => void) { - super(app); - this.setPlaceholder("Move to which archive folder?"); - } - getSuggestions(query: string): string[] { - const q = query.toLowerCase(); - return this.folders.filter((f) => f.toLowerCase().includes(q)); - } - renderSuggestion(folder: string, el: HTMLElement): void { - el.createDiv({ text: folder.split("/").pop() || folder }); - el.createEl("small", { text: folder, cls: "stashpad-suggest-path" }); - } - onChooseSuggestion(folder: string): void { this.onPick(folder); } -} +// (0.136.0: ArchiveFolderSuggestModal removed — archiving always targets the +// current folder's own `archive/` subfolder now, so there's nothing to pick.) /** 0.98.29: minimal restore picker over the encrypted trash. The richer grouped * trash VIEW is separate; this is the keyboard/command path. Entries are diff --git a/src/zip.ts b/src/zip.ts index c97ab7f..004c4fc 100644 --- a/src/zip.ts +++ b/src/zip.ts @@ -19,7 +19,16 @@ * verbatim (with slashes intact) so each caller can apply the right zip-slip * defense: `safeZipEntryName` (flatten) for `.stash`, `safeTrashRelPath` * (preserve nested dirs) for rawtrash. */ -import { strFromU8, strToU8, unzipSync, zipSync, type Zippable } from "fflate"; +import { strFromU8, strToU8, unzipSync, zipSync, type Zippable, type UnzipFileInfo } from "fflate"; + +/** 0.140.2: decompression-bomb caps. fflate's sync `unzipSync` decompresses + * everything at once, but its `filter` runs off the central directory — which + * carries each entry's UNCOMPRESSED size — so we can reject a bomb BEFORE it + * inflates. Generous enough for real note+attachment bundles; blocks the + * ~KB-that-inflates-to-GB attack that could freeze/OOM every synced device. */ +const MAX_TOTAL_INFLATED = 512 * 1024 * 1024; // 512 MiB across all entries +const MAX_ENTRY_INFLATED = 256 * 1024 * 1024; // 256 MiB single entry +const MAX_ENTRIES = 20_000; export interface ZipEntry { name: string; @@ -50,5 +59,14 @@ export function bytesToStr(b: Uint8Array): string { * by fflate; names are returned verbatim (slashes intact) — sanitize per call site. */ export function unzipFiles(buf: Uint8Array | ArrayBuffer): Promise<Record<string, Uint8Array>> { const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf); - return Promise.resolve(unzipSync(u8)); + let total = 0, count = 0; + const filter = (f: UnzipFileInfo): boolean => { + count += 1; + if (count > MAX_ENTRIES) throw new Error(`Refusing to unzip: too many entries (>${MAX_ENTRIES}) — possible zip bomb.`); + if (f.originalSize > MAX_ENTRY_INFLATED) throw new Error(`Refusing to unzip: entry "${f.name}" is ${Math.round(f.originalSize / 1048576)} MiB — possible zip bomb.`); + total += f.originalSize; + if (total > MAX_TOTAL_INFLATED) throw new Error(`Refusing to unzip: total size >${Math.round(MAX_TOTAL_INFLATED / 1048576)} MiB — possible zip bomb.`); + return true; + }; + return Promise.resolve(unzipSync(u8, { filter })); } diff --git a/styles.css b/styles.css index 80bed56..8d01e7c 100644 --- a/styles.css +++ b/styles.css @@ -2924,7 +2924,11 @@ body.is-mobile .stashpad-jd-actions .setting-item-control { inputs and squeezing the description. */ flex-shrink: 0; max-width: 100%; - justify-content: flex-start; + /* 0.134.0: right-align controls, matching Obsidian's own setting rows (they + were flex-start, which left every section control — the per-folder + encryption panel especially — drifting toward the middle while the plain + rows above sat at the right edge; the mix read as "randomly positioned"). */ + justify-content: flex-end; } /* 0.121.14: Obsidian 1.13's MOBILE settings use a 20px horizontal gutter (measured: .setting-item padding-left = 20px). Several stashpad insets were @@ -5408,8 +5412,98 @@ body.stashpad-folderpanel-resizing { cursor: row-resize; user-select: none; } background: rgba(var(--color-red-rgb, 224, 49, 49), 0.12); border-left: 3px solid var(--color-red, #e03131); /* 0.121.5: a touch more vertical padding + more bottom breathing room so the - advice banner doesn't feel cramped (L/R stays at the section's 14px inset). */ + advice banner doesn't feel cramped. */ padding: 11px 12px; border-radius: 4px; - margin: 4px 0 18px; + /* 0.134.3: now a single merged multi-paragraph callout at the top of the + Vault Encryption section (was three separate warnings) — inset 14px to + line up with the section's setting rows. */ + margin: 4px 14px 18px; + font-size: 13px; + line-height: 1.5; } +.stashpad-enc-warning p { margin: 0; } +.stashpad-enc-warning p + p { margin-top: 8px; } +/* 0.137.4: collapsible "how encryption works" concepts block — one toggle-free + reference so the individual setting rows can stay one-liners. Collapsed by + default (details), neutral (not a warning). */ +.stashpad-enc-info { + margin: 0 14px 14px; + border: 1px solid var(--background-modifier-border); + border-radius: 6px; + background: var(--background-secondary); + font-size: 13px; + line-height: 1.5; +} +.stashpad-enc-info > summary { + cursor: pointer; + padding: 9px 12px; + font-weight: 600; + color: var(--text-normal); + list-style-position: inside; +} +.stashpad-enc-info[open] > summary { border-bottom: 1px solid var(--background-modifier-border); } +.stashpad-enc-info-item { margin: 0; padding: 9px 12px; color: var(--text-muted); } +.stashpad-enc-info-item + .stashpad-enc-info-item { border-top: 1px solid var(--background-modifier-border); } +.stashpad-enc-info-item strong { color: var(--text-normal); } +/* 0.134.3: Obsidian's mod-warning buttons are solid bright red with white text — + glaring, and the white text reads poorly on it. Within Stashpad's settings + sections (Remove encryption, Rotate key, Turn off shared password, …) use a + quieter danger style: red tint + red text + red border, matching the + .stashpad-enc-warning callout language. Hover deepens the tint. */ +.stashpad-settings-section button.mod-warning { + background: rgba(var(--color-red-rgb, 224, 49, 49), 0.14); + color: var(--color-red, #e03131); + border: 1px solid rgba(var(--color-red-rgb, 224, 49, 49), 0.45); + text-shadow: none; +} +.stashpad-settings-section button.mod-warning:hover { + background: rgba(var(--color-red-rgb, 224, 49, 49), 0.25); + color: var(--color-red, #e03131); +} +/* 0.136.0: loading state for slow-running buttons (Remove encryption's state + check on slow drives). Dims the button and sweeps a loading bar across it; + clicks are swallowed in JS while the class is on. */ +button.stashpad-btn-loading { + position: relative; + opacity: 0.55; + pointer-events: none; + overflow: hidden; +} +button.stashpad-btn-loading::after { + content: ""; + position: absolute; + left: 0; bottom: 0; + height: 2px; width: 40%; + background: currentColor; + animation: stashpad-btn-loading-sweep 1.1s ease-in-out infinite; +} +@keyframes stashpad-btn-loading-sweep { + 0% { transform: translateX(-100%); } + 100% { transform: translateX(350%); } +} + +/* 0.138.0: re-encrypt sweep — review modal rows + the watch view's Removed + section. Reuses the trash row classes for the view itself. */ +.stashpad-reenc-list { max-height: 50vh; overflow-y: auto; margin: 8px 0 12px; } +.stashpad-reenc-row { + display: flex; align-items: flex-start; gap: 10px; + padding: 8px 6px; border-radius: 6px; cursor: pointer; +} +.stashpad-reenc-row:hover { background: var(--background-modifier-hover); } +.stashpad-reenc-row input[type="checkbox"] { margin-top: 3px; } +.stashpad-reenc-label { font-weight: 500; } +.stashpad-reenc-detail { font-size: var(--font-ui-smaller); color: var(--text-muted); } +.stashpad-watch-removed { margin-top: 16px; } +.stashpad-watch-removed > summary { + cursor: pointer; font-size: var(--font-ui-smaller); color: var(--text-muted); + padding: 4px 0; +} +/* 0.139.0: per-note peek override input in the "Previously encrypted" view. */ +.stashpad-watch-peek { width: 56px; text-align: center; } +/* 0.140.0: recurring/reminder fields in the due-date picker. */ +.stashpad-due-recur { margin-top: 10px; border-top: 1px solid var(--background-modifier-border); padding-top: 8px; } +.stashpad-due-recur > summary { cursor: pointer; font-size: var(--font-ui-smaller); color: var(--text-muted); } +.stashpad-due-recur-row { display: flex; align-items: center; gap: 8px; margin: 8px 0; } +.stashpad-due-recur-row label { flex: 0 0 130px; font-size: var(--font-ui-smaller); color: var(--text-muted); } +.stashpad-due-recur-row input { flex: 1; }