|
5 | 5 | <meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
6 | 6 | <title>dev_dashboard</title> |
7 | 7 | <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&family=Syne:wght@400;600;800&display=swap" rel="stylesheet" /> |
| 8 | +<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> |
8 | 9 | <style> |
9 | 10 | *,*::before,*::after{box-sizing:border-box;margin:0;padding:0} |
10 | 11 | :root{ |
|
234 | 235 | @keyframes blink{0%,80%,100%{opacity:.2;transform:scale(.8)}40%{opacity:1;transform:scale(1)}} |
235 | 236 | @keyframes slide{from{transform:translateX(-100%)}to{transform:translateX(350%)}} |
236 | 237 | @media(max-width:900px){.grid-2,.grid-4,.kanban-board,.suggestion-grid{grid-template-columns:1fr}} |
| 238 | +/* ── Markdown styles ── */ |
| 239 | +.md-content h1,.md-content h2,.md-content h3{color:var(--green);font-family:var(--font-display);font-weight:600;margin:10px 0 6px;line-height:1.3} |
| 240 | +.md-content h1{font-size:16px}.md-content h2{font-size:14px}.md-content h3{font-size:13px} |
| 241 | +.md-content p{margin-bottom:8px;line-height:1.7;font-size:12px;color:var(--text)} |
| 242 | +.md-content ul,.md-content ol{margin:6px 0 8px 16px;font-size:12px;color:var(--text);line-height:1.7} |
| 243 | +.md-content li{margin-bottom:3px} |
| 244 | +.md-content code{background:var(--surface2);border:1px solid var(--border);padding:1px 5px;font-family:var(--font-mono);font-size:11px;color:var(--green)} |
| 245 | +.md-content pre{background:var(--surface2);border:1px solid var(--border);padding:10px;margin:8px 0;overflow-x:auto} |
| 246 | +.md-content pre code{background:none;border:none;padding:0;color:var(--green);font-size:11px} |
| 247 | +.md-content blockquote{border-left:2px solid var(--green);padding-left:10px;color:var(--text-muted);font-style:italic;margin:8px 0} |
| 248 | +.md-content strong{color:var(--text);font-weight:700} |
| 249 | +.md-content em{color:var(--text-muted)} |
| 250 | +.md-content a{color:var(--blue);text-decoration:none} |
| 251 | +.md-content a:hover{text-decoration:underline} |
| 252 | +.md-content hr{border:none;border-top:1px solid var(--border);margin:10px 0} |
| 253 | +.md-content table{width:100%;border-collapse:collapse;font-size:11px;margin:8px 0} |
| 254 | +.md-content th{background:var(--surface2);border:1px solid var(--border);padding:6px 10px;text-align:left;color:var(--green);font-size:10px;text-transform:uppercase;letter-spacing:.5px} |
| 255 | +.md-content td{border:1px solid var(--border);padding:6px 10px;color:var(--text)} |
| 256 | +/* ── MD editor ── */ |
| 257 | +.md-editor-wrap{position:relative;margin-bottom:10px} |
| 258 | +.md-tabs{display:flex;gap:0;margin-bottom:0;border-bottom:1px solid var(--border)} |
| 259 | +.md-tab{background:transparent;border:none;border-bottom:2px solid transparent;color:var(--text-muted);font-family:var(--font-mono);font-size:10px;padding:6px 14px;cursor:pointer;letter-spacing:.5px;text-transform:uppercase;margin-bottom:-1px;transition:all .2s} |
| 260 | +.md-tab.active{color:var(--green);border-bottom-color:var(--green)} |
| 261 | +.md-textarea{width:100%;min-height:120px;background:var(--surface2);border:1px solid var(--border);border-top:none;color:var(--text);font-family:var(--font-mono);font-size:12px;padding:10px 12px;outline:none;resize:vertical;transition:border-color .2s;line-height:1.6} |
| 262 | +.md-textarea:focus{border-color:var(--green)} |
| 263 | +.md-preview{min-height:120px;background:var(--surface2);border:1px solid var(--border);border-top:none;padding:10px 12px;display:none} |
237 | 264 | </style> |
238 | 265 | </head> |
239 | 266 | <body> |
|
321 | 348 |
|
322 | 349 | <div class="nav-tabs"> |
323 | 350 | <button class="nav-tab active" onclick="switchTab('overview')">overview</button> |
324 | | - <button class="nav-tab" onclick="switchTab('todos')">todos</button> |
325 | 351 | <button class="nav-tab" onclick="switchTab('plans')">plans</button> |
326 | 352 | <button class="nav-tab" onclick="switchTab('ai')">ai</button> |
327 | 353 | <button class="nav-tab" onclick="switchTab('settings')">settings</button> |
|
362 | 388 | </div> |
363 | 389 | </div> |
364 | 390 |
|
365 | | - <!-- Todos tab --> |
366 | | - <div id="tab-todos" class="tab-panel"> |
367 | | - <div class="card"> |
368 | | - <div class="card-title"><div class="card-title-left"><span class="card-icon"></span>todos</div></div> |
| 391 | + <!-- Plans tab (includes todos) --> |
| 392 | + <div id="tab-plans" class="tab-panel"> |
| 393 | + |
| 394 | + <!-- Quick todos section --> |
| 395 | + <div class="card" style="margin-bottom:16px"> |
| 396 | + <div class="card-title"> |
| 397 | + <div class="card-title-left"><span class="card-icon"></span>todos</div> |
| 398 | + <div style="display:flex;gap:6px"> |
| 399 | + <button class="filter-btn active" onclick="filterTodos('all',this)">all</button> |
| 400 | + <button class="filter-btn" onclick="filterTodos('todo',this)">todo</button> |
| 401 | + <button class="filter-btn" onclick="filterTodos('wip',this)">wip</button> |
| 402 | + <button class="filter-btn" onclick="filterTodos('done',this)">done</button> |
| 403 | + </div> |
| 404 | + </div> |
369 | 405 | <div class="todo-form"> |
370 | 406 | <input class="todo-input" id="todo-title-input" placeholder="add a new todo..." /> |
371 | 407 | <select class="todo-select" id="todo-priority-input"> |
|
376 | 412 | <input class="todo-input" id="todo-due-input" type="date" style="flex:0 0 140px;color:var(--text-muted)" title="due date (optional)" /> |
377 | 413 | <button class="btn-primary" onclick="addTodo()">[ add ]</button> |
378 | 414 | </div> |
379 | | - <div class="todo-filters"> |
380 | | - <button class="filter-btn active" onclick="filterTodos('all',this)">all</button> |
381 | | - <button class="filter-btn" onclick="filterTodos('todo',this)">todo</button> |
382 | | - <button class="filter-btn" onclick="filterTodos('wip',this)">wip</button> |
383 | | - <button class="filter-btn" onclick="filterTodos('done',this)">done</button> |
384 | | - </div> |
385 | 415 | <div id="todo-list"></div> |
386 | 416 | </div> |
387 | | - </div> |
388 | 417 |
|
389 | | - <!-- Plans tab --> |
390 | | - <div id="tab-plans" class="tab-panel"> |
| 418 | + <!-- Kanban plans section --> |
391 | 419 | <div class="card"> |
392 | 420 | <div class="card-title"><div class="card-title-left"><span class="card-icon"></span>plans</div></div> |
393 | 421 | <div class="plan-form"> |
394 | 422 | <input class="plan-input" id="plan-title-input" placeholder="plan title..." /> |
395 | | - <input class="plan-input" id="plan-desc-input" placeholder="description (optional)" style="flex:2" /> |
| 423 | + |
396 | 424 | <input class="todo-input" id="plan-due-input" type="date" style="flex:0 0 140px;color:var(--text-muted);background:var(--surface2);border:1px solid var(--border);padding:8px 10px;font-family:var(--font-mono);font-size:11px;outline:none" title="due date (optional)" /> |
397 | 425 | <select class="todo-select" id="plan-col-input"> |
398 | 426 | <option value="backlog">backlog</option> |
|
401 | 429 | </select> |
402 | 430 | <button class="btn-primary" onclick="addPlan()">[ add ]</button> |
403 | 431 | </div> |
404 | | - <div class="kanban-board"> |
405 | | - <div class="kanban-col" id="col-backlog"> |
| 432 | + <div class="kanban-board" id="kanban-board" |
| 433 | + ondragover="event.preventDefault()" |
| 434 | + ondrop="event.preventDefault()"> |
| 435 | + <div class="kanban-col" id="col-backlog" |
| 436 | + ondragover="event.preventDefault();this.style.borderColor='var(--green)'" |
| 437 | + ondragleave="this.style.borderColor='var(--border)'" |
| 438 | + ondrop="dropPlan(event,'backlog');this.style.borderColor='var(--border)'"> |
406 | 439 | <div class="kanban-col-header"> |
407 | 440 | <div class="kanban-col-title"><div class="col-dot backlog"></div>backlog</div> |
408 | 441 | <span class="col-count" id="count-backlog">0</span> |
409 | 442 | </div> |
410 | 443 | <div id="cards-backlog"></div> |
411 | 444 | </div> |
412 | | - <div class="kanban-col" id="col-inprogress"> |
| 445 | + <div class="kanban-col" id="col-inprogress" |
| 446 | + ondragover="event.preventDefault();this.style.borderColor='var(--amber)'" |
| 447 | + ondragleave="this.style.borderColor='var(--border)'" |
| 448 | + ondrop="dropPlan(event,'inprogress');this.style.borderColor='var(--border)'"> |
413 | 449 | <div class="kanban-col-header"> |
414 | 450 | <div class="kanban-col-title"><div class="col-dot inprogress"></div>in progress</div> |
415 | 451 | <span class="col-count" id="count-inprogress">0</span> |
416 | 452 | </div> |
417 | 453 | <div id="cards-inprogress"></div> |
418 | 454 | </div> |
419 | | - <div class="kanban-col" id="col-done"> |
| 455 | + <div class="kanban-col" id="col-done" |
| 456 | + ondragover="event.preventDefault();this.style.borderColor='var(--green)'" |
| 457 | + ondragleave="this.style.borderColor='var(--border)'" |
| 458 | + ondrop="dropPlan(event,'done');this.style.borderColor='var(--border)'"> |
420 | 459 | <div class="kanban-col-header"> |
421 | 460 | <div class="kanban-col-title"><div class="col-dot done"></div>done</div> |
422 | 461 | <span class="col-count" id="count-done">0</span> |
|
480 | 519 | </div> |
481 | 520 | </div> |
482 | 521 |
|
| 522 | +<!-- ── Plan detail panel ──────────────────────────────────── --> |
| 523 | +<div class="modal-overlay" id="plan-detail-overlay" onclick="closePlanDetail()"> |
| 524 | + <div class="modal" style="width:520px;max-height:80vh;overflow-y:auto" onclick="event.stopPropagation()"> |
| 525 | + <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px"> |
| 526 | + <div class="modal-title" id="pd-title">Plan Detail</div> |
| 527 | + <button class="btn-ghost" onclick="closePlanDetail()">[ close ]</button> |
| 528 | + </div> |
| 529 | + <div style="margin-bottom:12px"> |
| 530 | + <div style="font-size:10px;text-transform:uppercase;letter-spacing:1px;color:var(--text-muted);margin-bottom:4px">status</div> |
| 531 | + <div id="pd-status" style="font-size:12px;color:var(--green)">—</div> |
| 532 | + </div> |
| 533 | + <div style="margin-bottom:12px"> |
| 534 | + <div style="font-size:10px;text-transform:uppercase;letter-spacing:1px;color:var(--text-muted);margin-bottom:4px">description</div> |
| 535 | + <div id="pd-desc" class="md-content" style="font-size:12px;line-height:1.7">—</div> |
| 536 | + </div> |
| 537 | + <div style="margin-bottom:12px"> |
| 538 | + <div style="font-size:10px;text-transform:uppercase;letter-spacing:1px;color:var(--text-muted);margin-bottom:4px">due date</div> |
| 539 | + <div id="pd-due" style="font-size:12px;color:var(--text)">—</div> |
| 540 | + </div> |
| 541 | + <div style="margin-bottom:20px"> |
| 542 | + <div style="font-size:10px;text-transform:uppercase;letter-spacing:1px;color:var(--text-muted);margin-bottom:4px">tags</div> |
| 543 | + <div id="pd-tags" style="display:flex;gap:4px;flex-wrap:wrap"></div> |
| 544 | + </div> |
| 545 | + <div id="pd-edit-section" style="display:none;margin-bottom:16px"> |
| 546 | + <div class="md-editor-wrap"> |
| 547 | + <div class="md-tabs"> |
| 548 | + <button class="md-tab active" onclick="switchPdTab('write',this)">write</button> |
| 549 | + <button class="md-tab" onclick="switchPdTab('preview',this)">preview</button> |
| 550 | + </div> |
| 551 | + <textarea class="md-textarea" id="pd-edit-textarea" style="min-height:160px" placeholder="edit description..."></textarea> |
| 552 | + <div class="md-preview md-content" id="pd-edit-preview"></div> |
| 553 | + </div> |
| 554 | + <div style="display:flex;gap:8px;margin-top:8px"> |
| 555 | + <button class="btn-primary" onclick="pdSaveEdit()">[ save ]</button> |
| 556 | + <button class="btn-outline" onclick="pdCancelEdit()">[ cancel ]</button> |
| 557 | + </div> |
| 558 | + </div> |
| 559 | + <div style="display:flex;gap:8px;flex-wrap:wrap"> |
| 560 | + <button class="btn-primary" id="pd-move-next" onclick="pdMoveNext()">[ move forward ]</button> |
| 561 | + <button class="btn-outline" onclick="pdStartEdit()">[ edit ]</button> |
| 562 | + <button class="btn-outline danger" id="pd-delete" onclick="pdDelete()">[ delete ]</button> |
| 563 | + </div> |
| 564 | + </div> |
| 565 | +</div> |
| 566 | + |
483 | 567 | <!-- ── Confirm modal ─────────────────────────────────────── --> |
484 | 568 | <div class="modal-overlay" id="modal-overlay"> |
485 | 569 | <div class="modal"> |
|
558 | 642 | // ── Tab switching ───────────────────────────────────────────── |
559 | 643 | function switchTab(name){ |
560 | 644 | document.querySelectorAll('.nav-tab').forEach((t,i)=>{ |
561 | | - const tabs=['overview','todos','plans','ai','settings']; |
| 645 | + const tabs=['overview','plans','ai','settings']; |
562 | 646 | t.classList.toggle('active',tabs[i]===name); |
563 | 647 | }); |
564 | 648 | document.querySelectorAll('.tab-panel').forEach(p=>p.classList.remove('active')); |
|
818 | 902 | const pNow=new Date(); |
819 | 903 | const pOverdue=pDue&&pDue<pNow&&col!=='done'; |
820 | 904 | const pDueStr=pDue?pDue.toLocaleDateString('en-GB',{day:'2-digit',month:'short',year:'2-digit'}):''; |
821 | | - return`<div class="kanban-card"> |
| 905 | + return`<div class="kanban-card" draggable="true" |
| 906 | + ondragstart="dragStart(event,${p.id})" |
| 907 | + ondragend="this.style.opacity='1'" |
| 908 | + onclick="openPlanDetail(${p.id})"> |
822 | 909 | <div style="display:flex;align-items:flex-start;justify-content:space-between;gap:8px;margin-bottom:4px"> |
823 | 910 | <div class="kanban-card-title" style="flex:1">${p.title}</div> |
824 | 911 | ${pDueStr?`<span style="font-size:10px;color:${pOverdue?'var(--red)':'var(--text-dim)'};flex-shrink:0;white-space:nowrap">${pOverdue?'⚠ ':''}${pDueStr}</span>`:''} |
825 | 912 | </div> |
826 | | - ${p.description?`<div class="kanban-card-desc">${p.description}</div>`:''} |
| 913 | + ${p.description?`<div class="kanban-card-desc md-content" style="max-height:48px;overflow:hidden;mask-image:linear-gradient(180deg,#fff 60%,transparent)">${typeof marked !== 'undefined' ? marked.parse(p.description) : p.description}</div>`:''} |
827 | 914 | ${p.tags?.length?`<div class="kanban-card-tags">${p.tags.map(t=>`<span class="kanban-tag">${t}</span>`).join('')}</div>`:''} |
828 | | - <div class="kanban-card-actions"> |
| 915 | + <div class="kanban-card-actions" onclick="event.stopPropagation()"> |
829 | 916 | ${prevCol?`<button class="kanban-move" onclick="movePlan(${p.id},'${prevCol}')">← ${prevCol==='backlog'?'backlog':'in progress'}</button>`:''} |
830 | 917 | ${nextCol?`<button class="kanban-move" onclick="movePlan(${p.id},'${nextCol}')">${nextCol==='done'?'done':'in progress'} →</button>`:''} |
831 | 918 | <button class="kanban-del" onclick="deletePlan(${p.id})">del</button> |
|
1040 | 1127 | } |
1041 | 1128 | } |
1042 | 1129 |
|
| 1130 | +// ── MD tab switcher ─────────────────────────────────────────── |
| 1131 | +function switchMdTab(tab, btn) { |
| 1132 | + document.querySelectorAll('.md-tab').forEach(b => b.classList.remove('active')); |
| 1133 | + btn.classList.add('active'); |
| 1134 | + const textarea = document.getElementById('plan-desc-input'); |
| 1135 | + const preview = document.getElementById('plan-desc-preview'); |
| 1136 | + if (tab === 'preview') { |
| 1137 | + const md = textarea.value.trim(); |
| 1138 | + preview.innerHTML = md ? (typeof marked !== 'undefined' ? marked.parse(md) : md) : '<span style="color:var(--text-dim);font-size:12px">nothing to preview</span>'; |
| 1139 | + textarea.style.display = 'none'; |
| 1140 | + preview.style.display = 'block'; |
| 1141 | + } else { |
| 1142 | + textarea.style.display = 'block'; |
| 1143 | + preview.style.display = 'none'; |
| 1144 | + } |
| 1145 | +} |
| 1146 | + |
| 1147 | +// ── Drag & drop ─────────────────────────────────────────────── |
| 1148 | +let draggedPlanId = null; |
| 1149 | + |
| 1150 | +function dragStart(e, id) { |
| 1151 | + draggedPlanId = id; |
| 1152 | + e.dataTransfer.effectAllowed = 'move'; |
| 1153 | + setTimeout(() => { if(e.target) e.target.style.opacity = '0.4'; }, 0); |
| 1154 | +} |
| 1155 | + |
| 1156 | +function dropPlan(e, col) { |
| 1157 | + e.preventDefault(); |
| 1158 | + if (!draggedPlanId) return; |
| 1159 | + movePlan(draggedPlanId, col); |
| 1160 | + draggedPlanId = null; |
| 1161 | +} |
| 1162 | + |
| 1163 | +// ── Plan detail panel ───────────────────────────────────────── |
| 1164 | +let activePlanId = null; |
| 1165 | + |
| 1166 | +function openPlanDetail(id) { |
| 1167 | + const p = plans.find(x => x.id === id); |
| 1168 | + if (!p) return; |
| 1169 | + activePlanId = id; |
| 1170 | + const colLabel = {backlog:'Backlog', inprogress:'In Progress', done:'Done'}; |
| 1171 | + const colColors = {backlog:'var(--text-muted)', inprogress:'var(--amber)', done:'var(--green)'}; |
| 1172 | + document.getElementById('pd-title').textContent = p.title; |
| 1173 | + document.getElementById('pd-status').textContent = colLabel[p.plan_column] || p.plan_column; |
| 1174 | + document.getElementById('pd-status').style.color = colColors[p.plan_column] || 'var(--text)'; |
| 1175 | + document.getElementById('pd-desc').innerHTML = p.description ? (typeof marked !== 'undefined' ? marked.parse(p.description) : p.description) : '<span style="color:var(--text-dim)">No description</span>'; |
| 1176 | + const due = p.due_date ? new Date(p.due_date) : null; |
| 1177 | + const isOverdue = due && due < new Date() && p.plan_column !== 'done'; |
| 1178 | + document.getElementById('pd-due').textContent = due |
| 1179 | + ? due.toLocaleDateString('en-GB',{weekday:'short',day:'2-digit',month:'short',year:'numeric'}) + (isOverdue ? ' ⚠ overdue' : '') |
| 1180 | + : 'No due date'; |
| 1181 | + document.getElementById('pd-due').style.color = isOverdue ? 'var(--red)' : 'var(--text)'; |
| 1182 | + document.getElementById('pd-tags').innerHTML = (p.tags||[]).map(t=>`<span class="kanban-tag">${t}</span>`).join('') || '<span style="color:var(--text-dim);font-size:11px">no tags</span>'; |
| 1183 | + const cols = ['backlog','inprogress','done']; |
| 1184 | + const nextCol = cols[cols.indexOf(p.plan_column)+1]; |
| 1185 | + const moveBtn = document.getElementById('pd-move-next'); |
| 1186 | + if (nextCol) { |
| 1187 | + moveBtn.textContent = `[ move to ${nextCol==='inprogress'?'in progress':nextCol} ]`; |
| 1188 | + moveBtn.style.display = 'block'; |
| 1189 | + } else { |
| 1190 | + moveBtn.style.display = 'none'; |
| 1191 | + } |
| 1192 | + document.getElementById('plan-detail-overlay').classList.add('active'); |
| 1193 | +} |
| 1194 | + |
| 1195 | +function closePlanDetail() { |
| 1196 | + document.getElementById('plan-detail-overlay').classList.remove('active'); |
| 1197 | + activePlanId = null; |
| 1198 | +} |
| 1199 | + |
| 1200 | +function pdMoveNext() { |
| 1201 | + if (!activePlanId) return; |
| 1202 | + const p = plans.find(x => x.id === activePlanId); |
| 1203 | + if (!p) return; |
| 1204 | + const cols = ['backlog','inprogress','done']; |
| 1205 | + const next = cols[cols.indexOf(p.plan_column)+1]; |
| 1206 | + if (next) { closePlanDetail(); movePlan(activePlanId, next); } |
| 1207 | +} |
| 1208 | + |
| 1209 | +function pdStartEdit() { |
| 1210 | + const p = plans.find(x => x.id === activePlanId); |
| 1211 | + if (!p) return; |
| 1212 | + document.getElementById('pd-edit-textarea').value = p.description || ''; |
| 1213 | + document.getElementById('pd-edit-section').style.display = 'block'; |
| 1214 | + document.getElementById('pd-edit-preview').style.display = 'none'; |
| 1215 | + document.getElementById('pd-edit-textarea').style.display = 'block'; |
| 1216 | + document.querySelectorAll('#pd-edit-section .md-tab').forEach((b,i) => b.classList.toggle('active', i===0)); |
| 1217 | +} |
| 1218 | + |
| 1219 | +function pdCancelEdit() { |
| 1220 | + document.getElementById('pd-edit-section').style.display = 'none'; |
| 1221 | +} |
| 1222 | + |
| 1223 | +async function pdSaveEdit() { |
| 1224 | + const desc = document.getElementById('pd-edit-textarea').value.trim(); |
| 1225 | + const updated = await api('PUT', `/api/plans/${activePlanId}`, { description: desc || null }); |
| 1226 | + if (updated.id) { |
| 1227 | + plans = plans.map(p => p.id === activePlanId ? updated : p); |
| 1228 | + document.getElementById('pd-desc').innerHTML = desc |
| 1229 | + ? (typeof marked !== 'undefined' ? marked.parse(desc) : desc) |
| 1230 | + : '<span style="color:var(--text-dim)">No description</span>'; |
| 1231 | + document.getElementById('pd-edit-section').style.display = 'none'; |
| 1232 | + renderPlans(); |
| 1233 | + } |
| 1234 | +} |
| 1235 | + |
| 1236 | +function switchPdTab(tab, btn) { |
| 1237 | + document.querySelectorAll('#pd-edit-section .md-tab').forEach(b => b.classList.remove('active')); |
| 1238 | + btn.classList.add('active'); |
| 1239 | + const ta = document.getElementById('pd-edit-textarea'); |
| 1240 | + const pr = document.getElementById('pd-edit-preview'); |
| 1241 | + if (tab === 'preview') { |
| 1242 | + pr.innerHTML = ta.value.trim() |
| 1243 | + ? (typeof marked !== 'undefined' ? marked.parse(ta.value) : ta.value) |
| 1244 | + : '<span style="color:var(--text-dim);font-size:12px">nothing to preview</span>'; |
| 1245 | + ta.style.display = 'none'; pr.style.display = 'block'; |
| 1246 | + } else { |
| 1247 | + ta.style.display = 'block'; pr.style.display = 'none'; |
| 1248 | + } |
| 1249 | +} |
| 1250 | + |
| 1251 | +function pdDelete() { |
| 1252 | + if (!activePlanId) return; |
| 1253 | + closePlanDetail(); |
| 1254 | + deletePlan(activePlanId); |
| 1255 | +} |
| 1256 | + |
1043 | 1257 | // Enter key on todo input |
1044 | 1258 | document.addEventListener('DOMContentLoaded',()=>{ |
1045 | 1259 | document.getElementById('todo-title-input')?.addEventListener('keydown',e=>{if(e.key==='Enter')addTodo()}); |
|
0 commit comments