@brainfile/cli 0.9.2 → 0.10.1
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/CHANGELOG.md +47 -0
- package/README.md +31 -4
- package/dist/commands/tui.d.ts +1 -1
- package/dist/commands/tui.d.ts.map +1 -1
- package/dist/commands/tui.js +8 -2
- package/dist/commands/tui.js.map +1 -1
- package/dist/tui/BrainfileTUI.d.ts.map +1 -1
- package/dist/tui/BrainfileTUI.js +47 -14
- package/dist/tui/BrainfileTUI.js.map +1 -1
- package/dist/tui/actions.d.ts +32 -2
- package/dist/tui/actions.d.ts.map +1 -1
- package/dist/tui/actions.js +274 -2
- package/dist/tui/actions.js.map +1 -1
- package/dist/tui/components/ArchivePanel.d.ts +15 -0
- package/dist/tui/components/ArchivePanel.d.ts.map +1 -0
- package/dist/tui/components/ArchivePanel.js +114 -0
- package/dist/tui/components/ArchivePanel.js.map +1 -0
- package/dist/tui/components/HelpOverlay.d.ts.map +1 -1
- package/dist/tui/components/HelpOverlay.js +43 -27
- package/dist/tui/components/HelpOverlay.js.map +1 -1
- package/dist/tui/components/MainPanelTabs.d.ts +9 -0
- package/dist/tui/components/MainPanelTabs.d.ts.map +1 -0
- package/dist/tui/components/MainPanelTabs.js +37 -0
- package/dist/tui/components/MainPanelTabs.js.map +1 -0
- package/dist/tui/components/RulesPanel.d.ts +24 -0
- package/dist/tui/components/RulesPanel.d.ts.map +1 -0
- package/dist/tui/components/RulesPanel.js +125 -0
- package/dist/tui/components/RulesPanel.js.map +1 -0
- package/dist/tui/components/StatusBar.d.ts +3 -2
- package/dist/tui/components/StatusBar.d.ts.map +1 -1
- package/dist/tui/components/StatusBar.js +6 -3
- package/dist/tui/components/StatusBar.js.map +1 -1
- package/dist/tui/components/index.d.ts +6 -0
- package/dist/tui/components/index.d.ts.map +1 -1
- package/dist/tui/components/index.js +7 -1
- package/dist/tui/components/index.js.map +1 -1
- package/dist/tui/hooks/useKeyboardNavigation.d.ts.map +1 -1
- package/dist/tui/hooks/useKeyboardNavigation.js +508 -190
- package/dist/tui/hooks/useKeyboardNavigation.js.map +1 -1
- package/dist/tui/index.d.ts +1 -1
- package/dist/tui/index.d.ts.map +1 -1
- package/dist/tui/theme.d.ts +6 -0
- package/dist/tui/theme.d.ts.map +1 -1
- package/dist/tui/theme.js +6 -0
- package/dist/tui/theme.js.map +1 -1
- package/dist/tui/types.d.ts +16 -2
- package/dist/tui/types.d.ts.map +1 -1
- package/dist/tui/types.js.map +1 -1
- package/package.json +1 -1
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.useKeyboardNavigation = useKeyboardNavigation;
|
|
4
4
|
const ink_1 = require("ink");
|
|
5
5
|
const actions_js_1 = require("../actions.js");
|
|
6
|
+
const RULE_TYPES = ['always', 'never', 'prefer', 'context'];
|
|
6
7
|
// Helper to show status message
|
|
7
8
|
function showStatus(setState, text, type) {
|
|
8
9
|
const timestamp = Date.now();
|
|
@@ -173,7 +174,7 @@ function useKeyboardNavigation({ state, setState, currentTasks, maxTaskIndex, fi
|
|
|
173
174
|
}
|
|
174
175
|
return;
|
|
175
176
|
}
|
|
176
|
-
// Search mode handling
|
|
177
|
+
// Search mode handling (Tasks panel)
|
|
177
178
|
if (state.mode === 'search') {
|
|
178
179
|
if (key.escape) {
|
|
179
180
|
setState(prev => ({ ...prev, mode: 'browse', searchQuery: '' }));
|
|
@@ -193,7 +194,162 @@ function useKeyboardNavigation({ state, setState, currentTasks, maxTaskIndex, fi
|
|
|
193
194
|
}
|
|
194
195
|
return;
|
|
195
196
|
}
|
|
196
|
-
//
|
|
197
|
+
// Rule add/edit mode
|
|
198
|
+
if (state.mode === 'rule-add' || state.mode === 'rule-edit') {
|
|
199
|
+
if (key.escape) {
|
|
200
|
+
setState(prev => ({ ...prev, mode: 'browse', ruleEditText: '', ruleEditId: null }));
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (key.return) {
|
|
204
|
+
const text = state.ruleEditText.trim();
|
|
205
|
+
if (!text) {
|
|
206
|
+
showStatus(setState, 'Rule text required', 'error');
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
if (state.mode === 'rule-add') {
|
|
210
|
+
const result = (0, actions_js_1.addRuleAction)(filePath, state.activeRuleType, text);
|
|
211
|
+
if (result.success) {
|
|
212
|
+
showStatus(setState, result.message || 'Rule added', 'success');
|
|
213
|
+
loadBrainfile(true);
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
showStatus(setState, result.error || 'Failed to add rule', 'error');
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
else if (state.ruleEditId !== null) {
|
|
220
|
+
const result = (0, actions_js_1.updateRuleAction)(filePath, state.activeRuleType, state.ruleEditId, text);
|
|
221
|
+
if (result.success) {
|
|
222
|
+
showStatus(setState, result.message || 'Rule updated', 'success');
|
|
223
|
+
loadBrainfile(true);
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
showStatus(setState, result.error || 'Failed to update rule', 'error');
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
setState(prev => ({ ...prev, mode: 'browse', ruleEditText: '', ruleEditId: null }));
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
if (key.backspace || key.delete) {
|
|
233
|
+
setState(prev => ({ ...prev, ruleEditText: prev.ruleEditText.slice(0, -1) }));
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (input && !key.ctrl && !key.meta) {
|
|
237
|
+
setState(prev => ({ ...prev, ruleEditText: prev.ruleEditText + input }));
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
// Rule delete confirmation
|
|
243
|
+
if (state.mode === 'rule-delete-confirm') {
|
|
244
|
+
const rules = state.board?.rules?.[state.activeRuleType] || [];
|
|
245
|
+
const rule = rules[state.selectedRuleIndex];
|
|
246
|
+
if (input === 'y' || input === 'Y') {
|
|
247
|
+
if (rule) {
|
|
248
|
+
const result = (0, actions_js_1.deleteRuleAction)(filePath, state.activeRuleType, rule.id);
|
|
249
|
+
if (result.success) {
|
|
250
|
+
showStatus(setState, result.message || 'Rule deleted', 'success');
|
|
251
|
+
loadBrainfile(true);
|
|
252
|
+
// Adjust selection if needed
|
|
253
|
+
setState(prev => ({
|
|
254
|
+
...prev,
|
|
255
|
+
mode: 'browse',
|
|
256
|
+
selectedRuleIndex: Math.max(0, prev.selectedRuleIndex - 1),
|
|
257
|
+
}));
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
showStatus(setState, result.error || 'Failed to delete rule', 'error');
|
|
261
|
+
setState(prev => ({ ...prev, mode: 'browse' }));
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
if (input === 'n' || input === 'N' || key.escape) {
|
|
267
|
+
setState(prev => ({ ...prev, mode: 'browse' }));
|
|
268
|
+
showStatus(setState, 'Delete cancelled', 'info');
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
// Archive restore mode - column picker
|
|
274
|
+
if (state.mode === 'archive-restore') {
|
|
275
|
+
if (key.escape) {
|
|
276
|
+
setState(prev => ({ ...prev, mode: 'browse' }));
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
if (key.downArrow || input === 'j') {
|
|
280
|
+
setState(prev => ({
|
|
281
|
+
...prev,
|
|
282
|
+
archiveRestoreColumnIndex: Math.min(prev.archiveRestoreColumnIndex + 1, allColumns.length - 1),
|
|
283
|
+
}));
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
if (key.upArrow || input === 'k') {
|
|
287
|
+
setState(prev => ({
|
|
288
|
+
...prev,
|
|
289
|
+
archiveRestoreColumnIndex: Math.max(prev.archiveRestoreColumnIndex - 1, 0),
|
|
290
|
+
}));
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
if (key.return) {
|
|
294
|
+
const task = state.archive[state.selectedArchiveIndex];
|
|
295
|
+
const column = allColumns[state.archiveRestoreColumnIndex];
|
|
296
|
+
if (task && column) {
|
|
297
|
+
const result = (0, actions_js_1.restoreTaskAction)(filePath, task.id, column.id);
|
|
298
|
+
if (result.success) {
|
|
299
|
+
showStatus(setState, result.message || 'Task restored', 'success');
|
|
300
|
+
loadBrainfile(true);
|
|
301
|
+
// Reload archive
|
|
302
|
+
const archiveResult = (0, actions_js_1.loadArchive)(filePath);
|
|
303
|
+
setState(prev => ({
|
|
304
|
+
...prev,
|
|
305
|
+
mode: 'browse',
|
|
306
|
+
archive: archiveResult.archive,
|
|
307
|
+
selectedArchiveIndex: Math.max(0, prev.selectedArchiveIndex - 1),
|
|
308
|
+
}));
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
showStatus(setState, result.error || 'Failed to restore task', 'error');
|
|
312
|
+
setState(prev => ({ ...prev, mode: 'browse' }));
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
// Archive delete confirmation
|
|
320
|
+
if (state.mode === 'archive-delete-confirm') {
|
|
321
|
+
const task = state.archive[state.selectedArchiveIndex];
|
|
322
|
+
if (input === 'y' || input === 'Y') {
|
|
323
|
+
if (task) {
|
|
324
|
+
const result = (0, actions_js_1.deleteArchivedTaskAction)(filePath, task.id);
|
|
325
|
+
if (result.success) {
|
|
326
|
+
showStatus(setState, result.message || 'Task permanently deleted', 'success');
|
|
327
|
+
// Reload archive
|
|
328
|
+
const archiveResult = (0, actions_js_1.loadArchive)(filePath);
|
|
329
|
+
setState(prev => ({
|
|
330
|
+
...prev,
|
|
331
|
+
mode: 'browse',
|
|
332
|
+
archive: archiveResult.archive,
|
|
333
|
+
selectedArchiveIndex: Math.max(0, prev.selectedArchiveIndex - 1),
|
|
334
|
+
}));
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
showStatus(setState, result.error || 'Failed to delete task', 'error');
|
|
338
|
+
setState(prev => ({ ...prev, mode: 'browse' }));
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
if (input === 'n' || input === 'N' || key.escape) {
|
|
344
|
+
setState(prev => ({ ...prev, mode: 'browse' }));
|
|
345
|
+
showStatus(setState, 'Delete cancelled', 'info');
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
// ============================================================
|
|
351
|
+
// GLOBAL KEYS (work in browse mode across all panels)
|
|
352
|
+
// ============================================================
|
|
197
353
|
if (input === 'q' || (key.ctrl && input === 'c')) {
|
|
198
354
|
exit();
|
|
199
355
|
return;
|
|
@@ -202,232 +358,394 @@ function useKeyboardNavigation({ state, setState, currentTasks, maxTaskIndex, fi
|
|
|
202
358
|
setState(prev => ({ ...prev, mode: 'help' }));
|
|
203
359
|
return;
|
|
204
360
|
}
|
|
205
|
-
|
|
206
|
-
|
|
361
|
+
// Panel switching: 1/2/3
|
|
362
|
+
if (input === '1') {
|
|
363
|
+
setState(prev => ({ ...prev, activePanel: 'tasks', mode: 'browse' }));
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
if (input === '2') {
|
|
367
|
+
setState(prev => ({ ...prev, activePanel: 'rules', mode: 'browse' }));
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
if (input === '3') {
|
|
371
|
+
setState(prev => ({ ...prev, activePanel: 'archive', mode: 'browse' }));
|
|
372
|
+
// Archive is loaded via useEffect in BrainfileTUI
|
|
207
373
|
return;
|
|
208
374
|
}
|
|
209
375
|
// Refresh (force refresh bypasses hash check)
|
|
210
|
-
if (input === 'r') {
|
|
376
|
+
if (input === 'r' && state.activePanel !== 'archive') {
|
|
211
377
|
loadBrainfile(true);
|
|
212
378
|
showStatus(setState, 'Refreshed', 'info');
|
|
213
379
|
return;
|
|
214
380
|
}
|
|
215
|
-
//
|
|
216
|
-
//
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
381
|
+
// ============================================================
|
|
382
|
+
// TASKS PANEL - Browse mode
|
|
383
|
+
// ============================================================
|
|
384
|
+
if (state.activePanel === 'tasks') {
|
|
385
|
+
if (input === '/') {
|
|
386
|
+
setState(prev => ({ ...prev, mode: 'search', searchQuery: '' }));
|
|
220
387
|
return;
|
|
221
388
|
}
|
|
222
|
-
//
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
389
|
+
// 'e' - Edit task in $EDITOR
|
|
390
|
+
if (input === 'e') {
|
|
391
|
+
if (!currentTask) {
|
|
392
|
+
showStatus(setState, 'No task selected', 'error');
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
const result = (0, actions_js_1.editTaskInEditor)(filePath, currentTask.id);
|
|
396
|
+
if (result.success) {
|
|
397
|
+
showStatus(setState, result.message || 'Task updated', 'success');
|
|
398
|
+
loadBrainfile(true);
|
|
399
|
+
}
|
|
400
|
+
else {
|
|
401
|
+
showStatus(setState, result.error || 'Edit failed', 'error');
|
|
402
|
+
}
|
|
403
|
+
return;
|
|
227
404
|
}
|
|
228
|
-
|
|
229
|
-
|
|
405
|
+
// 'm' - Move task
|
|
406
|
+
if (input === 'm') {
|
|
407
|
+
if (!currentTask) {
|
|
408
|
+
showStatus(setState, 'No task selected', 'error');
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
setState(prev => ({ ...prev, mode: 'move', moveTargetIndex: prev.activeColumnIndex }));
|
|
412
|
+
return;
|
|
230
413
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
414
|
+
// 'd' - Delete task
|
|
415
|
+
if (input === 'd') {
|
|
416
|
+
if (!currentTask) {
|
|
417
|
+
showStatus(setState, 'No task selected', 'error');
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
setState(prev => ({ ...prev, mode: 'delete-confirm' }));
|
|
237
421
|
return;
|
|
238
422
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
423
|
+
// 'A' - Archive task
|
|
424
|
+
if (input === 'A') {
|
|
425
|
+
if (!currentTask) {
|
|
426
|
+
showStatus(setState, 'No task selected', 'error');
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
const result = (0, actions_js_1.archiveTaskAction)(filePath, currentTask.id);
|
|
430
|
+
if (result.success) {
|
|
431
|
+
showStatus(setState, result.message || 'Task archived', 'success');
|
|
432
|
+
loadBrainfile(true);
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
showStatus(setState, result.error || 'Archive failed', 'error');
|
|
436
|
+
}
|
|
246
437
|
return;
|
|
247
438
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
439
|
+
// 'p' - Cycle priority
|
|
440
|
+
if (input === 'p') {
|
|
441
|
+
if (!currentTask) {
|
|
442
|
+
showStatus(setState, 'No task selected', 'error');
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
const result = (0, actions_js_1.cyclePriorityAction)(filePath, currentTask.id);
|
|
446
|
+
if (result.success) {
|
|
447
|
+
showStatus(setState, result.message || 'Priority updated', 'success');
|
|
448
|
+
loadBrainfile(true);
|
|
449
|
+
}
|
|
450
|
+
else {
|
|
451
|
+
showStatus(setState, result.error || 'Failed to update priority', 'error');
|
|
452
|
+
}
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
// 't' - Toggle subtask
|
|
456
|
+
if (input === 't') {
|
|
457
|
+
if (!currentTask) {
|
|
458
|
+
showStatus(setState, 'No task selected', 'error');
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
if (!currentTask.subtasks || currentTask.subtasks.length === 0) {
|
|
462
|
+
showStatus(setState, 'No subtasks', 'info');
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
setState(prev => ({ ...prev, mode: 'subtask', selectedSubtaskIndex: 0 }));
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
// 'y' - Copy task ID
|
|
469
|
+
if (input === 'y') {
|
|
470
|
+
if (!currentTask) {
|
|
471
|
+
showStatus(setState, 'No task selected', 'error');
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
const result = (0, actions_js_1.copyToClipboard)(currentTask.id);
|
|
475
|
+
if (result.success) {
|
|
476
|
+
showStatus(setState, `Copied ${currentTask.id}`, 'success');
|
|
477
|
+
}
|
|
478
|
+
else {
|
|
479
|
+
showStatus(setState, result.error || 'Copy failed', 'error');
|
|
480
|
+
}
|
|
255
481
|
return;
|
|
256
482
|
}
|
|
257
|
-
|
|
258
|
-
if (
|
|
259
|
-
|
|
260
|
-
|
|
483
|
+
// 'n' - New task (quick)
|
|
484
|
+
if (input === 'n') {
|
|
485
|
+
setState(prev => ({ ...prev, mode: 'new-task', newTaskTitle: '' }));
|
|
486
|
+
return;
|
|
261
487
|
}
|
|
262
|
-
|
|
263
|
-
|
|
488
|
+
// 'N' - New task (editor)
|
|
489
|
+
if (input === 'N') {
|
|
490
|
+
const currentCol = allColumns[state.activeColumnIndex];
|
|
491
|
+
if (currentCol) {
|
|
492
|
+
const result = (0, actions_js_1.newTaskInEditor)(filePath, currentCol.id);
|
|
493
|
+
if (result.success) {
|
|
494
|
+
showStatus(setState, result.message || 'Task created', 'success');
|
|
495
|
+
loadBrainfile(true);
|
|
496
|
+
}
|
|
497
|
+
else {
|
|
498
|
+
showStatus(setState, result.error || 'Failed to create task', 'error');
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
return;
|
|
264
502
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
503
|
+
// Navigation: up/down
|
|
504
|
+
if (key.downArrow || input === 'j') {
|
|
505
|
+
setState(prev => ({
|
|
506
|
+
...prev,
|
|
507
|
+
selectedTaskIndex: Math.min(prev.selectedTaskIndex + 1, maxTaskIndex),
|
|
508
|
+
}));
|
|
271
509
|
return;
|
|
272
510
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
511
|
+
if (key.upArrow || input === 'k') {
|
|
512
|
+
setState(prev => ({
|
|
513
|
+
...prev,
|
|
514
|
+
selectedTaskIndex: Math.max(prev.selectedTaskIndex - 1, 0),
|
|
515
|
+
}));
|
|
516
|
+
return;
|
|
277
517
|
}
|
|
278
|
-
|
|
279
|
-
|
|
518
|
+
// Page scrolling
|
|
519
|
+
if (key.ctrl && input === 'd') {
|
|
520
|
+
setState(prev => ({
|
|
521
|
+
...prev,
|
|
522
|
+
selectedTaskIndex: Math.min(prev.selectedTaskIndex + Math.floor(viewportHeight / 2), maxTaskIndex),
|
|
523
|
+
}));
|
|
524
|
+
return;
|
|
280
525
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
showStatus(setState, 'No task selected', 'error');
|
|
526
|
+
if (key.ctrl && input === 'u') {
|
|
527
|
+
setState(prev => ({
|
|
528
|
+
...prev,
|
|
529
|
+
selectedTaskIndex: Math.max(prev.selectedTaskIndex - Math.floor(viewportHeight / 2), 0),
|
|
530
|
+
}));
|
|
287
531
|
return;
|
|
288
532
|
}
|
|
289
|
-
|
|
290
|
-
|
|
533
|
+
// Column switching
|
|
534
|
+
if (key.tab || key.rightArrow || input === 'l') {
|
|
535
|
+
setState(prev => ({
|
|
536
|
+
...prev,
|
|
537
|
+
activeColumnIndex: (prev.activeColumnIndex + 1) % filteredColumnsLength,
|
|
538
|
+
selectedTaskIndex: 0,
|
|
539
|
+
}));
|
|
291
540
|
return;
|
|
292
541
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
542
|
+
if ((key.shift && key.tab) || key.leftArrow || input === 'h') {
|
|
543
|
+
setState(prev => ({
|
|
544
|
+
...prev,
|
|
545
|
+
activeColumnIndex: prev.activeColumnIndex === 0
|
|
546
|
+
? filteredColumnsLength - 1
|
|
547
|
+
: prev.activeColumnIndex - 1,
|
|
548
|
+
selectedTaskIndex: 0,
|
|
549
|
+
}));
|
|
300
550
|
return;
|
|
301
551
|
}
|
|
302
|
-
|
|
303
|
-
if (
|
|
304
|
-
|
|
552
|
+
// Home/End
|
|
553
|
+
if (input === 'g') {
|
|
554
|
+
setState(prev => ({ ...prev, selectedTaskIndex: 0 }));
|
|
555
|
+
return;
|
|
305
556
|
}
|
|
306
|
-
|
|
307
|
-
|
|
557
|
+
if (input === 'G') {
|
|
558
|
+
setState(prev => ({ ...prev, selectedTaskIndex: maxTaskIndex }));
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
// Expand/collapse task
|
|
562
|
+
if (key.return) {
|
|
563
|
+
const task = currentTasks[state.selectedTaskIndex];
|
|
564
|
+
if (task) {
|
|
565
|
+
setState(prev => {
|
|
566
|
+
const newExpanded = new Set(prev.expandedTaskIds);
|
|
567
|
+
if (newExpanded.has(task.id)) {
|
|
568
|
+
newExpanded.delete(task.id);
|
|
569
|
+
}
|
|
570
|
+
else {
|
|
571
|
+
newExpanded.add(task.id);
|
|
572
|
+
}
|
|
573
|
+
return { ...prev, expandedTaskIds: newExpanded };
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
// Escape: collapse all or clear search
|
|
579
|
+
if (key.escape) {
|
|
580
|
+
setState(prev => ({
|
|
581
|
+
...prev,
|
|
582
|
+
expandedTaskIds: new Set(),
|
|
583
|
+
searchQuery: '',
|
|
584
|
+
}));
|
|
585
|
+
return;
|
|
308
586
|
}
|
|
309
|
-
return;
|
|
310
|
-
}
|
|
311
|
-
// 'n' - New task (quick add - title only)
|
|
312
|
-
if (input === 'n') {
|
|
313
|
-
setState(prev => ({ ...prev, mode: 'new-task', newTaskTitle: '' }));
|
|
314
|
-
return;
|
|
315
587
|
}
|
|
316
|
-
//
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
588
|
+
// ============================================================
|
|
589
|
+
// RULES PANEL - Browse mode
|
|
590
|
+
// ============================================================
|
|
591
|
+
if (state.activePanel === 'rules') {
|
|
592
|
+
const rules = state.board?.rules?.[state.activeRuleType] || [];
|
|
593
|
+
const maxRuleIndex = Math.max(0, rules.length - 1);
|
|
594
|
+
// h/l or left/right or Shift+TAB to switch rule type left
|
|
595
|
+
if (key.leftArrow || input === 'h' || (key.shift && key.tab)) {
|
|
596
|
+
const currentIdx = RULE_TYPES.indexOf(state.activeRuleType);
|
|
597
|
+
const newIdx = currentIdx === 0 ? RULE_TYPES.length - 1 : currentIdx - 1;
|
|
598
|
+
setState(prev => ({ ...prev, activeRuleType: RULE_TYPES[newIdx], selectedRuleIndex: 0 }));
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
// TAB or l or right to switch rule type right
|
|
602
|
+
if (key.tab || key.rightArrow || input === 'l') {
|
|
603
|
+
const currentIdx = RULE_TYPES.indexOf(state.activeRuleType);
|
|
604
|
+
const newIdx = (currentIdx + 1) % RULE_TYPES.length;
|
|
605
|
+
setState(prev => ({ ...prev, activeRuleType: RULE_TYPES[newIdx], selectedRuleIndex: 0 }));
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
// j/k to navigate rules
|
|
609
|
+
if (key.downArrow || input === 'j') {
|
|
610
|
+
setState(prev => ({
|
|
611
|
+
...prev,
|
|
612
|
+
selectedRuleIndex: Math.min(prev.selectedRuleIndex + 1, maxRuleIndex),
|
|
613
|
+
}));
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
if (key.upArrow || input === 'k') {
|
|
617
|
+
setState(prev => ({
|
|
618
|
+
...prev,
|
|
619
|
+
selectedRuleIndex: Math.max(prev.selectedRuleIndex - 1, 0),
|
|
620
|
+
}));
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
// n - New rule
|
|
624
|
+
if (input === 'n') {
|
|
625
|
+
setState(prev => ({ ...prev, mode: 'rule-add', ruleEditText: '', ruleEditId: null }));
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
// e - Edit rule
|
|
629
|
+
if (input === 'e') {
|
|
630
|
+
const rule = rules[state.selectedRuleIndex];
|
|
631
|
+
if (rule) {
|
|
632
|
+
setState(prev => ({
|
|
633
|
+
...prev,
|
|
634
|
+
mode: 'rule-edit',
|
|
635
|
+
ruleEditText: rule.rule,
|
|
636
|
+
ruleEditId: rule.id,
|
|
637
|
+
}));
|
|
324
638
|
}
|
|
325
639
|
else {
|
|
326
|
-
showStatus(setState,
|
|
640
|
+
showStatus(setState, 'No rule selected', 'error');
|
|
327
641
|
}
|
|
642
|
+
return;
|
|
328
643
|
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
}
|
|
354
|
-
if (key.ctrl && input === 'u') {
|
|
355
|
-
setState(prev => ({
|
|
356
|
-
...prev,
|
|
357
|
-
selectedTaskIndex: Math.max(prev.selectedTaskIndex - Math.floor(viewportHeight / 2), 0),
|
|
358
|
-
}));
|
|
359
|
-
return;
|
|
360
|
-
}
|
|
361
|
-
// Column switching: TAB / left/right
|
|
362
|
-
if (key.tab || key.rightArrow || input === 'l') {
|
|
363
|
-
setState(prev => ({
|
|
364
|
-
...prev,
|
|
365
|
-
activeColumnIndex: (prev.activeColumnIndex + 1) % filteredColumnsLength,
|
|
366
|
-
selectedTaskIndex: 0,
|
|
367
|
-
}));
|
|
368
|
-
return;
|
|
369
|
-
}
|
|
370
|
-
if ((key.shift && key.tab) || key.leftArrow || input === 'h') {
|
|
371
|
-
setState(prev => ({
|
|
372
|
-
...prev,
|
|
373
|
-
activeColumnIndex: prev.activeColumnIndex === 0
|
|
374
|
-
? filteredColumnsLength - 1
|
|
375
|
-
: prev.activeColumnIndex - 1,
|
|
376
|
-
selectedTaskIndex: 0,
|
|
377
|
-
}));
|
|
378
|
-
return;
|
|
379
|
-
}
|
|
380
|
-
// Jump to column headers with { and }
|
|
381
|
-
if (input === '{') {
|
|
382
|
-
setState(prev => ({
|
|
383
|
-
...prev,
|
|
384
|
-
activeColumnIndex: Math.max(0, prev.activeColumnIndex - 1),
|
|
385
|
-
selectedTaskIndex: 0,
|
|
386
|
-
}));
|
|
387
|
-
return;
|
|
388
|
-
}
|
|
389
|
-
if (input === '}') {
|
|
390
|
-
setState(prev => ({
|
|
391
|
-
...prev,
|
|
392
|
-
activeColumnIndex: Math.min(filteredColumnsLength - 1, prev.activeColumnIndex + 1),
|
|
393
|
-
selectedTaskIndex: 0,
|
|
394
|
-
}));
|
|
395
|
-
return;
|
|
396
|
-
}
|
|
397
|
-
// Home/End
|
|
398
|
-
if (input === 'g') {
|
|
399
|
-
setState(prev => ({ ...prev, selectedTaskIndex: 0 }));
|
|
400
|
-
return;
|
|
401
|
-
}
|
|
402
|
-
if (input === 'G') {
|
|
403
|
-
setState(prev => ({ ...prev, selectedTaskIndex: maxTaskIndex }));
|
|
404
|
-
return;
|
|
405
|
-
}
|
|
406
|
-
// Expand/collapse task
|
|
407
|
-
if (key.return) {
|
|
408
|
-
const task = currentTasks[state.selectedTaskIndex];
|
|
409
|
-
if (task) {
|
|
410
|
-
setState(prev => {
|
|
411
|
-
const newExpanded = new Set(prev.expandedTaskIds);
|
|
412
|
-
if (newExpanded.has(task.id)) {
|
|
413
|
-
newExpanded.delete(task.id);
|
|
414
|
-
}
|
|
415
|
-
else {
|
|
416
|
-
newExpanded.add(task.id);
|
|
417
|
-
}
|
|
418
|
-
return { ...prev, expandedTaskIds: newExpanded };
|
|
419
|
-
});
|
|
644
|
+
// d - Delete rule
|
|
645
|
+
if (input === 'd') {
|
|
646
|
+
const rule = rules[state.selectedRuleIndex];
|
|
647
|
+
if (rule) {
|
|
648
|
+
setState(prev => ({ ...prev, mode: 'rule-delete-confirm' }));
|
|
649
|
+
}
|
|
650
|
+
else {
|
|
651
|
+
showStatus(setState, 'No rule selected', 'error');
|
|
652
|
+
}
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
// Home/End
|
|
656
|
+
if (input === 'g') {
|
|
657
|
+
setState(prev => ({ ...prev, selectedRuleIndex: 0 }));
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
if (input === 'G') {
|
|
661
|
+
setState(prev => ({ ...prev, selectedRuleIndex: maxRuleIndex }));
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
// Escape
|
|
665
|
+
if (key.escape) {
|
|
666
|
+
setState(prev => ({ ...prev, selectedRuleIndex: 0 }));
|
|
667
|
+
return;
|
|
420
668
|
}
|
|
421
|
-
return;
|
|
422
669
|
}
|
|
423
|
-
//
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
670
|
+
// ============================================================
|
|
671
|
+
// ARCHIVE PANEL - Browse mode
|
|
672
|
+
// ============================================================
|
|
673
|
+
if (state.activePanel === 'archive') {
|
|
674
|
+
const maxArchiveIndex = Math.max(0, state.archive.length - 1);
|
|
675
|
+
// r - Refresh archive
|
|
676
|
+
if (input === 'r') {
|
|
677
|
+
const result = (0, actions_js_1.loadArchive)(filePath);
|
|
678
|
+
setState(prev => ({ ...prev, archive: result.archive }));
|
|
679
|
+
showStatus(setState, 'Archive refreshed', 'info');
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
// j/k navigation
|
|
683
|
+
if (key.downArrow || input === 'j') {
|
|
684
|
+
setState(prev => ({
|
|
685
|
+
...prev,
|
|
686
|
+
selectedArchiveIndex: Math.min(prev.selectedArchiveIndex + 1, maxArchiveIndex),
|
|
687
|
+
}));
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
if (key.upArrow || input === 'k') {
|
|
691
|
+
setState(prev => ({
|
|
692
|
+
...prev,
|
|
693
|
+
selectedArchiveIndex: Math.max(prev.selectedArchiveIndex - 1, 0),
|
|
694
|
+
}));
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
// Enter - Expand/collapse
|
|
698
|
+
if (key.return) {
|
|
699
|
+
const task = state.archive[state.selectedArchiveIndex];
|
|
700
|
+
if (task) {
|
|
701
|
+
setState(prev => {
|
|
702
|
+
const newExpanded = new Set(prev.expandedArchiveIds);
|
|
703
|
+
if (newExpanded.has(task.id)) {
|
|
704
|
+
newExpanded.delete(task.id);
|
|
705
|
+
}
|
|
706
|
+
else {
|
|
707
|
+
newExpanded.add(task.id);
|
|
708
|
+
}
|
|
709
|
+
return { ...prev, expandedArchiveIds: newExpanded };
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
// r key for restore (use 'R' to avoid conflict with refresh)
|
|
715
|
+
if (input === 'R') {
|
|
716
|
+
if (state.archive.length === 0) {
|
|
717
|
+
showStatus(setState, 'No archived tasks', 'error');
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
setState(prev => ({ ...prev, mode: 'archive-restore', archiveRestoreColumnIndex: 0 }));
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
// d - Delete permanently
|
|
724
|
+
if (input === 'd') {
|
|
725
|
+
if (state.archive.length === 0) {
|
|
726
|
+
showStatus(setState, 'No archived tasks', 'error');
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
setState(prev => ({ ...prev, mode: 'archive-delete-confirm' }));
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
// Home/End
|
|
733
|
+
if (input === 'g') {
|
|
734
|
+
setState(prev => ({ ...prev, selectedArchiveIndex: 0 }));
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
if (input === 'G') {
|
|
738
|
+
setState(prev => ({ ...prev, selectedArchiveIndex: maxArchiveIndex }));
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
// Escape - collapse all expanded
|
|
742
|
+
if (key.escape) {
|
|
743
|
+
setState(prev => ({
|
|
744
|
+
...prev,
|
|
745
|
+
expandedArchiveIds: new Set(),
|
|
746
|
+
}));
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
431
749
|
}
|
|
432
750
|
});
|
|
433
751
|
}
|