diff --git a/cmd/verstak-gui/app.go b/cmd/verstak-gui/app.go
index 1040202..c537b17 100644
--- a/cmd/verstak-gui/app.go
+++ b/cmd/verstak-gui/app.go
@@ -315,7 +315,30 @@ func (a *App) RenameNode(nodeID, newTitle string) error {
return a.nodes.UpdateTitle(nodeID, newTitle)
}
+func (a *App) ValidateName(name string) error {
+ return files.ValidateName(name)
+}
+
func (a *App) MoveNode(nodeID, newParentID string) error {
+ // Check for name conflict at destination
+ destChildren, err := a.nodes.ListChildren(newParentID, false)
+ if err != nil {
+ return err
+ }
+ node, err := a.nodes.GetActive(nodeID)
+ if err != nil {
+ return err
+ }
+ for i := range destChildren {
+ if destChildren[i].Title == node.Title {
+ // Conflict: auto-rename
+ newName := a.files.UniqueTitleCopy(newParentID, node.Title)
+ if err := a.nodes.UpdateTitle(nodeID, newName); err != nil {
+ return err
+ }
+ break
+ }
+ }
return a.nodes.Move(nodeID, newParentID, 0)
}
diff --git a/cmd/verstak-gui/frontend-dist/assets/main-B8EIu1OK.js b/cmd/verstak-gui/frontend-dist/assets/main-B8EIu1OK.js
new file mode 100644
index 0000000..3522987
--- /dev/null
+++ b/cmd/verstak-gui/frontend-dist/assets/main-B8EIu1OK.js
@@ -0,0 +1,16 @@
+var Yn=Object.defineProperty;var Qn=(n,e,t)=>e in n?Yn(n,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):n[e]=t;var tt=(n,e,t)=>Qn(n,typeof e!="symbol"?e+"":e,t);(function(){const e=document.createElement("link").relList;if(e&&e.supports&&e.supports("modulepreload"))return;for(const i of document.querySelectorAll('link[rel="modulepreload"]'))l(i);new MutationObserver(i=>{for(const s of i)if(s.type==="childList")for(const o of s.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&l(o)}).observe(document,{childList:!0,subtree:!0});function t(i){const s={};return i.integrity&&(s.integrity=i.integrity),i.referrerPolicy&&(s.referrerPolicy=i.referrerPolicy),i.crossOrigin==="use-credentials"?s.credentials="include":i.crossOrigin==="anonymous"?s.credentials="omit":s.credentials="same-origin",s}function l(i){if(i.ep)return;i.ep=!0;const s=t(i);fetch(i.href,s)}})();function Z(){}function Sl(n){return n()}function Et(){return Object.create(null)}function ce(n){n.forEach(Sl)}function Tl(n){return typeof n=="function"}function Ne(n,e){return n!=n?e==e:n!==e||n&&typeof n=="object"||typeof n=="function"}let Ae;function Ve(n,e){return n===e?!0:(Ae||(Ae=document.createElement("a")),Ae.href=e,n===Ae.href)}function xn(n){return Object.keys(n).length===0}function $n(n){return n??""}function f(n,e){n.appendChild(e)}function k(n,e,t){n.insertBefore(e,t||null)}function b(n){n.parentNode&&n.parentNode.removeChild(n)}function _e(n,e){for(let t=0;tn.removeEventListener(e,t,l)}function be(n){return function(e){return e.stopPropagation(),n.call(this,e)}}function Oe(n){return function(e){e.target===this&&n.call(this,e)}}function r(n,e,t){t==null?n.removeAttribute(e):n.getAttribute(e)!==t&&n.setAttribute(e,t)}function Ll(n){return n===""?null:+n}function ei(n){return Array.from(n.childNodes)}function G(n,e){e=""+e,n.data!==e&&(n.data=e)}function me(n,e){n.value=e??""}function Nt(n,e,t){for(let l=0;l{const i=n.$$.callbacks[e];if(i){const s=li(e,t,{cancelable:l});return i.slice().forEach(o=>{o.call(n,s)}),!s.defaultPrevented}return!0}}function ni(n,e){const t=n.$$.callbacks[e.type];t&&t.slice().forEach(l=>l.call(this,e))}const De=[],Ft=[];let Ee=[];const Ht=[],ii=Promise.resolve();let nt=!1;function si(){nt||(nt=!0,ii.then(Nl))}function Re(n){Ee.push(n)}const lt=new Set;let Le=0;function Nl(){if(Le!==0)return;const n=Be;do{try{for(;Len.indexOf(l)===-1?e.push(l):t.push(l)),t.forEach(l=>l()),Ee=e}const ze=new Set;let Me;function ke(){Me={r:0,c:[],p:Me}}function ge(){Me.r||ce(Me.c),Me=Me.p}function le(n,e){n&&n.i&&(ze.delete(n),n.i(e))}function oe(n,e,t,l){if(n&&n.o){if(ze.has(n))return;ze.add(n),Me.c.push(()=>{ze.delete(n),l&&(t&&n.d(1),l())}),n.o(e)}else l&&l()}function se(n){return(n==null?void 0:n.length)!==void 0?n:Array.from(n)}function ci(n,e){oe(n,1,1,()=>{e.delete(n.key)})}function ai(n,e,t,l,i,s,o,a,c,m,_,h){let p=n.length,v=s.length,w=p;const g={};for(;w--;)g[n[w].key]=w;const y=[],C=new Map,T=new Map,j=[];for(w=v;w--;){const E=h(i,s,w),I=t(E);let M=o.get(I);M?j.push(()=>M.p(E,e)):(M=m(I,E),M.c()),C.set(I,y[w]=M),I in g&&T.set(I,Math.abs(w-g[I]))}const F=new Set,A=new Set;function O(E){le(E,1),E.m(a,_),o.set(E.key,E),_=E.first,v--}for(;p&&v;){const E=y[v-1],I=n[p-1],M=E.key,D=I.key;E===I?(_=E.first,p--,v--):C.has(D)?!o.has(M)||F.has(M)?O(E):A.has(D)?p--:T.get(M)>T.get(D)?(A.add(M),O(E)):(F.add(D),p--):(c(I,o),p--)}for(;p--;){const E=n[p];C.has(E.key)||c(E,o)}for(;v;)O(y[v-1]);return ce(j),y}function Se(n){n&&n.c()}function ye(n,e,t){const{fragment:l,after_update:i}=n.$$;l&&l.m(e,t),Re(()=>{const s=n.$$.on_mount.map(Sl).filter(Tl);n.$$.on_destroy?n.$$.on_destroy.push(...s):ce(s),n.$$.on_mount=[]}),i.forEach(Re)}function qe(n,e){const t=n.$$;t.fragment!==null&&(ri(t.after_update),ce(t.on_destroy),t.fragment&&t.fragment.d(e),t.on_destroy=t.fragment=null,t.ctx=[])}function fi(n,e){n.$$.dirty[0]===-1&&(De.push(n),si(),n.$$.dirty.fill(0)),n.$$.dirty[e/31|0]|=1<{const w=v.length?v[0]:p;return m.ctx&&i(m.ctx[h],m.ctx[h]=w)&&(!m.skip_bound&&m.bound[h]&&m.bound[h](w),_&&fi(n,h)),p}):[],m.update(),_=!0,ce(m.before_update),m.fragment=l?l(m.ctx):!1,e.target){if(e.hydrate){const h=ei(e.target);m.fragment&&m.fragment.l(h),h.forEach(b)}else m.fragment&&m.fragment.c();e.intro&&le(n.$$.fragment),ye(n,e.target,e.anchor),Nl()}je(c)}class Fe{constructor(){tt(this,"$$");tt(this,"$$set")}$destroy(){qe(this,1),this.$destroy=Z}$on(e,t){if(!Tl(t))return Z;const l=this.$$.callbacks[e]||(this.$$.callbacks[e]=[]);return l.push(t),()=>{const i=l.indexOf(t);i!==-1&&l.splice(i,1)}}$set(e){this.$$set&&!xn(e)&&(this.$$.skip_bound=!0,this.$$set(e),this.$$.skip_bound=!1)}}const ui="4";typeof window<"u"&&(window.__svelte||(window.__svelte={v:new Set})).v.add(ui);function di(n){let e,t;return{c(){e=R("path"),t=R("polyline"),r(e,"d","M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"),r(t,"points","14 2 14 8 20 8")},m(l,i){k(l,e,i),k(l,t,i)},d(l){l&&(b(e),b(t))}}}function mi(n){let e,t,l,i,s;return{c(){e=R("path"),t=R("polyline"),l=R("line"),i=R("line"),s=R("polyline"),r(e,"d","M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"),r(t,"points","14 2 14 8 20 8"),r(l,"x1","16"),r(l,"y1","13"),r(l,"x2","8"),r(l,"y2","13"),r(i,"x1","16"),r(i,"y1","17"),r(i,"x2","8"),r(i,"y2","17"),r(s,"points","10 9 9 9 8 9")},m(o,a){k(o,e,a),k(o,t,a),k(o,l,a),k(o,i,a),k(o,s,a)},d(o){o&&(b(e),b(t),b(l),b(i),b(s))}}}function pi(n){let e,t;return{c(){e=R("polyline"),t=R("polyline"),r(e,"points","16 18 22 12 16 6"),r(t,"points","8 6 2 12 8 18")},m(l,i){k(l,e,i),k(l,t,i)},d(l){l&&(b(e),b(t))}}}function _i(n){let e,t,l,i;return{c(){e=R("path"),t=R("polyline"),l=R("line"),i=R("rect"),r(e,"d","M21 8v13a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8"),r(t,"points","7 3 12 8 17 3"),r(l,"x1","3"),r(l,"y1","8"),r(l,"x2","21"),r(l,"y2","8"),r(i,"x","10"),r(i,"y","12"),r(i,"width","4"),r(i,"height","4"),r(i,"rx","1")},m(s,o){k(s,e,o),k(s,t,o),k(s,l,o),k(s,i,o)},d(s){s&&(b(e),b(t),b(l),b(i))}}}function hi(n){let e,t,l,i,s;return{c(){e=R("path"),t=R("polyline"),l=R("line"),i=R("line"),s=R("line"),r(e,"d","M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"),r(t,"points","14 2 14 8 20 8"),r(l,"x1","9"),r(l,"y1","12"),r(l,"x2","15"),r(l,"y2","12"),r(i,"x1","9"),r(i,"y1","15"),r(i,"x2","13"),r(i,"y2","15"),r(s,"x1","12"),r(s,"y1","15"),r(s,"x2","12"),r(s,"y2","18")},m(o,a){k(o,e,a),k(o,t,a),k(o,l,a),k(o,i,a),k(o,s,a)},d(o){o&&(b(e),b(t),b(l),b(i),b(s))}}}function vi(n){let e,t,l,i,s,o;return{c(){e=R("path"),t=R("polyline"),l=R("line"),i=R("line"),s=R("line"),o=R("line"),r(e,"d","M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"),r(t,"points","14 2 14 8 20 8"),r(l,"x1","8"),r(l,"y1","12"),r(l,"x2","16"),r(l,"y2","12"),r(i,"x1","8"),r(i,"y1","16"),r(i,"x2","16"),r(i,"y2","16"),r(s,"x1","8"),r(s,"y1","14"),r(s,"x2","12"),r(s,"y2","14"),r(o,"x1","12"),r(o,"y1","12"),r(o,"x2","12"),r(o,"y2","18")},m(a,c){k(a,e,c),k(a,t,c),k(a,l,c),k(a,i,c),k(a,s,c),k(a,o,c)},d(a){a&&(b(e),b(t),b(l),b(i),b(s),b(o))}}}function bi(n){let e,t,l,i;return{c(){e=R("path"),t=R("polyline"),l=R("line"),i=R("line"),r(e,"d","M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"),r(t,"points","14 2 14 8 20 8"),r(l,"x1","16"),r(l,"y1","13"),r(l,"x2","8"),r(l,"y2","13"),r(i,"x1","16"),r(i,"y1","17"),r(i,"x2","8"),r(i,"y2","17")},m(s,o){k(s,e,o),k(s,t,o),k(s,l,o),k(s,i,o)},d(s){s&&(b(e),b(t),b(l),b(i))}}}function ki(n){let e,t,l,i,s;return{c(){e=R("path"),t=R("polyline"),l=R("line"),i=R("line"),s=R("line"),r(e,"d","M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"),r(t,"points","14 2 14 8 20 8"),r(l,"x1","8"),r(l,"y1","12"),r(l,"x2","16"),r(l,"y2","12"),r(i,"x1","8"),r(i,"y1","16"),r(i,"x2","16"),r(i,"y2","16"),r(s,"x1","8"),r(s,"y1","14"),r(s,"x2","12"),r(s,"y2","14")},m(o,a){k(o,e,a),k(o,t,a),k(o,l,a),k(o,i,a),k(o,s,a)},d(o){o&&(b(e),b(t),b(l),b(i),b(s))}}}function gi(n){let e,t,l;return{c(){e=R("path"),t=R("circle"),l=R("circle"),r(e,"d","M9 18V5l12-2v13"),r(t,"cx","6"),r(t,"cy","18"),r(t,"r","3"),r(l,"cx","18"),r(l,"cy","16"),r(l,"r","3")},m(i,s){k(i,e,s),k(i,t,s),k(i,l,s)},d(i){i&&(b(e),b(t),b(l))}}}function wi(n){let e,t;return{c(){e=R("rect"),t=R("polyline"),r(e,"x","2"),r(e,"y","4"),r(e,"width","20"),r(e,"height","16"),r(e,"rx","2"),r(t,"points","10 9 16 12 10 15 10 9")},m(l,i){k(l,e,i),k(l,t,i)},d(l){l&&(b(e),b(t))}}}function yi(n){let e,t,l;return{c(){e=R("rect"),t=R("circle"),l=R("polyline"),r(e,"x","3"),r(e,"y","3"),r(e,"width","18"),r(e,"height","18"),r(e,"rx","2"),r(e,"ry","2"),r(t,"cx","8.5"),r(t,"cy","8.5"),r(t,"r","1.5"),r(l,"points","21 15 16 10 5 21")},m(i,s){k(i,e,s),k(i,t,s),k(i,l,s)},d(i){i&&(b(e),b(t),b(l))}}}function qi(n){let e;return{c(){e=R("path"),r(e,"d","M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z")},m(t,l){k(t,e,l)},d(t){t&&b(e)}}}function Ci(n){let e;function t(s,o){return s[0]==="folder"?qi:s[0]==="image"?yi:s[0]==="video"?wi:s[0]==="audio"?gi:s[0]==="pdf"?ki:s[0]==="document"?bi:s[0]==="spreadsheet"?vi:s[0]==="presentation"?hi:s[0]==="archive"?_i:s[0]==="code"?pi:s[0]==="text"?mi:di}let l=t(n),i=l(n);return{c(){e=R("svg"),i.c(),r(e,"width",n[1]),r(e,"height",n[1]),r(e,"viewBox","0 0 24 24"),r(e,"fill","none"),r(e,"stroke","currentColor"),r(e,"stroke-width","1.5"),r(e,"stroke-linecap","round"),r(e,"stroke-linejoin","round"),r(e,"xmlns","http://www.w3.org/2000/svg")},m(s,o){k(s,e,o),i.m(e,null)},p(s,[o]){l!==(l=t(s))&&(i.d(1),i=l(s),i&&(i.c(),i.m(e,null))),o&2&&r(e,"width",s[1]),o&2&&r(e,"height",s[1])},i:Z,o:Z,d(s){s&&b(e),i.d()}}}function Mi(n,e,t){let{kind:l="generic"}=e,{size:i=20}=e;return n.$$set=s=>{"kind"in s&&t(0,l=s.kind),"size"in s&&t(1,i=s.size)},[l,i]}class Il extends Fe{constructor(e){super(),Ie(this,e,Mi,Ci,Ne,{kind:0,size:1})}}function We(n){if(n==null||n<0)return"—";if(n===0)return"0 B";const e=["B","KB","MB","GB"],t=Math.min(Math.floor(Math.log(n)/Math.log(1024)),e.length-1),l=n/Math.pow(1024,t);return(t===0?l.toFixed(0):l.toFixed(1))+" "+e[t]}const it={"image/jpeg":"Изображение JPEG","image/png":"Изображение PNG","image/gif":"Изображение GIF","image/webp":"Изображение WebP","image/svg+xml":"Изображение SVG","image/bmp":"Изображение BMP","image/tiff":"Изображение TIFF","image/avif":"Изображение AVIF","application/pdf":"PDF документ","application/msword":"Документ Word","application/vnd.openxmlformats-officedocument.wordprocessingml.document":"Документ Word","application/vnd.ms-excel":"Таблица Excel","application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":"Таблица Excel","application/vnd.ms-powerpoint":"Презентация PowerPoint","application/vnd.openxmlformats-officedocument.presentationml.presentation":"Презентация PowerPoint","application/zip":"ZIP архив","application/gzip":"GZIP архив","application/x-tar":"TAR архив","application/x-7z-compressed":"7z архив","application/x-rar-compressed":"RAR архив","text/plain":"Текстовый файл","text/html":"HTML файл","text/css":"CSS файл","text/javascript":"JavaScript файл","application/json":"JSON файл","application/xml":"XML файл","application/x-yaml":"YAML файл","application/octet-stream":"Бинарный файл","application/x-msdos-program":"Исполняемый файл","inode/directory":"Папка"};function jt(n){return n?it[n]||n:"Неизвестно"}function Si(n){if(n.type==="folder")return"Папка";const e=(n.mime||"").toLowerCase();if(it[e])return it[e];const l=(n.name||"").toLowerCase().split(".").pop();return l?l.toUpperCase():"Файл"}function Fl(n){if(n.type==="folder")return"folder";const e=(n.mime||"").toLowerCase();if(e.startsWith("image/"))return"image";if(e.startsWith("video/"))return"video";if(e.startsWith("audio/"))return"audio";if(e.startsWith("text/"))return"text";if(e.includes("pdf"))return"pdf";if(e.includes("word")||e.includes("document"))return"document";if(e.includes("spreadsheet")||e.includes("excel"))return"spreadsheet";if(e.includes("presentation")||e.includes("powerpoint"))return"presentation";if(e.includes("zip")||e.includes("tar")||e.includes("gzip")||e.includes("rar")||e.includes("7z")||e.includes("compress"))return"archive";if(e.includes("json")||e.includes("xml")||e.includes("yaml")||e.includes("javascript")||e.includes("css")||e.includes("html"))return"code";const l=(n.name||"").toLowerCase().split(".").pop();return["js","ts","jsx","tsx","vue","svelte","py","rs","go","c","cpp","h","hpp","java","kt","swift","rb","php","pl","sh","bash","zsh","fish","yml","yaml","json","xml","toml","ini","cfg","conf","md","markdown","css","scss","less","sass","sql","graphql","proto","gradle","cmake","makefile","dockerfile","env","gitignore"].includes(l)?"code":"generic"}const Ti=["image/jpeg","image/png","image/gif","image/webp","image/bmp","image/tiff","image/avif","image/svg+xml"],Li=["text/plain","text/html","text/css","text/javascript","application/json","application/xml","application/x-yaml","text/x-shellscript"],Di=["txt","log","conf","ini","yaml","yml","json","xml","csv","sh","py","js","ts","css","html","md","markdown","cfg"],Ei=["jpg","jpeg","png","gif","webp","bmp","tiff","tif","avif","svg"];function Hl(n){const e=(n.mime||"").toLowerCase(),l=(n.name||"").toLowerCase().split(".").pop();return Ti.includes(e)||Ei.includes(l)}function jl(n){const e=(n.mime||"").toLowerCase(),l=(n.name||"").toLowerCase().split(".").pop();return Li.includes(e)||Di.includes(l)&&l!=="md"&&l!=="markdown"}function Bl(n){return(n.mime||"").toLowerCase().includes("pdf")}function Ol(n){const e=(n.name||"").toLowerCase();return e.endsWith(".md")||e.endsWith(".markdown")}function Ni(n){return Hl(n)||Bl(n)}function Ii(n){return jl(n)||Ol(n)}function Fi(n){let e,t,l,i=We(n[0].size)+"",s;return{c(){e=d("span"),e.textContent="·",t=q(),l=d("span"),s=H(i),r(e,"class","meta-sep svelte-1u905d2")},m(o,a){k(o,e,a),k(o,t,a),k(o,l,a),f(l,s)},p(o,a){a&1&&i!==(i=We(o[0].size)+"")&&G(s,i)},d(o){o&&(b(e),b(t),b(l))}}}function Hi(n){let e,t,l;return{c(){e=d("button"),e.innerHTML=' ',r(e,"class","action-btn svelte-1u905d2"),r(e,"title","Открыть папку"),r(e,"aria-label","Открыть папку")},m(i,s){k(i,e,s),t||(l=L(e,"click",be(n[27])),t=!0)},p:Z,d(i){i&&b(e),t=!1,l()}}}function ji(n){let e,t,l,i,s;return{c(){e=d("button"),e.innerHTML=' ',t=q(),l=d("button"),l.innerHTML=' ',r(e,"class","action-btn svelte-1u905d2"),r(e,"title","Предпросмотр"),r(e,"aria-label","Предпросмотр"),r(l,"class","action-btn svelte-1u905d2"),r(l,"title","Открыть во внешней программе"),r(l,"aria-label","Открыть внешне")},m(o,a){k(o,e,a),k(o,t,a),k(o,l,a),i||(s=[L(e,"click",be(n[26])),L(l,"click",be(n[8]))],i=!0)},p:Z,d(o){o&&(b(e),b(t),b(l)),i=!1,ce(s)}}}function Bt(n){let e,t,l,i,s,o,a,c,m,_,h,p,v,w,g,y,C,T,j,F,A,O,E,I=n[5]&&Bi(n);return{c(){e=d("div"),t=q(),l=d("div"),i=d("button"),i.innerHTML=`
+ Открыть`,s=q(),o=d("button"),o.innerHTML=`
+ Открыть во внешней программе`,a=q(),I&&I.c(),c=q(),m=d("div"),_=q(),h=d("button"),h.innerHTML=`
+ Переименовать`,p=q(),v=d("button"),v.innerHTML=`
+ Дублировать`,w=q(),g=d("button"),g.innerHTML=`
+ Вырезать`,y=q(),C=d("button"),C.innerHTML=`
+ Копировать`,T=q(),j=d("div"),F=q(),A=d("button"),A.innerHTML=`
+ Удалить`,r(e,"class","menu-backdrop svelte-1u905d2"),r(e,"role","presentation"),r(i,"class","menu-item svelte-1u905d2"),r(i,"role","menuitem"),r(o,"class","menu-item svelte-1u905d2"),r(o,"role","menuitem"),r(m,"class","menu-sep svelte-1u905d2"),r(h,"class","menu-item svelte-1u905d2"),r(h,"role","menuitem"),r(v,"class","menu-item svelte-1u905d2"),r(v,"role","menuitem"),r(g,"class","menu-item svelte-1u905d2"),r(g,"role","menuitem"),r(C,"class","menu-item svelte-1u905d2"),r(C,"role","menuitem"),r(j,"class","menu-sep svelte-1u905d2"),r(A,"class","menu-item menu-item-danger svelte-1u905d2"),r(A,"role","menuitem"),r(l,"class","menu svelte-1u905d2"),r(l,"role","menu")},m(M,D){k(M,e,D),k(M,t,D),k(M,l,D),f(l,i),f(l,s),f(l,o),f(l,a),I&&I.m(l,null),f(l,c),f(l,m),f(l,_),f(l,h),f(l,p),f(l,v),f(l,w),f(l,g),f(l,y),f(l,C),f(l,T),f(l,j),f(l,F),f(l,A),O||(E=[L(e,"click",be(n[17])),L(i,"click",n[15]),L(o,"click",n[8]),L(h,"click",n[11]),L(v,"click",n[12]),L(g,"click",n[13]),L(C,"click",n[14]),L(A,"click",n[10]),L(l,"click",be(n[25]))],O=!0)},p(M,D){M[5]&&I.p(M,D)},d(M){M&&(b(e),b(t),b(l)),I&&I.d(),O=!1,ce(E)}}}function Bi(n){let e,t,l;return{c(){e=d("button"),e.innerHTML=`
+ Показать в проводнике`,r(e,"class","menu-item svelte-1u905d2"),r(e,"role","menuitem")},m(i,s){k(i,e,s),t||(l=L(e,"click",n[9]),t=!0)},p:Z,d(i){i&&b(e),t=!1,l()}}}function Oi(n){let e,t,l,i,s,o,a=n[0].name+"",c,m,_,h,p,v,w,g,y,C,T,j,F,A,O,E,I,M,D,U,J,W;l=new Il({props:{kind:n[4],size:22}});let B=!n[5]&&Fi(n);function ee(V,P){return V[5]?Hi:ji}let Q=ee(n)(n),Y=n[2]&&Bt(n);return{c(){e=d("div"),t=d("div"),Se(l.$$.fragment),i=q(),s=d("div"),o=d("div"),c=H(a),_=q(),h=d("div"),p=d("span"),p.textContent=`${n[6]}`,v=q(),B&&B.c(),w=q(),g=d("div"),Q.c(),y=q(),C=d("button"),T=R("svg"),j=R("circle"),F=R("circle"),A=R("circle"),O=q(),E=d("button"),E.innerHTML=' ',M=q(),Y&&Y.c(),D=ve(),r(t,"class","file-row-icon svelte-1u905d2"),r(o,"class","file-row-name svelte-1u905d2"),r(o,"title",m=n[0].name),r(h,"class","file-row-meta svelte-1u905d2"),r(s,"class","file-row-body svelte-1u905d2"),r(j,"cx","12"),r(j,"cy","5"),r(j,"r","2"),r(F,"cx","12"),r(F,"cy","12"),r(F,"r","2"),r(A,"cx","12"),r(A,"cy","19"),r(A,"r","2"),r(T,"width","16"),r(T,"height","16"),r(T,"viewBox","0 0 24 24"),r(T,"fill","currentColor"),r(C,"class","action-btn svelte-1u905d2"),r(C,"title","Ещё"),r(C,"aria-label","Ещё"),r(C,"aria-expanded",n[2]),r(E,"class","action-btn action-btn-danger svelte-1u905d2"),r(E,"title","Удалить"),r(E,"aria-label","Удалить"),r(g,"class","file-row-actions svelte-1u905d2"),r(e,"class","file-row svelte-1u905d2"),r(e,"role","button"),r(e,"tabindex","0"),r(e,"draggable","true"),r(e,"aria-label",I=n[5]?`Папка ${n[0].name}`:`Файл ${n[0].name}`),It(e,"file-row--selected",n[1])},m(V,P){k(V,e,P),f(e,t),ye(l,t,null),f(e,i),f(e,s),f(s,o),f(o,c),f(s,_),f(s,h),f(h,p),f(h,v),B&&B.m(h,null),f(e,w),f(e,g),Q.m(g,null),f(g,y),f(g,C),f(C,T),f(T,j),f(T,F),f(T,A),f(g,O),f(g,E),k(V,M,P),Y&&Y.m(V,P),k(V,D,P),U=!0,J||(W=[L(window,"click",n[17]),L(C,"click",be(n[16])),L(E,"click",be(n[10])),L(e,"click",n[7]),L(e,"contextmenu",n[21]),L(e,"dragstart",n[18]),L(e,"dragover",n[19]),L(e,"drop",n[20])],J=!0)},p(V,[P]){(!U||P&1)&&a!==(a=V[0].name+"")&&G(c,a),(!U||P&1&&m!==(m=V[0].name))&&r(o,"title",m),V[5]||B.p(V,P),Q.p(V,P),(!U||P&4)&&r(C,"aria-expanded",V[2]),(!U||P&1&&I!==(I=V[5]?`Папка ${V[0].name}`:`Файл ${V[0].name}`))&&r(e,"aria-label",I),(!U||P&2)&&It(e,"file-row--selected",V[1]),V[2]?Y?Y.p(V,P):(Y=Bt(V),Y.c(),Y.m(D.parentNode,D)):Y&&(Y.d(1),Y=null)},i(V){U||(le(l.$$.fragment,V),U=!0)},o(V){oe(l.$$.fragment,V),U=!1},d(V){V&&(b(e),b(M),b(D)),qe(l),B&&B.d(),Q.d(),Y&&Y.d(V),J=!1,ce(W)}}}function Pi(n,e,t){let{item:l}=e,{selected:i=!1}=e,{onDragStart:s}=e,{onDragOver:o}=e,{onDrop:a}=e;const c=Ke(),m=Fl(l),_=l.type==="folder",h=Si(l);let p=!1,v=null;function w(N){N.ctrlKey||N.metaKey?c("toggleSelect",l.id):N.shiftKey?c("rangeSelect",l.id):v?(clearTimeout(v),v=null,_?c("navigate",l.id):c("preview",l)):v=setTimeout(()=>{v=null,c("selectOne",l.id)},250)}function g(){c("openExternal",l.fileId)}function y(){c("showInFolder",l.id)}function C(){t(2,p=!1),c("delete",{id:l.id,type:l.type})}function T(){t(2,p=!1),c("rename",{id:l.id,name:l.name})}function j(){t(2,p=!1),c("duplicate",l.id)}function F(){t(2,p=!1),c("cut",l.id)}function A(){t(2,p=!1),c("copy",l.id)}function O(){t(2,p=!1),_?c("navigate",l.id):c("preview",l)}function E(){t(2,p=!p)}function I(){t(2,p=!1)}function M(N){s&&s(N,l.id)}function D(N){o&&_&&o(N,l.id)}function U(N){a&&_&&a(N,l.id)}function J(N){N.preventDefault(),t(2,p=!0)}function W(N){ni.call(this,n,N)}const B=()=>c("preview",l),ee=()=>c("navigate",l.id);return n.$$set=N=>{"item"in N&&t(0,l=N.item),"selected"in N&&t(1,i=N.selected),"onDragStart"in N&&t(22,s=N.onDragStart),"onDragOver"in N&&t(23,o=N.onDragOver),"onDrop"in N&&t(24,a=N.onDrop)},[l,i,p,c,m,_,h,w,g,y,C,T,j,F,A,O,E,I,M,D,U,J,s,o,a,W,B,ee]}class Ai extends Fe{constructor(e){super(),Ie(this,e,Pi,Oi,Ne,{item:0,selected:1,onDragStart:22,onDragOver:23,onDrop:24})}}function Ot(n,e,t){const l=n.slice();return l[4]=e[t],l[6]=t,l}function zi(n){let e;return{c(){e=d("span"),e.textContent="/",r(e,"class","sep svelte-csi2lb")},m(t,l){k(t,e,l)},d(t){t&&b(e)}}}function Vi(n){let e,t=n[4].name+"",l,i,s;function o(){return n[2](n[6])}return{c(){e=d("button"),l=H(t),r(e,"class","crumb crumb--link svelte-csi2lb")},m(a,c){k(a,e,c),f(e,l),i||(s=L(e,"click",o),i=!0)},p(a,c){n=a,c&1&&t!==(t=n[4].name+"")&&G(l,t)},d(a){a&&b(e),i=!1,s()}}}function Ri(n){let e,t=n[4].name+"",l;return{c(){e=d("span"),l=H(t),r(e,"class","crumb crumb--current svelte-csi2lb")},m(i,s){k(i,e,s),f(e,l)},p(i,s){s&1&&t!==(t=i[4].name+"")&&G(l,t)},d(i){i&&b(e)}}}function Pt(n){let e,t,l=n[6]>0&&zi();function i(a,c){return a[6]===a[0].length-1?Ri:Vi}let s=i(n),o=s(n);return{c(){l&&l.c(),e=q(),o.c(),t=ve()},m(a,c){l&&l.m(a,c),k(a,e,c),o.m(a,c),k(a,t,c)},p(a,c){s===(s=i(a))&&o?o.p(a,c):(o.d(1),o=s(a),o&&(o.c(),o.m(t.parentNode,t)))},d(a){a&&(b(e),b(t)),l&&l.d(a),o.d(a)}}}function Wi(n){let e,t=se(n[0]),l=[];for(let i=0;is(a);return n.$$set=a=>{"crumbs"in a&&t(0,l=a.crumbs)},[l,s,o]}class Pl extends Fe{constructor(e){super(),Ie(this,e,Ki,Wi,Ne,{crumbs:0})}}function Gi(n){let e,t,l,i,s,o;return{c(){e=d("div"),t=d("p"),t.textContent="Предпросмотр недоступен для этого типа файлов.",l=q(),i=d("button"),i.textContent="Открыть во внешней программе",r(i,"class","btn btn-sm svelte-1cw3u0m"),r(e,"class","preview-status svelte-1cw3u0m")},m(a,c){k(a,e,c),f(e,t),f(e,l),f(e,i),s||(o=L(i,"click",n[9]),s=!0)},p:Z,d(a){a&&b(e),s=!1,o()}}}function Ui(n){let e,t;function l(o,a){return a&2&&(e=null),e==null&&(e=!!(o[1]&&o[1].startsWith("data:"))),e?xi:Qi}let i=l(n,-1),s=i(n);return{c(){s.c(),t=ve()},m(o,a){s.m(o,a),k(o,t,a)},p(o,a){i===(i=l(o,a))&&s?s.p(o,a):(s.d(1),s=i(o),s&&(s.c(),s.m(t.parentNode,t)))},d(o){o&&b(t),s.d(o)}}}function Ji(n){let e,t,l;return{c(){e=d("pre"),t=d("code"),l=H(n[1]),r(e,"class","preview-text svelte-1cw3u0m")},m(i,s){k(i,e,s),f(e,t),f(t,l)},p(i,s){s&2&&G(l,i[1])},d(i){i&&b(e)}}}function Xi(n){let e,t,l,i;return{c(){e=d("div"),t=d("img"),Ve(t.src,l=n[1])||r(t,"src",l),r(t,"alt",i=n[0].name),r(t,"class","preview-image svelte-1cw3u0m"),r(e,"class","preview-image-container svelte-1cw3u0m")},m(s,o){k(s,e,o),f(e,t)},p(s,o){o&2&&!Ve(t.src,l=s[1])&&r(t,"src",l),o&1&&i!==(i=s[0].name)&&r(t,"alt",i)},d(s){s&&b(e)}}}function Zi(n){let e,t,l,i,s,o,a;return{c(){e=d("div"),t=d("p"),l=H(n[3]),i=q(),s=d("button"),s.textContent="Открыть во внешней программе",r(s,"class","btn btn-sm svelte-1cw3u0m"),r(e,"class","preview-status svelte-1cw3u0m")},m(c,m){k(c,e,m),f(e,t),f(t,l),f(e,i),f(e,s),o||(a=L(s,"click",n[9]),o=!0)},p(c,m){m&8&&G(l,c[3])},d(c){c&&b(e),o=!1,a()}}}function Yi(n){let e;return{c(){e=d("div"),e.innerHTML="Загрузка...
",r(e,"class","preview-status svelte-1cw3u0m")},m(t,l){k(t,e,l)},p:Z,d(t){t&&b(e)}}}function Qi(n){let e,t,l,i,s,o;return{c(){e=d("div"),t=d("p"),t.textContent="Предпросмотр PDF недоступен.",l=q(),i=d("button"),i.textContent="Открыть во внешней программе",r(i,"class","btn btn-sm svelte-1cw3u0m"),r(e,"class","preview-status svelte-1cw3u0m")},m(a,c){k(a,e,c),f(e,t),f(e,l),f(e,i),s||(o=L(i,"click",n[9]),s=!0)},p:Z,d(a){a&&b(e),s=!1,o()}}}function xi(n){let e,t,l;return{c(){e=d("div"),t=d("embed"),Ve(t.src,l=n[1])||r(t,"src",l),r(t,"type","application/pdf"),r(t,"class","preview-pdf svelte-1cw3u0m"),r(e,"class","preview-pdf-container svelte-1cw3u0m")},m(i,s){k(i,e,s),f(e,t)},p(i,s){s&2&&!Ve(t.src,l=i[1])&&r(t,"src",l)},d(i){i&&b(e)}}}function $i(n){let e,t,l,i,s,o,a,c=n[0].name+"",m,_,h,p,v=We(n[0].size)+"",w,g,y=jt(n[0].mime)+"",C,T,j,F,A,O,E,I,M,D,U,J;s=new Il({props:{kind:n[8],size:18}});function W(N,Q){return N[2]?Yi:N[3]?Zi:N[6]&&N[1]?Xi:N[5]&&N[1]?Ji:N[4]?Ui:Gi}let B=W(n),ee=B(n);return{c(){e=d("div"),t=d("div"),l=d("header"),i=d("div"),Se(s.$$.fragment),o=q(),a=d("span"),m=H(c),h=q(),p=d("div"),w=H(v),g=H(" · "),C=H(y),T=q(),j=d("div"),F=d("button"),F.innerHTML=' ',A=q(),O=d("button"),O.innerHTML=' ',E=q(),I=d("div"),ee.c(),r(a,"class","preview-name svelte-1cw3u0m"),r(a,"title",_=n[0].name),r(i,"class","preview-title svelte-1cw3u0m"),r(p,"class","preview-meta svelte-1cw3u0m"),r(F,"class","action-btn svelte-1cw3u0m"),r(F,"title","Открыть во внешней программе"),r(F,"aria-label","Открыть внешне"),r(O,"class","action-btn action-btn-close svelte-1cw3u0m"),r(O,"title","Close"),r(O,"aria-label","Close preview"),r(j,"class","preview-actions svelte-1cw3u0m"),r(l,"class","preview-header svelte-1cw3u0m"),r(I,"class","preview-body svelte-1cw3u0m"),r(t,"class","modal svelte-1cw3u0m"),r(e,"class","overlay svelte-1cw3u0m"),r(e,"role","dialog"),r(e,"aria-modal","true"),r(e,"aria-label",M=`Preview: ${n[0].name}`)},m(N,Q){k(N,e,Q),f(e,t),f(t,l),f(l,i),ye(s,i,null),f(i,o),f(i,a),f(a,m),f(l,h),f(l,p),f(p,w),f(p,g),f(p,C),f(l,T),f(l,j),f(j,F),f(j,A),f(j,O),f(t,E),f(t,I),ee.m(I,null),D=!0,U||(J=[L(F,"click",n[9]),L(O,"click",n[10]),L(e,"click",Oe(n[11]))],U=!0)},p(N,[Q]){(!D||Q&1)&&c!==(c=N[0].name+"")&&G(m,c),(!D||Q&1&&_!==(_=N[0].name))&&r(a,"title",_),(!D||Q&1)&&v!==(v=We(N[0].size)+"")&&G(w,v),(!D||Q&1)&&y!==(y=jt(N[0].mime)+"")&&G(C,y),B===(B=W(N))&&ee?ee.p(N,Q):(ee.d(1),ee=B(N),ee&&(ee.c(),ee.m(I,null))),(!D||Q&1&&M!==(M=`Preview: ${N[0].name}`))&&r(e,"aria-label",M)},i(N){D||(le(s.$$.fragment,N),D=!0)},o(N){oe(s.$$.fragment,N),D=!1},d(N){N&&b(e),qe(s),ee.d(),U=!1,ce(J)}}}function es(n,e,t){let l,i,s,{item:o}=e,{content:a=""}=e,{loading:c=!1}=e,{error:m=""}=e;const _=Ke(),h=Fl(o);function p(y){y.key==="Escape"&&_("close")}function v(){_("openExternal",o.fileId)}Dl(()=>{window.addEventListener("keydown",p)}),El(()=>{window.removeEventListener("keydown",p)});const w=()=>_("close"),g=()=>_("close");return n.$$set=y=>{"item"in y&&t(0,o=y.item),"content"in y&&t(1,a=y.content),"loading"in y&&t(2,c=y.loading),"error"in y&&t(3,m=y.error)},n.$$.update=()=>{n.$$.dirty&3&&t(6,l=Hl(o)&&a&&a.startsWith("data:")),n.$$.dirty&1&&t(5,i=jl(o)||Ol(o)),n.$$.dirty&1&&t(4,s=Bl(o))},[o,a,c,m,s,i,l,_,h,v,w,g]}class ts extends Fe{constructor(e){super(),Ie(this,e,es,$i,Ne,{item:0,content:1,loading:2,error:3})}}function ls(n){let e,t,l,i,s,o,a,c,m,_,h,p,v,w,g,y,C;return{c(){e=d("div"),t=d("div"),l=d("h3"),i=H(n[0]),s=q(),o=d("p"),a=H(n[1]),c=q(),m=d("div"),_=d("button"),h=H(n[2]),v=q(),w=d("button"),g=H(n[3]),r(l,"class","svelte-1fv6yyk"),r(o,"class","message svelte-1fv6yyk"),r(_,"class",p="btn "+(n[4]?"btn-danger":"btn-primary")+" svelte-1fv6yyk"),r(w,"class","btn svelte-1fv6yyk"),r(m,"class","actions svelte-1fv6yyk"),r(t,"class","modal svelte-1fv6yyk"),r(e,"class","overlay svelte-1fv6yyk"),r(e,"role","dialog"),r(e,"aria-modal","true"),r(e,"aria-label",n[0])},m(T,j){k(T,e,j),f(e,t),f(t,l),f(l,i),f(t,s),f(t,o),f(o,a),f(t,c),f(t,m),f(m,_),f(_,h),f(m,v),f(m,w),f(w,g),y||(C=[L(_,"click",n[6]),L(w,"click",n[7]),L(e,"click",Oe(n[8]))],y=!0)},p(T,[j]){j&1&&G(i,T[0]),j&2&&G(a,T[1]),j&4&&G(h,T[2]),j&16&&p!==(p="btn "+(T[4]?"btn-danger":"btn-primary")+" svelte-1fv6yyk")&&r(_,"class",p),j&8&&G(g,T[3]),j&1&&r(e,"aria-label",T[0])},i:Z,o:Z,d(T){T&&b(e),y=!1,ce(C)}}}function ns(n,e,t){let{title:l="Подтверждение"}=e,{message:i=""}=e,{confirmText:s="Удалить"}=e,{cancelText:o="Отмена"}=e,{danger:a=!1}=e;const c=Ke(),m=()=>c("confirm"),_=()=>c("cancel"),h=()=>c("cancel");return n.$$set=p=>{"title"in p&&t(0,l=p.title),"message"in p&&t(1,i=p.message),"confirmText"in p&&t(2,s=p.confirmText),"cancelText"in p&&t(3,o=p.cancelText),"danger"in p&&t(4,a=p.danger)},[l,i,s,o,a,c,m,_,h]}class is extends Fe{constructor(e){super(),Ie(this,e,ns,ls,Ne,{title:0,message:1,confirmText:2,cancelText:3,danger:4})}}function At(n,e,t){const l=n.slice();return l[148]=e[t],l}function zt(n,e,t){const l=n.slice();return l[151]=e[t],l}function Vt(n,e,t){const l=n.slice();return l[162]=e[t],l}function Rt(n,e,t){const l=n.slice();return l[159]=e[t],l}function Wt(n,e,t){const l=n.slice();return l[154]=e[t],l}function Kt(n,e,t){const l=n.slice();return l[151]=e[t],l}function Gt(n,e,t){const l=n.slice();return l[154]=e[t],l}function Ut(n,e,t){const l=n.slice();return l[167]=e[t],l}function Jt(n,e,t){const l=n.slice();return l[148]=e[t],l}function Xt(n,e,t){const l=n.slice();return l[172]=e[t],l}function Zt(n,e,t){const l=n.slice();return l[175]=e[t],l}function Yt(n){let e,t=n[175].label+"",l,i,s,o,a;function c(){return n[83](n[175])}return{c(){e=d("button"),l=H(t),i=q(),r(e,"class",s="nav-item "+(n[4]===n[175].id?"selected":"")+" svelte-i359q7")},m(m,_){k(m,e,_),f(e,l),f(e,i),o||(a=L(e,"click",c),o=!0)},p(m,_){n=m,_[0]&1&&t!==(t=n[175].label+"")&&G(l,t),_[0]&17&&s!==(s="nav-item "+(n[4]===n[175].id?"selected":"")+" svelte-i359q7")&&r(e,"class",s)},d(m){m&&b(e),o=!1,a()}}}function Qt(n){let e,t,l,i,s,o=n[1].length>0&&xt(n),a=se(n[1]),c=[];for(let _=0;_0?o?o.p(_,h):(o=xt(_),o.c(),o.m(t,null)):o&&(o.d(1),o=null),h[0]&34|h[1]&2048){a=se(_[1]);let p;for(p=0;p ',r(i,"class","dismiss-btn svelte-i359q7"),r(i,"aria-label","Dismiss"),r(e,"class","error-banner svelte-i359q7")},m(a,c){k(a,e,c),f(e,t),f(e,l),f(e,i),s||(o=[L(i,"click",be(n[85])),L(e,"click",n[86])],s=!0)},p(a,c){c[0]&8&&G(t,a[3])},d(a){a&&b(e),s=!1,ce(o)}}}function cs(n){let e,t,l;function i(a,c){if(a[18])return ms;if(a[0].length>0)return ds;if(a[3])return us}let s=i(n),o=s&&s(n);return{c(){e=d("div"),t=d("h2"),t.textContent="Верстак",l=q(),o&&o.c(),r(t,"class","svelte-i359q7"),r(e,"class","welcome svelte-i359q7")},m(a,c){k(a,e,c),f(e,t),f(e,l),o&&o.m(e,null)},p(a,c){s===(s=i(a))&&o?o.p(a,c):(o&&o.d(1),o=s&&s(a),o&&(o.c(),o.m(e,null)))},i:Z,o:Z,d(a){a&&b(e),o&&o.d()}}}function as(n){let e,t,l,i,s,o,a=se(n[40]),c=[];for(let p=0;p{_[w]=null}),ge()),~i?(s=_[i],s?s.p(p,v):(s=_[i]=m[i](p),s.c()),le(s,1),s.m(l,null)):s=null)},i(p){o||(le(s),o=!0)},o(p){oe(s),o=!1},d(p){p&&(b(e),b(t),b(l)),_e(c,p),~i&&_[i].d()}}}function fs(n){let e,t,l,i=n[8].title+"",s,o,a,c,m,_,h,p,v,w,g,y=n[8].dirty&&hl();return{c(){e=d("div"),t=d("div"),l=d("span"),s=H(i),o=q(),y&&y.c(),a=q(),c=d("div"),m=d("button"),m.textContent="Сохранить",_=q(),h=d("button"),h.textContent="Закрыть",p=q(),v=d("textarea"),r(l,"class","note-title svelte-i359q7"),r(m,"class","btn btn-primary svelte-i359q7"),r(h,"class","btn svelte-i359q7"),r(c,"class","note-editor-actions svelte-i359q7"),r(t,"class","note-editor-header svelte-i359q7"),r(v,"class","note-textarea svelte-i359q7"),r(v,"placeholder","Начните писать..."),r(e,"class","note-editor svelte-i359q7")},m(C,T){k(C,e,T),f(e,t),f(t,l),f(l,s),f(t,o),y&&y.m(t,null),f(t,a),f(t,c),f(c,m),f(c,_),f(c,h),f(e,p),f(e,v),me(v,n[8].content),w||(g=[L(m,"click",n[75]),L(h,"click",n[73]),L(v,"input",n[87]),L(v,"input",n[74])],w=!0)},p(C,T){T[0]&256&&i!==(i=C[8].title+"")&&G(s,i),C[8].dirty?y||(y=hl(),y.c(),y.m(t,a)):y&&(y.d(1),y=null),T[0]&256&&me(v,C[8].content)},i:Z,o:Z,d(C){C&&b(e),y&&y.d(),w=!1,ce(g)}}}function us(n){let e,t,l;return{c(){e=d("p"),t=H("Ошибка: "),l=H(n[3]),r(e,"class","error-text svelte-i359q7")},m(i,s){k(i,e,s),f(e,t),f(e,l)},p(i,s){s[0]&8&&G(l,i[3])},d(i){i&&b(e)}}}function ds(n){let e,t,l;return{c(){e=d("p"),e.textContent="Выберите раздел в боковой панели.",t=q(),l=d("p"),l.textContent="Или создайте новое дело кнопкой «+».",r(e,"class","svelte-i359q7"),r(l,"class","hint svelte-i359q7")},m(i,s){k(i,e,s),k(i,t,s),k(i,l,s)},p:Z,d(i){i&&(b(e),b(t),b(l))}}}function ms(n){let e;return{c(){e=d("p"),e.textContent="Загрузка...",r(e,"class","svelte-i359q7")},m(t,l){k(t,e,l)},p:Z,d(t){t&&b(e)}}}function nl(n){let e,t=n[167].label+"",l,i,s;function o(){return n[88](n[167])}return{c(){e=d("button"),l=H(t),r(e,"class",$n(n[82](n[167].id))+" svelte-i359q7")},m(a,c){k(a,e,c),f(e,l),i||(s=L(e,"click",o),i=!0)},p(a,c){n=a},d(a){a&&b(e),i=!1,s()}}}function ps(n){let e;return{c(){e=d("div"),e.innerHTML='Активность появится позже
',r(e,"class","empty-state svelte-i359q7")},m(t,l){k(t,e,l)},p:Z,i:Z,o:Z,d(t){t&&b(e)}}}function _s(n){let e,t,l,i,s,o,a,c,m,_,h,p;function v(y,C){return y[10].length===0?ws:gs}let w=v(n),g=w(n);return{c(){e=d("div"),t=d("div"),l=d("input"),i=q(),s=d("input"),o=q(),a=d("button"),c=H("Записать"),_=q(),g.c(),r(l,"type","text"),r(l,"placeholder","Что сделано"),r(l,"class","svelte-i359q7"),r(s,"type","number"),r(s,"placeholder","Мин"),r(s,"min","1"),r(s,"class","svelte-i359q7"),r(a,"class","btn btn-primary svelte-i359q7"),a.disabled=m=!n[12].trim()||!n[11],r(t,"class","worklog-form svelte-i359q7"),r(e,"class","worklog-tab svelte-i359q7")},m(y,C){k(y,e,C),f(e,t),f(t,l),me(l,n[12]),f(t,i),f(t,s),me(s,n[11]),f(t,o),f(t,a),f(a,c),f(e,_),g.m(e,null),h||(p=[L(l,"input",n[111]),L(s,"input",n[112]),L(a,"click",n[76])],h=!0)},p(y,C){C[0]&4096&&l.value!==y[12]&&me(l,y[12]),C[0]&2048&&Ll(s.value)!==y[11]&&me(s,y[11]),C[0]&6144&&m!==(m=!y[12].trim()||!y[11])&&(a.disabled=m),w===(w=v(y))&&g?g.p(y,C):(g.d(1),g=w(y),g&&(g.c(),g.m(e,null)))},i:Z,o:Z,d(y){y&&b(e),g.d(),h=!1,ce(p)}}}function hs(n){let e;function t(s,o){return s[9].length===0?qs:ys}let l=t(n),i=l(n);return{c(){i.c(),e=ve()},m(s,o){i.m(s,o),k(s,e,o)},p(s,o){l===(l=t(s))&&i?i.p(s,o):(i.d(1),i=l(s),i&&(i.c(),i.m(e.parentNode,e)))},i:Z,o:Z,d(s){s&&b(e),i.d(s)}}}function vs(n){let e,t,l,i,s,o,a,c,m,_,h,p,v,w,g,y,C,T,j,F=n[30].items.length>0&&ol(n);const A=[Ms,Cs],O=[];function E(D,U){return D[22]?0:1}p=E(n),v=O[p]=A[p](n);let I=n[19]&&!n[21]&&cl(),M=n[26]&&al(n);return{c(){e=d("div"),t=d("div"),l=d("button"),i=H("+ Добавить файл"),s=q(),o=d("button"),a=H("+ Добавить папку"),c=q(),m=d("button"),m.textContent="+ Новый файл",_=q(),F&&F.c(),h=q(),v.c(),w=q(),I&&I.c(),g=q(),M&&M.c(),y=ve(),r(l,"class","btn btn-primary svelte-i359q7"),l.disabled=n[19],r(o,"class","btn svelte-i359q7"),o.disabled=n[19],r(m,"class","btn svelte-i359q7"),r(t,"class","tab-toolbar svelte-i359q7"),r(e,"class","files-tab svelte-i359q7")},m(D,U){k(D,e,U),f(e,t),f(t,l),f(l,i),f(t,s),f(t,o),f(o,a),f(t,c),f(t,m),f(t,_),F&&F.m(t,null),f(e,h),O[p].m(e,null),f(e,w),I&&I.m(e,null),k(D,g,U),M&&M.m(D,U),k(D,y,U),C=!0,T||(j=[L(l,"click",n[77]),L(o,"click",n[78]),L(m,"click",n[49])],T=!0)},p(D,U){(!C||U[0]&524288)&&(l.disabled=D[19]),(!C||U[0]&524288)&&(o.disabled=D[19]),D[30].items.length>0?F?F.p(D,U):(F=ol(D),F.c(),F.m(t,null)):F&&(F.d(1),F=null);let J=p;p=E(D),p===J?O[p].p(D,U):(ke(),oe(O[J],1,1,()=>{O[J]=null}),ge(),v=O[p],v?v.p(D,U):(v=O[p]=A[p](D),v.c()),le(v,1),v.m(e,w)),D[19]&&!D[21]?I||(I=cl(),I.c(),I.m(e,null)):I&&(I.d(1),I=null),D[26]?M?(M.p(D,U),U[0]&67108864&&le(M,1)):(M=al(D),M.c(),le(M,1),M.m(y.parentNode,y)):M&&(ke(),oe(M,1,1,()=>{M=null}),ge())},i(D){C||(le(v),le(M),C=!0)},o(D){oe(v),oe(M),C=!1},d(D){D&&(b(e),b(g),b(y)),F&&F.d(),O[p].d(),I&&I.d(),M&&M.d(D),T=!1,ce(j)}}}function bs(n){let e,t,l,i,s,o,a,c=n[16]&&fl(n);function m(p,v){return p[7].length===0&&!p[16]?Ns:Es}let _=m(n),h=_(n);return{c(){e=d("div"),t=d("div"),l=d("button"),l.textContent="+ Добавить заметку",i=q(),c&&c.c(),s=q(),h.c(),r(l,"class","btn btn-primary svelte-i359q7"),r(t,"class","tab-toolbar svelte-i359q7"),r(e,"class","notes-tab svelte-i359q7")},m(p,v){k(p,e,v),f(e,t),f(t,l),f(e,i),c&&c.m(e,null),f(e,s),h.m(e,null),o||(a=L(l,"click",n[69]),o=!0)},p(p,v){p[16]?c?c.p(p,v):(c=fl(p),c.c(),c.m(e,s)):c&&(c.d(1),c=null),_===(_=m(p))&&h?h.p(p,v):(h.d(1),h=_(p),h&&(h.c(),h.m(e,null)))},i:Z,o:Z,d(p){p&&b(e),c&&c.d(),h.d(),o=!1,a()}}}function ks(n){let e,t,l=n[5].title+"",i,s,o,a,c,m,_=n[5].type+"",h,p,v,w,g,y=(n[5].section||"—")+"",C,T,j,F,A,O=we(n[5].createdAt)+"",E,I,M,D,U,J,W,B,ee,N,Q,Y,V,P,te=n[7].length>0&&dl(n),K=n[10].length>0&&pl(n);return{c(){e=d("div"),t=d("h2"),i=H(l),s=q(),o=d("div"),a=d("div"),c=d("span"),c.textContent="Тип",m=d("span"),h=H(_),p=q(),v=d("div"),w=d("span"),w.textContent="Раздел",g=d("span"),C=H(y),T=q(),j=d("div"),F=d("span"),F.textContent="Создано",A=d("span"),E=H(O),I=q(),M=d("div"),D=d("button"),D.innerHTML=`
+ Новая заметка`,U=q(),J=d("button"),J.innerHTML=`
+ Добавить файл`,W=q(),B=d("button"),B.innerHTML=`
+ Добавить действие`,ee=q(),N=d("button"),N.innerHTML=`
+ Записать время`,Q=q(),te&&te.c(),Y=q(),K&&K.c(),r(t,"class","svelte-i359q7"),r(c,"class","meta-label svelte-i359q7"),r(m,"class","svelte-i359q7"),r(a,"class","meta-item svelte-i359q7"),r(w,"class","meta-label svelte-i359q7"),r(g,"class","svelte-i359q7"),r(v,"class","meta-item svelte-i359q7"),r(F,"class","meta-label svelte-i359q7"),r(A,"class","svelte-i359q7"),r(j,"class","meta-item svelte-i359q7"),r(o,"class","meta-grid svelte-i359q7"),r(D,"class","qa-btn svelte-i359q7"),r(J,"class","qa-btn svelte-i359q7"),r(B,"class","qa-btn svelte-i359q7"),B.disabled=!0,r(B,"title","Следующий этап"),r(N,"class","qa-btn svelte-i359q7"),r(M,"class","quick-actions svelte-i359q7"),r(e,"class","overview svelte-i359q7")},m(X,$){k(X,e,$),f(e,t),f(t,i),f(e,s),f(e,o),f(o,a),f(a,c),f(a,m),f(m,h),f(o,p),f(o,v),f(v,w),f(v,g),f(g,C),f(o,T),f(o,j),f(j,F),f(j,A),f(A,E),f(e,I),f(e,M),f(M,D),f(M,U),f(M,J),f(M,W),f(M,B),f(M,ee),f(M,N),f(e,Q),te&&te.m(e,null),f(e,Y),K&&K.m(e,null),V||(P=[L(D,"click",n[89]),L(J,"click",n[90]),L(N,"click",n[91])],V=!0)},p(X,$){$[0]&32&&l!==(l=X[5].title+"")&&G(i,l),$[0]&32&&_!==(_=X[5].type+"")&&G(h,_),$[0]&32&&y!==(y=(X[5].section||"—")+"")&&G(C,y),$[0]&32&&O!==(O=we(X[5].createdAt)+"")&&G(E,O),X[7].length>0?te?te.p(X,$):(te=dl(X),te.c(),te.m(e,Y)):te&&(te.d(1),te=null),X[10].length>0?K?K.p(X,$):(K=pl(X),K.c(),K.m(e,null)):K&&(K.d(1),K=null)},i:Z,o:Z,d(X){X&&b(e),te&&te.d(),K&&K.d(),V=!1,ce(P)}}}function gs(n){let e,t=se(n[10]),l=[];for(let i=0;iЗаписей работы пока нет
',r(e,"class","empty-state svelte-i359q7")},m(t,l){k(t,e,l)},p:Z,d(t){t&&b(e)}}}function il(n){let e,t,l=n[151].summary+"",i,s,o,a=n[151].minutes+"",c,m,_=we(n[151].createdAt)+"",h,p;return{c(){e=d("div"),t=d("div"),i=H(l),s=q(),o=d("div"),c=H(a),m=H(" мин · "),h=H(_),p=q(),r(t,"class","svelte-i359q7"),r(o,"class","wl-meta svelte-i359q7"),r(e,"class","worklog-entry svelte-i359q7")},m(v,w){k(v,e,w),f(e,t),f(t,i),f(e,s),f(e,o),f(o,c),f(o,m),f(o,h),f(e,p)},p(v,w){w[0]&1024&&l!==(l=v[151].summary+"")&&G(i,l),w[0]&1024&&a!==(a=v[151].minutes+"")&&G(c,a),w[0]&1024&&_!==(_=we(v[151].createdAt)+"")&&G(h,_)},d(v){v&&b(e)}}}function ys(n){let e,t=se(n[9]),l=[];for(let i=0;iДействий пока нет',r(e,"class","empty-state svelte-i359q7")},m(t,l){k(t,e,l)},p:Z,d(t){t&&b(e)}}}function sl(n){let e,t,l=n[162].title+"",i,s,o=n[162].type+"",a,c,m,_,h,p;function v(){return n[110](n[162])}return{c(){e=d("div"),t=d("span"),i=H(l),s=d("span"),a=H(o),c=q(),m=d("button"),m.textContent="Запустить",_=q(),r(t,"class","svelte-i359q7"),r(s,"class","action-type svelte-i359q7"),r(m,"class","btn btn-sm svelte-i359q7"),r(e,"class","action-card svelte-i359q7")},m(w,g){k(w,e,g),f(e,t),f(t,i),f(e,s),f(s,a),f(e,c),f(e,m),f(e,_),h||(p=L(m,"click",v),h=!0)},p(w,g){n=w,g[0]&512&&l!==(l=n[162].title+"")&&G(i,l),g[0]&512&&o!==(o=n[162].type+"")&&G(a,o)},d(w){w&&b(e),h=!1,p()}}}function ol(n){let e,t,l=n[30].items.length+"",i,s,o;return{c(){e=d("button"),t=H("Вставить "),i=H(l),r(e,"class","btn svelte-i359q7")},m(a,c){k(a,e,c),f(e,t),f(e,i),s||(o=L(e,"click",n[54]),s=!0)},p(a,c){c[0]&1073741824&&l!==(l=a[30].items.length+"")&&G(i,l)},d(a){a&&b(e),s=!1,o()}}}function Cs(n){let e,t,l,i,s,o,a;const c=[Ts,Ss],m=[];function _(w,g){return w[24].length>0?0:1}e=_(n),t=m[e]=c[e](n);const h=[Ds,Ls],p=[];function v(w,g){return w[25].length===0?0:1}return i=v(n),s=p[i]=h[i](n),{c(){t.c(),l=q(),s.c(),o=ve()},m(w,g){m[e].m(w,g),k(w,l,g),p[i].m(w,g),k(w,o,g),a=!0},p(w,g){let y=e;e=_(w),e===y?m[e].p(w,g):(ke(),oe(m[y],1,1,()=>{m[y]=null}),ge(),t=m[e],t?t.p(w,g):(t=m[e]=c[e](w),t.c()),le(t,1),t.m(l.parentNode,l));let C=i;i=v(w),i===C?p[i].p(w,g):(ke(),oe(p[C],1,1,()=>{p[C]=null}),ge(),s=p[i],s?s.p(w,g):(s=p[i]=h[i](w),s.c()),le(s,1),s.m(o.parentNode,o))},i(w){a||(le(t),le(s),a=!0)},o(w){oe(t),oe(s),a=!1},d(w){w&&(b(l),b(o)),m[e].d(w),p[i].d(w)}}}function Ms(n){let e;return{c(){e=d("div"),e.innerHTML='Загрузка...
',r(e,"class","empty-state svelte-i359q7")},m(t,l){k(t,e,l)},p:Z,i:Z,o:Z,d(t){t&&b(e)}}}function Ss(n){let e,t;return e=new Pl({props:{crumbs:[{name:"Файлы"}]}}),{c(){Se(e.$$.fragment)},m(l,i){ye(e,l,i),t=!0},p:Z,i(l){t||(le(e.$$.fragment,l),t=!0)},o(l){oe(e.$$.fragment,l),t=!1},d(l){qe(e,l)}}}function Ts(n){let e,t,l,i,s,o;return e=new Pl({props:{crumbs:[{name:"Файлы"},...n[24]]}}),e.$on("navigate",n[96]),{c(){Se(e.$$.fragment),t=q(),l=d("button"),l.innerHTML=`
+ Back`,r(l,"class","btn btn-sm back-btn svelte-i359q7")},m(a,c){ye(e,a,c),k(a,t,c),k(a,l,c),i=!0,s||(o=L(l,"click",n[45]),s=!0)},p(a,c){const m={};c[0]&16777216&&(m.crumbs=[{name:"Файлы"},...a[24]]),e.$set(m)},i(a){i||(le(e.$$.fragment,a),i=!0)},o(a){oe(e.$$.fragment,a),i=!1},d(a){a&&(b(t),b(l)),qe(e,a),s=!1,o()}}}function Ls(n){let e,t=[],l=new Map,i,s=se(n[25]);const o=a=>a[159].id;for(let a=0;a0?"В этой папке пока нет файлов":"В этом проекте пока нет файлов",o,a,c,m,_,h,p,v,w,g;return{c(){e=d("div"),t=d("div"),t.innerHTML=' ',l=q(),i=d("p"),o=H(s),a=q(),c=d("p"),c.textContent="Добавьте файл или папку, чтобы сохранить материалы проекта.",m=q(),_=d("div"),h=d("button"),h.textContent="Добавить файл",p=q(),v=d("button"),v.textContent="Добавить папку",r(t,"class","empty-icon svelte-i359q7"),r(i,"class","svelte-i359q7"),r(c,"class","hint svelte-i359q7"),r(h,"class","btn btn-primary svelte-i359q7"),r(v,"class","btn svelte-i359q7"),r(_,"class","empty-actions svelte-i359q7"),r(e,"class","empty-state svelte-i359q7")},m(y,C){k(y,e,C),f(e,t),f(e,l),f(e,i),f(i,o),f(e,a),f(e,c),f(e,m),f(e,_),f(_,h),f(_,p),f(_,v),w||(g=[L(h,"click",n[77]),L(v,"click",n[78])],w=!0)},p(y,C){C[0]&16777216&&s!==(s=y[24].length>0?"В этой папке пока нет файлов":"В этом проекте пока нет файлов")&&G(o,s)},i:Z,o:Z,d(y){y&&b(e),w=!1,ce(g)}}}function rl(n,e){let t,l,i;return l=new Ai({props:{item:e[159],selected:e[31].includes(e[159].id),onDragStart:e[58],onDragOver:e[59],onDrop:e[60]}}),l.$on("navigate",e[97]),l.$on("preview",e[98]),l.$on("openExternal",e[99]),l.$on("showInFolder",e[100]),l.$on("delete",e[101]),l.$on("rename",e[102]),l.$on("duplicate",e[103]),l.$on("cut",e[104]),l.$on("copy",e[105]),l.$on("selectOne",e[106]),l.$on("toggleSelect",e[107]),l.$on("rangeSelect",e[108]),{key:n,first:null,c(){t=ve(),Se(l.$$.fragment),this.first=t},m(s,o){k(s,t,o),ye(l,s,o),i=!0},p(s,o){e=s;const a={};o[0]&33554432&&(a.item=e[159]),o[0]&33554432|o[1]&1&&(a.selected=e[31].includes(e[159].id)),l.$set(a)},i(s){i||(le(l.$$.fragment,s),i=!0)},o(s){oe(l.$$.fragment,s),i=!1},d(s){s&&b(t),qe(l,s)}}}function cl(n){let e;return{c(){e=d("div"),e.innerHTML='Сканирование...
',r(e,"class","empty-state svelte-i359q7")},m(t,l){k(t,e,l)},d(t){t&&b(e)}}}function al(n){let e,t;return e=new ts({props:{item:n[26],content:n[27],loading:n[28],error:n[29]}}),e.$on("close",n[48]),e.$on("openExternal",n[109]),{c(){Se(e.$$.fragment)},m(l,i){ye(e,l,i),t=!0},p(l,i){const s={};i[0]&67108864&&(s.item=l[26]),i[0]&134217728&&(s.content=l[27]),i[0]&268435456&&(s.loading=l[28]),i[0]&536870912&&(s.error=l[29]),e.$set(s)},i(l){t||(le(e.$$.fragment,l),t=!0)},o(l){oe(e.$$.fragment,l),t=!1},d(l){qe(e,l)}}}function fl(n){let e,t,l,i,s,o,a,c,m;return{c(){e=d("div"),t=d("input"),l=q(),i=d("div"),s=d("button"),s.textContent="Создать",o=q(),a=d("button"),a.textContent="Отмена",r(t,"type","text"),r(t,"placeholder","Название заметки"),r(t,"class","svelte-i359q7"),r(s,"class","btn btn-primary svelte-i359q7"),r(a,"class","btn svelte-i359q7"),r(i,"class","form-actions svelte-i359q7"),r(e,"class","create-form svelte-i359q7")},m(_,h){k(_,e,h),f(e,t),me(t,n[17]),f(e,l),f(e,i),f(i,s),f(i,o),f(i,a),c||(m=[L(t,"input",n[93]),L(t,"keydown",n[94]),L(s,"click",n[71]),L(a,"click",n[70])],c=!0)},p(_,h){h[0]&131072&&t.value!==_[17]&&me(t,_[17])},d(_){_&&b(e),c=!1,ce(m)}}}function Es(n){let e,t=se(n[7]),l=[];for(let i=0;iНет заметокСоздайте первую заметку для этого дела.
',r(e,"class","empty-state svelte-i359q7")},m(t,l){k(t,e,l)},p:Z,d(t){t&&b(e)}}}function ul(n){let e,t,l=n[154].title+"",i,s,o,a=we(n[154].createdAt)+"",c,m,_,h;function p(){return n[95](n[154])}return{c(){e=d("div"),t=d("div"),i=H(l),s=q(),o=d("div"),c=H(a),m=q(),r(t,"class","note-card-title svelte-i359q7"),r(o,"class","note-card-date svelte-i359q7"),r(e,"class","note-card svelte-i359q7")},m(v,w){k(v,e,w),f(e,t),f(t,i),f(e,s),f(e,o),f(o,c),f(e,m),_||(h=L(e,"click",p),_=!0)},p(v,w){n=v,w[0]&128&&l!==(l=n[154].title+"")&&G(i,l),w[0]&128&&a!==(a=we(n[154].createdAt)+"")&&G(c,a)},d(v){v&&b(e),_=!1,h()}}}function dl(n){let e,t,l,i=se(n[7].slice(0,5)),s=[];for(let o=0;on[115].call(v)),r(_,"class","form-group svelte-i359q7"),r(y,"class","btn btn-primary svelte-i359q7"),r(T,"class","btn svelte-i359q7"),r(g,"class","modal-actions svelte-i359q7"),r(t,"class","modal svelte-i359q7"),r(e,"class","modal-overlay svelte-i359q7")},m(E,I){k(E,e,I),f(e,t),f(t,l),f(t,i),f(t,s),f(s,o),f(s,a),f(s,c),me(c,n[14]),f(t,m),f(t,_),f(_,h),f(_,p),f(_,v);for(let M=0;M⚒ Верстак ',i=q(),s=d("nav"),o=d("div"),a=d("div"),a.textContent="Разделы",c=q();for(let z=0;z{P[pe]=null}),ge(),A=P[F],A?A.p(z,ie):(A=P[F]=V[F](z),A.c()),le(A,1),A.m(g,O)),!z[8]&&!z[5]?K?K.p(z,ie):(K=vl(z),K.c(),K.m(g,E)):K&&(K.d(1),K=null),z[13]?X?X.p(z,ie):(X=bl(z),X.c(),X.m(g,I)):X&&(X.d(1),X=null),z[21]&&z[20]?$?$.p(z,ie):($=gl(z),$.c(),$.m(g,M)):$&&($.d(1),$=null),z[37]?fe?fe.p(z,ie):(fe=yl(z),fe.c(),fe.m(g,D)):fe&&(fe.d(1),fe=null),z[32]?re?(re.p(z,ie),ie[1]&2&&le(re,1)):(re=Cl(z),re.c(),le(re,1),re.m(g,null)):re&&(ke(),oe(re,1,1,()=>{re=null}),ge())},i(z){U||(le(A),le(re),U=!0)},o(z){oe(A),oe(re),U=!1},d(z){z&&b(e),_e(W,z),B&&B.d(),Q.d(),Y&&Y.d(),P[F].d(),K&&K.d(),X&&X.d(),$&&$.d(),fe&&fe.d(),re&&re.d()}}}function x(n,...e){try{if(window.go&&window.go.main&&window.go.main.App){const t=window.go.main.App[n];if(typeof t=="function")return t(...e)}}catch(t){console.error("Wails call error:",n,t)}return Promise.reject(new Error("Wails not connected: "+n))}function we(n){if(!n)return"";try{return new Date(n).toLocaleDateString("ru-RU",{day:"numeric",month:"short"})}catch{return n}}const Ml=n=>n.id!=="today"&&n.id!=="inbox";function Fs(n,e,t){let l=[],i=[],s="",o="",a="",c=null,m="overview",_=[],h=null,p=[],v=[],w=[],g="",y="",C=!1,T="",j="clients",F=!1,A="",O=!0,E=!1,I=null,M=!1,D="",U=!1,J=null,W=[],B=[],ee=null,N="",Q=!1,Y="",V={items:[],mode:"copy"},P=[],te=[],K=!1,X="",$="",fe=!1,re="Удалить",z=null,ie=null,pe=!1,ue="",he="",Te="";const Al=[{id:"overview",label:"Обзор"},{id:"notes",label:"Заметки"},{id:"files",label:"Файлы"},{id:"actions",label:"Действия"},{id:"worklog",label:"Журнал"},{id:"activity",label:"Активность"}];let Ge=null;Dl(async()=>{try{t(2,s=await x("VerstakVersion")||"verstak-gui/v2"),t(0,l=await x("ListSections")||[])}catch(u){t(3,o=String(u)),t(0,l=[{id:"today",label:"Сегодня"},{id:"inbox",label:"Неразобранное"},{id:"clients",label:"Клиенты"},{id:"projects",label:"Проекты"},{id:"recipes",label:"Рецепты"},{id:"documents",label:"Документы"},{id:"archive",label:"Архив"}])}window.runtime&&window.runtime.EventsOn&&(window.runtime.EventsOn("files-dropped",un),Ge=()=>window.runtime.EventsOff("files-dropped")),window.addEventListener("keydown",bt),t(18,O=!1)}),El(()=>{Ge&&Ge(),window.removeEventListener("keydown",bt)});async function Ue(u){t(4,a=u),t(5,c=null),t(6,m="overview"),t(7,_=[]),p=[],t(9,v=[]),t(10,w=[]),t(13,C=!1),t(3,o="");try{t(1,i=await x("ListNodesBySection",u)||[])}catch(S){t(3,o=String(S)),t(1,i=[])}}async function ot(u){t(5,c=u),t(6,m="overview"),t(7,_=[]),p=[],t(9,v=[]),t(10,w=[]),t(25,B=[]),t(24,W=[]),t(23,J=null),t(26,ee=null),t(27,N=""),t(31,P=[]),te=[],t(8,h=null),t(13,C=!1),t(16,F=!1),t(3,o=""),await rt(u.id)}async function rt(u){try{t(7,_=await x("ListNotes",u)||[])}catch{}try{p=await x("ListFiles",u)||[]}catch{}try{t(9,v=await x("ListActions",u)||[])}catch{}try{t(10,w=await x("ListWorklog",u)||[])}catch{}}async function de(u){t(22,U=!0);try{let S=await x("ListItems",u)||[];S.sort((ne,ae)=>ne.type!==ae.type?ne.type==="folder"?-1:1:(ne.name||"").localeCompare(ae.name||"")),t(25,B=S)}catch{t(25,B=[])}t(22,U=!1)}async function Je(u){if(c){try{const S=await x("GetNodeDetail",u);S&&t(24,W=[...W,{id:u,name:S.title}])}catch{t(24,W=[...W,{id:u,name:"..."}])}t(23,J=u),await de(u)}}function ct(){if(W.length<2)t(24,W=[]),t(23,J=null),de(c.id);else{const u=W[W.length-2];t(24,W=W.slice(0,-1)),t(23,J=u.id),de(u.id)}}function at(u){const S=W[u];t(24,W=W.slice(0,u+1)),t(23,J=S.id),de(S.id)}async function Xe(u){t(26,ee=u),t(27,N=""),t(29,Y=""),t(28,Q=!0);try{Ni(u)?t(27,N=await x("GetFileBase64",u.fileId)||""):Ii(u)&&t(27,N=await x("ReadFileText",u.fileId)||"")}catch(S){t(29,Y=String(S))}t(28,Q=!1)}function Ze(){t(26,ee=null),t(27,N=""),t(29,Y="")}async function zl(){const u=prompt("Введите имя файла:");if(!(!u||!u.trim()))try{const S=J||c.id;await x("CreateEmptyFile",S,u.trim()),await de(S)}catch(S){t(3,o=String(S))}}async function ft(u){try{await x("DuplicateNode",u);const S=J||c.id;await de(S)}catch(S){t(3,o=String(S))}}function ut(u){const S=B.find(ne=>ne.id===u);S&>(S.id,S.name)}function dt(u){t(30,V={items:[u],mode:"cut"})}function mt(u){t(30,V={items:[u],mode:"copy"})}async function pt(){if(V.items.length===0)return;const u=J||c.id;try{if(V.mode==="copy")for(const S of V.items)await x("DuplicateNode",S);else for(const S of V.items)await x("MoveNode",S,u);t(30,V={items:[],mode:"copy"}),await de(u)}catch(S){t(3,o=String(S))}}function _t(u){P.includes(u)?t(31,P=P.filter(S=>S!==u)):t(31,P=[...P,u])}function ht(u){t(31,P=[u])}function Vl(){t(31,P=B.map(u=>u.id))}function vt(u){if(B.length===0)return;const S=P.length>0?P[P.length-1]:B[0].id,ne=B.findIndex(Ce=>Ce.id===S),ae=B.findIndex(Ce=>Ce.id===u);if(ne===-1||ae===-1)return;const He=Math.min(ne,ae),et=Math.max(ne,ae),Zn=B.slice(He,et+1).map(Ce=>Ce.id),Dt=new Set(P);Zn.forEach(Ce=>Dt.add(Ce)),t(31,P=[...Dt])}function Rl(){t(31,P=[])}function Ye(u){return u.length>0?u:B.map(S=>S.id)}async function Wl(){var ne;const u=Ye(P),S=u.length===1&&((ne=B.find(ae=>ae.id===u[0]))==null?void 0:ne.type)==="folder"?"папку":`файлов (${u.length})`;Pe({title:"Удаление",message:`Удалить ${S}?`,confirmText:"Удалить",danger:!0,onConfirm:async()=>{for(const He of u)try{await x("DeleteFileOrFolder",He)}catch(et){t(3,o=String(et))}t(31,P=[]);const ae=J||c.id;await de(ae)}})}function Kl(){const u=Ye(P);t(30,V={items:u,mode:"cut"}),t(31,P=[])}function Gl(){const u=Ye(P);t(30,V={items:u,mode:"copy"}),t(31,P=[])}function Ul(u,S){const ne=P.includes(S)?P:[S];te=ne,u.dataTransfer.effectAllowed="move",u.dataTransfer.setData("text/plain",ne.join(","))}function Jl(u,S){const ne=B.find(ae=>ae.id===S);ne&&ne.type==="folder"&&(u.preventDefault(),u.dataTransfer.dropEffect="move")}async function Xl(u,S){if(u.preventDefault(),te.length!==0){for(const ne of te)try{await x("MoveNode",ne,S)}catch(ae){t(3,o=String(ae))}te=[],t(31,P=[]),await de(J||c.id)}}function bt(u){if(m==="files"&&!(u.target.tagName==="INPUT"||u.target.tagName==="TEXTAREA"))if(u.ctrlKey||u.metaKey)u.key==="c"||u.key==="C"?(u.preventDefault(),Gl()):u.key==="x"||u.key==="X"?(u.preventDefault(),Kl()):u.key==="v"||u.key==="V"?(u.preventDefault(),pt()):u.key==="a"||u.key==="A"?(u.preventDefault(),Vl()):u.key==="o"||u.key==="O"?(u.preventDefault(),Zl()):u.key==="Enter"&&(u.preventDefault(),kt());else if(u.key==="Enter")u.preventDefault(),kt();else if(u.key==="Delete"||u.key==="Backspace"){if(ee){u.preventDefault(),Ze();return}if(P.length>0){u.preventDefault(),Wl();return}if(u.key==="Backspace"&&W.length>0){u.preventDefault(),ct();return}}else if(u.key==="Escape"){if(ee){Ze();return}if(P.length>0){Rl();return}}else u.key==="F2"&&(u.preventDefault(),Yl())}function kt(){if(P.length===1){const u=B.find(S=>S.id===P[0]);u&&(u.type==="folder"?Je(u.id):Xe(u))}}function Zl(){if(P.length===1){const u=B.find(S=>S.id===P[0]);u&&u.fileId&&x("OpenFile",u.fileId)}}function gt(u,S){ue=u,t(38,he=S),t(39,Te=""),t(37,pe=!0)}function Yl(){if(P.length===1){const u=B.find(S=>S.id===P[0]);u&>(u.id,u.name)}}async function wt(){const u=he.trim();if(!u){t(39,Te="Имя не может быть пустым");return}try{await x("ValidateName",u)}catch{t(39,Te="Недопустимое имя");return}t(37,pe=!1),ue="";try{await x("RenameNode",ue,u);const S=J||c.id;await de(S)}catch(S){t(3,o=String(S))}}function Ql(){t(37,pe=!1),ue="",t(38,he=""),t(39,Te="")}function xl(u){u.key==="Enter"?wt():t(39,Te="")}function Pe(u){t(33,X=u.title||"Подтверждение"),t(34,$=u.message||""),t(35,fe=u.danger!==void 0?u.danger:!0),t(36,re=u.confirmText||"Удалить"),z=u.onConfirm||null,ie=u.onCancel||null,t(32,K=!0)}function yt(){t(32,K=!1),z=null,ie=null}function $l(){z&&z(),yt()}function en(){ie&&ie(),yt()}function tn(){t(13,C=!0),t(14,T=""),t(15,j=a||"clients")}function ln(){t(13,C=!1),t(14,T="")}async function qt(){if(T.trim())try{const u=await x("CreateNode","","case",T.trim(),j);t(13,C=!1),t(14,T=""),await Ue(j)}catch(u){t(3,o=String(u))}}function Ct(){t(16,F=!0),t(17,A="")}function nn(){t(16,F=!1),t(17,A="")}async function Mt(){if(!(!A.trim()||!c))try{const u=await x("CreateNote",c.id,A.trim());t(7,_=[..._,u&&u.id?u:{id:Date.now().toString(),title:A.trim(),createdAt:new Date().toISOString()}]),t(16,F=!1),t(17,A="")}catch{const S={id:Date.now().toString(),title:A.trim(),createdAt:new Date().toISOString()};t(7,_=[..._,S]),t(16,F=!1),t(17,A="")}}async function Qe(u){if(h&&h.dirty){Pe({title:"Несохранённые изменения",message:"Закрыть редактор? Все несохранённые изменения будут потеряны.",confirmText:"Закрыть",danger:!1,onConfirm:async()=>{await St(u)}});return}await St(u)}async function St(u){try{const S=await x("ReadNote",u.id);t(8,h={id:u.id,title:u.title,content:S||"",dirty:!1})}catch{t(8,h={id:u.id,title:u.title,content:"# "+u.title+`
+
+`,dirty:!1})}}function sn(){if(h&&h.dirty){Pe({title:"Несохранённые изменения",message:"Закрыть редактор? Все несохранённые изменения будут потеряны.",confirmText:"Закрыть",danger:!1,onConfirm:()=>{t(8,h=null)}});return}t(8,h=null)}function on(u){h&&(t(8,h.content=u.target.value,h),t(8,h.dirty=!0,h))}async function rn(){if(h)try{await x("SaveNote",h.id,h.content),t(8,h.dirty=!1,h)}catch{t(8,h.dirty=!1,h)}}async function cn(){const u=parseInt(g,10);if(!(!y.trim()||isNaN(u)||u<=0||!c)){try{const S=await x("CreateWorklog",c.id,y.trim(),u);t(10,w=[...w,S&&S.id?S:{id:Date.now().toString(),nodeId:c.id,summary:y.trim(),minutes:u,createdAt:new Date().toISOString()}])}catch{t(10,w=[...w,{id:Date.now().toString(),nodeId:c.id,summary:y.trim(),minutes:u,createdAt:new Date().toISOString()}])}t(12,y=""),t(11,g="")}}async function Tt(){const u=await x("PickFile");u&&await xe(c.id,u)}async function an(){const u=await x("PickDirectory");u&&await xe(c.id,u)}async function xe(u,S){t(19,E=!0);try{const ne=await x("PreviewImport",S);t(20,I=ne),D=S,t(21,M=!0)}catch(ne){t(3,o=String(ne))}t(19,E=!1)}async function $e(u){try{const S=u==="copy"?await x("AddPathCopy",c.id,D):await x("AddPathLink",c.id,D);t(21,M=!1),t(20,I=null),t(24,W=[]),t(23,J=null),await Promise.all([rt(c.id),de(c.id)])}catch(S){t(3,o=String(S))}}function fn(){t(21,M=!1),t(20,I=null)}async function Lt({id:u,type:S}){Pe({title:"Удаление",message:`Удалить ${S==="folder"?"папку":"файл"}?`,confirmText:"Удалить",danger:!0,onConfirm:async()=>{try{await x("DeleteFileOrFolder",u),p=p.filter(He=>He.nodeId!==u);const ae=J||c.id;await de(ae)}catch(ae){t(3,o=String(ae))}}})}async function un(u){if(!u||u.length===0)return;if(!c){t(3,o="Сначала выберите дело для добавления файлов");return}const S=u[0];await xe(c.id,S)}function dn(u){return m===u?"tab active":"tab"}const mn=u=>Ue(u.id),pn=u=>ot(u),_n=()=>t(3,o=""),hn=()=>t(3,o="");function vn(){h.content=this.value,t(8,h)}const bn=u=>{t(6,m=u.id),u.id==="files"&&c&&B.length===0&&!J&&de(c.id)},kn=()=>{t(6,m="notes"),Ct()},gn=()=>{t(6,m="files"),Tt()},wn=()=>t(6,m="worklog"),yn=u=>Qe(u);function qn(){A=this.value,t(17,A)}const Cn=u=>u.key==="Enter"&&Mt(),Mn=u=>Qe(u),Sn=u=>{const S=u.detail;S===0?(t(24,W=[]),t(23,J=null),de(c.id)):at(S-1)},Tn=u=>Je(u.detail),Ln=u=>Xe(u.detail),Dn=u=>x("OpenFile",u.detail),En=u=>x("OpenFolder",u.detail),Nn=u=>Lt(u.detail),In=u=>ut(u.detail.id),Fn=u=>ft(u.detail),Hn=u=>dt(u.detail),jn=u=>mt(u.detail),Bn=u=>ht(u.detail),On=u=>_t(u.detail),Pn=u=>vt(u.detail),An=u=>x("OpenFile",u.detail),zn=u=>x("RunAction",u.id);function Vn(){y=this.value,t(12,y)}function Rn(){g=Ll(this.value),t(11,g)}function Wn(){T=this.value,t(14,T)}const Kn=u=>u.key==="Enter"&&qt();function Gn(){j=ti(this),t(15,j),t(0,l)}const Un=()=>$e("copy"),Jn=()=>$e("link");function Xn(){he=this.value,t(38,he)}return[l,i,s,o,a,c,m,_,h,v,w,g,y,C,T,j,F,A,O,E,I,M,U,J,W,B,ee,N,Q,Y,V,P,K,X,$,fe,re,pe,he,Te,Al,Ue,ot,de,Je,ct,at,Xe,Ze,zl,ft,ut,dt,mt,pt,_t,ht,vt,Ul,Jl,Xl,wt,Ql,xl,$l,en,tn,ln,qt,Ct,nn,Mt,Qe,sn,on,rn,cn,Tt,an,$e,fn,Lt,dn,mn,pn,_n,hn,vn,bn,kn,gn,wn,yn,qn,Cn,Mn,Sn,Tn,Ln,Dn,En,Nn,In,Fn,Hn,jn,Bn,On,Pn,An,zn,Vn,Rn,Wn,Kn,Gn,Un,Jn,Xn]}class Hs extends Fe{constructor(e){super(),Ie(this,e,Fs,Is,Ne,{},null,[-1,-1,-1,-1,-1,-1])}}new Hs({target:document.getElementById("app")});
diff --git a/cmd/verstak-gui/frontend-dist/assets/main-Bo58X7Pc.css b/cmd/verstak-gui/frontend-dist/assets/main-Bo58X7Pc.css
new file mode 100644
index 0000000..1f5ca39
--- /dev/null
+++ b/cmd/verstak-gui/frontend-dist/assets/main-Bo58X7Pc.css
@@ -0,0 +1 @@
+.file-row.svelte-1u905d2.svelte-1u905d2{display:flex;align-items:center;gap:10px;padding:8px 12px;border-radius:6px;cursor:default;transition:background .12s;min-height:52px;-webkit-user-select:none;user-select:none;position:relative}.file-row.svelte-1u905d2.svelte-1u905d2:hover{background:#1e1e30}.file-row--selected.svelte-1u905d2.svelte-1u905d2{background:#1e1e3a;outline:1px solid #3a3a6c}.file-row--selected.svelte-1u905d2.svelte-1u905d2:hover{background:#252545}.file-row.svelte-1u905d2.svelte-1u905d2:focus-visible{outline:2px solid #5588ff;outline-offset:-2px}.file-row-icon.svelte-1u905d2.svelte-1u905d2{flex-shrink:0;display:flex;align-items:center;justify-content:center;width:32px;height:32px;color:#888}.file-row-body.svelte-1u905d2.svelte-1u905d2{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}.file-row-name.svelte-1u905d2.svelte-1u905d2{font-size:13px;color:#ddd;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;line-height:1.3}.file-row-meta.svelte-1u905d2.svelte-1u905d2{display:flex;align-items:center;gap:4px;font-size:11px;color:#666}.meta-sep.svelte-1u905d2.svelte-1u905d2{color:#444}.file-row-actions.svelte-1u905d2.svelte-1u905d2{display:flex;gap:2px;align-items:center;opacity:0;transition:opacity .15s ease;flex-shrink:0}.file-row.svelte-1u905d2:hover .file-row-actions.svelte-1u905d2{opacity:1}.action-btn.svelte-1u905d2.svelte-1u905d2{display:flex;align-items:center;justify-content:center;width:28px;height:28px;border:none;border-radius:4px;background:transparent;color:#666;cursor:pointer;transition:background .12s,color .12s}.action-btn.svelte-1u905d2.svelte-1u905d2:hover{background:#2a2a3c;color:#ccc}.action-btn-danger.svelte-1u905d2.svelte-1u905d2:hover{background:#3a2222;color:#ff6b6b}.action-btn.svelte-1u905d2.svelte-1u905d2:focus-visible{outline:2px solid #5588ff;outline-offset:1px}.menu-backdrop.svelte-1u905d2.svelte-1u905d2{position:fixed;top:0;right:0;bottom:0;left:0;z-index:99}.menu.svelte-1u905d2.svelte-1u905d2{position:absolute;right:12px;margin-top:4px;background:#1a1a28;border:1px solid #2a2a3c;border-radius:8px;padding:4px;z-index:100;min-width:220px;box-shadow:0 4px 16px #00000080}.menu-item.svelte-1u905d2.svelte-1u905d2{display:flex;align-items:center;gap:8px;width:100%;padding:7px 10px;border:none;background:transparent;color:#ccc;font-size:12px;text-align:left;cursor:pointer;border-radius:4px;font-family:inherit}.menu-item.svelte-1u905d2.svelte-1u905d2:hover{background:#2a2a3c;color:#fff}.menu-item-danger.svelte-1u905d2.svelte-1u905d2{color:#ff6b6b}.menu-item-danger.svelte-1u905d2.svelte-1u905d2:hover{background:#3a2222}.menu-item.svelte-1u905d2.svelte-1u905d2:focus-visible{outline:2px solid #5588ff;outline-offset:1px}.menu-sep.svelte-1u905d2.svelte-1u905d2{height:1px;background:#2a2a3c;margin:4px 8px}.breadcrumbs.svelte-csi2lb{display:flex;align-items:center;gap:4px;padding:8px 0;font-size:13px;color:#999}.sep.svelte-csi2lb{color:#444}.crumb.svelte-csi2lb{font-size:13px}.crumb--current.svelte-csi2lb{color:#ccc}.crumb--link.svelte-csi2lb{background:none;border:none;padding:2px 4px;color:#888;cursor:pointer;border-radius:3px;font-family:inherit;font-size:13px;transition:color .12s,background .12s}.crumb--link.svelte-csi2lb:hover{color:#ccc;background:#1e1e30}.crumb--link.svelte-csi2lb:focus-visible{outline:2px solid #5588ff;outline-offset:1px}.overlay.svelte-1cw3u0m{position:fixed;top:0;right:0;bottom:0;left:0;background:#000000a6;display:flex;align-items:center;justify-content:center;z-index:1000}.modal.svelte-1cw3u0m{background:#14141f;border:1px solid #2a2a3c;border-radius:10px;width:90vw;max-width:900px;height:85vh;max-height:700px;display:flex;flex-direction:column;overflow:hidden}.preview-header.svelte-1cw3u0m{display:flex;align-items:center;gap:10px;padding:12px 16px;border-bottom:1px solid #2a2a3c;flex-shrink:0}.preview-title.svelte-1cw3u0m{display:flex;align-items:center;gap:8px;color:#ddd;font-size:14px;min-width:0}.preview-name.svelte-1cw3u0m{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.preview-meta.svelte-1cw3u0m{font-size:11px;color:#666;margin-left:auto;white-space:nowrap}.preview-actions.svelte-1cw3u0m{display:flex;gap:4px;flex-shrink:0;margin-left:8px}.action-btn.svelte-1cw3u0m{display:flex;align-items:center;justify-content:center;width:30px;height:30px;border:none;border-radius:4px;background:transparent;color:#666;cursor:pointer;transition:background .12s,color .12s}.action-btn.svelte-1cw3u0m:hover{background:#2a2a3c;color:#ccc}.action-btn.svelte-1cw3u0m:focus-visible{outline:2px solid #5588ff;outline-offset:1px}.action-btn-close.svelte-1cw3u0m{color:#ff6b6b}.action-btn-close.svelte-1cw3u0m:hover{background:#3a2222;color:#f44}.preview-body.svelte-1cw3u0m{flex:1;overflow:auto;min-height:0}.preview-status.svelte-1cw3u0m{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;padding:48px 24px;color:#888;font-size:14px}.preview-image-container.svelte-1cw3u0m{display:flex;align-items:center;justify-content:center;padding:16px;min-height:200px;background:#0e0e18}.preview-image.svelte-1cw3u0m{max-width:100%;max-height:calc(85vh - 100px);object-fit:contain;border-radius:4px}.preview-text.svelte-1cw3u0m{margin:0;padding:16px;font-family:SF Mono,Fira Code,Cascadia Code,Consolas,monospace;font-size:12px;line-height:1.5;color:#ccc;white-space:pre-wrap;word-wrap:break-word;overflow:auto}.preview-pdf-container.svelte-1cw3u0m{width:100%;height:100%}.preview-pdf.svelte-1cw3u0m{width:100%;height:100%;border:none}.btn-sm.svelte-1cw3u0m{padding:6px 14px;border:1px solid #2a2a3c;background:#1a1a28;color:#ccc;border-radius:6px;cursor:pointer;font-size:12px;font-family:inherit;transition:background .12s}.btn-sm.svelte-1cw3u0m:hover{background:#223}.overlay.svelte-1fv6yyk{position:fixed;top:0;right:0;bottom:0;left:0;background:#0009;display:flex;align-items:center;justify-content:center;z-index:200}.modal.svelte-1fv6yyk{background:#1a1a28;border:1px solid #2a2a3c;border-radius:12px;padding:24px;width:360px;max-width:90vw}h3.svelte-1fv6yyk{font-size:18px;margin-bottom:12px;color:#e4e4ef}.message.svelte-1fv6yyk{font-size:14px;color:#aaa;margin-bottom:20px;line-height:1.4}.actions.svelte-1fv6yyk{display:flex;gap:8px;justify-content:flex-end}.btn.svelte-1fv6yyk{padding:8px 16px;border:1px solid #2a2a3c;background:#1a1a28;color:#ccc;border-radius:6px;cursor:pointer;font-size:13px;font-family:inherit}.btn.svelte-1fv6yyk:hover{background:#223}.btn-primary.svelte-1fv6yyk{background:#6366f1;border-color:#6366f1;color:#fff}.btn-primary.svelte-1fv6yyk:hover{background:#4f46e5}.btn-danger.svelte-1fv6yyk{background:#dc2626;border-color:#dc2626;color:#fff}.btn-danger.svelte-1fv6yyk:hover{background:#b91c1c}.btn.svelte-1fv6yyk:focus-visible{outline:2px solid #5588ff;outline-offset:1px}.svelte-i359q7.svelte-i359q7,.svelte-i359q7.svelte-i359q7:before,.svelte-i359q7.svelte-i359q7:after{box-sizing:border-box;margin:0;padding:0}.app.svelte-i359q7.svelte-i359q7{display:flex;width:100vw;height:100vh;overflow:hidden;background:#13131f;color:#e4e4ef;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:14px}.sidebar.svelte-i359q7.svelte-i359q7{width:260px;min-width:200px;height:100vh;display:flex;flex-direction:column;background:#1a1a28;border-right:1px solid #2a2a3c;flex-shrink:0;overflow:hidden}.sidebar-brand.svelte-i359q7.svelte-i359q7{padding:16px 20px;display:flex;align-items:center;gap:10px;border-bottom:1px solid #2a2a3c;flex-shrink:0}.logo.svelte-i359q7.svelte-i359q7{font-size:20px;line-height:1}.brand-name.svelte-i359q7.svelte-i359q7{font-size:16px;font-weight:600}.sidebar-nav.svelte-i359q7.svelte-i359q7{flex:1;overflow-y:auto;padding:12px 0}.nav-group.svelte-i359q7.svelte-i359q7{margin-bottom:16px}.nav-label.svelte-i359q7.svelte-i359q7{font-size:10px;text-transform:uppercase;letter-spacing:.5px;color:#666;padding:4px 20px;margin-bottom:4px}.nav-item.svelte-i359q7.svelte-i359q7{display:block;width:100%;padding:8px 20px;border:none;background:none;color:#ccc;font-size:13px;text-align:left;cursor:pointer;border-radius:0;font-family:inherit}.nav-item.svelte-i359q7.svelte-i359q7:hover{background:#223}.nav-item.selected.svelte-i359q7.svelte-i359q7{background:#2a2a4a;color:#fff;font-weight:500}.nav-empty.svelte-i359q7.svelte-i359q7{padding:8px 20px;color:#555;font-size:12px}.sidebar-footer.svelte-i359q7.svelte-i359q7{padding:12px 20px;border-top:1px solid #2a2a3c;flex-shrink:0}.version.svelte-i359q7.svelte-i359q7{font-size:11px;color:#555}.main.svelte-i359q7.svelte-i359q7{flex:1;display:flex;flex-direction:column;height:100vh;min-width:0;overflow:hidden;background:#13131f}.header.svelte-i359q7.svelte-i359q7{padding:12px 24px;border-bottom:1px solid #2a2a3c;display:flex;align-items:center;flex-shrink:0;min-height:48px}.crumb.svelte-i359q7.svelte-i359q7{font-size:14px;font-weight:500}.crumb.placeholder.svelte-i359q7.svelte-i359q7{color:#666}.crumb-type.svelte-i359q7.svelte-i359q7{font-size:11px;color:#555;background:#1e1e2e;padding:2px 8px;border-radius:10px;margin-left:8px}.error-banner.svelte-i359q7.svelte-i359q7{background:#3a2222;color:#f88;padding:8px 24px;font-size:12px;border-bottom:1px solid #4a2222;flex-shrink:0;cursor:pointer;display:flex;justify-content:space-between;align-items:center}.dismiss-btn.svelte-i359q7.svelte-i359q7{background:none;border:none;color:#f66;cursor:pointer;padding:2px;display:flex;align-items:center;border-radius:2px}.dismiss-btn.svelte-i359q7.svelte-i359q7:hover{color:#f44}.tabs.svelte-i359q7.svelte-i359q7{display:flex;border-bottom:1px solid #2a2a3c;flex-shrink:0;padding:0 24px}.tab.svelte-i359q7.svelte-i359q7{padding:10px 16px;border:none;background:none;color:#888;font-size:13px;cursor:pointer;border-bottom:2px solid transparent;font-family:inherit}.tab.svelte-i359q7.svelte-i359q7:hover{color:#ccc}.tab.active.svelte-i359q7.svelte-i359q7{color:#e4e4ef;border-bottom-color:#6366f1}.tab-content.svelte-i359q7.svelte-i359q7{flex:1;overflow-y:auto}.note-editor.svelte-i359q7.svelte-i359q7{flex:1;display:flex;flex-direction:column;height:100%}.note-editor-header.svelte-i359q7.svelte-i359q7{padding:12px 24px;border-bottom:1px solid #2a2a3c;display:flex;align-items:center;gap:12px;flex-shrink:0}.note-title.svelte-i359q7.svelte-i359q7{font-size:16px;font-weight:500}.dirty-mark.svelte-i359q7.svelte-i359q7{color:#f59e0b;font-size:10px}.note-editor-actions.svelte-i359q7.svelte-i359q7{margin-left:auto;display:flex;gap:8px}.note-textarea.svelte-i359q7.svelte-i359q7{flex:1;width:100%;border:none;outline:none;background:#13131f;color:#e4e4ef;font-family:SF Mono,Fira Code,monospace;font-size:14px;line-height:1.6;padding:24px;resize:none}.overview.svelte-i359q7.svelte-i359q7{padding:24px}.overview.svelte-i359q7 h2.svelte-i359q7{font-size:24px;margin-bottom:16px}.meta-grid.svelte-i359q7.svelte-i359q7{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:12px;margin-bottom:24px}.meta-item.svelte-i359q7.svelte-i359q7{background:#1a1a28;padding:12px 16px;border-radius:8px}.meta-label.svelte-i359q7.svelte-i359q7{display:block;font-size:11px;color:#666;margin-bottom:4px;text-transform:uppercase}.quick-actions.svelte-i359q7.svelte-i359q7{display:flex;gap:8px;margin-bottom:24px;flex-wrap:wrap}.qa-btn.svelte-i359q7.svelte-i359q7{padding:10px 16px;border:1px solid #2a2a3c;background:#1a1a28;color:#ccc;border-radius:8px;cursor:pointer;font-size:13px;font-family:inherit;display:inline-flex;align-items:center;gap:6px}.qa-btn.svelte-i359q7.svelte-i359q7:hover{background:#223}.qa-btn.svelte-i359q7.svelte-i359q7:disabled{opacity:.4;cursor:not-allowed}.recent-section.svelte-i359q7.svelte-i359q7{margin-bottom:24px}.recent-section.svelte-i359q7 h3.svelte-i359q7{font-size:13px;color:#666;text-transform:uppercase;margin-bottom:8px}.recent-note.svelte-i359q7.svelte-i359q7{padding:8px 12px;border-radius:6px;cursor:pointer;display:flex;justify-content:space-between}.recent-note.svelte-i359q7.svelte-i359q7:hover{background:#1a1a28}.recent-date.svelte-i359q7.svelte-i359q7{font-size:11px;color:#555}.recent-entry.svelte-i359q7.svelte-i359q7{padding:6px 0;font-size:13px;color:#888;border-bottom:1px solid #1a1a28}.notes-tab.svelte-i359q7.svelte-i359q7{padding:24px}.tab-toolbar.svelte-i359q7.svelte-i359q7{margin-bottom:16px}.create-form.svelte-i359q7.svelte-i359q7{background:#1a1a28;padding:16px;border-radius:8px;margin-bottom:16px}.create-form.svelte-i359q7 input.svelte-i359q7{width:100%;padding:8px 12px;border:1px solid #2a2a3c;background:#13131f;color:#e4e4ef;border-radius:4px;font-size:14px;font-family:inherit;margin-bottom:8px}.create-form.svelte-i359q7 input.svelte-i359q7:focus{outline:none;border-color:#6366f1}.form-actions.svelte-i359q7.svelte-i359q7{display:flex;gap:8px}.notes-list.svelte-i359q7.svelte-i359q7{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:12px}.note-card.svelte-i359q7.svelte-i359q7{background:#1a1a28;border:1px solid #2a2a3c;border-radius:8px;padding:16px;cursor:pointer}.note-card.svelte-i359q7.svelte-i359q7:hover{border-color:#3a3a5c}.note-card-title.svelte-i359q7.svelte-i359q7{font-size:14px;font-weight:500;margin-bottom:4px}.note-card-date.svelte-i359q7.svelte-i359q7{font-size:11px;color:#555}.worklog-tab.svelte-i359q7.svelte-i359q7{padding:24px}.worklog-form.svelte-i359q7.svelte-i359q7{display:flex;gap:8px;margin-bottom:24px;align-items:center}.worklog-form.svelte-i359q7 input.svelte-i359q7{padding:8px 12px;border:1px solid #2a2a3c;background:#13131f;color:#e4e4ef;border-radius:4px;font-size:14px;font-family:inherit}.worklog-form.svelte-i359q7 input.svelte-i359q7:focus{outline:none;border-color:#6366f1}.worklog-form.svelte-i359q7 input[type=text].svelte-i359q7{flex:1}.worklog-form.svelte-i359q7 input[type=number].svelte-i359q7{width:70px}.worklog-entry.svelte-i359q7.svelte-i359q7{padding:12px 0;border-bottom:1px solid #1a1a28}.wl-meta.svelte-i359q7.svelte-i359q7{font-size:11px;color:#555;margin-top:2px}.action-card.svelte-i359q7.svelte-i359q7{background:#1a1a28;padding:12px 16px;border-radius:8px;display:flex;align-items:center;gap:12px;margin-bottom:8px}.action-type.svelte-i359q7.svelte-i359q7{font-size:11px;color:#888;background:#223;padding:2px 8px;border-radius:10px}.empty-state.svelte-i359q7.svelte-i359q7{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:48px 24px;text-align:center}.empty-state.svelte-i359q7 p.svelte-i359q7{margin:0;font-size:14px;color:#666}.empty-state.svelte-i359q7 .empty-icon.svelte-i359q7{margin-bottom:12px;color:#444}.empty-state.svelte-i359q7 .hint.svelte-i359q7{font-size:12px;color:#555;margin-top:6px}.empty-state.svelte-i359q7 .empty-actions.svelte-i359q7{display:flex;gap:8px;justify-content:center;margin-top:16px}.empty-note.svelte-i359q7.svelte-i359q7{font-size:12px;color:#444;margin-top:16px}.welcome.svelte-i359q7.svelte-i359q7{padding:48px 24px;text-align:center}.welcome.svelte-i359q7 h2.svelte-i359q7{font-size:32px;font-weight:300;color:#8888a4;margin-bottom:16px}.welcome.svelte-i359q7 p.svelte-i359q7{color:#666;font-size:14px}.error-text.svelte-i359q7.svelte-i359q7{color:#f88}.fab.svelte-i359q7.svelte-i359q7{position:fixed;bottom:24px;right:24px;width:56px;height:56px;border-radius:50%;background:#6366f1;color:#fff;font-size:28px;display:flex;align-items:center;justify-content:center;cursor:pointer;box-shadow:0 4px 12px #6366f166}.fab.svelte-i359q7.svelte-i359q7:hover{background:#4f46e5}.modal-overlay.svelte-i359q7.svelte-i359q7{position:fixed;top:0;right:0;bottom:0;left:0;background:#0009;display:flex;align-items:center;justify-content:center;z-index:100}.modal.svelte-i359q7.svelte-i359q7{background:#1a1a28;border:1px solid #2a2a3c;border-radius:12px;padding:24px;width:400px;max-width:90vw}.modal.svelte-i359q7 h3.svelte-i359q7{font-size:18px;margin-bottom:16px}.form-group.svelte-i359q7.svelte-i359q7{margin-bottom:12px}.form-group.svelte-i359q7 label.svelte-i359q7{display:block;font-size:12px;color:#666;margin-bottom:4px}.form-group.svelte-i359q7 input.svelte-i359q7,.form-group.svelte-i359q7 select.svelte-i359q7{width:100%;padding:8px 12px;border:1px solid #2a2a3c;background:#13131f;color:#e4e4ef;border-radius:4px;font-size:14px;font-family:inherit}.form-group.svelte-i359q7 select.svelte-i359q7{-moz-appearance:none;appearance:none;-webkit-appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23888' d='M2 4l4 4 4-4'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 10px center;padding-right:32px}.form-group.svelte-i359q7 input.svelte-i359q7:focus,.form-group.svelte-i359q7 select.svelte-i359q7:focus{outline:none;border-color:#6366f1}.modal-actions.svelte-i359q7.svelte-i359q7{display:flex;gap:8px;justify-content:flex-end;margin-top:16px}.btn.svelte-i359q7.svelte-i359q7{padding:8px 16px;border:1px solid #2a2a3c;background:#1a1a28;color:#ccc;border-radius:6px;cursor:pointer;font-size:13px;font-family:inherit}.btn.svelte-i359q7.svelte-i359q7:hover{background:#223}.btn-primary.svelte-i359q7.svelte-i359q7{background:#6366f1;border-color:#6366f1;color:#fff}.btn-primary.svelte-i359q7.svelte-i359q7:hover{background:#4f46e5}.btn.svelte-i359q7.svelte-i359q7:disabled{opacity:.4;cursor:not-allowed}.btn-sm.svelte-i359q7.svelte-i359q7{padding:4px 10px;font-size:12px}.btn-danger.svelte-i359q7.svelte-i359q7{color:#ff6b6b;border-color:#4a2222}.btn-danger.svelte-i359q7.svelte-i359q7:hover{background:#3a2222}.files-tab.svelte-i359q7.svelte-i359q7{padding:20px}.files-tab.svelte-i359q7 .tab-toolbar.svelte-i359q7{display:flex;gap:8px;align-items:center;margin-bottom:16px}.file-list.svelte-i359q7.svelte-i359q7{display:flex;flex-direction:column}.back-btn.svelte-i359q7.svelte-i359q7{margin-bottom:4px;display:inline-flex;align-items:center;gap:4px}.import-summary.svelte-i359q7.svelte-i359q7{margin-bottom:16px}.summary-row.svelte-i359q7.svelte-i359q7{display:flex;justify-content:space-between;padding:6px 0;font-size:14px;border-bottom:1px solid #2a2a3c}.summary-warn.svelte-i359q7.svelte-i359q7{margin-top:8px;padding:8px 12px;background:#3a2a22;border-radius:6px;color:#fa6;font-size:13px}.rename-error.svelte-i359q7.svelte-i359q7{color:#ff6b6b;font-size:12px;margin-top:4px}
diff --git a/cmd/verstak-gui/frontend-dist/index.html b/cmd/verstak-gui/frontend-dist/index.html
index 33d5116..795d984 100644
--- a/cmd/verstak-gui/frontend-dist/index.html
+++ b/cmd/verstak-gui/frontend-dist/index.html
@@ -16,8 +16,8 @@
background: #13131f;
}
-
-
+
+
diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte
index 4f5fff7..08360b6 100644
--- a/frontend/src/App.svelte
+++ b/frontend/src/App.svelte
@@ -72,6 +72,11 @@
let confirmAction = null
let cancelAction = null
+ let showRename = false
+ let renameId = ''
+ let renameValue = ''
+ let renameError = ''
+
const tabs = [
{ id: 'overview', label: 'Обзор' },
{ id: 'notes', label: 'Заметки' },
@@ -282,19 +287,8 @@
}
function renameItem(id) {
- const name = prompt('Новое имя:')
- if (!name || !name.trim()) return
- renameSubmit(id, name.trim())
- }
-
- async function renameSubmit(id, newName) {
- try {
- await wailsCall('RenameNode', id, newName)
- const parentId = currentFolderId || selectedNode.id
- await loadFolder(parentId)
- } catch (e) {
- error = String(e)
- }
+ const item = fileItems.find(x => x.id === id)
+ if (item) openRename(item.id, item.name)
}
function cutItem(id) {
@@ -343,6 +337,20 @@
selectedIds = fileItems.map(x => x.id)
}
+ function rangeSelect(id) {
+ if (fileItems.length === 0) return
+ const lastId = selectedIds.length > 0 ? selectedIds[selectedIds.length - 1] : fileItems[0].id
+ const lastIdx = fileItems.findIndex(x => x.id === lastId)
+ const curIdx = fileItems.findIndex(x => x.id === id)
+ if (lastIdx === -1 || curIdx === -1) return
+ const start = Math.min(lastIdx, curIdx)
+ const end = Math.max(lastIdx, curIdx)
+ const range = fileItems.slice(start, end + 1).map(x => x.id)
+ const set = new Set(selectedIds)
+ range.forEach(x => set.add(x))
+ selectedIds = [...set]
+ }
+
function clearSelection() {
selectedIds = []
}
@@ -421,15 +429,106 @@
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return
if (e.ctrlKey || e.metaKey) {
- if (e.key === 'c') { e.preventDefault(); copySelected() }
- else if (e.key === 'x') { e.preventDefault(); cutSelected() }
- else if (e.key === 'v') { e.preventDefault(); pasteItem() }
- else if (e.key === 'a') { e.preventDefault(); selectAll() }
+ if (e.key === 'c' || e.key === 'C') { e.preventDefault(); copySelected() }
+ else if (e.key === 'x' || e.key === 'X') { e.preventDefault(); cutSelected() }
+ else if (e.key === 'v' || e.key === 'V') { e.preventDefault(); pasteItem() }
+ else if (e.key === 'a' || e.key === 'A') { e.preventDefault(); selectAll() }
+ else if (e.key === 'o' || e.key === 'O') { e.preventDefault(); openSelectedExternal() }
+ else if (e.key === 'Enter') { e.preventDefault(); openSelected() }
+ } else if (e.key === 'Enter') {
+ e.preventDefault()
+ openSelected()
} else if (e.key === 'Delete' || e.key === 'Backspace') {
- if (selectedIds.length > 0) { e.preventDefault(); deleteSelected() }
+ if (previewItem) { e.preventDefault(); closePreview(); return }
+ if (selectedIds.length > 0) { e.preventDefault(); deleteSelected(); return }
+ // Backspace without selection → navigate up
+ if (e.key === 'Backspace' && folderStack.length > 0) {
+ e.preventDefault()
+ navigateBack()
+ return
+ }
+ } else if (e.key === 'Escape') {
+ if (previewItem) { closePreview(); return }
+ if (selectedIds.length > 0) { clearSelection(); return }
+ } else if (e.key === 'F2') {
+ e.preventDefault()
+ openRenameForSelection()
}
}
+ function openSelected() {
+ if (selectedIds.length === 1) {
+ const item = fileItems.find(x => x.id === selectedIds[0])
+ if (item) {
+ if (item.type === 'folder') {
+ navigateToFolder(item.id)
+ } else {
+ openPreview(item)
+ }
+ }
+ }
+ }
+
+ function openSelectedExternal() {
+ if (selectedIds.length === 1) {
+ const item = fileItems.find(x => x.id === selectedIds[0])
+ if (item && item.fileId) {
+ wailsCall('OpenFile', item.fileId)
+ }
+ }
+ }
+
+ // ===== Rename modal =====
+
+ function openRename(id, currentName) {
+ renameId = id
+ renameValue = currentName
+ renameError = ''
+ showRename = true
+ }
+
+ function openRenameForSelection() {
+ if (selectedIds.length === 1) {
+ const item = fileItems.find(x => x.id === selectedIds[0])
+ if (item) {
+ openRename(item.id, item.name)
+ }
+ }
+ }
+
+ async function submitRename() {
+ const name = renameValue.trim()
+ if (!name) { renameError = 'Имя не может быть пустым'; return }
+ // Validate name via backend
+ try {
+ await wailsCall('ValidateName', name)
+ } catch (e) {
+ renameError = 'Недопустимое имя'
+ return
+ }
+ showRename = false
+ renameId = ''
+ try {
+ await wailsCall('RenameNode', renameId, name)
+ const parentId = currentFolderId || selectedNode.id
+ await loadFolder(parentId)
+ } catch (e) {
+ error = String(e)
+ }
+ }
+
+ function cancelRename() {
+ showRename = false
+ renameId = ''
+ renameValue = ''
+ renameError = ''
+ }
+
+ function onRenameKeydown(e) {
+ if (e.key === 'Enter') submitRename()
+ else renameError = ''
+ }
+
// ===== Confirm modal =====
function openConfirm(opts) {
@@ -832,7 +931,7 @@
{:else}
{#if folderStack.length > 0}
- {
+ {
const i = e.detail
if (i === 0) {
folderStack = []
@@ -847,7 +946,7 @@
Back
{:else}
-
+
{/if}
{#if fileItems.length === 0}
@@ -877,6 +976,7 @@
on:navigate={(e) => navigateToFolder(e.detail)}
on:preview={(e) => openPreview(e.detail)}
on:openExternal={(e) => wailsCall('OpenFile', e.detail)}
+ on:showInFolder={(e) => wailsCall('OpenFolder', e.detail)}
on:delete={(e) => deleteFile(e.detail)}
on:rename={(e) => renameItem(e.detail.id)}
on:duplicate={(e) => duplicateItem(e.detail)}
@@ -884,6 +984,7 @@
on:copy={(e) => copyItem(e.detail)}
on:selectOne={(e) => selectOne(e.detail)}
on:toggleSelect={(e) => toggleSelection(e.detail)}
+ on:rangeSelect={(e) => rangeSelect(e.detail)}
/>
{/each}
@@ -1007,6 +1108,26 @@
{/if}
+ {#if showRename}
+
+
+
Переименовать
+
+ Новое имя
+
+
+ {#if renameError}
+
{renameError}
+ {/if}
+
+ Переименовать
+ Отмена
+
+
+
+ {/if}
+
{#if showConfirm}
diff --git a/frontend/src/FileTreeRow.svelte b/frontend/src/FileTreeRow.svelte
index 2c310f5..7b58bc4 100644
--- a/frontend/src/FileTreeRow.svelte
+++ b/frontend/src/FileTreeRow.svelte
@@ -1,7 +1,7 @@
+ aria-label={isFolder ? `Папка ${item.name}` : `Файл ${item.name}`}>
{item.name}
- {#if isFolder}
- Folder
- {:else}
+ {fileType}
+ {#if !isFolder}
+ ·
{formatFileSize(item.size)}
- {#if item.mime}
- ·
- {formatMimeType(item.mime)}
- {/if}
{/if}
{#if !isFolder}
-
dispatch('preview', item)} title="Preview" aria-label="Preview">
+ dispatch('preview', item)} title="Предпросмотр" aria-label="Предпросмотр">
-
+
@@ -156,21 +152,21 @@
{:else}
- dispatch('navigate', item.id)} title="Open folder" aria-label="Open folder">
+ dispatch('navigate', item.id)} title="Открыть папку" aria-label="Открыть папку">
{/if}
-
+
-
+
@@ -183,10 +179,42 @@
{/if}
@@ -201,6 +229,7 @@
transition: background 0.12s;
min-height: 52px;
user-select: none;
+ position: relative;
}
.file-row:hover {
@@ -314,17 +343,19 @@
margin-top: 4px;
background: #1a1a28;
border: 1px solid #2a2a3c;
- border-radius: 6px;
+ border-radius: 8px;
padding: 4px;
z-index: 100;
- min-width: 140px;
- box-shadow: 0 4px 12px rgba(0,0,0,0.4);
+ min-width: 220px;
+ box-shadow: 0 4px 16px rgba(0,0,0,0.5);
}
.menu-item {
- display: block;
+ display: flex;
+ align-items: center;
+ gap: 8px;
width: 100%;
- padding: 6px 12px;
+ padding: 7px 10px;
border: none;
background: transparent;
color: #ccc;
@@ -337,10 +368,25 @@
.menu-item:hover {
background: #2a2a3c;
+ color: #fff;
+ }
+
+ .menu-item-danger {
+ color: #ff6b6b;
+ }
+
+ .menu-item-danger:hover {
+ background: #3a2222;
}
.menu-item:focus-visible {
outline: 2px solid #5588ff;
outline-offset: 1px;
}
+
+ .menu-sep {
+ height: 1px;
+ background: #2a2a3c;
+ margin: 4px 8px;
+ }
diff --git a/frontend/src/lib/FilePreviewModal.svelte b/frontend/src/lib/FilePreviewModal.svelte
index 4513959..b8861df 100644
--- a/frontend/src/lib/FilePreviewModal.svelte
+++ b/frontend/src/lib/FilePreviewModal.svelte
@@ -43,7 +43,7 @@
{formatFileSize(item.size)} · {formatMimeType(item.mime)}
-
+
@@ -60,11 +60,11 @@
{#if loading}
-
+
{:else if error}
{error}
-
Open in external program
+
Открыть во внешней программе
{:else if showImage && content}
@@ -79,14 +79,14 @@
{:else}
-
PDF preview is not available in this environment.
-
Open in external program
+
Предпросмотр PDF недоступен.
+
Открыть во внешней программе
{/if}
{:else}
-
Preview is not available for this file type.
-
Open in external program
+
Предпросмотр недоступен для этого типа файлов.
+
Открыть во внешней программе
{/if}
diff --git a/frontend/src/lib/fileUtils.js b/frontend/src/lib/fileUtils.js
index 5d02ee2..8c37547 100644
--- a/frontend/src/lib/fileUtils.js
+++ b/frontend/src/lib/fileUtils.js
@@ -8,43 +8,53 @@ export function formatFileSize(bytes) {
}
const mimeLabels = {
- 'image/jpeg': 'JPEG image',
- 'image/png': 'PNG image',
- 'image/gif': 'GIF image',
- 'image/webp': 'WebP image',
- 'image/svg+xml': 'SVG image',
- 'image/bmp': 'BMP image',
- 'image/tiff': 'TIFF image',
- 'image/avif': 'AVIF image',
- 'application/pdf': 'PDF document',
- 'application/msword': 'Word document',
- 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'Word document',
- 'application/vnd.ms-excel': 'Excel spreadsheet',
- 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'Excel spreadsheet',
- 'application/vnd.ms-powerpoint': 'PowerPoint presentation',
- 'application/vnd.openxmlformats-officedocument.presentationml.presentation': 'PowerPoint presentation',
- 'application/zip': 'ZIP archive',
- 'application/gzip': 'GZIP archive',
- 'application/x-tar': 'TAR archive',
- 'application/x-7z-compressed': '7z archive',
- 'application/x-rar-compressed': 'RAR archive',
- 'text/plain': 'Text file',
- 'text/html': 'HTML file',
- 'text/css': 'CSS file',
- 'text/javascript': 'JavaScript file',
- 'application/json': 'JSON file',
- 'application/xml': 'XML file',
- 'application/x-yaml': 'YAML file',
- 'application/octet-stream': 'Binary file',
- 'application/x-msdos-program': 'Executable',
- 'inode/directory': 'Folder',
+ 'image/jpeg': 'Изображение JPEG',
+ 'image/png': 'Изображение PNG',
+ 'image/gif': 'Изображение GIF',
+ 'image/webp': 'Изображение WebP',
+ 'image/svg+xml': 'Изображение SVG',
+ 'image/bmp': 'Изображение BMP',
+ 'image/tiff': 'Изображение TIFF',
+ 'image/avif': 'Изображение AVIF',
+ 'application/pdf': 'PDF документ',
+ 'application/msword': 'Документ Word',
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'Документ Word',
+ 'application/vnd.ms-excel': 'Таблица Excel',
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'Таблица Excel',
+ 'application/vnd.ms-powerpoint': 'Презентация PowerPoint',
+ 'application/vnd.openxmlformats-officedocument.presentationml.presentation': 'Презентация PowerPoint',
+ 'application/zip': 'ZIP архив',
+ 'application/gzip': 'GZIP архив',
+ 'application/x-tar': 'TAR архив',
+ 'application/x-7z-compressed': '7z архив',
+ 'application/x-rar-compressed': 'RAR архив',
+ 'text/plain': 'Текстовый файл',
+ 'text/html': 'HTML файл',
+ 'text/css': 'CSS файл',
+ 'text/javascript': 'JavaScript файл',
+ 'application/json': 'JSON файл',
+ 'application/xml': 'XML файл',
+ 'application/x-yaml': 'YAML файл',
+ 'application/octet-stream': 'Бинарный файл',
+ 'application/x-msdos-program': 'Исполняемый файл',
+ 'inode/directory': 'Папка',
}
export function formatMimeType(mime) {
- if (!mime) return 'Unknown'
+ if (!mime) return 'Неизвестно'
return mimeLabels[mime] || mime
}
+export function formatFileType(item) {
+ if (item.type === 'folder') return 'Папка'
+ const mime = (item.mime || '').toLowerCase()
+ if (mimeLabels[mime]) return mimeLabels[mime]
+ const name = (item.name || '').toLowerCase()
+ const ext = name.split('.').pop()
+ if (ext) return ext.toUpperCase()
+ return 'Файл'
+}
+
export function getFileKind(item) {
if (item.type === 'folder') return 'folder'
const mime = (item.mime || '').toLowerCase()
diff --git a/internal/core/files/file.go b/internal/core/files/file.go
index 8b5db47..b75e6a6 100644
--- a/internal/core/files/file.go
+++ b/internal/core/files/file.go
@@ -60,6 +60,59 @@ func (s *Service) DB() *storage.DB {
return s.db
}
+// --- security helpers ---
+
+// ValidateName is an exported wrapper for validateName.
+func ValidateName(name string) error {
+ return validateName(name)
+}
+
+// validateName rejects filenames with path separators, relative components,
+// and other dangerous patterns.
+func validateName(name string) error {
+ if name == "" {
+ return fmt.Errorf("name is required")
+ }
+ if strings.Contains(name, "/") || strings.Contains(name, "\\") {
+ return fmt.Errorf("name must not contain path separators")
+ }
+ if strings.Contains(name, "..") {
+ return fmt.Errorf("name must not contain '..'")
+ }
+ if strings.Contains(name, "\x00") {
+ return fmt.Errorf("name must not contain null bytes")
+ }
+ if len(name) > 255 {
+ return fmt.Errorf("name too long (max 255)")
+ }
+ return nil
+}
+
+// vaultPath resolves a relative vault path and checks it stays within jail.
+func (s *Service) vaultPath(rel string) (string, error) {
+ abs := filepath.Join(s.vaultRoot, rel)
+ cleaned := filepath.Clean(abs)
+ if !strings.HasPrefix(cleaned, filepath.Clean(s.vaultRoot)) {
+ return "", fmt.Errorf("path escapes vault root")
+ }
+ return cleaned, nil
+}
+
+// absPathSafe resolves an absolute path and checks jail if it's under vault.
+// For "external" mode records the path stored may be an absolute external path.
+// This function only checks path safety — it does not enforce that external
+// files must be inside the vault.
+func (s *Service) absPathSafe(rec *Record) (string, error) {
+ if rec.StorageMode == "vault" {
+ return s.vaultPath(rec.Path)
+ }
+ abs, err := filepath.Abs(rec.Path)
+ if err != nil {
+ return "", fmt.Errorf("abs: %w", err)
+ }
+ return filepath.Clean(abs), nil
+}
+
// --- public operations ---
// AddExternal registers an external file (absolute path) without copying.
@@ -84,6 +137,9 @@ func (s *Service) CopyIntoVault(nodeID, absPath, nodeSlug string) (*Record, erro
}
destDir := filepath.Join(s.vaultRoot, "spaces", nodeSlug)
+ if _, err := s.vaultPath(filepath.Join("spaces", nodeSlug)); err != nil {
+ return nil, fmt.Errorf("path safety: %w", err)
+ }
if err := os.MkdirAll(destDir, 0o750); err != nil {
return nil, fmt.Errorf("mkdir: %w", err)
}
@@ -105,6 +161,9 @@ func (s *Service) CopyIntoVault(nodeID, absPath, nodeSlug string) (*Record, erro
}
relPath, _ := filepath.Rel(s.vaultRoot, dest)
+ if _, err := s.vaultPath(relPath); err != nil {
+ return nil, fmt.Errorf("path safety: %w", err)
+ }
return s.insertRecord(nodeID, filename, relPath, "vault", info.Size(), hash)
}
@@ -149,12 +208,19 @@ func (s *Service) DeleteToTrash(id string) error {
return err
}
if rec.StorageMode == "vault" {
- src := filepath.Join(s.vaultRoot, rec.Path)
+ src, err := s.vaultPath(rec.Path)
+ if err != nil {
+ return err
+ }
trashDir := filepath.Join(s.vaultRoot, ".verstak", "trash")
if err := os.MkdirAll(trashDir, 0o750); err != nil {
return err
}
dest := filepath.Join(trashDir, rec.ID+"_"+rec.Filename)
+ // verify trash is inside vault
+ if _, err := s.vaultPath(filepath.Join(".verstak", "trash", rec.ID+"_"+rec.Filename)); err != nil {
+ return err
+ }
if err := os.Rename(src, dest); err != nil {
return fmt.Errorf("move to trash: %w", err)
}
@@ -169,11 +235,9 @@ func (s *Service) Open(id string) error {
if err != nil {
return err
}
- var abs string
- if rec.StorageMode == "vault" {
- abs = filepath.Join(s.vaultRoot, rec.Path)
- } else {
- abs = rec.Path
+ abs, err := s.absPathSafe(rec)
+ if err != nil {
+ return err
}
return openWithSystem(abs)
}
@@ -190,11 +254,9 @@ func (s *Service) ReadText(id string) (string, error) {
if rec.Size > maxPreviewSize {
return "", fmt.Errorf("file too large for preview (%d bytes)", rec.Size)
}
- var abs string
- if rec.StorageMode == "vault" {
- abs = filepath.Join(s.vaultRoot, rec.Path)
- } else {
- abs = rec.Path
+ abs, err := s.absPathSafe(rec)
+ if err != nil {
+ return "", err
}
b, err := os.ReadFile(abs)
if err != nil {
@@ -212,11 +274,9 @@ func (s *Service) ReadBase64(id string) (string, error) {
if rec.Size > maxPreviewSize {
return "", fmt.Errorf("file too large for preview (%d bytes)", rec.Size)
}
- var abs string
- if rec.StorageMode == "vault" {
- abs = filepath.Join(s.vaultRoot, rec.Path)
- } else {
- abs = rec.Path
+ abs, err := s.absPathSafe(rec)
+ if err != nil {
+ return "", err
}
b, err := os.ReadFile(abs)
if err != nil {
@@ -231,6 +291,9 @@ func (s *Service) ReadBase64(id string) (string, error) {
// CreateEmptyFile creates a file node and an empty vault file.
func (s *Service) CreateEmptyFile(parentID, filename string) (*nodes.Node, error) {
+ if err := validateName(filename); err != nil {
+ return nil, fmt.Errorf("invalid filename: %w", err)
+ }
filename = s.uniqueTitle(parentID, filename)
node, err := s.nodes.Create(parentID, nodes.TypeFile, filename, "")
if err != nil {
@@ -247,6 +310,10 @@ func (s *Service) CreateEmptyFile(parentID, filename string) (*nodes.Node, error
}
f.Close()
relPath, _ := filepath.Rel(s.vaultRoot, dest)
+ // Verify dest is inside vault
+ if _, err := s.vaultPath(relPath); err != nil {
+ return nil, fmt.Errorf("path safety: %w", err)
+ }
_, err = s.insertRecord(node.ID, filename, relPath, "vault", 0, "")
return node, err
}
@@ -261,7 +328,7 @@ func (s *Service) Duplicate(nodeID string) (*nodes.Node, error) {
if original.ParentID != nil {
parentID = *original.ParentID
}
- newName := s.uniqueTitle(parentID, original.Title)
+ newName := s.copyTitle(parentID, original.Title)
node, err := s.nodes.Create(parentID, original.Type, newName, original.Section)
if err != nil {
return nil, err
@@ -271,7 +338,10 @@ func (s *Service) Duplicate(nodeID string) (*nodes.Node, error) {
if len(records) > 0 {
src := &records[0]
if src.StorageMode == "vault" {
- srcPath := filepath.Join(s.vaultRoot, src.Path)
+ srcPath, err := s.vaultPath(src.Path)
+ if err != nil {
+ return nil, err
+ }
dir := filepath.Join(s.vaultRoot, "spaces", node.Slug)
os.MkdirAll(dir, 0o750)
dst := filepath.Join(dir, newName)
@@ -280,6 +350,9 @@ func (s *Service) Duplicate(nodeID string) (*nodes.Node, error) {
return nil, fmt.Errorf("copy file: %w", err)
}
relPath, _ := filepath.Rel(s.vaultRoot, dst)
+ if _, err := s.vaultPath(relPath); err != nil {
+ return nil, fmt.Errorf("path safety: %w", err)
+ }
_, err = s.insertRecord(node.ID, newName, relPath, "vault", src.Size, hash)
if err != nil {
return nil, err
@@ -452,6 +525,35 @@ func (s *Service) uniqueTitle(parentID, desired string) string {
}
}
+// copyTitle generates a unique "Name (copy).ext" style name for duplicates.
+// For files with extensions: "photo.jpg" → "photo (copy).jpg", "photo (copy 2).jpg"
+// For folders: "Docs" → "Docs (copy)", "Docs (copy 2)"
+func (s *Service) copyTitle(parentID, desired string) string {
+ children, _ := s.nodes.ListChildren(parentID, false)
+ used := make(map[string]bool, len(children))
+ for i := range children {
+ used[children[i].Title] = true
+ }
+
+ ext := filepath.Ext(desired)
+ base := strings.TrimSuffix(desired, ext)
+ copyName := base + " (copy)" + ext
+ if !used[copyName] {
+ return copyName
+ }
+ for n := 2; ; n++ {
+ candidate := fmt.Sprintf("%s (copy %d)%s", base, n, ext)
+ if !used[candidate] {
+ return candidate
+ }
+ }
+}
+
+// UniqueTitleCopy returns a copy-style unique name for use in conflict resolution.
+func (s *Service) UniqueTitleCopy(parentID, desired string) string {
+ return s.copyTitle(parentID, desired)
+}
+
// --- implementation details ---
func (s *Service) insertRecord(nodeID, filename, path, mode string, size int64, sha string) (*Record, error) {