kandown 0.7.2 → 0.7.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 +112 -8
- package/dist/index.html +1 -1
- package/package.json +1 -1
package/bin/kandown.js
CHANGED
|
@@ -53,7 +53,7 @@ import {
|
|
|
53
53
|
statSync,
|
|
54
54
|
unlinkSync,
|
|
55
55
|
} from 'node:fs';
|
|
56
|
-
import { spawnSync, spawn } from 'node:child_process';
|
|
56
|
+
import { spawnSync, spawn, execSync } from 'node:child_process';
|
|
57
57
|
|
|
58
58
|
const __filename = fileURLToPath(import.meta.url);
|
|
59
59
|
const __dirname = dirname(__filename);
|
|
@@ -791,26 +791,130 @@ function listen(server, port) {
|
|
|
791
791
|
}
|
|
792
792
|
|
|
793
793
|
async function listenOnAvailablePort(kandownDir, preferredPort) {
|
|
794
|
-
const
|
|
795
|
-
const
|
|
794
|
+
const port = preferredPort ?? START_PORT_RANGE;
|
|
795
|
+
const isSinglePort = preferredPort !== null;
|
|
796
|
+
|
|
797
|
+
// 📖 Check if the target port is already occupied by a stale kandown process.
|
|
798
|
+
// This happens when the TUI crashes but the HTTP server stays alive.
|
|
799
|
+
const staleInfo = detectStaleKandown(port, kandownDir);
|
|
800
|
+
if (staleInfo) {
|
|
801
|
+
warn(staleInfo.reason);
|
|
802
|
+
try {
|
|
803
|
+
process.kill(staleInfo.pid, 'SIGTERM');
|
|
804
|
+
// Give it a moment to clean up
|
|
805
|
+
await new Promise(r => setTimeout(r, 300));
|
|
806
|
+
info(`Reclaimed port ${c.cyan}${port}${c.reset} (killed stale kandown PID ${staleInfo.pid})`);
|
|
807
|
+
} catch {
|
|
808
|
+
// Process already dead
|
|
809
|
+
}
|
|
810
|
+
}
|
|
796
811
|
|
|
797
|
-
|
|
812
|
+
// If user specified a specific port, only try that one
|
|
813
|
+
if (isSinglePort) {
|
|
798
814
|
const server = createServeServer(kandownDir);
|
|
799
815
|
try {
|
|
800
816
|
await listen(server, port);
|
|
801
817
|
return { server, port };
|
|
818
|
+
} catch (e) {
|
|
819
|
+
if (e.code === 'EADDRINUSE') {
|
|
820
|
+
err(`Port ${c.bold}${port}${c.reset} is in use by another application.`);
|
|
821
|
+
process.exit(1);
|
|
822
|
+
}
|
|
823
|
+
throw e;
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
// 📖 Scan range — try ports until one works
|
|
828
|
+
for (let p = START_PORT_RANGE; p <= END_PORT_RANGE; p++) {
|
|
829
|
+
// Skip port if occupied by a stale kandown from a DIFFERENT project
|
|
830
|
+
const stale = detectStaleKandown(p, kandownDir);
|
|
831
|
+
if (stale && !stale.reason) {
|
|
832
|
+
// reason is null → different project, skip this port
|
|
833
|
+
continue;
|
|
834
|
+
}
|
|
835
|
+
if (stale) {
|
|
836
|
+
// Same project — already killed above for the first port, but handle edge case
|
|
837
|
+
try { process.kill(stale.pid, 'SIGTERM'); } catch {}
|
|
838
|
+
await new Promise(r => setTimeout(r, 200));
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
const server = createServeServer(kandownDir);
|
|
842
|
+
try {
|
|
843
|
+
await listen(server, p);
|
|
844
|
+
return { server, port: p };
|
|
802
845
|
} catch (e) {
|
|
803
846
|
if (e.code !== 'EADDRINUSE') throw e;
|
|
804
847
|
}
|
|
805
848
|
}
|
|
806
849
|
|
|
807
|
-
|
|
808
|
-
? `${START_PORT_RANGE}-${END_PORT_RANGE}`
|
|
809
|
-
: String(preferredPort);
|
|
810
|
-
err(`No free port available in ${c.bold}${range}${c.reset}.`);
|
|
850
|
+
err(`No free port available in ${c.bold}${START_PORT_RANGE}-${END_PORT_RANGE}${c.reset}.`);
|
|
811
851
|
process.exit(1);
|
|
812
852
|
}
|
|
813
853
|
|
|
854
|
+
/**
|
|
855
|
+
* 📖 Detects if a port is occupied by a stale/zombie kandown process.
|
|
856
|
+
* A "stale" kandown is one whose TUI has exited but the HTTP server is still alive.
|
|
857
|
+
* Returns { pid, cwd, reason } or null if the port is free or used by something else.
|
|
858
|
+
*/
|
|
859
|
+
function detectStaleKandown(port, currentKandownDir) {
|
|
860
|
+
let pid;
|
|
861
|
+
try {
|
|
862
|
+
pid = execSync(`lsof -ti :${port} -sTCP:LISTEN`, { encoding: 'utf8', timeout: 2000 }).trim();
|
|
863
|
+
} catch {
|
|
864
|
+
return null; // Port is free
|
|
865
|
+
}
|
|
866
|
+
if (!pid) return null;
|
|
867
|
+
|
|
868
|
+
const pids = pid.split('\n').filter(Boolean);
|
|
869
|
+
if (pids.length === 0) return null;
|
|
870
|
+
pid = parseInt(pids[0], 10);
|
|
871
|
+
if (isNaN(pid) || pid === process.pid) return null;
|
|
872
|
+
|
|
873
|
+
// Check if it's a kandown process
|
|
874
|
+
let cmdline;
|
|
875
|
+
try {
|
|
876
|
+
cmdline = execSync(`ps -p ${pid} -o command=`, { encoding: 'utf8', timeout: 2000 }).trim();
|
|
877
|
+
} catch {
|
|
878
|
+
return null; // Process gone
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
if (!/[/\\]kandown\b|^kandown\b|\skandown\b/.test(cmdline)) return null; // Not a kandown process
|
|
882
|
+
|
|
883
|
+
// Get the working directory of the existing process
|
|
884
|
+
let cwd;
|
|
885
|
+
try {
|
|
886
|
+
if (process.platform === 'linux') {
|
|
887
|
+
cwd = execSync(`readlink -f /proc/${pid}/cwd`, { encoding: 'utf8', timeout: 2000 }).trim();
|
|
888
|
+
} else {
|
|
889
|
+
// macOS
|
|
890
|
+
cwd = execSync(`lsof -p ${pid} -Fn -a -d cwd 2>/dev/null | grep '^n' | cut -c2-`, { encoding: 'utf8', timeout: 2000, shell: true }).trim();
|
|
891
|
+
}
|
|
892
|
+
} catch {
|
|
893
|
+
cwd = null;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
// 📖 Compare with our project root (parent of .kandown/), not process.cwd().
|
|
897
|
+
// process.cwd() could be a subdirectory; the project root is stable.
|
|
898
|
+
const ourProjectRoot = currentKandownDir ? dirname(currentKandownDir) : process.cwd();
|
|
899
|
+
const isSameProject = cwd && (cwd === ourProjectRoot || cwd === process.cwd());
|
|
900
|
+
|
|
901
|
+
// 📖 If it's our own process, skip (we're checking our own port)
|
|
902
|
+
if (pid === process.pid) return null;
|
|
903
|
+
|
|
904
|
+
// 📖 Different project — don't touch, just skip the port
|
|
905
|
+
if (!isSameProject) {
|
|
906
|
+
return { pid, cwd: cwd || 'unknown', reason: null };
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
// 📖 Same project — stale/zombie or legitimate, kill it either way.
|
|
910
|
+
// The user is explicitly launching kandown, so they want a fresh instance.
|
|
911
|
+
return {
|
|
912
|
+
pid,
|
|
913
|
+
cwd: cwd || 'unknown',
|
|
914
|
+
reason: `${c.yellow}Existing kandown found on port ${c.cyan}${port}${c.yellow} (PID ${pid}, same project). Reconnecting...${c.reset}`,
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
|
|
814
918
|
/**
|
|
815
919
|
* 📖 Starts the local web UI server, opens it in the browser, then hands the
|
|
816
920
|
* terminal to the board TUI. The server intentionally stays in this process so
|
package/dist/index.html
CHANGED
|
@@ -112,7 +112,7 @@ Error generating stack: `+k.message+`
|
|
|
112
112
|
|
|
113
113
|
## Subtasks
|
|
114
114
|
|
|
115
|
-
`}}}async function of(e,t,n,a){if(na())return C1e(t,nR(n,a));const r=await(await e.getFileHandle(`${t}.md`,{create:!0})).createWritable();await r.write(nR(n,a)),await r.close()}async function rR(e,t){if(na())return A1e(t);try{await e.removeEntry(`${t}.md`)}catch{}}const T1e="kanban-md",M1e=1,Vf="recentProjects";function PY(){return new Promise((e,t)=>{const n=indexedDB.open(T1e,M1e);n.onerror=()=>t(n.error),n.onsuccess=()=>e(n.result),n.onupgradeneeded=()=>{const a=n.result;a.objectStoreNames.contains(Vf)||a.createObjectStore(Vf,{keyPath:"id"})}})}async function wN(e){const t=await PY();return new Promise((n,a)=>{const i=t.transaction(Vf,"readwrite");i.objectStore(Vf).put(e),i.oncomplete=()=>n(),i.onerror=()=>a(i.error)})}async function F$(){const e=await PY();return new Promise((t,n)=>{const i=e.transaction(Vf,"readonly").objectStore(Vf).getAll();i.onsuccess=()=>{const r=i.result||[];r.sort((s,o)=>o.lastOpened-s.lastOpened),t(r.slice(0,10))},i.onerror=()=>n(i.error)})}async function sR(e,t=!0){const n={mode:t?"readwrite":"read"};return await e.queryPermission(n)==="granted"||await e.requestPermission(n)==="granted"}class L1e{dirHandle=null;tasksDirHandle=null;intervalId=null;configHash=null;taskHashes=new Map;knownTaskIds=new Set;listeners=new Map;debounceTimers=new Map;debounceDelay=150;start(t,n){this.dirHandle=t,this.tasksDirHandle=n,this.initHashes(),this.intervalId=setInterval(()=>void this.tick(),300)}stop(){this.intervalId&&(clearInterval(this.intervalId),this.intervalId=null),this.configHash=null,this.taskHashes.clear(),this.knownTaskIds.clear(),this.listeners.clear(),this.debounceTimers.forEach(t=>clearTimeout(t)),this.debounceTimers.clear()}on(t,n){return this.listeners.has(t)||this.listeners.set(t,new Set),this.listeners.get(t).add(n),()=>{this.listeners.get(t)?.delete(n)}}getKnownTaskIds(){return Array.from(this.knownTaskIds)}async initHashes(){if(!this.dirHandle||!this.tasksDirHandle)return;const t=await oR(this.dirHandle);t!==null&&(this.configHash=await this.hash(t)),await this.syncTaskDir(!1)}async tick(){if(!this.dirHandle||!this.tasksDirHandle)return;const t=await oR(this.dirHandle);if(t!==null){const n=await this.hash(t);this.configHash!==null&&n!==this.configHash&&(this.configHash=n,this.debouncedEmit("configChanged"))}for(const n of this.knownTaskIds){const a=await cR(this.tasksDirHandle,n);if(a!==null){const i=await this.hash(a),r=this.taskHashes.get(n);r!==void 0&&i!==r&&(this.taskHashes.set(n,i),this.debouncedEmit("taskChanged",n))}}await this.syncTaskDir(!0)}debouncedEmit(t,...n){const a=t+JSON.stringify(n),i=this.debounceTimers.get(a);i&&clearTimeout(i);const r=setTimeout(()=>{this.debounceTimers.delete(a),this.emit(t,...n)},this.debounceDelay);this.debounceTimers.set(a,r)}async syncTaskDir(t){if(this.tasksDirHandle){for await(const n of this.tasksDirHandle.values())if(n.kind==="file"&&n.name.endsWith(".md")){const a=n.name.replace(".md","");if(!this.knownTaskIds.has(a)){this.knownTaskIds.add(a);const i=await cR(this.tasksDirHandle,a);i!==null&&(this.taskHashes.set(a,await this.hash(i)),t&&this.debouncedEmit("newTaskDetected",a))}}}}async hash(t){const a=new TextEncoder().encode(t),i=await crypto.subtle.digest("SHA-256",a);return Array.from(new Uint8Array(i)).map(s=>s.toString(16).padStart(2,"0")).join("")}emit(t,...n){this.listeners.get(t)?.forEach(i=>{if(t==="configChanged"){i();return}if(t==="taskChanged"){const[s]=n;i(s);return}const[r]=n;i(r)})}}async function oR(e){try{return await(await(await e.getFileHandle("kandown.json")).getFile()).text()}catch{return null}}async function cR(e,t){try{return await(await(await e.getFileHandle(`${t}.md`)).getFile()).text()}catch{return null}}const cf=new L1e,j1e={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 pR=null;function TY(){return"Notification"in window?Notification.permission:"unsupported"}async function D1e(){return"Notification"in window?await Notification.requestPermission():"unsupported"}function xN({title:e,body:t,config:n}){if(n.notifications.sound&&F1e(n.notifications.soundId),!(!n.notifications.browser||TY()!=="granted"))try{new Notification(e,{body:t})}catch{}}function F1e(e){const t=j1e[e],n=window.AudioContext??window.webkitAudioContext;if(!(!n||t.length===0))try{pR??=new n;const a=pR;a.state==="suspended"&&a.resume();const i=a.currentTime+.01;t.forEach(r=>{const s=a.createOscillator(),o=a.createGain(),c=i+r.delayMs/1e3,p=c+r.durationMs/1e3;s.type=r.type??"sine",s.frequency.setValueAtTime(r.frequency,c),o.gain.setValueAtTime(1e-4,c),o.gain.exponentialRampToValueAtTime(.08,c+.01),o.gain.exponentialRampToValueAtTime(1e-4,p),s.connect(o),o.connect(a.destination),s.start(c),s.stop(p+.02)})}catch{}}function I1e(e){let t=-1;for(const n of e)for(const a of n.tasks){const i=a.id.match(/^t(\d+)$/);if(i){const r=parseInt(i[1],10);r>t&&(t=r)}}return"t"+(t+1)}async function R1e(){const e=await U6();return await Promise.all(e.map(async n=>{const{frontmatter:a,body:i}=await SY(n),r={...a,id:a.id||n,status:a.status||"Backlog"},{subtasks:s,bodyWithoutSubtasks:o}=Wo(i);return{id:n,frontmatter:r,body:o,subtasks:s}}))}async function O1e(e){const t=await P1e(e);return await Promise.all(t.map(async a=>{const{frontmatter:i,body:r}=await ep(e,a),s={...i,id:i.id||a,status:i.status||"Backlog"},{subtasks:o,bodyWithoutSubtasks:c}=Wo(r);return{id:a,frontmatter:s,body:c,subtasks:o}}))}async function lR(e,t,n){const a=[];for(const i of t){const r=i.name;i.tasks.forEach((s,o)=>{a.push((async()=>{const{frontmatter:c,body:p}=await ep(e,s.id);await of(e,s.id,{...c,id:s.id,status:r,order:o},p)})())})}await Promise.all(a)}function Vu(e){_1e(e.ui.theme,e.ui.skin,e.ui.font,e.ui.background)}function I$(e){return{title:e.frontmatter.title||e.id,status:e.frontmatter.status||"Backlog",body:e.body,subtasks:e.subtasks}}function CN(e){_f.clear(),e.forEach(t=>{_f.set(t.id,I$(t))})}function B1e(e){const t=e.split(/[\\/]+/).filter(Boolean),n=t[t.length-1];return n===".kandown"?t[t.length-2]??"Project":n??"Project"}function z1e(e,t){return t.reduce((n,a,i)=>{const r=e[i]?.done??!1;return n+(a.done&&!r?1:0)},0)}function H1e(e,t){const n=e.subtasks.map(i=>({text:i.text,description:i.description??"",report:i.report??""})),a=t.subtasks.map(i=>({text:i.text,description:i.description??"",report:i.report??""}));return e.title!==t.title||e.body!==t.body||JSON.stringify(n)!==JSON.stringify(a)}let q1e=0;const _f=new Map,tb=new Map;let AN=null;const Me=l1e((e,t)=>({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:bs,recentProjects:[],toasts:[],drawerBaseVersion:null,conflictState:null,showConflictModal:!1,openFolder:async()=>{const n=await E1e();if(!n)return;const{projectHandle:a,kandownHandle:i}=n,r=await Hb(i),s=a.name;e({dirHandle:i,tasksDirHandle:r,projectName:s}),window.history.pushState({},"",`?p=${encodeURIComponent(s)}`);const o=na()?kN():null;await wN({id:a.name,name:a.name,handle:a,lastOpened:Date.now(),...o?{kandownDir:o}:{}}),await t().loadConfig(),await t().reloadBoard();const c=await F$();e({recentProjects:c}),t().setupWatcher()},openRecentProject:async n=>{if(!await sR(n.handle,!0)){t().toast("Permission denied","error");return}const i=await iR(n.handle),r=await Hb(i),s=n.handle.name;e({dirHandle:i,tasksDirHandle:r,projectName:s}),window.history.pushState({},"",`?p=${encodeURIComponent(s)}`),await wN({...n,lastOpened:Date.now()}),await t().loadConfig(),await t().reloadBoard(),t().setupWatcher()},openServerProject:async()=>{e({loading:!0});try{const n=kN();if(!n)throw new Error("No server root");const a=B1e(n),i=await EY();Vu(i);const r=await U6(),s=await Promise.all(r.map(async u=>{const{frontmatter:m,body:g}=await SY(u),h={...m,id:m.id||u,status:m.status||"Backlog"},{subtasks:v,bodyWithoutSubtasks:w}=Wo(g);return{id:u,frontmatter:h,body:w,subtasks:v}}));CN(s);const o=s.map(u=>({frontmatter:u.frontmatter,body:Hm(u.body,u.subtasks)})),c=_N(o,i.board.columns),p=c.reduce((u,m)=>u+m.tasks.length,0),l=new Map;if(p<=10)for(const u of s)l.set(u.frontmatter.id,{frontmatter:u.frontmatter,subtasks:u.subtasks,body:u.body});e({loading:!1,isOpen:!0,config:i,columns:c,boardTitle:"Project Kanban",projectName:a,taskContents:l,searchMatches:new Map}),window.history.pushState({},"",`?p=${encodeURIComponent(a)}`),t().setupWatcher()}catch{e({loading:!1,isOpen:!1}),t().toast("Impossible de charger le projet. Relancez `kandown`.","error")}},tryAutoOpenServerProject:async()=>{if(!na())return;const n=kN();if(!n)return;const a=await F$(),i=a.find(p=>p.kandownDir===n);if(!i){await t().openServerProject();return}if(!await sR(i.handle,!0)){await t().openServerProject();return}const s=await iR(i.handle),o=await Hb(s),c=i.handle.name;e({dirHandle:s,tasksDirHandle:o,projectName:c,recentProjects:a,isOpen:!0}),window.history.pushState({},"",`?p=${encodeURIComponent(c)}`),await wN({...i,lastOpened:Date.now()}),await t().loadConfig(),await t().reloadBoard(),t().setupWatcher()},loadConfig:async()=>{const{dirHandle:n}=t();if(!(!n&&!na()))try{const a=await S1e(n);a?(e({config:a}),Vu(a)):(e({config:bs}),Vu(bs))}catch{e({config:bs}),Vu(bs)}},updateConfig:async n=>{const{dirHandle:a,config:i}=t();if(!a&&!na())return;const r=n(i);e({config:r}),Vu(r);try{await $1e(a,r)}catch(s){t().toast("Failed to save config: "+s.message,"error")}},reloadBoard:async()=>{const{tasksDirHandle:n,config:a}=t();if(na())try{const i=await R1e();CN(i);const r=i.map(p=>({frontmatter:p.frontmatter,body:Hm(p.body,p.subtasks)})),s=_N(r,a.board.columns);e({boardTitle:"Project Kanban",columns:s});const o=s.reduce((p,l)=>p+l.tasks.length,0),c=new Map;if(o<=10)for(const p of i)c.set(p.frontmatter.id,{frontmatter:p.frontmatter,subtasks:p.subtasks,body:p.body});e({taskContents:c,searchMatches:new Map})}catch(i){t().toast("Failed to load board: "+i.message,"error")}else if(n)try{const i=await O1e(n);CN(i);const r=i.map(p=>({frontmatter:p.frontmatter,body:Hm(p.body,p.subtasks)})),s=_N(r,a.board.columns);e({boardTitle:"Project Kanban",columns:s});const o=s.reduce((p,l)=>p+l.tasks.length,0),c=new Map;if(o<=10)for(const p of i)c.set(p.frontmatter.id,{frontmatter:p.frontmatter,subtasks:p.subtasks,body:p.body});e({taskContents:c,searchMatches:new Map})}catch(i){t().toast("Failed to load board: "+i.message,"error")}},moveTask:async(n,a,i,r)=>{const{columns:s,config:o}=t();if(!na()&&!t().tasksDirHandle)return;const p=s.find(w=>w.name===a),l=s.find(w=>w.name===i);if(!p||!l)return;const u=p.tasks.findIndex(w=>w.id===n);if(u===-1)return;const m=s.map(w=>({...w,tasks:[...w.tasks]})),g=m.find(w=>w.name===a),h=m.find(w=>w.name===i),[v]=g.tasks.splice(u,1);/done|termin|closed|complet/i.test(i)?v.checked=!0:v.checked=!1,r!==void 0?h.tasks.splice(r,0,v):h.tasks.push(v),e({columns:m});try{if(na())return;const{tasksDirHandle:w}=t();if(!w)return;const x=a===i?m.filter(A=>A.name===i):m.filter(A=>A.name===a||A.name===i);await lR(w,x,o.board.columns)}catch(w){t().toast("Failed to save: "+w.message,"error"),e({columns:s})}},reorderInColumn:async(n,a,i)=>{const{columns:r,tasksDirHandle:s,config:o}=t();if(!s&&!na())return;const c=r.map(u=>({...u,tasks:[...u.tasks]})),p=c.find(u=>u.name===n);if(!p)return;const[l]=p.tasks.splice(a,1);p.tasks.splice(i,0,l),e({columns:c});try{if(na())return;const{tasksDirHandle:u}=t();if(!u)return;await lR(u,[p],o.board.columns)}catch(u){t().toast("Failed to save: "+u.message,"error"),e({columns:r})}},addColumn:async n=>{const a=n.trim();if(!a)return;const{config:i}=t();i.board.columns.some(r=>r.toLowerCase()===a.toLowerCase())||(await t().updateConfig(r=>({...r,board:{...r.board,columns:[...r.board.columns,a]}})),await t().reloadBoard())},renameColumn:async(n,a)=>{const i=a.trim(),{columns:r,tasksDirHandle:s,config:o}=t();if(!s||!i||i.toLowerCase()===n.toLowerCase())return;if(r.some(l=>l.name.toLowerCase()===i.toLowerCase())){t().toast("Column already exists","error");return}const c=r,p=r.map(l=>l.name===n?{...l,name:i}:l);e({columns:p});try{const l=c.find(u=>u.name===n);l&&await Promise.all(l.tasks.map(async(u,m)=>{const{frontmatter:g,body:h}=await ep(s,u.id);await of(s,u.id,{...g,id:u.id,status:i,order:m},h)})),await t().updateConfig(u=>{const m={...u.board.columnColors??{}},g=m[n.toLowerCase()];g&&(m[i.toLowerCase()]=g,delete m[n.toLowerCase()]);const h=u.board.columns.some(v=>v.toLowerCase()===n.toLowerCase())?u.board.columns:[...u.board.columns,n];return{...u,board:{...u.board,columns:h.map(v=>v.toLowerCase()===n.toLowerCase()?i:v),columnColors:m}}}),await t().reloadBoard()}catch(l){t().toast("Failed to rename column: "+l.message,"error"),e({columns:c})}},deleteColumn:async n=>{const{columns:a,tasksDirHandle:i}=t();if(!i&&!na())return;const r=a.find(o=>o.name===n);if(!r)return;const s=a;e({columns:a.filter(o=>o.name!==n)});try{await Promise.all(r.tasks.map(o=>rR(i,o.id))),await t().updateConfig(o=>{const c={...o.board.columnColors??{}};return delete c[n.toLowerCase()],{...o,board:{...o.board,columns:o.board.columns.filter(p=>p.toLowerCase()!==n.toLowerCase()),columnColors:c}}}),await t().reloadBoard(),t().toast("Column deleted")}catch(o){t().toast("Failed to delete column: "+o.message,"error"),e({columns:s})}},createTask:async n=>{const{columns:a,tasksDirHandle:i,config:r,taskContents:s}=t();if(!i&&!na()||!a.length)return null;const o=n||r.board.columns[0]||a[0].name,c=I1e(a),p=a.find(m=>m.name===o)?.tasks.length??0,l={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},u=a.map(m=>m.name===o?{...m,tasks:[...m.tasks,l]}:m);e({columns:u});try{const m={id:c,title:"",status:o,order:p,priority:r.fields.priority?r.board.defaultPriority:"",tags:[],assignee:"",created:new Date().toISOString().slice(0,10),ownerType:r.fields.ownerType?r.board.defaultOwnerType:"",tools:""},g="";await of(i||null,c,m,g);const v=new Map(s);return v.set(c,{frontmatter:m,subtasks:[],body:g}),e({taskContents:v}),t().toast(`Created ${c.replace(/^t/,"")}`),await t().openDrawer(c),c}catch(m){return t().toast("Failed to create: "+m.message,"error"),e({columns:a}),null}},deleteTask:async n=>{const{columns:a,tasksDirHandle:i,taskContents:r}=t();if(!i&&!na())return;const s=a.map(p=>({...p,tasks:p.tasks.filter(l=>l.id!==n)}));e({columns:s});const o=new Map(r);o.delete(n);const c=new Map(t().searchMatches);c.delete(n),e({taskContents:o,searchMatches:c});try{await rR(i||null,n),t().toast("Deleted")}catch(p){t().toast("Failed to delete: "+p.message,"error"),e({columns:a})}},openDrawer:async n=>{const{tasksDirHandle:a}=t();if(!(!a&&!na()))try{const{frontmatter:i,body:r}=await ep(a,n),{subtasks:s,bodyWithoutSubtasks:o}=Wo(r),c={frontmatter:i,subtasks:s,body:o,savedAt:Date.now()};e({drawerTaskId:n,drawerData:{frontmatter:i,subtasks:s,body:o},drawerBaseVersion:c,conflictState:null,showConflictModal:!1})}catch(i){t().toast("Failed to open: "+i.message,"error")}},closeDrawer:()=>e({drawerTaskId:null,drawerData:null,drawerBaseVersion:null,conflictState:null,showConflictModal:!1}),updateDrawerData:n=>{const{drawerData:a}=t();a&&e({drawerData:n(a)})},saveDrawer:async()=>{const{drawerTaskId:n,drawerData:a,tasksDirHandle:i,taskContents:r}=t();if(!n||!a)return;const s=Hm(a.body,a.subtasks),o={...a.frontmatter,id:n};try{await of(i||null,n,o,s),t().toast("Saved"),e({drawerTaskId:null,drawerData:null});const c=new Map(r);c.set(n,{frontmatter:o,subtasks:a.subtasks,body:a.body}),e({taskContents:c}),await t().reloadBoard()}catch(c){t().toast("Failed to save: "+c.message,"error")}},saveDrawerMetadata:async()=>{const{drawerTaskId:n,drawerData:a,tasksDirHandle:i,taskContents:r}=t();if(!(!n||!a))try{const s=Hm(a.body,a.subtasks),o={...a.frontmatter,id:n};await of(i||null,n,o,s);const c=new Map(r);c.set(n,{frontmatter:o,subtasks:a.subtasks,body:a.body}),e({taskContents:c}),await t().reloadBoard()}catch(s){t().toast("Failed to save: "+s.message,"error")}},setViewMode:n=>{localStorage.setItem("kandown:view",n),e({viewMode:n})},setDensity:n=>{localStorage.setItem("kandown:density",n),e({density:n})},setFilter:(n,a)=>{if(e(i=>({filters:{...i.filters,[n]:a}})),n==="search"){const{columns:i,tasksDirHandle:r,taskContents:s}=t(),o=a,c=i.flatMap(p=>p.tasks.map(l=>l.id));if(r){const p=c.filter(l=>!s.has(l));p.length>0?t().loadTaskContents(p).then(()=>{t().computeSearchMatches(o)}):t().computeSearchMatches(o)}}},clearFilters:()=>e({filters:{search:"",priority:null,tag:null,assignee:null,ownerType:null},searchMatches:new Map}),setCommandOpen:n=>e({commandOpen:n}),setCurrentPage:n=>e({currentPage:n}),loadTaskContents:async n=>{const{tasksDirHandle:a}=t();if(!a)return;const i=new Map(t().taskContents);await Promise.all(n.map(async r=>{if(!i.has(r))try{const{frontmatter:s,body:o}=await ep(a,r),{subtasks:c,bodyWithoutSubtasks:p}=Wo(o);i.set(r,{frontmatter:s,subtasks:c,body:p})}catch{}})),e({taskContents:i})},computeSearchMatches:n=>{if(!n.trim()){e({searchMatches:new Map});return}const{taskContents:a}=t(),i=new Map,r=n.toLowerCase();for(const[s,o]of a){const c=NY(o,r);c.length>0&&i.set(s,c)}e({searchMatches:i})},toast:(n,a="success")=>{const i=++q1e;e(r=>({toasts:[...r.toasts,{id:i,message:n,type:a}]})),setTimeout(()=>{e(r=>({toasts:r.toasts.filter(s=>s.id!==i)}))},2500)},dismissToast:n=>e(a=>({toasts:a.toasts.filter(i=>i.id!==n)})),resolveConflict:async n=>{const{conflictState:a,drawerData:i,tasksDirHandle:r,drawerTaskId:s,drawerBaseVersion:o}=t();if(!(!a||!r||!s))if(n==="reload"){const{frontmatter:c,body:p}=await ep(r,s),{subtasks:l,bodyWithoutSubtasks:u}=Wo(p);e({drawerData:{frontmatter:c,subtasks:l,body:u},drawerBaseVersion:{frontmatter:c,subtasks:l,body:u,savedAt:Date.now()},conflictState:null,showConflictModal:!1}),t().toast("Reloaded from disk")}else if(n==="overwrite"){if(i&&s&&o){const c=Hm(i.body,i.subtasks),p={...i.frontmatter,id:s};await of(r,s,p,c),e({drawerBaseVersion:{...i,savedAt:Date.now()},conflictState:null,showConflictModal:!1}),t().toast("Overwritten remote changes")}}else e({conflictState:null,showConflictModal:!1})},setupWatcher:()=>{if(na()){AN&&clearInterval(AN),AN=setInterval(()=>{t().reloadBoard()},2e3);return}const{dirHandle:n,tasksDirHandle:a}=t();if(!n||!a)return;cf.stop(),tb.forEach(s=>clearTimeout(s)),tb.clear(),cf.start(n,a);const i=(s,o)=>{const c=tb.get(s);c&&clearTimeout(c);const p=Math.max(2e3,t().config.notifications.editDebounceMs),l=setTimeout(()=>{tb.delete(s);const u=t().config;u.notifications.taskEdits&&xN({title:"Task edited",body:`${o} changed on disk.`,config:u})},p);tb.set(s,l)},r=async s=>{const{tasksDirHandle:o,config:c}=t();if(!o)return;const{frontmatter:p,body:l}=await ep(o,s),{subtasks:u,bodyWithoutSubtasks:m}=Wo(l),g={id:s,frontmatter:{...p,id:p.id||s,status:p.status||"Backlog"},body:m,subtasks:u},h=I$(g),v=_f.get(s);if(!v){_f.set(s,h);return}c.notifications.statusChanges&&v.status!==h.status&&xN({title:"Task status changed",body:`${h.title}: ${v.status} → ${h.status}`,config:c});const w=z1e(v.subtasks,h.subtasks);c.notifications.subtaskCompletions&&w>0&&xN({title:"Subtask completed",body:w===1?`${h.title}: 1 subtask completed.`:`${h.title}: ${w} subtasks completed.`,config:c}),H1e(v,h)&&i(s,h.title),_f.set(s,h)};cf.on("configChanged",()=>{t().loadConfig(),t().toast("Settings updated externally","info")}),cf.on("taskChanged",async s=>{const{drawerTaskId:o,drawerBaseVersion:c,tasksDirHandle:p}=t();if(await r(s),o===s&&c&&p){const{frontmatter:l,body:u}=await ep(p,s),{subtasks:m,bodyWithoutSubtasks:g}=Wo(u),h=c,v=JSON.stringify(h.frontmatter)!==JSON.stringify(l),w=h.body!==g,x=JSON.stringify(h.subtasks)!==JSON.stringify(m);if(!v&&!w&&!x)return;let A="none";v&&(w||x)?A="full":v?A="metadata-only":(w||x)&&(A="body-only"),e({conflictState:{taskId:s,type:A,local:h,remote:{frontmatter:l,body:g,subtasks:m}},showConflictModal:A==="full"})}else t().reloadBoard()}),cf.on("newTaskDetected",async s=>{const{tasksDirHandle:o}=t();if(o){const{frontmatter:c,body:p}=await ep(o,s),{subtasks:l,bodyWithoutSubtasks:u}=Wo(p);_f.set(s,I$({id:s,frontmatter:{...c,id:c.id||s,status:c.status||"Backlog"},body:u,subtasks:l}))}t().reloadBoard()})}}));F$().then(e=>{Me.setState({recentProjects:e})});Vu(bs);window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",()=>{Vu(Me.getState().config)});function U1e(){const e=Me(o=>o.config),t=Me(o=>o.updateConfig),[n,a]=S.useState(!1);if(S.useEffect(()=>{a(!0)},[]),!n)return null;const i=e.ui.theme==="auto"?window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light":e.ui.theme,r=i==="dark",s=()=>{const o=r?"light":"dark";t(c=>({...c,ui:{...c.ui,theme:o}}))};return y.jsx($t.button,{onClick:s,className:L$("relative inline-flex items-center rounded-full transition-all duration-300","border border-black/[0.08] dark:border-white/[0.12]","bg-black/[0.05] dark:bg-white/[0.08]","h-8 w-[52px] px-1"),title:r?"Switch to light mode":"Switch to dark mode",children:y.jsx($t.div,{className:L$("inline-flex items-center justify-center rounded-full shadow-md",r?"bg-[#1e293b] border border-white/[0.15]":"bg-black border border-black/20","h-[26px] w-[26px]"),animate:{x:r?22:0},transition:{type:"spring",stiffness:500,damping:30},children:y.jsx($t.div,{initial:{rotate:-90,scale:.5,opacity:0},animate:{rotate:0,scale:1,opacity:1},transition:{duration:.25,ease:"easeOut"},children:r?y.jsx(va.Moon,{size:13,className:"text-sky-300"}):y.jsx(va.Sun,{size:13,className:"text-amber-400"})},i)})})}function V1e(e){const t=kve(e,{stiffness:180,damping:22,mass:.8});return S.useEffect(()=>{t.set(e)},[e,t]),nY(t,a=>Math.round(a).toString())}const R$="0.7.2",Z1e=({className:e})=>y.jsxs("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 150 150",className:e,fill:"currentColor",children:[y.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"}),y.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"}),y.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 G1e(){const{t:e}=za(),t=Me(q=>q.dirHandle),n=Me(q=>q.isOpen);Me(q=>q.projectName);const a=Me(q=>q.columns),i=Me(q=>q.openFolder),r=Me(q=>q.reloadBoard),s=Me(q=>q.createTask),o=Me(q=>q.setCommandOpen),c=Me(q=>q.viewMode),p=Me(q=>q.setViewMode),l=Me(q=>q.density),u=Me(q=>q.setDensity),m=Me(q=>q.setCurrentPage),g=Me(q=>q.recentProjects),h=Me(q=>q.openRecentProject),v=Me(q=>q.filters),w=Me(q=>q.setFilter),x=Me(q=>q.clearFilters),A=Me(q=>q.config.fields),[N,M]=S.useState(!1),T=S.useRef(null),P=S.useRef(null),F=a.reduce((q,W)=>q+W.tasks.length,0),R=V1e(F),B=[];A.priority&&v.priority&&B.push({type:"priority",label:v.priority,value:v.priority}),A.tags&&v.tag&&B.push({type:"tag",label:"#"+v.tag,value:v.tag}),A.assignee&&v.assignee&&B.push({type:"assignee",label:"@"+v.assignee,value:v.assignee});const j=[{label:e("filterBar.ownerAll"),value:""},{label:e("filterBar.ownerHuman"),value:"human"},{label:e("filterBar.ownerAI"),value:"ai"}],V=B.length>0||v.search||A.ownerType&&v.ownerType;return S.useEffect(()=>{if(!N)return;const q=W=>{T.current&&!T.current.contains(W.target)&&M(!1)};return document.addEventListener("mousedown",q),()=>document.removeEventListener("mousedown",q)},[N]),y.jsxs("header",{className:"flex items-center justify-between px-5 h-[64px] border-b border-border bg-card/80 backdrop-blur-xl relative z-10",children:[y.jsxs("div",{className:"flex items-center gap-4 min-w-0",children:[y.jsx("div",{className:"flex items-center gap-2.5 flex-shrink-0",children:y.jsxs("button",{onClick:()=>window.history.pushState({},"",window.location.pathname),className:"flex items-center gap-2 cursor-pointer",children:[y.jsx(Z1e,{className:"w-[34px] h-[34px] dark:text-white text-black"}),y.jsx("span",{className:"text-[15px] font-semibold tracking-tight text-fg",children:"kandown"}),y.jsxs("span",{className:"inline-flex items-center h-5 px-1.5 text-[10.5px] font-semibold text-red-600 bg-red-50 dark:text-red-400 dark:bg-red-500/15 rounded-md",children:["v",R$]})]})}),t&&y.jsxs(y.Fragment,{children:[y.jsx("div",{className:"w-px h-[20px] bg-black/[0.08] dark:bg-white/[0.08] flex-shrink-0"}),y.jsxs("div",{className:"flex items-center gap-2 px-3 h-9 bg-secondary/60 border border-border rounded-xl min-w-[200px] max-w-[280px] focus-within:border-border-focus focus-within:bg-secondary transition-all",children:[y.jsx(va.Search,{size:14,className:"text-fg-muted/60 flex-shrink-0"}),y.jsx("input",{ref:P,type:"text",placeholder:e("filterBar.searchPlaceholder"),value:v.search,onChange:q=>w("search",q.target.value),className:"bg-transparent border-none outline-none text-fg text-[13px] w-full placeholder:text-fg-muted/60"}),v.search?y.jsx("button",{onClick:()=>w("search",""),className:"text-fg-muted/60 hover:text-fg flex-shrink-0",children:y.jsx(va.X,{size:14})}):y.jsx("kbd",{className:"inline-flex items-center h-5 px-1.5 text-[10px] font-medium text-fg-muted/50 bg-black/[0.04] dark:bg-white/[0.08] rounded border border-black/[0.06] dark:border-white/[0.1]",children:"⌘K"})]}),y.jsxs("div",{className:"flex items-center gap-1.5 flex-wrap overflow-hidden",children:[y.jsx(Zr,{children:B.map(q=>y.jsxs($t.button,{initial:{opacity:0,scale:.9},animate:{opacity:1,scale:1},exit:{opacity:0,scale:.9},transition:{duration:.15},onClick:()=>w(q.type,null),className:"inline-flex items-center gap-1 h-6 px-2.5 text-[12px] text-fg bg-black/[0.05] dark:bg-white/[0.1] border border-black/[0.08] dark:border-white/[0.12] rounded-lg hover:bg-black/[0.08] dark:hover:bg-white/[0.15] transition-colors",children:[q.label,y.jsx(va.X,{size:10,className:"text-fg-muted/60"})]},q.type+q.value))}),A.ownerType&&y.jsx("div",{className:"flex items-center h-6 border border-black/[0.06] dark:border-white/[0.1] rounded-lg overflow-hidden",children:j.map(q=>y.jsx("button",{onClick:()=>w("ownerType",q.value),className:`h-full px-2.5 text-[12px] transition-colors ${v.ownerType===q.value?"bg-black/[0.06] dark:bg-white/[0.12] text-fg":"text-fg-muted/70 hover:text-fg"}`,children:q.label},q.value))}),V&&y.jsx("button",{onClick:x,className:"text-[12px] text-fg-muted/60 hover:text-fg transition-colors",children:e("filterBar.clearAll")})]})]}),t&&y.jsxs(y.Fragment,{children:[y.jsx("div",{className:"w-px h-[20px] bg-black/[0.08] dark:bg-white/[0.08] flex-shrink-0"}),y.jsxs("div",{className:"relative flex-shrink-0",ref:T,children:[y.jsxs("button",{onClick:()=>M(q=>!q),className:"flex items-center gap-1.5 px-2.5 py-1.5 text-[13px] text-fg-muted hover:text-fg hover:bg-black/[0.04] dark:hover:bg-white/[0.06] rounded-lg transition-colors border border-transparent hover:border-black/[0.06] dark:hover:border-white/[0.1]",children:[y.jsx(va.Folder,{size:13,className:"text-fg-muted/70"}),y.jsxs("span",{className:"font-medium",children:[".",t.name]}),y.jsx(va.ChevronDown,{size:11,className:"opacity-50"})]}),y.jsx(Zr,{children:N&&y.jsx($t.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-2 min-w-[240px] glass rounded-xl shadow-[0_16px_48px_rgba(0,0,0,0.5)] overflow-hidden z-50",children:y.jsxs("div",{className:"py-1.5",children:[g.length>0&&y.jsxs(y.Fragment,{children:[y.jsx("div",{className:"px-3 py-1.5 text-[11px] font-semibold uppercase tracking-wider text-fg-muted/60",children:e("header.recentProjects")}),g.map(q=>y.jsxs("button",{onClick:()=>{M(!1),h(q)},className:"w-full flex items-center gap-2 px-3 py-2 text-[13.5px] text-left hover:bg-black/[0.04] dark:hover:bg-white/[0.06] transition-colors",children:[y.jsx(va.Folder,{size:12,className:"text-fg-muted/60"}),y.jsx("span",{className:"truncate",children:q.name}),q.id===t.name&&y.jsx(va.Check,{size:12,className:"ml-auto text-emerald-500"})]},q.id)),y.jsx("div",{className:"h-px bg-black/[0.06] dark:bg-white/[0.08] my-1.5 mx-2"})]}),y.jsxs("button",{onClick:()=>{M(!1),i()},className:"w-full flex items-center gap-2 px-3 py-2 text-[13.5px] text-left hover:bg-black/[0.04] dark:hover:bg-white/[0.06] transition-colors",children:[y.jsx(va.Plus,{size:12,className:"text-fg-muted/60"}),y.jsx("span",{children:e("header.openFolder...")})]})]})})})]})]})]}),y.jsx("div",{className:"flex items-center gap-2 flex-shrink-0",children:n||t?y.jsxs(y.Fragment,{children:[y.jsxs("div",{className:"flex items-center gap-2 mr-2 text-[12.5px] text-fg-muted/70",children:[y.jsx("span",{className:"inline-block w-1.5 h-1.5 rounded-full bg-emerald-500"}),y.jsx($t.span,{className:"tabular-nums font-medium",children:R}),y.jsx("span",{children:e("header.tasks")})]}),y.jsxs("div",{className:"flex items-center bg-black/[0.04] dark:bg-white/[0.06] border border-black/[0.06] dark:border-white/[0.1] rounded-xl p-0.5 h-10",children:[y.jsx("button",{onClick:()=>p("board"),className:`w-9 h-9 inline-flex items-center justify-center rounded-lg transition-all ${c==="board"?"bg-card text-fg shadow-sm":"text-fg-muted/70 hover:text-fg"}`,title:e("common.board"),children:y.jsx(va.LayoutBoard,{size:18})}),y.jsx("button",{onClick:()=>p("list"),className:`w-9 h-9 inline-flex items-center justify-center rounded-lg transition-all ${c==="list"?"bg-card text-fg shadow-sm":"text-fg-muted/70 hover:text-fg"}`,title:e("common.list"),children:y.jsx(va.LayoutList,{size:18})})]}),y.jsx(dr,{variant:"icon",icon:"Density",onClick:()=>u(l==="compact"?"comfortable":"compact"),title:`Density: ${l}`}),y.jsx(dr,{variant:"icon",icon:"Settings",onClick:()=>m("settings"),title:e("common.settings")}),y.jsx(U1e,{}),y.jsx("div",{className:"w-px h-5 bg-black/[0.08] dark:bg-white/[0.08] mx-1"}),y.jsx(dr,{variant:"secondary",icon:"Search",label:e("common.search"),shortcut:"⌘K",onClick:()=>o(!0),title:"Command palette (⌘K)"}),y.jsx(dr,{variant:"icon",icon:"Refresh",onClick:r,title:`${e("common.reload")} (R)`}),y.jsx(dr,{variant:"primary",icon:"Plus",label:e("common.newTask"),shortcut:"N",onClick:()=>s()})]}):y.jsx(dr,{variant:"primary",label:e("common.openFolder"),onClick:i})})]})}/**
|
|
115
|
+
`}}}async function of(e,t,n,a){if(na())return C1e(t,nR(n,a));const r=await(await e.getFileHandle(`${t}.md`,{create:!0})).createWritable();await r.write(nR(n,a)),await r.close()}async function rR(e,t){if(na())return A1e(t);try{await e.removeEntry(`${t}.md`)}catch{}}const T1e="kanban-md",M1e=1,Vf="recentProjects";function PY(){return new Promise((e,t)=>{const n=indexedDB.open(T1e,M1e);n.onerror=()=>t(n.error),n.onsuccess=()=>e(n.result),n.onupgradeneeded=()=>{const a=n.result;a.objectStoreNames.contains(Vf)||a.createObjectStore(Vf,{keyPath:"id"})}})}async function wN(e){const t=await PY();return new Promise((n,a)=>{const i=t.transaction(Vf,"readwrite");i.objectStore(Vf).put(e),i.oncomplete=()=>n(),i.onerror=()=>a(i.error)})}async function F$(){const e=await PY();return new Promise((t,n)=>{const i=e.transaction(Vf,"readonly").objectStore(Vf).getAll();i.onsuccess=()=>{const r=i.result||[];r.sort((s,o)=>o.lastOpened-s.lastOpened),t(r.slice(0,10))},i.onerror=()=>n(i.error)})}async function sR(e,t=!0){const n={mode:t?"readwrite":"read"};return await e.queryPermission(n)==="granted"||await e.requestPermission(n)==="granted"}class L1e{dirHandle=null;tasksDirHandle=null;intervalId=null;configHash=null;taskHashes=new Map;knownTaskIds=new Set;listeners=new Map;debounceTimers=new Map;debounceDelay=150;start(t,n){this.dirHandle=t,this.tasksDirHandle=n,this.initHashes(),this.intervalId=setInterval(()=>void this.tick(),300)}stop(){this.intervalId&&(clearInterval(this.intervalId),this.intervalId=null),this.configHash=null,this.taskHashes.clear(),this.knownTaskIds.clear(),this.listeners.clear(),this.debounceTimers.forEach(t=>clearTimeout(t)),this.debounceTimers.clear()}on(t,n){return this.listeners.has(t)||this.listeners.set(t,new Set),this.listeners.get(t).add(n),()=>{this.listeners.get(t)?.delete(n)}}getKnownTaskIds(){return Array.from(this.knownTaskIds)}async initHashes(){if(!this.dirHandle||!this.tasksDirHandle)return;const t=await oR(this.dirHandle);t!==null&&(this.configHash=await this.hash(t)),await this.syncTaskDir(!1)}async tick(){if(!this.dirHandle||!this.tasksDirHandle)return;const t=await oR(this.dirHandle);if(t!==null){const n=await this.hash(t);this.configHash!==null&&n!==this.configHash&&(this.configHash=n,this.debouncedEmit("configChanged"))}for(const n of this.knownTaskIds){const a=await cR(this.tasksDirHandle,n);if(a!==null){const i=await this.hash(a),r=this.taskHashes.get(n);r!==void 0&&i!==r&&(this.taskHashes.set(n,i),this.debouncedEmit("taskChanged",n))}}await this.syncTaskDir(!0)}debouncedEmit(t,...n){const a=t+JSON.stringify(n),i=this.debounceTimers.get(a);i&&clearTimeout(i);const r=setTimeout(()=>{this.debounceTimers.delete(a),this.emit(t,...n)},this.debounceDelay);this.debounceTimers.set(a,r)}async syncTaskDir(t){if(this.tasksDirHandle){for await(const n of this.tasksDirHandle.values())if(n.kind==="file"&&n.name.endsWith(".md")){const a=n.name.replace(".md","");if(!this.knownTaskIds.has(a)){this.knownTaskIds.add(a);const i=await cR(this.tasksDirHandle,a);i!==null&&(this.taskHashes.set(a,await this.hash(i)),t&&this.debouncedEmit("newTaskDetected",a))}}}}async hash(t){const a=new TextEncoder().encode(t),i=await crypto.subtle.digest("SHA-256",a);return Array.from(new Uint8Array(i)).map(s=>s.toString(16).padStart(2,"0")).join("")}emit(t,...n){this.listeners.get(t)?.forEach(i=>{if(t==="configChanged"){i();return}if(t==="taskChanged"){const[s]=n;i(s);return}const[r]=n;i(r)})}}async function oR(e){try{return await(await(await e.getFileHandle("kandown.json")).getFile()).text()}catch{return null}}async function cR(e,t){try{return await(await(await e.getFileHandle(`${t}.md`)).getFile()).text()}catch{return null}}const cf=new L1e,j1e={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 pR=null;function TY(){return"Notification"in window?Notification.permission:"unsupported"}async function D1e(){return"Notification"in window?await Notification.requestPermission():"unsupported"}function xN({title:e,body:t,config:n}){if(n.notifications.sound&&F1e(n.notifications.soundId),!(!n.notifications.browser||TY()!=="granted"))try{new Notification(e,{body:t})}catch{}}function F1e(e){const t=j1e[e],n=window.AudioContext??window.webkitAudioContext;if(!(!n||t.length===0))try{pR??=new n;const a=pR;a.state==="suspended"&&a.resume();const i=a.currentTime+.01;t.forEach(r=>{const s=a.createOscillator(),o=a.createGain(),c=i+r.delayMs/1e3,p=c+r.durationMs/1e3;s.type=r.type??"sine",s.frequency.setValueAtTime(r.frequency,c),o.gain.setValueAtTime(1e-4,c),o.gain.exponentialRampToValueAtTime(.08,c+.01),o.gain.exponentialRampToValueAtTime(1e-4,p),s.connect(o),o.connect(a.destination),s.start(c),s.stop(p+.02)})}catch{}}function I1e(e){let t=-1;for(const n of e)for(const a of n.tasks){const i=a.id.match(/^t(\d+)$/);if(i){const r=parseInt(i[1],10);r>t&&(t=r)}}return"t"+(t+1)}async function R1e(){const e=await U6();return await Promise.all(e.map(async n=>{const{frontmatter:a,body:i}=await SY(n),r={...a,id:a.id||n,status:a.status||"Backlog"},{subtasks:s,bodyWithoutSubtasks:o}=Wo(i);return{id:n,frontmatter:r,body:o,subtasks:s}}))}async function O1e(e){const t=await P1e(e);return await Promise.all(t.map(async a=>{const{frontmatter:i,body:r}=await ep(e,a),s={...i,id:i.id||a,status:i.status||"Backlog"},{subtasks:o,bodyWithoutSubtasks:c}=Wo(r);return{id:a,frontmatter:s,body:c,subtasks:o}}))}async function lR(e,t,n){const a=[];for(const i of t){const r=i.name;i.tasks.forEach((s,o)=>{a.push((async()=>{const{frontmatter:c,body:p}=await ep(e,s.id);await of(e,s.id,{...c,id:s.id,status:r,order:o},p)})())})}await Promise.all(a)}function Vu(e){_1e(e.ui.theme,e.ui.skin,e.ui.font,e.ui.background)}function I$(e){return{title:e.frontmatter.title||e.id,status:e.frontmatter.status||"Backlog",body:e.body,subtasks:e.subtasks}}function CN(e){_f.clear(),e.forEach(t=>{_f.set(t.id,I$(t))})}function B1e(e){const t=e.split(/[\\/]+/).filter(Boolean),n=t[t.length-1];return n===".kandown"?t[t.length-2]??"Project":n??"Project"}function z1e(e,t){return t.reduce((n,a,i)=>{const r=e[i]?.done??!1;return n+(a.done&&!r?1:0)},0)}function H1e(e,t){const n=e.subtasks.map(i=>({text:i.text,description:i.description??"",report:i.report??""})),a=t.subtasks.map(i=>({text:i.text,description:i.description??"",report:i.report??""}));return e.title!==t.title||e.body!==t.body||JSON.stringify(n)!==JSON.stringify(a)}let q1e=0;const _f=new Map,tb=new Map;let AN=null;const Me=l1e((e,t)=>({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:bs,recentProjects:[],toasts:[],drawerBaseVersion:null,conflictState:null,showConflictModal:!1,openFolder:async()=>{const n=await E1e();if(!n)return;const{projectHandle:a,kandownHandle:i}=n,r=await Hb(i),s=a.name;e({dirHandle:i,tasksDirHandle:r,projectName:s}),window.history.pushState({},"",`?p=${encodeURIComponent(s)}`);const o=na()?kN():null;await wN({id:a.name,name:a.name,handle:a,lastOpened:Date.now(),...o?{kandownDir:o}:{}}),await t().loadConfig(),await t().reloadBoard();const c=await F$();e({recentProjects:c}),t().setupWatcher()},openRecentProject:async n=>{if(!await sR(n.handle,!0)){t().toast("Permission denied","error");return}const i=await iR(n.handle),r=await Hb(i),s=n.handle.name;e({dirHandle:i,tasksDirHandle:r,projectName:s}),window.history.pushState({},"",`?p=${encodeURIComponent(s)}`),await wN({...n,lastOpened:Date.now()}),await t().loadConfig(),await t().reloadBoard(),t().setupWatcher()},openServerProject:async()=>{e({loading:!0});try{const n=kN();if(!n)throw new Error("No server root");const a=B1e(n),i=await EY();Vu(i);const r=await U6(),s=await Promise.all(r.map(async u=>{const{frontmatter:m,body:g}=await SY(u),h={...m,id:m.id||u,status:m.status||"Backlog"},{subtasks:v,bodyWithoutSubtasks:w}=Wo(g);return{id:u,frontmatter:h,body:w,subtasks:v}}));CN(s);const o=s.map(u=>({frontmatter:u.frontmatter,body:Hm(u.body,u.subtasks)})),c=_N(o,i.board.columns),p=c.reduce((u,m)=>u+m.tasks.length,0),l=new Map;if(p<=10)for(const u of s)l.set(u.frontmatter.id,{frontmatter:u.frontmatter,subtasks:u.subtasks,body:u.body});e({loading:!1,isOpen:!0,config:i,columns:c,boardTitle:"Project Kanban",projectName:a,taskContents:l,searchMatches:new Map}),window.history.pushState({},"",`?p=${encodeURIComponent(a)}`),t().setupWatcher()}catch{e({loading:!1,isOpen:!1}),t().toast("Impossible de charger le projet. Relancez `kandown`.","error")}},tryAutoOpenServerProject:async()=>{if(!na())return;const n=kN();if(!n)return;const a=await F$(),i=a.find(p=>p.kandownDir===n);if(!i){await t().openServerProject();return}if(!await sR(i.handle,!0)){await t().openServerProject();return}const s=await iR(i.handle),o=await Hb(s),c=i.handle.name;e({dirHandle:s,tasksDirHandle:o,projectName:c,recentProjects:a,isOpen:!0}),window.history.pushState({},"",`?p=${encodeURIComponent(c)}`),await wN({...i,lastOpened:Date.now()}),await t().loadConfig(),await t().reloadBoard(),t().setupWatcher()},loadConfig:async()=>{const{dirHandle:n}=t();if(!(!n&&!na()))try{const a=await S1e(n);a?(e({config:a}),Vu(a)):(e({config:bs}),Vu(bs))}catch{e({config:bs}),Vu(bs)}},updateConfig:async n=>{const{dirHandle:a,config:i}=t();if(!a&&!na())return;const r=n(i);e({config:r}),Vu(r);try{await $1e(a,r)}catch(s){t().toast("Failed to save config: "+s.message,"error")}},reloadBoard:async()=>{const{tasksDirHandle:n,config:a}=t();if(na())try{const i=await R1e();CN(i);const r=i.map(p=>({frontmatter:p.frontmatter,body:Hm(p.body,p.subtasks)})),s=_N(r,a.board.columns);e({boardTitle:"Project Kanban",columns:s});const o=s.reduce((p,l)=>p+l.tasks.length,0),c=new Map;if(o<=10)for(const p of i)c.set(p.frontmatter.id,{frontmatter:p.frontmatter,subtasks:p.subtasks,body:p.body});e({taskContents:c,searchMatches:new Map})}catch(i){t().toast("Failed to load board: "+i.message,"error")}else if(n)try{const i=await O1e(n);CN(i);const r=i.map(p=>({frontmatter:p.frontmatter,body:Hm(p.body,p.subtasks)})),s=_N(r,a.board.columns);e({boardTitle:"Project Kanban",columns:s});const o=s.reduce((p,l)=>p+l.tasks.length,0),c=new Map;if(o<=10)for(const p of i)c.set(p.frontmatter.id,{frontmatter:p.frontmatter,subtasks:p.subtasks,body:p.body});e({taskContents:c,searchMatches:new Map})}catch(i){t().toast("Failed to load board: "+i.message,"error")}},moveTask:async(n,a,i,r)=>{const{columns:s,config:o}=t();if(!na()&&!t().tasksDirHandle)return;const p=s.find(w=>w.name===a),l=s.find(w=>w.name===i);if(!p||!l)return;const u=p.tasks.findIndex(w=>w.id===n);if(u===-1)return;const m=s.map(w=>({...w,tasks:[...w.tasks]})),g=m.find(w=>w.name===a),h=m.find(w=>w.name===i),[v]=g.tasks.splice(u,1);/done|termin|closed|complet/i.test(i)?v.checked=!0:v.checked=!1,r!==void 0?h.tasks.splice(r,0,v):h.tasks.push(v),e({columns:m});try{if(na())return;const{tasksDirHandle:w}=t();if(!w)return;const x=a===i?m.filter(A=>A.name===i):m.filter(A=>A.name===a||A.name===i);await lR(w,x,o.board.columns)}catch(w){t().toast("Failed to save: "+w.message,"error"),e({columns:s})}},reorderInColumn:async(n,a,i)=>{const{columns:r,tasksDirHandle:s,config:o}=t();if(!s&&!na())return;const c=r.map(u=>({...u,tasks:[...u.tasks]})),p=c.find(u=>u.name===n);if(!p)return;const[l]=p.tasks.splice(a,1);p.tasks.splice(i,0,l),e({columns:c});try{if(na())return;const{tasksDirHandle:u}=t();if(!u)return;await lR(u,[p],o.board.columns)}catch(u){t().toast("Failed to save: "+u.message,"error"),e({columns:r})}},addColumn:async n=>{const a=n.trim();if(!a)return;const{config:i}=t();i.board.columns.some(r=>r.toLowerCase()===a.toLowerCase())||(await t().updateConfig(r=>({...r,board:{...r.board,columns:[...r.board.columns,a]}})),await t().reloadBoard())},renameColumn:async(n,a)=>{const i=a.trim(),{columns:r,tasksDirHandle:s,config:o}=t();if(!s||!i||i.toLowerCase()===n.toLowerCase())return;if(r.some(l=>l.name.toLowerCase()===i.toLowerCase())){t().toast("Column already exists","error");return}const c=r,p=r.map(l=>l.name===n?{...l,name:i}:l);e({columns:p});try{const l=c.find(u=>u.name===n);l&&await Promise.all(l.tasks.map(async(u,m)=>{const{frontmatter:g,body:h}=await ep(s,u.id);await of(s,u.id,{...g,id:u.id,status:i,order:m},h)})),await t().updateConfig(u=>{const m={...u.board.columnColors??{}},g=m[n.toLowerCase()];g&&(m[i.toLowerCase()]=g,delete m[n.toLowerCase()]);const h=u.board.columns.some(v=>v.toLowerCase()===n.toLowerCase())?u.board.columns:[...u.board.columns,n];return{...u,board:{...u.board,columns:h.map(v=>v.toLowerCase()===n.toLowerCase()?i:v),columnColors:m}}}),await t().reloadBoard()}catch(l){t().toast("Failed to rename column: "+l.message,"error"),e({columns:c})}},deleteColumn:async n=>{const{columns:a,tasksDirHandle:i}=t();if(!i&&!na())return;const r=a.find(o=>o.name===n);if(!r)return;const s=a;e({columns:a.filter(o=>o.name!==n)});try{await Promise.all(r.tasks.map(o=>rR(i,o.id))),await t().updateConfig(o=>{const c={...o.board.columnColors??{}};return delete c[n.toLowerCase()],{...o,board:{...o.board,columns:o.board.columns.filter(p=>p.toLowerCase()!==n.toLowerCase()),columnColors:c}}}),await t().reloadBoard(),t().toast("Column deleted")}catch(o){t().toast("Failed to delete column: "+o.message,"error"),e({columns:s})}},createTask:async n=>{const{columns:a,tasksDirHandle:i,config:r,taskContents:s}=t();if(!i&&!na()||!a.length)return null;const o=n||r.board.columns[0]||a[0].name,c=I1e(a),p=a.find(m=>m.name===o)?.tasks.length??0,l={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},u=a.map(m=>m.name===o?{...m,tasks:[...m.tasks,l]}:m);e({columns:u});try{const m={id:c,title:"",status:o,order:p,priority:r.fields.priority?r.board.defaultPriority:"",tags:[],assignee:"",created:new Date().toISOString().slice(0,10),ownerType:r.fields.ownerType?r.board.defaultOwnerType:"",tools:""},g="";await of(i||null,c,m,g);const v=new Map(s);return v.set(c,{frontmatter:m,subtasks:[],body:g}),e({taskContents:v}),t().toast(`Created ${c.replace(/^t/,"")}`),await t().openDrawer(c),c}catch(m){return t().toast("Failed to create: "+m.message,"error"),e({columns:a}),null}},deleteTask:async n=>{const{columns:a,tasksDirHandle:i,taskContents:r}=t();if(!i&&!na())return;const s=a.map(p=>({...p,tasks:p.tasks.filter(l=>l.id!==n)}));e({columns:s});const o=new Map(r);o.delete(n);const c=new Map(t().searchMatches);c.delete(n),e({taskContents:o,searchMatches:c});try{await rR(i||null,n),t().toast("Deleted")}catch(p){t().toast("Failed to delete: "+p.message,"error"),e({columns:a})}},openDrawer:async n=>{const{tasksDirHandle:a}=t();if(!(!a&&!na()))try{const{frontmatter:i,body:r}=await ep(a,n),{subtasks:s,bodyWithoutSubtasks:o}=Wo(r),c={frontmatter:i,subtasks:s,body:o,savedAt:Date.now()};e({drawerTaskId:n,drawerData:{frontmatter:i,subtasks:s,body:o},drawerBaseVersion:c,conflictState:null,showConflictModal:!1})}catch(i){t().toast("Failed to open: "+i.message,"error")}},closeDrawer:()=>e({drawerTaskId:null,drawerData:null,drawerBaseVersion:null,conflictState:null,showConflictModal:!1}),updateDrawerData:n=>{const{drawerData:a}=t();a&&e({drawerData:n(a)})},saveDrawer:async()=>{const{drawerTaskId:n,drawerData:a,tasksDirHandle:i,taskContents:r}=t();if(!n||!a)return;const s=Hm(a.body,a.subtasks),o={...a.frontmatter,id:n};try{await of(i||null,n,o,s),t().toast("Saved"),e({drawerTaskId:null,drawerData:null});const c=new Map(r);c.set(n,{frontmatter:o,subtasks:a.subtasks,body:a.body}),e({taskContents:c}),await t().reloadBoard()}catch(c){t().toast("Failed to save: "+c.message,"error")}},saveDrawerMetadata:async()=>{const{drawerTaskId:n,drawerData:a,tasksDirHandle:i,taskContents:r}=t();if(!(!n||!a))try{const s=Hm(a.body,a.subtasks),o={...a.frontmatter,id:n};await of(i||null,n,o,s);const c=new Map(r);c.set(n,{frontmatter:o,subtasks:a.subtasks,body:a.body}),e({taskContents:c}),await t().reloadBoard()}catch(s){t().toast("Failed to save: "+s.message,"error")}},setViewMode:n=>{localStorage.setItem("kandown:view",n),e({viewMode:n})},setDensity:n=>{localStorage.setItem("kandown:density",n),e({density:n})},setFilter:(n,a)=>{if(e(i=>({filters:{...i.filters,[n]:a}})),n==="search"){const{columns:i,tasksDirHandle:r,taskContents:s}=t(),o=a,c=i.flatMap(p=>p.tasks.map(l=>l.id));if(r){const p=c.filter(l=>!s.has(l));p.length>0?t().loadTaskContents(p).then(()=>{t().computeSearchMatches(o)}):t().computeSearchMatches(o)}}},clearFilters:()=>e({filters:{search:"",priority:null,tag:null,assignee:null,ownerType:null},searchMatches:new Map}),setCommandOpen:n=>e({commandOpen:n}),setCurrentPage:n=>e({currentPage:n}),loadTaskContents:async n=>{const{tasksDirHandle:a}=t();if(!a)return;const i=new Map(t().taskContents);await Promise.all(n.map(async r=>{if(!i.has(r))try{const{frontmatter:s,body:o}=await ep(a,r),{subtasks:c,bodyWithoutSubtasks:p}=Wo(o);i.set(r,{frontmatter:s,subtasks:c,body:p})}catch{}})),e({taskContents:i})},computeSearchMatches:n=>{if(!n.trim()){e({searchMatches:new Map});return}const{taskContents:a}=t(),i=new Map,r=n.toLowerCase();for(const[s,o]of a){const c=NY(o,r);c.length>0&&i.set(s,c)}e({searchMatches:i})},toast:(n,a="success")=>{const i=++q1e;e(r=>({toasts:[...r.toasts,{id:i,message:n,type:a}]})),setTimeout(()=>{e(r=>({toasts:r.toasts.filter(s=>s.id!==i)}))},2500)},dismissToast:n=>e(a=>({toasts:a.toasts.filter(i=>i.id!==n)})),resolveConflict:async n=>{const{conflictState:a,drawerData:i,tasksDirHandle:r,drawerTaskId:s,drawerBaseVersion:o}=t();if(!(!a||!r||!s))if(n==="reload"){const{frontmatter:c,body:p}=await ep(r,s),{subtasks:l,bodyWithoutSubtasks:u}=Wo(p);e({drawerData:{frontmatter:c,subtasks:l,body:u},drawerBaseVersion:{frontmatter:c,subtasks:l,body:u,savedAt:Date.now()},conflictState:null,showConflictModal:!1}),t().toast("Reloaded from disk")}else if(n==="overwrite"){if(i&&s&&o){const c=Hm(i.body,i.subtasks),p={...i.frontmatter,id:s};await of(r,s,p,c),e({drawerBaseVersion:{...i,savedAt:Date.now()},conflictState:null,showConflictModal:!1}),t().toast("Overwritten remote changes")}}else e({conflictState:null,showConflictModal:!1})},setupWatcher:()=>{if(na()){AN&&clearInterval(AN),AN=setInterval(()=>{t().reloadBoard()},2e3);return}const{dirHandle:n,tasksDirHandle:a}=t();if(!n||!a)return;cf.stop(),tb.forEach(s=>clearTimeout(s)),tb.clear(),cf.start(n,a);const i=(s,o)=>{const c=tb.get(s);c&&clearTimeout(c);const p=Math.max(2e3,t().config.notifications.editDebounceMs),l=setTimeout(()=>{tb.delete(s);const u=t().config;u.notifications.taskEdits&&xN({title:"Task edited",body:`${o} changed on disk.`,config:u})},p);tb.set(s,l)},r=async s=>{const{tasksDirHandle:o,config:c}=t();if(!o)return;const{frontmatter:p,body:l}=await ep(o,s),{subtasks:u,bodyWithoutSubtasks:m}=Wo(l),g={id:s,frontmatter:{...p,id:p.id||s,status:p.status||"Backlog"},body:m,subtasks:u},h=I$(g),v=_f.get(s);if(!v){_f.set(s,h);return}c.notifications.statusChanges&&v.status!==h.status&&xN({title:"Task status changed",body:`${h.title}: ${v.status} → ${h.status}`,config:c});const w=z1e(v.subtasks,h.subtasks);c.notifications.subtaskCompletions&&w>0&&xN({title:"Subtask completed",body:w===1?`${h.title}: 1 subtask completed.`:`${h.title}: ${w} subtasks completed.`,config:c}),H1e(v,h)&&i(s,h.title),_f.set(s,h)};cf.on("configChanged",()=>{t().loadConfig(),t().toast("Settings updated externally","info")}),cf.on("taskChanged",async s=>{const{drawerTaskId:o,drawerBaseVersion:c,tasksDirHandle:p}=t();if(await r(s),o===s&&c&&p){const{frontmatter:l,body:u}=await ep(p,s),{subtasks:m,bodyWithoutSubtasks:g}=Wo(u),h=c,v=JSON.stringify(h.frontmatter)!==JSON.stringify(l),w=h.body!==g,x=JSON.stringify(h.subtasks)!==JSON.stringify(m);if(!v&&!w&&!x)return;let A="none";v&&(w||x)?A="full":v?A="metadata-only":(w||x)&&(A="body-only"),e({conflictState:{taskId:s,type:A,local:h,remote:{frontmatter:l,body:g,subtasks:m}},showConflictModal:A==="full"})}else t().reloadBoard()}),cf.on("newTaskDetected",async s=>{const{tasksDirHandle:o}=t();if(o){const{frontmatter:c,body:p}=await ep(o,s),{subtasks:l,bodyWithoutSubtasks:u}=Wo(p);_f.set(s,I$({id:s,frontmatter:{...c,id:c.id||s,status:c.status||"Backlog"},body:u,subtasks:l}))}t().reloadBoard()})}}));F$().then(e=>{Me.setState({recentProjects:e})});Vu(bs);window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",()=>{Vu(Me.getState().config)});function U1e(){const e=Me(o=>o.config),t=Me(o=>o.updateConfig),[n,a]=S.useState(!1);if(S.useEffect(()=>{a(!0)},[]),!n)return null;const i=e.ui.theme==="auto"?window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light":e.ui.theme,r=i==="dark",s=()=>{const o=r?"light":"dark";t(c=>({...c,ui:{...c.ui,theme:o}}))};return y.jsx($t.button,{onClick:s,className:L$("relative inline-flex items-center rounded-full transition-all duration-300","border border-black/[0.08] dark:border-white/[0.12]","bg-black/[0.05] dark:bg-white/[0.08]","h-8 w-[52px] px-1"),title:r?"Switch to light mode":"Switch to dark mode",children:y.jsx($t.div,{className:L$("inline-flex items-center justify-center rounded-full shadow-md",r?"bg-[#1e293b] border border-white/[0.15]":"bg-black border border-black/20","h-[26px] w-[26px]"),animate:{x:r?22:0},transition:{type:"spring",stiffness:500,damping:30},children:y.jsx($t.div,{initial:{rotate:-90,scale:.5,opacity:0},animate:{rotate:0,scale:1,opacity:1},transition:{duration:.25,ease:"easeOut"},children:r?y.jsx(va.Moon,{size:13,className:"text-sky-300"}):y.jsx(va.Sun,{size:13,className:"text-amber-400"})},i)})})}function V1e(e){const t=kve(e,{stiffness:180,damping:22,mass:.8});return S.useEffect(()=>{t.set(e)},[e,t]),nY(t,a=>Math.round(a).toString())}const R$="0.7.3",Z1e=({className:e})=>y.jsxs("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 150 150",className:e,fill:"currentColor",children:[y.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"}),y.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"}),y.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 G1e(){const{t:e}=za(),t=Me(q=>q.dirHandle),n=Me(q=>q.isOpen);Me(q=>q.projectName);const a=Me(q=>q.columns),i=Me(q=>q.openFolder),r=Me(q=>q.reloadBoard),s=Me(q=>q.createTask),o=Me(q=>q.setCommandOpen),c=Me(q=>q.viewMode),p=Me(q=>q.setViewMode),l=Me(q=>q.density),u=Me(q=>q.setDensity),m=Me(q=>q.setCurrentPage),g=Me(q=>q.recentProjects),h=Me(q=>q.openRecentProject),v=Me(q=>q.filters),w=Me(q=>q.setFilter),x=Me(q=>q.clearFilters),A=Me(q=>q.config.fields),[N,M]=S.useState(!1),T=S.useRef(null),P=S.useRef(null),F=a.reduce((q,W)=>q+W.tasks.length,0),R=V1e(F),B=[];A.priority&&v.priority&&B.push({type:"priority",label:v.priority,value:v.priority}),A.tags&&v.tag&&B.push({type:"tag",label:"#"+v.tag,value:v.tag}),A.assignee&&v.assignee&&B.push({type:"assignee",label:"@"+v.assignee,value:v.assignee});const j=[{label:e("filterBar.ownerAll"),value:""},{label:e("filterBar.ownerHuman"),value:"human"},{label:e("filterBar.ownerAI"),value:"ai"}],V=B.length>0||v.search||A.ownerType&&v.ownerType;return S.useEffect(()=>{if(!N)return;const q=W=>{T.current&&!T.current.contains(W.target)&&M(!1)};return document.addEventListener("mousedown",q),()=>document.removeEventListener("mousedown",q)},[N]),y.jsxs("header",{className:"flex items-center justify-between px-5 h-[64px] border-b border-border bg-card/80 backdrop-blur-xl relative z-10",children:[y.jsxs("div",{className:"flex items-center gap-4 min-w-0",children:[y.jsx("div",{className:"flex items-center gap-2.5 flex-shrink-0",children:y.jsxs("button",{onClick:()=>window.history.pushState({},"",window.location.pathname),className:"flex items-center gap-2 cursor-pointer",children:[y.jsx(Z1e,{className:"w-[34px] h-[34px] dark:text-white text-black"}),y.jsx("span",{className:"text-[15px] font-semibold tracking-tight text-fg",children:"kandown"}),y.jsxs("span",{className:"inline-flex items-center h-5 px-1.5 text-[10.5px] font-semibold text-red-600 bg-red-50 dark:text-red-400 dark:bg-red-500/15 rounded-md",children:["v",R$]})]})}),t&&y.jsxs(y.Fragment,{children:[y.jsx("div",{className:"w-px h-[20px] bg-black/[0.08] dark:bg-white/[0.08] flex-shrink-0"}),y.jsxs("div",{className:"flex items-center gap-2 px-3 h-9 bg-secondary/60 border border-border rounded-xl min-w-[200px] max-w-[280px] focus-within:border-border-focus focus-within:bg-secondary transition-all",children:[y.jsx(va.Search,{size:14,className:"text-fg-muted/60 flex-shrink-0"}),y.jsx("input",{ref:P,type:"text",placeholder:e("filterBar.searchPlaceholder"),value:v.search,onChange:q=>w("search",q.target.value),className:"bg-transparent border-none outline-none text-fg text-[13px] w-full placeholder:text-fg-muted/60"}),v.search?y.jsx("button",{onClick:()=>w("search",""),className:"text-fg-muted/60 hover:text-fg flex-shrink-0",children:y.jsx(va.X,{size:14})}):y.jsx("kbd",{className:"inline-flex items-center h-5 px-1.5 text-[10px] font-medium text-fg-muted/50 bg-black/[0.04] dark:bg-white/[0.08] rounded border border-black/[0.06] dark:border-white/[0.1]",children:"⌘K"})]}),y.jsxs("div",{className:"flex items-center gap-1.5 flex-wrap overflow-hidden",children:[y.jsx(Zr,{children:B.map(q=>y.jsxs($t.button,{initial:{opacity:0,scale:.9},animate:{opacity:1,scale:1},exit:{opacity:0,scale:.9},transition:{duration:.15},onClick:()=>w(q.type,null),className:"inline-flex items-center gap-1 h-6 px-2.5 text-[12px] text-fg bg-black/[0.05] dark:bg-white/[0.1] border border-black/[0.08] dark:border-white/[0.12] rounded-lg hover:bg-black/[0.08] dark:hover:bg-white/[0.15] transition-colors",children:[q.label,y.jsx(va.X,{size:10,className:"text-fg-muted/60"})]},q.type+q.value))}),A.ownerType&&y.jsx("div",{className:"flex items-center h-6 border border-black/[0.06] dark:border-white/[0.1] rounded-lg overflow-hidden",children:j.map(q=>y.jsx("button",{onClick:()=>w("ownerType",q.value),className:`h-full px-2.5 text-[12px] transition-colors ${v.ownerType===q.value?"bg-black/[0.06] dark:bg-white/[0.12] text-fg":"text-fg-muted/70 hover:text-fg"}`,children:q.label},q.value))}),V&&y.jsx("button",{onClick:x,className:"text-[12px] text-fg-muted/60 hover:text-fg transition-colors",children:e("filterBar.clearAll")})]})]}),t&&y.jsxs(y.Fragment,{children:[y.jsx("div",{className:"w-px h-[20px] bg-black/[0.08] dark:bg-white/[0.08] flex-shrink-0"}),y.jsxs("div",{className:"relative flex-shrink-0",ref:T,children:[y.jsxs("button",{onClick:()=>M(q=>!q),className:"flex items-center gap-1.5 px-2.5 py-1.5 text-[13px] text-fg-muted hover:text-fg hover:bg-black/[0.04] dark:hover:bg-white/[0.06] rounded-lg transition-colors border border-transparent hover:border-black/[0.06] dark:hover:border-white/[0.1]",children:[y.jsx(va.Folder,{size:13,className:"text-fg-muted/70"}),y.jsxs("span",{className:"font-medium",children:[".",t.name]}),y.jsx(va.ChevronDown,{size:11,className:"opacity-50"})]}),y.jsx(Zr,{children:N&&y.jsx($t.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-2 min-w-[240px] glass rounded-xl shadow-[0_16px_48px_rgba(0,0,0,0.5)] overflow-hidden z-50",children:y.jsxs("div",{className:"py-1.5",children:[g.length>0&&y.jsxs(y.Fragment,{children:[y.jsx("div",{className:"px-3 py-1.5 text-[11px] font-semibold uppercase tracking-wider text-fg-muted/60",children:e("header.recentProjects")}),g.map(q=>y.jsxs("button",{onClick:()=>{M(!1),h(q)},className:"w-full flex items-center gap-2 px-3 py-2 text-[13.5px] text-left hover:bg-black/[0.04] dark:hover:bg-white/[0.06] transition-colors",children:[y.jsx(va.Folder,{size:12,className:"text-fg-muted/60"}),y.jsx("span",{className:"truncate",children:q.name}),q.id===t.name&&y.jsx(va.Check,{size:12,className:"ml-auto text-emerald-500"})]},q.id)),y.jsx("div",{className:"h-px bg-black/[0.06] dark:bg-white/[0.08] my-1.5 mx-2"})]}),y.jsxs("button",{onClick:()=>{M(!1),i()},className:"w-full flex items-center gap-2 px-3 py-2 text-[13.5px] text-left hover:bg-black/[0.04] dark:hover:bg-white/[0.06] transition-colors",children:[y.jsx(va.Plus,{size:12,className:"text-fg-muted/60"}),y.jsx("span",{children:e("header.openFolder...")})]})]})})})]})]})]}),y.jsx("div",{className:"flex items-center gap-2 flex-shrink-0",children:n||t?y.jsxs(y.Fragment,{children:[y.jsxs("div",{className:"flex items-center gap-2 mr-2 text-[12.5px] text-fg-muted/70",children:[y.jsx("span",{className:"inline-block w-1.5 h-1.5 rounded-full bg-emerald-500"}),y.jsx($t.span,{className:"tabular-nums font-medium",children:R}),y.jsx("span",{children:e("header.tasks")})]}),y.jsxs("div",{className:"flex items-center bg-black/[0.04] dark:bg-white/[0.06] border border-black/[0.06] dark:border-white/[0.1] rounded-xl p-0.5 h-10",children:[y.jsx("button",{onClick:()=>p("board"),className:`w-9 h-9 inline-flex items-center justify-center rounded-lg transition-all ${c==="board"?"bg-card text-fg shadow-sm":"text-fg-muted/70 hover:text-fg"}`,title:e("common.board"),children:y.jsx(va.LayoutBoard,{size:18})}),y.jsx("button",{onClick:()=>p("list"),className:`w-9 h-9 inline-flex items-center justify-center rounded-lg transition-all ${c==="list"?"bg-card text-fg shadow-sm":"text-fg-muted/70 hover:text-fg"}`,title:e("common.list"),children:y.jsx(va.LayoutList,{size:18})})]}),y.jsx(dr,{variant:"icon",icon:"Density",onClick:()=>u(l==="compact"?"comfortable":"compact"),title:`Density: ${l}`}),y.jsx(dr,{variant:"icon",icon:"Settings",onClick:()=>m("settings"),title:e("common.settings")}),y.jsx(U1e,{}),y.jsx("div",{className:"w-px h-5 bg-black/[0.08] dark:bg-white/[0.08] mx-1"}),y.jsx(dr,{variant:"secondary",icon:"Search",label:e("common.search"),shortcut:"⌘K",onClick:()=>o(!0),title:"Command palette (⌘K)"}),y.jsx(dr,{variant:"icon",icon:"Refresh",onClick:r,title:`${e("common.reload")} (R)`}),y.jsx(dr,{variant:"primary",icon:"Plus",label:e("common.newTask"),shortcut:"N",onClick:()=>s()})]}):y.jsx(dr,{variant:"primary",label:e("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.
|