kandown 0.3.2 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/kandown.js +10 -1
- package/dist/index.html +1 -1
- package/package.json +1 -1
package/bin/kandown.js
CHANGED
|
@@ -91,7 +91,16 @@ async function checkForUpdate(argv = process.argv) {
|
|
|
91
91
|
})).trim().replace(/^"|"$/g, '');
|
|
92
92
|
if (newVersion === latest) {
|
|
93
93
|
log(`${c.green}✓ Updated to v${newVersion}${c.reset} — restarting…`);
|
|
94
|
-
|
|
94
|
+
// Resolve the global bin directory from npm prefix and spawn directly,
|
|
95
|
+
// bypassing npx which may re-resolve the cached old version.
|
|
96
|
+
const npmPrefix = String(execSync('npm config get prefix 2>/dev/null', {
|
|
97
|
+
timeout: 5000, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
|
|
98
|
+
})).trim();
|
|
99
|
+
const isMacos = existsSync(join(npmPrefix, 'bin', 'kandown'));
|
|
100
|
+
const kandownBin = isMacos
|
|
101
|
+
? join(npmPrefix, 'bin', 'kandown')
|
|
102
|
+
: join(npmPrefix, 'bin', 'kandown');
|
|
103
|
+
const child = spawn(kandownBin, ['--experimental-vm-modules', ...argv.slice(1)], {
|
|
95
104
|
detached: true, stdio: 'ignore', env: { ...process.env } });
|
|
96
105
|
child.unref();
|
|
97
106
|
process.exit(0);
|
package/dist/index.html
CHANGED
|
@@ -112,7 +112,7 @@ Error generating stack: `+g.message+`
|
|
|
112
112
|
|
|
113
113
|
## Subtasks
|
|
114
114
|
|
|
115
|
-
`}}}async function Hu(t,e,n,i){if(fr())return lV(e,mA(n,i));const r=await(await t.getFileHandle(`${e}.md`,{create:!0})).createWritable();await r.write(mA(n,i)),await r.close()}async function bA(t,e){if(fr())return uV(e);try{await t.removeEntry(`${e}.md`)}catch{}}const mV="kanban-md",gV=1,uc="recentProjects";function OR(){return new Promise((t,e)=>{const n=indexedDB.open(mV,gV);n.onerror=()=>e(n.error),n.onsuccess=()=>t(n.result),n.onupgradeneeded=()=>{const i=n.result;i.objectStoreNames.contains(uc)||i.createObjectStore(uc,{keyPath:"id"})}})}async function D0(t){const e=await OR();return new Promise((n,i)=>{const a=e.transaction(uc,"readwrite");a.objectStore(uc).put(t),a.oncomplete=()=>n(),a.onerror=()=>i(a.error)})}async function wk(){const t=await OR();return new Promise((e,n)=>{const a=t.transaction(uc,"readonly").objectStore(uc).getAll();a.onsuccess=()=>{const r=a.result||[];r.sort((o,u)=>u.lastOpened-o.lastOpened),e(r.slice(0,10))},a.onerror=()=>n(a.error)})}async function kA(t,e=!0){const n={mode:e?"readwrite":"read"};return await t.queryPermission(n)==="granted"||await t.requestPermission(n)==="granted"}class vV{dirHandle=null;tasksDirHandle=null;intervalId=null;configHash=null;taskHashes=new Map;knownTaskIds=new Set;listeners=new Map;debounceTimers=new Map;debounceDelay=200;start(e,n){this.dirHandle=e,this.tasksDirHandle=n,this.initHashes(),this.intervalId=setInterval(()=>void this.tick(),500)}stop(){this.intervalId&&(clearInterval(this.intervalId),this.intervalId=null),this.configHash=null,this.taskHashes.clear(),this.knownTaskIds.clear(),this.listeners.clear(),this.debounceTimers.forEach(e=>clearTimeout(e)),this.debounceTimers.clear()}on(e,n){return this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(n),()=>{this.listeners.get(e)?.delete(n)}}getKnownTaskIds(){return Array.from(this.knownTaskIds)}async initHashes(){if(!this.dirHandle||!this.tasksDirHandle)return;const e=await wA(this.dirHandle);e!==null&&(this.configHash=await this.hash(e)),await this.syncTaskDir(!1)}async tick(){if(!this.dirHandle||!this.tasksDirHandle)return;const e=await wA(this.dirHandle);if(e!==null){const n=await this.hash(e);this.configHash!==null&&n!==this.configHash&&(this.configHash=n,this.debouncedEmit("configChanged"))}for(const n of this.knownTaskIds){const i=await xA(this.tasksDirHandle,n);if(i!==null){const a=await this.hash(i),r=this.taskHashes.get(n);r!==void 0&&a!==r&&(this.taskHashes.set(n,a),this.debouncedEmit("taskChanged",n))}}await this.syncTaskDir(!0)}debouncedEmit(e,...n){const i=e+JSON.stringify(n),a=this.debounceTimers.get(i);a&&clearTimeout(a);const r=setTimeout(()=>{this.debounceTimers.delete(i),this.emit(e,...n)},this.debounceDelay);this.debounceTimers.set(i,r)}async syncTaskDir(e){if(this.tasksDirHandle){for await(const n of this.tasksDirHandle.values())if(n.kind==="file"&&n.name.endsWith(".md")){const i=n.name.replace(".md","");if(!this.knownTaskIds.has(i)){this.knownTaskIds.add(i);const a=await xA(this.tasksDirHandle,i);a!==null&&(this.taskHashes.set(i,await this.hash(a)),e&&this.debouncedEmit("newTaskDetected",i))}}}}async hash(e){const i=new TextEncoder().encode(e),a=await crypto.subtle.digest("SHA-256",i);return Array.from(new Uint8Array(a)).map(o=>o.toString(16).padStart(2,"0")).join("")}emit(e,...n){this.listeners.get(e)?.forEach(a=>{if(e==="configChanged"){a();return}if(e==="taskChanged"){const[o]=n;a(o);return}const[r]=n;a(r)})}}async function wA(t){try{return await(await(await t.getFileHandle("kandown.json")).getFile()).text()}catch{return null}}async function xA(t,e){try{return await(await(await t.getFileHandle(`${e}.md`)).getFile()).text()}catch{return null}}const $u=new vV,yV={soft:[{frequency:520,durationMs:90,delayMs:0,type:"sine"},{frequency:720,durationMs:120,delayMs:95,type:"sine"}],chime:[{frequency:660,durationMs:120,delayMs:0,type:"triangle"},{frequency:880,durationMs:160,delayMs:125,type:"triangle"}],ping:[{frequency:920,durationMs:110,delayMs:0,type:"sine"}],pop:[{frequency:260,durationMs:55,delayMs:0,type:"square"},{frequency:520,durationMs:80,delayMs:60,type:"square"}]};let _A=null;function NR(){return"Notification"in window?Notification.permission:"unsupported"}async function bV(){return"Notification"in window?await Notification.requestPermission():"unsupported"}function C0({title:t,body:e,config:n}){if(n.notifications.sound&&kV(n.notifications.soundId),!(!n.notifications.browser||NR()!=="granted"))try{new Notification(t,{body:e})}catch{}}function kV(t){const e=yV[t],n=window.AudioContext??window.webkitAudioContext;if(!(!n||e.length===0))try{_A??=new n;const i=_A;i.state==="suspended"&&i.resume();const a=i.currentTime+.01;e.forEach(r=>{const o=i.createOscillator(),u=i.createGain(),c=a+r.delayMs/1e3,d=c+r.durationMs/1e3;o.type=r.type??"sine",o.frequency.setValueAtTime(r.frequency,c),u.gain.setValueAtTime(1e-4,c),u.gain.exponentialRampToValueAtTime(.08,c+.01),u.gain.exponentialRampToValueAtTime(1e-4,d),o.connect(u),u.connect(i.destination),o.start(c),o.stop(d+.02)})}catch{}}function wV(t){let e=-1;for(const n of t)for(const i of n.tasks){const a=i.id.match(/^t(\d+)$/);if(a){const r=parseInt(a[1],10);r>e&&(e=r)}}return"t"+(e+1)}async function xV(t){const e=await pV(t);return await Promise.all(e.map(async i=>{const{frontmatter:a,body:r}=await ks(t,i),o={...a,id:a.id||i,status:a.status||"Backlog"},{subtasks:u,bodyWithoutSubtasks:c}=bs(r);return{id:i,frontmatter:o,body:c,subtasks:u}}))}async function SA(t,e,n){const i=[];for(const a of e){const r=a.name;a.tasks.forEach((o,u)=>{i.push((async()=>{const{frontmatter:c,body:d}=await ks(t,o.id);await Hu(t,o.id,{...c,id:o.id,status:r,order:u},d)})())})}await Promise.all(i)}function yl(t){iV(t.ui.theme,t.ui.skin,t.ui.font,t.ui.background)}function xk(t){return{title:t.frontmatter.title||t.id,status:t.frontmatter.status||"Backlog",body:t.body,subtasks:t.subtasks}}function DA(t){Zu.clear(),t.forEach(e=>{Zu.set(e.id,xk(e))})}function _V(t,e){return e.reduce((n,i,a)=>{const r=t[a]?.done??!1;return n+(i.done&&!r?1:0)},0)}function SV(t,e){const n=t.subtasks.map(a=>({text:a.text,description:a.description??"",report:a.report??""})),i=e.subtasks.map(a=>({text:a.text,description:a.description??"",report:a.report??""}));return t.title!==e.title||t.body!==e.body||JSON.stringify(n)!==JSON.stringify(i)}let DV=0;const Zu=new Map,Od=new Map,ze=WU((t,e)=>({isOpen:!1,loading:!1,dirHandle:null,projectName:null,tasksDirHandle:null,boardTitle:"Project Kanban",columns:[],taskContents:new Map,searchMatches:new Map,viewMode:localStorage.getItem("kandown:view")||"board",density:localStorage.getItem("kandown:density")||"comfortable",filters:{search:"",priority:null,tag:null,assignee:null,ownerType:null},commandOpen:!1,drawerTaskId:null,drawerData:null,currentPage:"board",config:La,recentProjects:[],toasts:[],drawerBaseVersion:null,conflictState:null,showConflictModal:!1,openFolder:async()=>{const n=await dV();if(!n)return;const{projectHandle:i,kandownHandle:a}=n,r=await uf(a),o=i.name;t({dirHandle:a,tasksDirHandle:r,projectName:o}),window.history.pushState({},"",`?p=${encodeURIComponent(o)}`);const u=fr()?S0():null;await D0({id:i.name,name:i.name,handle:i,lastOpened:Date.now(),...u?{kandownDir:u}:{}}),await e().loadConfig(),await e().reloadBoard();const c=await wk();t({recentProjects:c}),e().setupWatcher()},openRecentProject:async n=>{if(!await kA(n.handle,!0)){e().toast("Permission denied","error");return}const a=await yA(n.handle),r=await uf(a),o=n.handle.name;t({dirHandle:a,tasksDirHandle:r,projectName:o}),window.history.pushState({},"",`?p=${encodeURIComponent(o)}`),await D0({...n,lastOpened:Date.now()}),await e().loadConfig(),await e().reloadBoard(),e().setupWatcher()},openServerProject:async()=>{t({loading:!0});try{const n=S0();if(!n)throw new Error("No server root");const i=n.split("/").filter(Boolean).pop()??"Project",a=await FR();yl(a);const r=await BR(),o=await Promise.all(r.map(async p=>{const{frontmatter:m,body:v}=await oV(p),y={...m,id:m.id||p,status:m.status||"Backlog"},{subtasks:_,bodyWithoutSubtasks:w}=bs(v);return{id:p,frontmatter:y,body:w,subtasks:_}}));DA(o);const u=o.map(p=>({frontmatter:p.frontmatter,body:Fd(p.body,p.subtasks)})),c=vA(u,a.board.columns),d=c.reduce((p,m)=>p+m.tasks.length,0),h=new Map;if(d<=10)for(const p of o)h.set(p.frontmatter.id,{frontmatter:p.frontmatter,subtasks:p.subtasks,body:p.body});t({loading:!1,isOpen:!0,config:a,columns:c,boardTitle:"Project Kanban",projectName:i,taskContents:h,searchMatches:new Map}),window.history.pushState({},"",`?p=${encodeURIComponent(i)}`)}catch{t({loading:!1,isOpen:!1}),e().toast("Impossible de charger le projet. Relancez `kandown`.","error")}},tryAutoOpenServerProject:async()=>{if(!fr())return;const n=S0();if(!n)return;const i=await wk(),a=i.find(d=>d.kandownDir===n);if(!a){await e().openServerProject();return}if(!await kA(a.handle,!0)){await e().openServerProject();return}const o=await yA(a.handle),u=await uf(o),c=a.handle.name;t({dirHandle:o,tasksDirHandle:u,projectName:c,recentProjects:i}),window.history.pushState({},"",`?p=${encodeURIComponent(c)}`),await D0({...a,lastOpened:Date.now()}),await e().loadConfig(),await e().reloadBoard(),e().setupWatcher()},loadConfig:async()=>{const{dirHandle:n}=e();if(n)try{const i=await fV(n);i?(t({config:i}),yl(i)):(t({config:La}),yl(La))}catch{t({config:La}),yl(La)}},updateConfig:async n=>{const{dirHandle:i,config:a}=e();if(!i)return;const r=n(a);t({config:r}),yl(r);try{await hV(i,r)}catch(o){e().toast("Failed to save config: "+o.message,"error")}},reloadBoard:async()=>{const{tasksDirHandle:n,config:i}=e();if(n)try{const a=await xV(n);DA(a);const r=a.map(d=>({frontmatter:d.frontmatter,body:Fd(d.body,d.subtasks)})),o=vA(r,i.board.columns);t({boardTitle:"Project Kanban",columns:o});const u=o.reduce((d,h)=>d+h.tasks.length,0),c=new Map;if(u<=10)for(const d of a)c.set(d.frontmatter.id,{frontmatter:d.frontmatter,subtasks:d.subtasks,body:d.body});t({taskContents:c,searchMatches:new Map})}catch(a){e().toast("Failed to load board: "+a.message,"error")}},moveTask:async(n,i,a,r)=>{const{columns:o,tasksDirHandle:u,config:c}=e();if(!u)return;const d=o.find(w=>w.name===i),h=o.find(w=>w.name===a);if(!d||!h)return;const p=d.tasks.findIndex(w=>w.id===n);if(p===-1)return;const m=o.map(w=>({...w,tasks:[...w.tasks]})),v=m.find(w=>w.name===i),y=m.find(w=>w.name===a),[_]=v.tasks.splice(p,1);/done|termin|closed|complet/i.test(a)?_.checked=!0:_.checked=!1,r!==void 0?y.tasks.splice(r,0,_):y.tasks.push(_),t({columns:m});try{const w=i===a?m.filter(b=>b.name===a):m.filter(b=>b.name===i||b.name===a);await SA(u,w,c.board.columns)}catch(w){e().toast("Failed to save: "+w.message,"error"),t({columns:o})}},reorderInColumn:async(n,i,a)=>{const{columns:r,tasksDirHandle:o,config:u}=e();if(!o)return;const c=r.map(p=>({...p,tasks:[...p.tasks]})),d=c.find(p=>p.name===n);if(!d)return;const[h]=d.tasks.splice(i,1);d.tasks.splice(a,0,h),t({columns:c});try{await SA(o,[d],u.board.columns)}catch(p){e().toast("Failed to save: "+p.message,"error"),t({columns:r})}},addColumn:async n=>{const i=n.trim();if(!i)return;const{config:a}=e();a.board.columns.some(r=>r.toLowerCase()===i.toLowerCase())||(await e().updateConfig(r=>({...r,board:{...r.board,columns:[...r.board.columns,i]}})),await e().reloadBoard())},renameColumn:async(n,i)=>{const a=i.trim(),{columns:r,tasksDirHandle:o,config:u}=e();if(!o||!a||a.toLowerCase()===n.toLowerCase())return;if(r.some(h=>h.name.toLowerCase()===a.toLowerCase())){e().toast("Column already exists","error");return}const c=r,d=r.map(h=>h.name===n?{...h,name:a}:h);t({columns:d});try{const h=c.find(p=>p.name===n);h&&await Promise.all(h.tasks.map(async(p,m)=>{const{frontmatter:v,body:y}=await ks(o,p.id);await Hu(o,p.id,{...v,id:p.id,status:a,order:m},y)})),await e().updateConfig(p=>{const m={...p.board.columnColors??{}},v=m[n.toLowerCase()];v&&(m[a.toLowerCase()]=v,delete m[n.toLowerCase()]);const y=p.board.columns.some(_=>_.toLowerCase()===n.toLowerCase())?p.board.columns:[...p.board.columns,n];return{...p,board:{...p.board,columns:y.map(_=>_.toLowerCase()===n.toLowerCase()?a:_),columnColors:m}}}),await e().reloadBoard()}catch(h){e().toast("Failed to rename column: "+h.message,"error"),t({columns:c})}},deleteColumn:async n=>{const{columns:i,tasksDirHandle:a}=e();if(!a)return;const r=i.find(u=>u.name===n);if(!r)return;const o=i;t({columns:i.filter(u=>u.name!==n)});try{await Promise.all(r.tasks.map(u=>bA(a,u.id))),await e().updateConfig(u=>{const c={...u.board.columnColors??{}};return delete c[n.toLowerCase()],{...u,board:{...u.board,columns:u.board.columns.filter(d=>d.toLowerCase()!==n.toLowerCase()),columnColors:c}}}),await e().reloadBoard(),e().toast("Column deleted")}catch(u){e().toast("Failed to delete column: "+u.message,"error"),t({columns:o})}},createTask:async n=>{const{columns:i,tasksDirHandle:a,config:r,taskContents:o}=e();if(!a||!i.length)return null;const u=n||r.board.columns[0]||i[0].name,c=wV(i),d=i.find(m=>m.name===u)?.tasks.length??0,h={id:c,title:"",checked:!1,tags:[],assignee:null,priority:r.fields.priority?r.board.defaultPriority:null,ownerType:r.fields.ownerType?r.board.defaultOwnerType:"",progress:null},p=i.map(m=>m.name===u?{...m,tasks:[...m.tasks,h]}:m);t({columns:p});try{const m={id:c,title:"",status:u,order:d,priority:r.fields.priority?r.board.defaultPriority:"",tags:[],assignee:"",created:new Date().toISOString().slice(0,10),ownerType:r.fields.ownerType?r.board.defaultOwnerType:"",tools:""},v="";await Hu(a,c,m,v);const y=new Map(o);return y.set(c,{frontmatter:m,subtasks:[],body:v}),t({taskContents:y}),e().toast(`Created ${c.replace(/^t/,"")}`),await e().openDrawer(c),c}catch(m){return e().toast("Failed to create: "+m.message,"error"),t({columns:i}),null}},deleteTask:async n=>{const{columns:i,tasksDirHandle:a,taskContents:r}=e();if(!a)return;const o=i.map(d=>({...d,tasks:d.tasks.filter(h=>h.id!==n)}));t({columns:o});const u=new Map(r);u.delete(n);const c=new Map(e().searchMatches);c.delete(n),t({taskContents:u,searchMatches:c});try{await bA(a,n),e().toast("Deleted")}catch(d){e().toast("Failed to delete: "+d.message,"error"),t({columns:i})}},openDrawer:async n=>{const{tasksDirHandle:i}=e();if(i)try{const{frontmatter:a,body:r}=await ks(i,n),{subtasks:o,bodyWithoutSubtasks:u}=bs(r),c={frontmatter:a,subtasks:o,body:u,savedAt:Date.now()};t({drawerTaskId:n,drawerData:{frontmatter:a,subtasks:o,body:u},drawerBaseVersion:c,conflictState:null,showConflictModal:!1})}catch(a){e().toast("Failed to open: "+a.message,"error")}},closeDrawer:()=>t({drawerTaskId:null,drawerData:null,drawerBaseVersion:null,conflictState:null,showConflictModal:!1}),updateDrawerData:n=>{const{drawerData:i}=e();i&&t({drawerData:n(i)})},saveDrawer:async()=>{const{drawerTaskId:n,drawerData:i,tasksDirHandle:a,taskContents:r}=e();if(!n||!i||!a)return;const o=Fd(i.body,i.subtasks),u={...i.frontmatter,id:n};try{await Hu(a,n,u,o),e().toast("Saved"),t({drawerTaskId:null,drawerData:null});const c=new Map(r);c.set(n,{frontmatter:u,subtasks:i.subtasks,body:i.body}),t({taskContents:c}),await e().reloadBoard()}catch(c){e().toast("Failed to save: "+c.message,"error")}},saveDrawerMetadata:async()=>{const{drawerTaskId:n,drawerData:i,tasksDirHandle:a,taskContents:r}=e();if(!n||!i||!a)return;const o=Fd(i.body,i.subtasks),u={...i.frontmatter,id:n};try{await Hu(a,n,u,o);const c=new Map(r);c.set(n,{frontmatter:u,subtasks:i.subtasks,body:i.body}),t({taskContents:c}),await e().reloadBoard()}catch(c){e().toast("Failed to save: "+c.message,"error")}},setViewMode:n=>{localStorage.setItem("kandown:view",n),t({viewMode:n})},setDensity:n=>{localStorage.setItem("kandown:density",n),t({density:n})},setFilter:(n,i)=>{if(t(a=>({filters:{...a.filters,[n]:i}})),n==="search"){const{columns:a,tasksDirHandle:r,taskContents:o}=e(),u=i,c=a.flatMap(d=>d.tasks.map(h=>h.id));if(r){const d=c.filter(h=>!o.has(h));d.length>0?e().loadTaskContents(d).then(()=>{e().computeSearchMatches(u)}):e().computeSearchMatches(u)}}},clearFilters:()=>t({filters:{search:"",priority:null,tag:null,assignee:null,ownerType:null},searchMatches:new Map}),setCommandOpen:n=>t({commandOpen:n}),setCurrentPage:n=>t({currentPage:n}),loadTaskContents:async n=>{const{tasksDirHandle:i}=e();if(!i)return;const a=new Map(e().taskContents);await Promise.all(n.map(async r=>{if(!a.has(r))try{const{frontmatter:o,body:u}=await ks(i,r),{subtasks:c,bodyWithoutSubtasks:d}=bs(u);a.set(r,{frontmatter:o,subtasks:c,body:d})}catch{}})),t({taskContents:a})},computeSearchMatches:n=>{if(!n.trim()){t({searchMatches:new Map});return}const{taskContents:i}=e(),a=new Map,r=n.toLowerCase();for(const[o,u]of i){const c=RR(u,r);c.length>0&&a.set(o,c)}t({searchMatches:a})},toast:(n,i="success")=>{const a=++DV;t(r=>({toasts:[...r.toasts,{id:a,message:n,type:i}]})),setTimeout(()=>{t(r=>({toasts:r.toasts.filter(o=>o.id!==a)}))},2500)},dismissToast:n=>t(i=>({toasts:i.toasts.filter(a=>a.id!==n)})),resolveConflict:async n=>{const{conflictState:i,drawerData:a,tasksDirHandle:r,drawerTaskId:o,drawerBaseVersion:u}=e();if(!(!i||!r||!o))if(n==="reload"){const{frontmatter:c,body:d}=await ks(r,o),{subtasks:h,bodyWithoutSubtasks:p}=bs(d);t({drawerData:{frontmatter:c,subtasks:h,body:p},drawerBaseVersion:{frontmatter:c,subtasks:h,body:p,savedAt:Date.now()},conflictState:null,showConflictModal:!1}),e().toast("Reloaded from disk")}else if(n==="overwrite"){if(a&&o&&u){const c=Fd(a.body,a.subtasks),d={...a.frontmatter,id:o};await Hu(r,o,d,c),t({drawerBaseVersion:{...a,savedAt:Date.now()},conflictState:null,showConflictModal:!1}),e().toast("Overwritten remote changes")}}else t({conflictState:null,showConflictModal:!1})},setupWatcher:()=>{const{dirHandle:n,tasksDirHandle:i}=e();if(!n||!i)return;$u.stop(),Od.forEach(o=>clearTimeout(o)),Od.clear(),$u.start(n,i);const a=(o,u)=>{const c=Od.get(o);c&&clearTimeout(c);const d=Math.max(2e3,e().config.notifications.editDebounceMs),h=setTimeout(()=>{Od.delete(o);const p=e().config;p.notifications.taskEdits&&C0({title:"Task edited",body:`${u} changed on disk.`,config:p})},d);Od.set(o,h)},r=async o=>{const{tasksDirHandle:u,config:c}=e();if(!u)return;const{frontmatter:d,body:h}=await ks(u,o),{subtasks:p,bodyWithoutSubtasks:m}=bs(h),v={id:o,frontmatter:{...d,id:d.id||o,status:d.status||"Backlog"},body:m,subtasks:p},y=xk(v),_=Zu.get(o);if(!_){Zu.set(o,y);return}c.notifications.statusChanges&&_.status!==y.status&&C0({title:"Task status changed",body:`${y.title}: ${_.status} → ${y.status}`,config:c});const w=_V(_.subtasks,y.subtasks);c.notifications.subtaskCompletions&&w>0&&C0({title:"Subtask completed",body:w===1?`${y.title}: 1 subtask completed.`:`${y.title}: ${w} subtasks completed.`,config:c}),SV(_,y)&&a(o,y.title),Zu.set(o,y)};$u.on("configChanged",()=>{e().loadConfig(),e().toast("Settings updated externally","info")}),$u.on("taskChanged",async o=>{const{drawerTaskId:u,drawerBaseVersion:c,tasksDirHandle:d}=e();if(await r(o),u===o&&c&&d){const{frontmatter:h,body:p}=await ks(d,o),{subtasks:m,bodyWithoutSubtasks:v}=bs(p),y=c,_=JSON.stringify(y.frontmatter)!==JSON.stringify(h),w=y.body!==v,b=JSON.stringify(y.subtasks)!==JSON.stringify(m);if(!_&&!w&&!b)return;let C="none";_&&(w||b)?C="full":_?C="metadata-only":(w||b)&&(C="body-only"),t({conflictState:{taskId:o,type:C,local:y,remote:{frontmatter:h,body:v,subtasks:m}},showConflictModal:C==="full"})}else e().reloadBoard()}),$u.on("newTaskDetected",async o=>{const{tasksDirHandle:u}=e();if(u){const{frontmatter:c,body:d}=await ks(u,o),{subtasks:h,bodyWithoutSubtasks:p}=bs(d);Zu.set(o,xk({id:o,frontmatter:{...c,id:c.id||o,status:c.status||"Backlog"},body:p,subtasks:h}))}e().reloadBoard()})}}));wk().then(t=>{ze.setState({recentProjects:t})});yl(La);window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",()=>{yl(ze.getState().config)});function CV(t){const e=i7(t,{stiffness:180,damping:22,mass:.8});return H.useEffect(()=>{e.set(t)},[t,e]),oR(e,i=>Math.round(i).toString())}const _k="0.3.2",AV=({className:t})=>x.jsxs("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 150 150",className:t,fill:"currentColor",children:[x.jsx("path",{d:"m57.6 64.6 0.1-0.1v-31.3c-0.1-3.5-2.7-5.6-5.7-5.6h-16.3v59.6c0.2-1.3 0.9-2.6 1.7-3.2l20.2-19.4z"}),x.jsx("path",{d:"m87.6 43.8c-3.4 0.1-7.1 1.3-9.9 3.7l-38.5 38.7c-2.1 2.1-3.4 4.8-3.5 7.5v26.7l77.5-76.4v-0.2h-25.6z"}),x.jsx("path",{d:"m108.1 96.4-6 5-22.4-20.8-14.6 14.2 21.1 21.4-5 4.1c-0.6 0.5-0.3 0.9 0.2 0.9h27.6c0.4 0 0.7-0.4 0.7-0.6v-24.8c0-0.7-1.1-0.3-1.6 0.3v0.3z"})]});function EV(){const{t}=di(),e=ze(O=>O.dirHandle);ze(O=>O.projectName);const n=ze(O=>O.columns),i=ze(O=>O.openFolder),a=ze(O=>O.reloadBoard),r=ze(O=>O.createTask),o=ze(O=>O.setCommandOpen),u=ze(O=>O.viewMode),c=ze(O=>O.setViewMode),d=ze(O=>O.density),h=ze(O=>O.setDensity),p=ze(O=>O.setCurrentPage),m=ze(O=>O.recentProjects),v=ze(O=>O.openRecentProject),y=ze(O=>O.filters),_=ze(O=>O.setFilter),w=ze(O=>O.clearFilters),b=ze(O=>O.config.fields),[C,D]=H.useState(!1),A=H.useRef(null),M=H.useRef(null),T=n.reduce((O,$)=>O+$.tasks.length,0),F=CV(T),R=[];b.priority&&y.priority&&R.push({type:"priority",label:y.priority,value:y.priority}),b.tags&&y.tag&&R.push({type:"tag",label:"#"+y.tag,value:y.tag}),b.assignee&&y.assignee&&R.push({type:"assignee",label:"@"+y.assignee,value:y.assignee});const P=[{label:t("filterBar.ownerAll"),value:""},{label:t("filterBar.ownerHuman"),value:"human"},{label:t("filterBar.ownerAI"),value:"ai"}],E=R.length>0||y.search||b.ownerType&&y.ownerType;return H.useEffect(()=>{if(!C)return;const O=$=>{A.current&&!A.current.contains($.target)&&D(!1)};return document.addEventListener("mousedown",O),()=>document.removeEventListener("mousedown",O)},[C]),x.jsxs("header",{className:"flex items-center justify-between px-5 h-14 border-b border-border bg-bg relative z-10",children:[x.jsxs("div",{className:"flex items-center gap-3 min-w-0",children:[x.jsx("div",{className:"flex items-center gap-2 flex-shrink-0",children:x.jsxs("button",{onClick:()=>window.history.pushState({},"",window.location.pathname),className:"flex items-center gap-2 cursor-pointer",children:[x.jsx(AV,{className:"w-[36px] h-[36px] dark:text-white text-black"}),x.jsx("span",{className:"text-[15px] font-semibold tracking-tight",children:t("app.name")}),x.jsxs("span",{className:"text-[11px] font-mono font-bold text-red-500",children:["v",_k]})]})}),e&&x.jsxs(x.Fragment,{children:[x.jsx("div",{className:"w-px h-[18px] bg-border flex-shrink-0"}),x.jsxs("div",{className:"flex items-center gap-2 px-2.5 h-8 bg-bg-2 border border-border rounded-[8px] min-w-[180px] max-w-[260px] focus-within:border-border-focus focus-within:bg-bg-3 transition-colors",children:[x.jsx(oi.Search,{size:14,className:"text-fg-muted flex-shrink-0"}),x.jsx("input",{ref:M,type:"text",placeholder:t("filterBar.searchPlaceholder"),value:y.search,onChange:O=>_("search",O.target.value),className:"bg-transparent border-none outline-none text-fg text-[13px] w-full placeholder:text-fg-muted"}),y.search&&x.jsx("button",{onClick:()=>_("search",""),className:"text-fg-muted hover:text-fg flex-shrink-0",children:x.jsx(oi.X,{size:14})})]}),x.jsxs("div",{className:"flex items-center gap-1.5 flex-wrap overflow-hidden",children:[x.jsx(ba,{children:R.map(O=>x.jsxs(Xt.button,{initial:{opacity:0,scale:.9},animate:{opacity:1,scale:1},exit:{opacity:0,scale:.9},transition:{duration:.15},onClick:()=>_(O.type,null),className:"inline-flex items-center gap-1 h-6 px-2 text-[12px] text-fg bg-bg-3 border border-border-strong rounded-[4px] hover:bg-bg-hover transition-colors",children:[O.label,x.jsx(oi.X,{size:10,className:"text-fg-muted"})]},O.type+O.value))}),b.ownerType&&x.jsx("div",{className:"flex items-center h-6 border border-border-strong rounded-[4px] overflow-hidden",children:P.map(O=>x.jsx("button",{onClick:()=>_("ownerType",O.value),className:`h-full px-2 text-[12px] transition-colors ${y.ownerType===O.value?"bg-bg-hover text-fg":"text-fg-muted hover:text-fg"}`,children:O.label},O.value))}),E&&x.jsx("button",{onClick:w,className:"text-[12px] text-fg-muted hover:text-fg transition-colors",children:t("filterBar.clearAll")})]})]}),e&&x.jsxs(x.Fragment,{children:[x.jsx("div",{className:"w-px h-[18px] bg-border flex-shrink-0"}),x.jsxs("div",{className:"relative flex-shrink-0",ref:A,children:[x.jsxs("button",{onClick:()=>D(O=>!O),className:"flex items-center gap-1.5 px-2 py-1 text-[14px] text-fg-dim hover:text-fg hover:bg-bg-2 rounded-[6px] transition-colors",children:[x.jsx(oi.Folder,{size:12,className:"text-fg-muted"}),x.jsx("span",{children:e.name}),x.jsx(oi.ChevronDown,{size:10,className:"opacity-60"})]}),x.jsx(ba,{children:C&&x.jsx(Xt.div,{initial:{opacity:0,y:-4,scale:.98},animate:{opacity:1,y:0,scale:1},exit:{opacity:0,y:-4,scale:.98},transition:{duration:.12},className:"absolute top-full left-0 mt-1.5 min-w-[240px] glass rounded-[8px] shadow-[0_16px_48px_rgba(0,0,0,0.5)] overflow-hidden z-50",children:x.jsxs("div",{className:"py-1.5",children:[m.length>0&&x.jsxs(x.Fragment,{children:[x.jsx("div",{className:"px-3 py-1 text-[11px] font-semibold uppercase tracking-wider text-fg-faint",children:t("header.recentProjects")}),m.map(O=>x.jsxs("button",{onClick:()=>{D(!1),v(O)},className:"w-full flex items-center gap-2 px-3 py-1.5 text-[13.5px] text-left hover:bg-bg-3 transition-colors",children:[x.jsx(oi.Folder,{size:12,className:"text-fg-muted"}),x.jsx("span",{className:"truncate",children:O.name}),O.id===e.name&&x.jsx(oi.Check,{size:12,className:"ml-auto text-success"})]},O.id)),x.jsx("div",{className:"h-px bg-border my-1.5 mx-2"})]}),x.jsxs("button",{onClick:()=>{D(!1),i()},className:"w-full flex items-center gap-2 px-3 py-1.5 text-[13.5px] text-left hover:bg-bg-3 transition-colors",children:[x.jsx(oi.Plus,{size:12,className:"text-fg-muted"}),x.jsx("span",{children:t("header.openFolder...")})]})]})})})]})]})]}),x.jsx("div",{className:"flex items-center gap-1.5 flex-shrink-0",children:e?x.jsxs(x.Fragment,{children:[x.jsxs("div",{className:"flex items-center gap-2 mr-2 text-[13px] text-fg-muted",children:[x.jsx("span",{className:"inline-block w-1.5 h-1.5 rounded-full bg-success"}),x.jsx(Xt.span,{className:"tabular-nums",children:F}),x.jsx("span",{children:t("header.tasks")})]}),x.jsxs("div",{className:"flex items-center bg-bg-2 border border-border rounded-[10px] p-0.5 h-11",children:[x.jsx("button",{onClick:()=>c("board"),className:`w-10 h-10 inline-flex items-center justify-center rounded-[8px] transition-all ${u==="board"?"bg-bg-3 text-fg":"text-fg-muted hover:text-fg"}`,title:t("common.board"),children:x.jsx(oi.LayoutBoard,{size:20})}),x.jsx("button",{onClick:()=>c("list"),className:`w-10 h-10 inline-flex items-center justify-center rounded-[8px] transition-all ${u==="list"?"bg-bg-3 text-fg":"text-fg-muted hover:text-fg"}`,title:t("common.list"),children:x.jsx(oi.LayoutList,{size:20})})]}),x.jsx(Qi,{variant:"icon",icon:"Density",onClick:()=>h(d==="compact"?"comfortable":"compact"),title:`Density: ${d}`}),x.jsx(Qi,{variant:"icon",icon:"Settings",onClick:()=>p("settings"),title:t("common.settings")}),x.jsx(Qi,{variant:"secondary",icon:"Search",label:t("common.search"),shortcut:"⌘K",onClick:()=>o(!0),title:"Command palette (⌘K)"}),x.jsx(Qi,{variant:"icon",icon:"Refresh",onClick:a,title:`${t("common.reload")} (R)`}),x.jsx(Qi,{variant:"primary",icon:"Plus",label:t("common.newTask"),shortcut:"N",onClick:()=>r()})]}):x.jsx(Qi,{variant:"primary",label:t("common.openFolder"),onClick:i})})]})}/**
|
|
115
|
+
`}}}async function Hu(t,e,n,i){if(fr())return lV(e,mA(n,i));const r=await(await t.getFileHandle(`${e}.md`,{create:!0})).createWritable();await r.write(mA(n,i)),await r.close()}async function bA(t,e){if(fr())return uV(e);try{await t.removeEntry(`${e}.md`)}catch{}}const mV="kanban-md",gV=1,uc="recentProjects";function OR(){return new Promise((t,e)=>{const n=indexedDB.open(mV,gV);n.onerror=()=>e(n.error),n.onsuccess=()=>t(n.result),n.onupgradeneeded=()=>{const i=n.result;i.objectStoreNames.contains(uc)||i.createObjectStore(uc,{keyPath:"id"})}})}async function D0(t){const e=await OR();return new Promise((n,i)=>{const a=e.transaction(uc,"readwrite");a.objectStore(uc).put(t),a.oncomplete=()=>n(),a.onerror=()=>i(a.error)})}async function wk(){const t=await OR();return new Promise((e,n)=>{const a=t.transaction(uc,"readonly").objectStore(uc).getAll();a.onsuccess=()=>{const r=a.result||[];r.sort((o,u)=>u.lastOpened-o.lastOpened),e(r.slice(0,10))},a.onerror=()=>n(a.error)})}async function kA(t,e=!0){const n={mode:e?"readwrite":"read"};return await t.queryPermission(n)==="granted"||await t.requestPermission(n)==="granted"}class vV{dirHandle=null;tasksDirHandle=null;intervalId=null;configHash=null;taskHashes=new Map;knownTaskIds=new Set;listeners=new Map;debounceTimers=new Map;debounceDelay=200;start(e,n){this.dirHandle=e,this.tasksDirHandle=n,this.initHashes(),this.intervalId=setInterval(()=>void this.tick(),500)}stop(){this.intervalId&&(clearInterval(this.intervalId),this.intervalId=null),this.configHash=null,this.taskHashes.clear(),this.knownTaskIds.clear(),this.listeners.clear(),this.debounceTimers.forEach(e=>clearTimeout(e)),this.debounceTimers.clear()}on(e,n){return this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(n),()=>{this.listeners.get(e)?.delete(n)}}getKnownTaskIds(){return Array.from(this.knownTaskIds)}async initHashes(){if(!this.dirHandle||!this.tasksDirHandle)return;const e=await wA(this.dirHandle);e!==null&&(this.configHash=await this.hash(e)),await this.syncTaskDir(!1)}async tick(){if(!this.dirHandle||!this.tasksDirHandle)return;const e=await wA(this.dirHandle);if(e!==null){const n=await this.hash(e);this.configHash!==null&&n!==this.configHash&&(this.configHash=n,this.debouncedEmit("configChanged"))}for(const n of this.knownTaskIds){const i=await xA(this.tasksDirHandle,n);if(i!==null){const a=await this.hash(i),r=this.taskHashes.get(n);r!==void 0&&a!==r&&(this.taskHashes.set(n,a),this.debouncedEmit("taskChanged",n))}}await this.syncTaskDir(!0)}debouncedEmit(e,...n){const i=e+JSON.stringify(n),a=this.debounceTimers.get(i);a&&clearTimeout(a);const r=setTimeout(()=>{this.debounceTimers.delete(i),this.emit(e,...n)},this.debounceDelay);this.debounceTimers.set(i,r)}async syncTaskDir(e){if(this.tasksDirHandle){for await(const n of this.tasksDirHandle.values())if(n.kind==="file"&&n.name.endsWith(".md")){const i=n.name.replace(".md","");if(!this.knownTaskIds.has(i)){this.knownTaskIds.add(i);const a=await xA(this.tasksDirHandle,i);a!==null&&(this.taskHashes.set(i,await this.hash(a)),e&&this.debouncedEmit("newTaskDetected",i))}}}}async hash(e){const i=new TextEncoder().encode(e),a=await crypto.subtle.digest("SHA-256",i);return Array.from(new Uint8Array(a)).map(o=>o.toString(16).padStart(2,"0")).join("")}emit(e,...n){this.listeners.get(e)?.forEach(a=>{if(e==="configChanged"){a();return}if(e==="taskChanged"){const[o]=n;a(o);return}const[r]=n;a(r)})}}async function wA(t){try{return await(await(await t.getFileHandle("kandown.json")).getFile()).text()}catch{return null}}async function xA(t,e){try{return await(await(await t.getFileHandle(`${e}.md`)).getFile()).text()}catch{return null}}const $u=new vV,yV={soft:[{frequency:520,durationMs:90,delayMs:0,type:"sine"},{frequency:720,durationMs:120,delayMs:95,type:"sine"}],chime:[{frequency:660,durationMs:120,delayMs:0,type:"triangle"},{frequency:880,durationMs:160,delayMs:125,type:"triangle"}],ping:[{frequency:920,durationMs:110,delayMs:0,type:"sine"}],pop:[{frequency:260,durationMs:55,delayMs:0,type:"square"},{frequency:520,durationMs:80,delayMs:60,type:"square"}]};let _A=null;function NR(){return"Notification"in window?Notification.permission:"unsupported"}async function bV(){return"Notification"in window?await Notification.requestPermission():"unsupported"}function C0({title:t,body:e,config:n}){if(n.notifications.sound&&kV(n.notifications.soundId),!(!n.notifications.browser||NR()!=="granted"))try{new Notification(t,{body:e})}catch{}}function kV(t){const e=yV[t],n=window.AudioContext??window.webkitAudioContext;if(!(!n||e.length===0))try{_A??=new n;const i=_A;i.state==="suspended"&&i.resume();const a=i.currentTime+.01;e.forEach(r=>{const o=i.createOscillator(),u=i.createGain(),c=a+r.delayMs/1e3,d=c+r.durationMs/1e3;o.type=r.type??"sine",o.frequency.setValueAtTime(r.frequency,c),u.gain.setValueAtTime(1e-4,c),u.gain.exponentialRampToValueAtTime(.08,c+.01),u.gain.exponentialRampToValueAtTime(1e-4,d),o.connect(u),u.connect(i.destination),o.start(c),o.stop(d+.02)})}catch{}}function wV(t){let e=-1;for(const n of t)for(const i of n.tasks){const a=i.id.match(/^t(\d+)$/);if(a){const r=parseInt(a[1],10);r>e&&(e=r)}}return"t"+(e+1)}async function xV(t){const e=await pV(t);return await Promise.all(e.map(async i=>{const{frontmatter:a,body:r}=await ks(t,i),o={...a,id:a.id||i,status:a.status||"Backlog"},{subtasks:u,bodyWithoutSubtasks:c}=bs(r);return{id:i,frontmatter:o,body:c,subtasks:u}}))}async function SA(t,e,n){const i=[];for(const a of e){const r=a.name;a.tasks.forEach((o,u)=>{i.push((async()=>{const{frontmatter:c,body:d}=await ks(t,o.id);await Hu(t,o.id,{...c,id:o.id,status:r,order:u},d)})())})}await Promise.all(i)}function yl(t){iV(t.ui.theme,t.ui.skin,t.ui.font,t.ui.background)}function xk(t){return{title:t.frontmatter.title||t.id,status:t.frontmatter.status||"Backlog",body:t.body,subtasks:t.subtasks}}function DA(t){Zu.clear(),t.forEach(e=>{Zu.set(e.id,xk(e))})}function _V(t,e){return e.reduce((n,i,a)=>{const r=t[a]?.done??!1;return n+(i.done&&!r?1:0)},0)}function SV(t,e){const n=t.subtasks.map(a=>({text:a.text,description:a.description??"",report:a.report??""})),i=e.subtasks.map(a=>({text:a.text,description:a.description??"",report:a.report??""}));return t.title!==e.title||t.body!==e.body||JSON.stringify(n)!==JSON.stringify(i)}let DV=0;const Zu=new Map,Od=new Map,ze=WU((t,e)=>({isOpen:!1,loading:!1,dirHandle:null,projectName:null,tasksDirHandle:null,boardTitle:"Project Kanban",columns:[],taskContents:new Map,searchMatches:new Map,viewMode:localStorage.getItem("kandown:view")||"board",density:localStorage.getItem("kandown:density")||"comfortable",filters:{search:"",priority:null,tag:null,assignee:null,ownerType:null},commandOpen:!1,drawerTaskId:null,drawerData:null,currentPage:"board",config:La,recentProjects:[],toasts:[],drawerBaseVersion:null,conflictState:null,showConflictModal:!1,openFolder:async()=>{const n=await dV();if(!n)return;const{projectHandle:i,kandownHandle:a}=n,r=await uf(a),o=i.name;t({dirHandle:a,tasksDirHandle:r,projectName:o}),window.history.pushState({},"",`?p=${encodeURIComponent(o)}`);const u=fr()?S0():null;await D0({id:i.name,name:i.name,handle:i,lastOpened:Date.now(),...u?{kandownDir:u}:{}}),await e().loadConfig(),await e().reloadBoard();const c=await wk();t({recentProjects:c}),e().setupWatcher()},openRecentProject:async n=>{if(!await kA(n.handle,!0)){e().toast("Permission denied","error");return}const a=await yA(n.handle),r=await uf(a),o=n.handle.name;t({dirHandle:a,tasksDirHandle:r,projectName:o}),window.history.pushState({},"",`?p=${encodeURIComponent(o)}`),await D0({...n,lastOpened:Date.now()}),await e().loadConfig(),await e().reloadBoard(),e().setupWatcher()},openServerProject:async()=>{t({loading:!0});try{const n=S0();if(!n)throw new Error("No server root");const i=n.split("/").filter(Boolean).pop()??"Project",a=await FR();yl(a);const r=await BR(),o=await Promise.all(r.map(async p=>{const{frontmatter:m,body:v}=await oV(p),y={...m,id:m.id||p,status:m.status||"Backlog"},{subtasks:_,bodyWithoutSubtasks:w}=bs(v);return{id:p,frontmatter:y,body:w,subtasks:_}}));DA(o);const u=o.map(p=>({frontmatter:p.frontmatter,body:Fd(p.body,p.subtasks)})),c=vA(u,a.board.columns),d=c.reduce((p,m)=>p+m.tasks.length,0),h=new Map;if(d<=10)for(const p of o)h.set(p.frontmatter.id,{frontmatter:p.frontmatter,subtasks:p.subtasks,body:p.body});t({loading:!1,isOpen:!0,config:a,columns:c,boardTitle:"Project Kanban",projectName:i,taskContents:h,searchMatches:new Map}),window.history.pushState({},"",`?p=${encodeURIComponent(i)}`)}catch{t({loading:!1,isOpen:!1}),e().toast("Impossible de charger le projet. Relancez `kandown`.","error")}},tryAutoOpenServerProject:async()=>{if(!fr())return;const n=S0();if(!n)return;const i=await wk(),a=i.find(d=>d.kandownDir===n);if(!a){await e().openServerProject();return}if(!await kA(a.handle,!0)){await e().openServerProject();return}const o=await yA(a.handle),u=await uf(o),c=a.handle.name;t({dirHandle:o,tasksDirHandle:u,projectName:c,recentProjects:i}),window.history.pushState({},"",`?p=${encodeURIComponent(c)}`),await D0({...a,lastOpened:Date.now()}),await e().loadConfig(),await e().reloadBoard(),e().setupWatcher()},loadConfig:async()=>{const{dirHandle:n}=e();if(n)try{const i=await fV(n);i?(t({config:i}),yl(i)):(t({config:La}),yl(La))}catch{t({config:La}),yl(La)}},updateConfig:async n=>{const{dirHandle:i,config:a}=e();if(!i)return;const r=n(a);t({config:r}),yl(r);try{await hV(i,r)}catch(o){e().toast("Failed to save config: "+o.message,"error")}},reloadBoard:async()=>{const{tasksDirHandle:n,config:i}=e();if(n)try{const a=await xV(n);DA(a);const r=a.map(d=>({frontmatter:d.frontmatter,body:Fd(d.body,d.subtasks)})),o=vA(r,i.board.columns);t({boardTitle:"Project Kanban",columns:o});const u=o.reduce((d,h)=>d+h.tasks.length,0),c=new Map;if(u<=10)for(const d of a)c.set(d.frontmatter.id,{frontmatter:d.frontmatter,subtasks:d.subtasks,body:d.body});t({taskContents:c,searchMatches:new Map})}catch(a){e().toast("Failed to load board: "+a.message,"error")}},moveTask:async(n,i,a,r)=>{const{columns:o,tasksDirHandle:u,config:c}=e();if(!u)return;const d=o.find(w=>w.name===i),h=o.find(w=>w.name===a);if(!d||!h)return;const p=d.tasks.findIndex(w=>w.id===n);if(p===-1)return;const m=o.map(w=>({...w,tasks:[...w.tasks]})),v=m.find(w=>w.name===i),y=m.find(w=>w.name===a),[_]=v.tasks.splice(p,1);/done|termin|closed|complet/i.test(a)?_.checked=!0:_.checked=!1,r!==void 0?y.tasks.splice(r,0,_):y.tasks.push(_),t({columns:m});try{const w=i===a?m.filter(b=>b.name===a):m.filter(b=>b.name===i||b.name===a);await SA(u,w,c.board.columns)}catch(w){e().toast("Failed to save: "+w.message,"error"),t({columns:o})}},reorderInColumn:async(n,i,a)=>{const{columns:r,tasksDirHandle:o,config:u}=e();if(!o)return;const c=r.map(p=>({...p,tasks:[...p.tasks]})),d=c.find(p=>p.name===n);if(!d)return;const[h]=d.tasks.splice(i,1);d.tasks.splice(a,0,h),t({columns:c});try{await SA(o,[d],u.board.columns)}catch(p){e().toast("Failed to save: "+p.message,"error"),t({columns:r})}},addColumn:async n=>{const i=n.trim();if(!i)return;const{config:a}=e();a.board.columns.some(r=>r.toLowerCase()===i.toLowerCase())||(await e().updateConfig(r=>({...r,board:{...r.board,columns:[...r.board.columns,i]}})),await e().reloadBoard())},renameColumn:async(n,i)=>{const a=i.trim(),{columns:r,tasksDirHandle:o,config:u}=e();if(!o||!a||a.toLowerCase()===n.toLowerCase())return;if(r.some(h=>h.name.toLowerCase()===a.toLowerCase())){e().toast("Column already exists","error");return}const c=r,d=r.map(h=>h.name===n?{...h,name:a}:h);t({columns:d});try{const h=c.find(p=>p.name===n);h&&await Promise.all(h.tasks.map(async(p,m)=>{const{frontmatter:v,body:y}=await ks(o,p.id);await Hu(o,p.id,{...v,id:p.id,status:a,order:m},y)})),await e().updateConfig(p=>{const m={...p.board.columnColors??{}},v=m[n.toLowerCase()];v&&(m[a.toLowerCase()]=v,delete m[n.toLowerCase()]);const y=p.board.columns.some(_=>_.toLowerCase()===n.toLowerCase())?p.board.columns:[...p.board.columns,n];return{...p,board:{...p.board,columns:y.map(_=>_.toLowerCase()===n.toLowerCase()?a:_),columnColors:m}}}),await e().reloadBoard()}catch(h){e().toast("Failed to rename column: "+h.message,"error"),t({columns:c})}},deleteColumn:async n=>{const{columns:i,tasksDirHandle:a}=e();if(!a)return;const r=i.find(u=>u.name===n);if(!r)return;const o=i;t({columns:i.filter(u=>u.name!==n)});try{await Promise.all(r.tasks.map(u=>bA(a,u.id))),await e().updateConfig(u=>{const c={...u.board.columnColors??{}};return delete c[n.toLowerCase()],{...u,board:{...u.board,columns:u.board.columns.filter(d=>d.toLowerCase()!==n.toLowerCase()),columnColors:c}}}),await e().reloadBoard(),e().toast("Column deleted")}catch(u){e().toast("Failed to delete column: "+u.message,"error"),t({columns:o})}},createTask:async n=>{const{columns:i,tasksDirHandle:a,config:r,taskContents:o}=e();if(!a||!i.length)return null;const u=n||r.board.columns[0]||i[0].name,c=wV(i),d=i.find(m=>m.name===u)?.tasks.length??0,h={id:c,title:"",checked:!1,tags:[],assignee:null,priority:r.fields.priority?r.board.defaultPriority:null,ownerType:r.fields.ownerType?r.board.defaultOwnerType:"",progress:null},p=i.map(m=>m.name===u?{...m,tasks:[...m.tasks,h]}:m);t({columns:p});try{const m={id:c,title:"",status:u,order:d,priority:r.fields.priority?r.board.defaultPriority:"",tags:[],assignee:"",created:new Date().toISOString().slice(0,10),ownerType:r.fields.ownerType?r.board.defaultOwnerType:"",tools:""},v="";await Hu(a,c,m,v);const y=new Map(o);return y.set(c,{frontmatter:m,subtasks:[],body:v}),t({taskContents:y}),e().toast(`Created ${c.replace(/^t/,"")}`),await e().openDrawer(c),c}catch(m){return e().toast("Failed to create: "+m.message,"error"),t({columns:i}),null}},deleteTask:async n=>{const{columns:i,tasksDirHandle:a,taskContents:r}=e();if(!a)return;const o=i.map(d=>({...d,tasks:d.tasks.filter(h=>h.id!==n)}));t({columns:o});const u=new Map(r);u.delete(n);const c=new Map(e().searchMatches);c.delete(n),t({taskContents:u,searchMatches:c});try{await bA(a,n),e().toast("Deleted")}catch(d){e().toast("Failed to delete: "+d.message,"error"),t({columns:i})}},openDrawer:async n=>{const{tasksDirHandle:i}=e();if(i)try{const{frontmatter:a,body:r}=await ks(i,n),{subtasks:o,bodyWithoutSubtasks:u}=bs(r),c={frontmatter:a,subtasks:o,body:u,savedAt:Date.now()};t({drawerTaskId:n,drawerData:{frontmatter:a,subtasks:o,body:u},drawerBaseVersion:c,conflictState:null,showConflictModal:!1})}catch(a){e().toast("Failed to open: "+a.message,"error")}},closeDrawer:()=>t({drawerTaskId:null,drawerData:null,drawerBaseVersion:null,conflictState:null,showConflictModal:!1}),updateDrawerData:n=>{const{drawerData:i}=e();i&&t({drawerData:n(i)})},saveDrawer:async()=>{const{drawerTaskId:n,drawerData:i,tasksDirHandle:a,taskContents:r}=e();if(!n||!i||!a)return;const o=Fd(i.body,i.subtasks),u={...i.frontmatter,id:n};try{await Hu(a,n,u,o),e().toast("Saved"),t({drawerTaskId:null,drawerData:null});const c=new Map(r);c.set(n,{frontmatter:u,subtasks:i.subtasks,body:i.body}),t({taskContents:c}),await e().reloadBoard()}catch(c){e().toast("Failed to save: "+c.message,"error")}},saveDrawerMetadata:async()=>{const{drawerTaskId:n,drawerData:i,tasksDirHandle:a,taskContents:r}=e();if(!n||!i||!a)return;const o=Fd(i.body,i.subtasks),u={...i.frontmatter,id:n};try{await Hu(a,n,u,o);const c=new Map(r);c.set(n,{frontmatter:u,subtasks:i.subtasks,body:i.body}),t({taskContents:c}),await e().reloadBoard()}catch(c){e().toast("Failed to save: "+c.message,"error")}},setViewMode:n=>{localStorage.setItem("kandown:view",n),t({viewMode:n})},setDensity:n=>{localStorage.setItem("kandown:density",n),t({density:n})},setFilter:(n,i)=>{if(t(a=>({filters:{...a.filters,[n]:i}})),n==="search"){const{columns:a,tasksDirHandle:r,taskContents:o}=e(),u=i,c=a.flatMap(d=>d.tasks.map(h=>h.id));if(r){const d=c.filter(h=>!o.has(h));d.length>0?e().loadTaskContents(d).then(()=>{e().computeSearchMatches(u)}):e().computeSearchMatches(u)}}},clearFilters:()=>t({filters:{search:"",priority:null,tag:null,assignee:null,ownerType:null},searchMatches:new Map}),setCommandOpen:n=>t({commandOpen:n}),setCurrentPage:n=>t({currentPage:n}),loadTaskContents:async n=>{const{tasksDirHandle:i}=e();if(!i)return;const a=new Map(e().taskContents);await Promise.all(n.map(async r=>{if(!a.has(r))try{const{frontmatter:o,body:u}=await ks(i,r),{subtasks:c,bodyWithoutSubtasks:d}=bs(u);a.set(r,{frontmatter:o,subtasks:c,body:d})}catch{}})),t({taskContents:a})},computeSearchMatches:n=>{if(!n.trim()){t({searchMatches:new Map});return}const{taskContents:i}=e(),a=new Map,r=n.toLowerCase();for(const[o,u]of i){const c=RR(u,r);c.length>0&&a.set(o,c)}t({searchMatches:a})},toast:(n,i="success")=>{const a=++DV;t(r=>({toasts:[...r.toasts,{id:a,message:n,type:i}]})),setTimeout(()=>{t(r=>({toasts:r.toasts.filter(o=>o.id!==a)}))},2500)},dismissToast:n=>t(i=>({toasts:i.toasts.filter(a=>a.id!==n)})),resolveConflict:async n=>{const{conflictState:i,drawerData:a,tasksDirHandle:r,drawerTaskId:o,drawerBaseVersion:u}=e();if(!(!i||!r||!o))if(n==="reload"){const{frontmatter:c,body:d}=await ks(r,o),{subtasks:h,bodyWithoutSubtasks:p}=bs(d);t({drawerData:{frontmatter:c,subtasks:h,body:p},drawerBaseVersion:{frontmatter:c,subtasks:h,body:p,savedAt:Date.now()},conflictState:null,showConflictModal:!1}),e().toast("Reloaded from disk")}else if(n==="overwrite"){if(a&&o&&u){const c=Fd(a.body,a.subtasks),d={...a.frontmatter,id:o};await Hu(r,o,d,c),t({drawerBaseVersion:{...a,savedAt:Date.now()},conflictState:null,showConflictModal:!1}),e().toast("Overwritten remote changes")}}else t({conflictState:null,showConflictModal:!1})},setupWatcher:()=>{const{dirHandle:n,tasksDirHandle:i}=e();if(!n||!i)return;$u.stop(),Od.forEach(o=>clearTimeout(o)),Od.clear(),$u.start(n,i);const a=(o,u)=>{const c=Od.get(o);c&&clearTimeout(c);const d=Math.max(2e3,e().config.notifications.editDebounceMs),h=setTimeout(()=>{Od.delete(o);const p=e().config;p.notifications.taskEdits&&C0({title:"Task edited",body:`${u} changed on disk.`,config:p})},d);Od.set(o,h)},r=async o=>{const{tasksDirHandle:u,config:c}=e();if(!u)return;const{frontmatter:d,body:h}=await ks(u,o),{subtasks:p,bodyWithoutSubtasks:m}=bs(h),v={id:o,frontmatter:{...d,id:d.id||o,status:d.status||"Backlog"},body:m,subtasks:p},y=xk(v),_=Zu.get(o);if(!_){Zu.set(o,y);return}c.notifications.statusChanges&&_.status!==y.status&&C0({title:"Task status changed",body:`${y.title}: ${_.status} → ${y.status}`,config:c});const w=_V(_.subtasks,y.subtasks);c.notifications.subtaskCompletions&&w>0&&C0({title:"Subtask completed",body:w===1?`${y.title}: 1 subtask completed.`:`${y.title}: ${w} subtasks completed.`,config:c}),SV(_,y)&&a(o,y.title),Zu.set(o,y)};$u.on("configChanged",()=>{e().loadConfig(),e().toast("Settings updated externally","info")}),$u.on("taskChanged",async o=>{const{drawerTaskId:u,drawerBaseVersion:c,tasksDirHandle:d}=e();if(await r(o),u===o&&c&&d){const{frontmatter:h,body:p}=await ks(d,o),{subtasks:m,bodyWithoutSubtasks:v}=bs(p),y=c,_=JSON.stringify(y.frontmatter)!==JSON.stringify(h),w=y.body!==v,b=JSON.stringify(y.subtasks)!==JSON.stringify(m);if(!_&&!w&&!b)return;let C="none";_&&(w||b)?C="full":_?C="metadata-only":(w||b)&&(C="body-only"),t({conflictState:{taskId:o,type:C,local:y,remote:{frontmatter:h,body:v,subtasks:m}},showConflictModal:C==="full"})}else e().reloadBoard()}),$u.on("newTaskDetected",async o=>{const{tasksDirHandle:u}=e();if(u){const{frontmatter:c,body:d}=await ks(u,o),{subtasks:h,bodyWithoutSubtasks:p}=bs(d);Zu.set(o,xk({id:o,frontmatter:{...c,id:c.id||o,status:c.status||"Backlog"},body:p,subtasks:h}))}e().reloadBoard()})}}));wk().then(t=>{ze.setState({recentProjects:t})});yl(La);window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",()=>{yl(ze.getState().config)});function CV(t){const e=i7(t,{stiffness:180,damping:22,mass:.8});return H.useEffect(()=>{e.set(t)},[t,e]),oR(e,i=>Math.round(i).toString())}const _k="0.3.3",AV=({className:t})=>x.jsxs("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 150 150",className:t,fill:"currentColor",children:[x.jsx("path",{d:"m57.6 64.6 0.1-0.1v-31.3c-0.1-3.5-2.7-5.6-5.7-5.6h-16.3v59.6c0.2-1.3 0.9-2.6 1.7-3.2l20.2-19.4z"}),x.jsx("path",{d:"m87.6 43.8c-3.4 0.1-7.1 1.3-9.9 3.7l-38.5 38.7c-2.1 2.1-3.4 4.8-3.5 7.5v26.7l77.5-76.4v-0.2h-25.6z"}),x.jsx("path",{d:"m108.1 96.4-6 5-22.4-20.8-14.6 14.2 21.1 21.4-5 4.1c-0.6 0.5-0.3 0.9 0.2 0.9h27.6c0.4 0 0.7-0.4 0.7-0.6v-24.8c0-0.7-1.1-0.3-1.6 0.3v0.3z"})]});function EV(){const{t}=di(),e=ze(O=>O.dirHandle);ze(O=>O.projectName);const n=ze(O=>O.columns),i=ze(O=>O.openFolder),a=ze(O=>O.reloadBoard),r=ze(O=>O.createTask),o=ze(O=>O.setCommandOpen),u=ze(O=>O.viewMode),c=ze(O=>O.setViewMode),d=ze(O=>O.density),h=ze(O=>O.setDensity),p=ze(O=>O.setCurrentPage),m=ze(O=>O.recentProjects),v=ze(O=>O.openRecentProject),y=ze(O=>O.filters),_=ze(O=>O.setFilter),w=ze(O=>O.clearFilters),b=ze(O=>O.config.fields),[C,D]=H.useState(!1),A=H.useRef(null),M=H.useRef(null),T=n.reduce((O,$)=>O+$.tasks.length,0),F=CV(T),R=[];b.priority&&y.priority&&R.push({type:"priority",label:y.priority,value:y.priority}),b.tags&&y.tag&&R.push({type:"tag",label:"#"+y.tag,value:y.tag}),b.assignee&&y.assignee&&R.push({type:"assignee",label:"@"+y.assignee,value:y.assignee});const P=[{label:t("filterBar.ownerAll"),value:""},{label:t("filterBar.ownerHuman"),value:"human"},{label:t("filterBar.ownerAI"),value:"ai"}],E=R.length>0||y.search||b.ownerType&&y.ownerType;return H.useEffect(()=>{if(!C)return;const O=$=>{A.current&&!A.current.contains($.target)&&D(!1)};return document.addEventListener("mousedown",O),()=>document.removeEventListener("mousedown",O)},[C]),x.jsxs("header",{className:"flex items-center justify-between px-5 h-14 border-b border-border bg-bg relative z-10",children:[x.jsxs("div",{className:"flex items-center gap-3 min-w-0",children:[x.jsx("div",{className:"flex items-center gap-2 flex-shrink-0",children:x.jsxs("button",{onClick:()=>window.history.pushState({},"",window.location.pathname),className:"flex items-center gap-2 cursor-pointer",children:[x.jsx(AV,{className:"w-[36px] h-[36px] dark:text-white text-black"}),x.jsx("span",{className:"text-[15px] font-semibold tracking-tight",children:t("app.name")}),x.jsxs("span",{className:"text-[11px] font-mono font-bold text-red-500",children:["v",_k]})]})}),e&&x.jsxs(x.Fragment,{children:[x.jsx("div",{className:"w-px h-[18px] bg-border flex-shrink-0"}),x.jsxs("div",{className:"flex items-center gap-2 px-2.5 h-8 bg-bg-2 border border-border rounded-[8px] min-w-[180px] max-w-[260px] focus-within:border-border-focus focus-within:bg-bg-3 transition-colors",children:[x.jsx(oi.Search,{size:14,className:"text-fg-muted flex-shrink-0"}),x.jsx("input",{ref:M,type:"text",placeholder:t("filterBar.searchPlaceholder"),value:y.search,onChange:O=>_("search",O.target.value),className:"bg-transparent border-none outline-none text-fg text-[13px] w-full placeholder:text-fg-muted"}),y.search&&x.jsx("button",{onClick:()=>_("search",""),className:"text-fg-muted hover:text-fg flex-shrink-0",children:x.jsx(oi.X,{size:14})})]}),x.jsxs("div",{className:"flex items-center gap-1.5 flex-wrap overflow-hidden",children:[x.jsx(ba,{children:R.map(O=>x.jsxs(Xt.button,{initial:{opacity:0,scale:.9},animate:{opacity:1,scale:1},exit:{opacity:0,scale:.9},transition:{duration:.15},onClick:()=>_(O.type,null),className:"inline-flex items-center gap-1 h-6 px-2 text-[12px] text-fg bg-bg-3 border border-border-strong rounded-[4px] hover:bg-bg-hover transition-colors",children:[O.label,x.jsx(oi.X,{size:10,className:"text-fg-muted"})]},O.type+O.value))}),b.ownerType&&x.jsx("div",{className:"flex items-center h-6 border border-border-strong rounded-[4px] overflow-hidden",children:P.map(O=>x.jsx("button",{onClick:()=>_("ownerType",O.value),className:`h-full px-2 text-[12px] transition-colors ${y.ownerType===O.value?"bg-bg-hover text-fg":"text-fg-muted hover:text-fg"}`,children:O.label},O.value))}),E&&x.jsx("button",{onClick:w,className:"text-[12px] text-fg-muted hover:text-fg transition-colors",children:t("filterBar.clearAll")})]})]}),e&&x.jsxs(x.Fragment,{children:[x.jsx("div",{className:"w-px h-[18px] bg-border flex-shrink-0"}),x.jsxs("div",{className:"relative flex-shrink-0",ref:A,children:[x.jsxs("button",{onClick:()=>D(O=>!O),className:"flex items-center gap-1.5 px-2 py-1 text-[14px] text-fg-dim hover:text-fg hover:bg-bg-2 rounded-[6px] transition-colors",children:[x.jsx(oi.Folder,{size:12,className:"text-fg-muted"}),x.jsx("span",{children:e.name}),x.jsx(oi.ChevronDown,{size:10,className:"opacity-60"})]}),x.jsx(ba,{children:C&&x.jsx(Xt.div,{initial:{opacity:0,y:-4,scale:.98},animate:{opacity:1,y:0,scale:1},exit:{opacity:0,y:-4,scale:.98},transition:{duration:.12},className:"absolute top-full left-0 mt-1.5 min-w-[240px] glass rounded-[8px] shadow-[0_16px_48px_rgba(0,0,0,0.5)] overflow-hidden z-50",children:x.jsxs("div",{className:"py-1.5",children:[m.length>0&&x.jsxs(x.Fragment,{children:[x.jsx("div",{className:"px-3 py-1 text-[11px] font-semibold uppercase tracking-wider text-fg-faint",children:t("header.recentProjects")}),m.map(O=>x.jsxs("button",{onClick:()=>{D(!1),v(O)},className:"w-full flex items-center gap-2 px-3 py-1.5 text-[13.5px] text-left hover:bg-bg-3 transition-colors",children:[x.jsx(oi.Folder,{size:12,className:"text-fg-muted"}),x.jsx("span",{className:"truncate",children:O.name}),O.id===e.name&&x.jsx(oi.Check,{size:12,className:"ml-auto text-success"})]},O.id)),x.jsx("div",{className:"h-px bg-border my-1.5 mx-2"})]}),x.jsxs("button",{onClick:()=>{D(!1),i()},className:"w-full flex items-center gap-2 px-3 py-1.5 text-[13.5px] text-left hover:bg-bg-3 transition-colors",children:[x.jsx(oi.Plus,{size:12,className:"text-fg-muted"}),x.jsx("span",{children:t("header.openFolder...")})]})]})})})]})]})]}),x.jsx("div",{className:"flex items-center gap-1.5 flex-shrink-0",children:e?x.jsxs(x.Fragment,{children:[x.jsxs("div",{className:"flex items-center gap-2 mr-2 text-[13px] text-fg-muted",children:[x.jsx("span",{className:"inline-block w-1.5 h-1.5 rounded-full bg-success"}),x.jsx(Xt.span,{className:"tabular-nums",children:F}),x.jsx("span",{children:t("header.tasks")})]}),x.jsxs("div",{className:"flex items-center bg-bg-2 border border-border rounded-[10px] p-0.5 h-11",children:[x.jsx("button",{onClick:()=>c("board"),className:`w-10 h-10 inline-flex items-center justify-center rounded-[8px] transition-all ${u==="board"?"bg-bg-3 text-fg":"text-fg-muted hover:text-fg"}`,title:t("common.board"),children:x.jsx(oi.LayoutBoard,{size:20})}),x.jsx("button",{onClick:()=>c("list"),className:`w-10 h-10 inline-flex items-center justify-center rounded-[8px] transition-all ${u==="list"?"bg-bg-3 text-fg":"text-fg-muted hover:text-fg"}`,title:t("common.list"),children:x.jsx(oi.LayoutList,{size:20})})]}),x.jsx(Qi,{variant:"icon",icon:"Density",onClick:()=>h(d==="compact"?"comfortable":"compact"),title:`Density: ${d}`}),x.jsx(Qi,{variant:"icon",icon:"Settings",onClick:()=>p("settings"),title:t("common.settings")}),x.jsx(Qi,{variant:"secondary",icon:"Search",label:t("common.search"),shortcut:"⌘K",onClick:()=>o(!0),title:"Command palette (⌘K)"}),x.jsx(Qi,{variant:"icon",icon:"Refresh",onClick:a,title:`${t("common.reload")} (R)`}),x.jsx(Qi,{variant:"primary",icon:"Plus",label:t("common.newTask"),shortcut:"N",onClick:()=>r()})]}):x.jsx(Qi,{variant:"primary",label:t("common.openFolder"),onClick:i})})]})}/**
|
|
116
116
|
* @license @tabler/icons-react v3.41.1 - MIT
|
|
117
117
|
*
|
|
118
118
|
* This source code is licensed under the MIT license.
|