floq 1.4.0 → 1.5.0
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/dist/commands/done.js +1 -0
- package/dist/commands/insights.js +13 -10
- package/dist/commands/move.js +2 -1
- package/dist/db/index.js +36 -0
- package/dist/db/schema.d.ts +116 -0
- package/dist/db/schema.js +8 -0
- package/dist/i18n/en.d.ts +2 -0
- package/dist/i18n/en.js +1 -0
- package/dist/i18n/ja.js +1 -0
- package/dist/ui/App.js +7 -4
- package/dist/ui/components/GtdDQ.js +27 -5
- package/dist/ui/components/GtdMario.js +27 -5
- package/dist/ui/components/InsightsModal.js +13 -7
- package/dist/ui/components/KanbanBoard.js +3 -0
- package/dist/ui/components/KanbanDQ.js +2 -0
- package/dist/ui/components/KanbanMario.js +2 -0
- package/dist/ui/components/TaskItem.d.ts +2 -1
- package/dist/ui/components/TaskItem.js +4 -3
- package/dist/ui/history/HistoryContext.js +8 -0
- package/dist/ui/history/HistoryManager.d.ts +9 -1
- package/dist/ui/history/HistoryManager.js +140 -16
- package/dist/ui/history/commands/ConvertToProjectCommand.d.ts +5 -1
- package/dist/ui/history/commands/ConvertToProjectCommand.js +13 -0
- package/dist/ui/history/commands/CreateCommentCommand.d.ts +5 -1
- package/dist/ui/history/commands/CreateCommentCommand.js +22 -0
- package/dist/ui/history/commands/CreateTaskCommand.d.ts +5 -1
- package/dist/ui/history/commands/CreateTaskCommand.js +26 -1
- package/dist/ui/history/commands/DeleteCommentCommand.d.ts +5 -1
- package/dist/ui/history/commands/DeleteCommentCommand.js +22 -0
- package/dist/ui/history/commands/DeleteTaskCommand.d.ts +7 -2
- package/dist/ui/history/commands/DeleteTaskCommand.js +41 -0
- package/dist/ui/history/commands/LinkTaskCommand.d.ts +5 -1
- package/dist/ui/history/commands/LinkTaskCommand.js +14 -0
- package/dist/ui/history/commands/MoveTaskCommand.d.ts +7 -1
- package/dist/ui/history/commands/MoveTaskCommand.js +25 -0
- package/dist/ui/history/commands/SetContextCommand.d.ts +5 -1
- package/dist/ui/history/commands/SetContextCommand.js +14 -0
- package/dist/ui/history/commands/SetEffortCommand.d.ts +5 -1
- package/dist/ui/history/commands/SetEffortCommand.js +14 -0
- package/dist/ui/history/commands/SetFocusCommand.d.ts +5 -1
- package/dist/ui/history/commands/SetFocusCommand.js +14 -0
- package/dist/ui/history/commands/index.d.ts +1 -0
- package/dist/ui/history/commands/index.js +1 -0
- package/dist/ui/history/commands/registry.d.ts +2 -0
- package/dist/ui/history/commands/registry.js +28 -0
- package/dist/ui/history/index.d.ts +2 -2
- package/dist/ui/history/index.js +1 -1
- package/dist/ui/history/types.d.ts +9 -0
- package/package.json +1 -1
package/dist/commands/done.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { eq, and
|
|
1
|
+
import { eq, and } from 'drizzle-orm';
|
|
2
2
|
import { getDb, schema } from '../db/index.js';
|
|
3
3
|
import { t, fmt } from '../i18n/index.js';
|
|
4
4
|
function getWeekStart(date) {
|
|
@@ -53,8 +53,8 @@ function groupByWeek(tasks, weeks) {
|
|
|
53
53
|
const weekEnd = new Date(weekStart);
|
|
54
54
|
weekEnd.setDate(weekEnd.getDate() + 7);
|
|
55
55
|
const weekTasks = tasks.filter(task => {
|
|
56
|
-
const
|
|
57
|
-
return
|
|
56
|
+
const completionDate = task.completedAt ?? task.updatedAt;
|
|
57
|
+
return completionDate >= weekStart && completionDate < weekEnd;
|
|
58
58
|
});
|
|
59
59
|
result.push({
|
|
60
60
|
weekStart,
|
|
@@ -69,7 +69,7 @@ function groupByDayOfWeek(tasks) {
|
|
|
69
69
|
for (let i = 0; i < 7; i++)
|
|
70
70
|
days.set(i, 0);
|
|
71
71
|
for (const task of tasks) {
|
|
72
|
-
const day = task.updatedAt.getDay();
|
|
72
|
+
const day = (task.completedAt ?? task.updatedAt).getDay();
|
|
73
73
|
days.set(day, (days.get(day) || 0) + 1);
|
|
74
74
|
}
|
|
75
75
|
return days;
|
|
@@ -95,9 +95,9 @@ function calculateAverageCompletionDays(tasks) {
|
|
|
95
95
|
let validCount = 0;
|
|
96
96
|
for (const task of tasks) {
|
|
97
97
|
const created = task.createdAt.getTime();
|
|
98
|
-
const
|
|
99
|
-
if (
|
|
100
|
-
totalMs +=
|
|
98
|
+
const completed = (task.completedAt ?? task.updatedAt).getTime();
|
|
99
|
+
if (completed > created) {
|
|
100
|
+
totalMs += completed - created;
|
|
101
101
|
validCount++;
|
|
102
102
|
}
|
|
103
103
|
}
|
|
@@ -138,11 +138,14 @@ export async function showInsights(weeks) {
|
|
|
138
138
|
startDate.setDate(startDate.getDate() - (weeks - 1) * 7);
|
|
139
139
|
const endDate = new Date(now);
|
|
140
140
|
endDate.setHours(23, 59, 59, 999);
|
|
141
|
-
// Query
|
|
142
|
-
const completedTasks = await db
|
|
141
|
+
// Query completed tasks (use completedAt with updatedAt fallback)
|
|
142
|
+
const completedTasks = (await db
|
|
143
143
|
.select()
|
|
144
144
|
.from(schema.tasks)
|
|
145
|
-
.where(and(eq(schema.tasks.status, 'done'),
|
|
145
|
+
.where(and(eq(schema.tasks.status, 'done'), eq(schema.tasks.isProject, false)))).filter(task => {
|
|
146
|
+
const completionDate = task.completedAt ?? task.updatedAt;
|
|
147
|
+
return completionDate >= startDate;
|
|
148
|
+
});
|
|
146
149
|
// Header
|
|
147
150
|
console.log();
|
|
148
151
|
console.log(l.title);
|
package/dist/commands/move.js
CHANGED
|
@@ -4,7 +4,7 @@ import { t, fmt } from '../i18n/index.js';
|
|
|
4
4
|
export async function moveTask(taskId, targetStatus, waitingFor) {
|
|
5
5
|
const db = getDb();
|
|
6
6
|
const i18n = t();
|
|
7
|
-
const validStatuses = ['inbox', 'next', 'waiting', 'someday'];
|
|
7
|
+
const validStatuses = ['inbox', 'next', 'waiting', 'someday', 'done'];
|
|
8
8
|
if (!validStatuses.includes(targetStatus)) {
|
|
9
9
|
console.error(fmt(i18n.commands.move.invalidStatus, { status: targetStatus }));
|
|
10
10
|
console.error(fmt(i18n.commands.move.validStatuses, { statuses: validStatuses.join(', ') }));
|
|
@@ -35,6 +35,7 @@ export async function moveTask(taskId, targetStatus, waitingFor) {
|
|
|
35
35
|
.set({
|
|
36
36
|
status: targetStatus,
|
|
37
37
|
waitingFor: targetStatus === 'waiting' ? waitingFor : null,
|
|
38
|
+
completedAt: targetStatus === 'done' ? new Date() : null,
|
|
38
39
|
updatedAt: new Date(),
|
|
39
40
|
})
|
|
40
41
|
.where(eq(schema.tasks.id, task.id));
|
package/dist/db/index.js
CHANGED
|
@@ -26,6 +26,7 @@ async function initializeRemoteSchema(tursoUrl, authToken) {
|
|
|
26
26
|
const hasContext = tableInfo.some(col => col.name === 'context');
|
|
27
27
|
const hasIsFocused = tableInfo.some(col => col.name === 'is_focused');
|
|
28
28
|
const hasEffort = tableInfo.some(col => col.name === 'effort');
|
|
29
|
+
const hasCompletedAt = tableInfo.some(col => col.name === 'completed_at');
|
|
29
30
|
if (!tableExists) {
|
|
30
31
|
// Fresh install: create new schema on remote
|
|
31
32
|
await remoteClient.execute(`
|
|
@@ -41,6 +42,7 @@ async function initializeRemoteSchema(tursoUrl, authToken) {
|
|
|
41
42
|
is_focused INTEGER NOT NULL DEFAULT 0,
|
|
42
43
|
effort TEXT,
|
|
43
44
|
due_date INTEGER,
|
|
45
|
+
completed_at INTEGER,
|
|
44
46
|
created_at INTEGER NOT NULL,
|
|
45
47
|
updated_at INTEGER NOT NULL
|
|
46
48
|
)
|
|
@@ -63,6 +65,11 @@ async function initializeRemoteSchema(tursoUrl, authToken) {
|
|
|
63
65
|
if (tableExists && !hasEffort) {
|
|
64
66
|
await remoteClient.execute("ALTER TABLE tasks ADD COLUMN effort TEXT");
|
|
65
67
|
}
|
|
68
|
+
// Migration: add completed_at column if missing
|
|
69
|
+
if (tableExists && !hasCompletedAt) {
|
|
70
|
+
await remoteClient.execute("ALTER TABLE tasks ADD COLUMN completed_at INTEGER");
|
|
71
|
+
await remoteClient.execute("UPDATE tasks SET completed_at = updated_at WHERE status = 'done'");
|
|
72
|
+
}
|
|
66
73
|
// Create comments table
|
|
67
74
|
await remoteClient.execute(`
|
|
68
75
|
CREATE TABLE IF NOT EXISTS comments (
|
|
@@ -86,6 +93,17 @@ async function initializeRemoteSchema(tursoUrl, authToken) {
|
|
|
86
93
|
updated_at INTEGER NOT NULL
|
|
87
94
|
)
|
|
88
95
|
`);
|
|
96
|
+
// Create operation_history table for crash-safe undo
|
|
97
|
+
await remoteClient.execute(`
|
|
98
|
+
CREATE TABLE IF NOT EXISTS operation_history (
|
|
99
|
+
id TEXT PRIMARY KEY,
|
|
100
|
+
command_type TEXT NOT NULL,
|
|
101
|
+
command_data TEXT NOT NULL,
|
|
102
|
+
executed_at INTEGER NOT NULL,
|
|
103
|
+
is_undone INTEGER NOT NULL DEFAULT 0
|
|
104
|
+
)
|
|
105
|
+
`);
|
|
106
|
+
await remoteClient.execute("CREATE INDEX IF NOT EXISTS idx_operation_history_executed_at ON operation_history(executed_at)");
|
|
89
107
|
}
|
|
90
108
|
finally {
|
|
91
109
|
remoteClient.close();
|
|
@@ -102,6 +120,7 @@ async function initializeLocalSchema() {
|
|
|
102
120
|
const hasContext = tableInfo.some(col => col.name === 'context');
|
|
103
121
|
const hasIsFocused = tableInfo.some(col => col.name === 'is_focused');
|
|
104
122
|
const hasEffort = tableInfo.some(col => col.name === 'effort');
|
|
123
|
+
const hasCompletedAt = tableInfo.some(col => col.name === 'completed_at');
|
|
105
124
|
const tableExists = tableInfo.length > 0;
|
|
106
125
|
if (tableExists && hasProjectId && !hasIsProject) {
|
|
107
126
|
// Migration: old schema -> new schema
|
|
@@ -136,6 +155,7 @@ async function initializeLocalSchema() {
|
|
|
136
155
|
is_focused INTEGER NOT NULL DEFAULT 0,
|
|
137
156
|
effort TEXT,
|
|
138
157
|
due_date INTEGER,
|
|
158
|
+
completed_at INTEGER,
|
|
139
159
|
created_at INTEGER NOT NULL,
|
|
140
160
|
updated_at INTEGER NOT NULL
|
|
141
161
|
)
|
|
@@ -158,6 +178,11 @@ async function initializeLocalSchema() {
|
|
|
158
178
|
if (tableExists && !hasEffort) {
|
|
159
179
|
await client.execute("ALTER TABLE tasks ADD COLUMN effort TEXT");
|
|
160
180
|
}
|
|
181
|
+
// Migration: add completed_at column if missing
|
|
182
|
+
if (tableExists && !hasCompletedAt) {
|
|
183
|
+
await client.execute("ALTER TABLE tasks ADD COLUMN completed_at INTEGER");
|
|
184
|
+
await client.execute("UPDATE tasks SET completed_at = updated_at WHERE status = 'done'");
|
|
185
|
+
}
|
|
161
186
|
// Create comments table
|
|
162
187
|
await client.execute(`
|
|
163
188
|
CREATE TABLE IF NOT EXISTS comments (
|
|
@@ -181,6 +206,17 @@ async function initializeLocalSchema() {
|
|
|
181
206
|
updated_at INTEGER NOT NULL
|
|
182
207
|
)
|
|
183
208
|
`);
|
|
209
|
+
// Create operation_history table for crash-safe undo
|
|
210
|
+
await client.execute(`
|
|
211
|
+
CREATE TABLE IF NOT EXISTS operation_history (
|
|
212
|
+
id TEXT PRIMARY KEY,
|
|
213
|
+
command_type TEXT NOT NULL,
|
|
214
|
+
command_data TEXT NOT NULL,
|
|
215
|
+
executed_at INTEGER NOT NULL,
|
|
216
|
+
is_undone INTEGER NOT NULL DEFAULT 0
|
|
217
|
+
)
|
|
218
|
+
`);
|
|
219
|
+
await client.execute("CREATE INDEX IF NOT EXISTS idx_operation_history_executed_at ON operation_history(executed_at)");
|
|
184
220
|
}
|
|
185
221
|
// DB 初期化
|
|
186
222
|
export async function initDb() {
|
package/dist/db/schema.d.ts
CHANGED
|
@@ -205,6 +205,23 @@ export declare const tasks: import("drizzle-orm/sqlite-core").SQLiteTableWithCol
|
|
|
205
205
|
identity: undefined;
|
|
206
206
|
generated: undefined;
|
|
207
207
|
}, {}, {}>;
|
|
208
|
+
completedAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
209
|
+
name: "completed_at";
|
|
210
|
+
tableName: "tasks";
|
|
211
|
+
dataType: "date";
|
|
212
|
+
columnType: "SQLiteTimestamp";
|
|
213
|
+
data: Date;
|
|
214
|
+
driverParam: number;
|
|
215
|
+
notNull: false;
|
|
216
|
+
hasDefault: false;
|
|
217
|
+
isPrimaryKey: false;
|
|
218
|
+
isAutoincrement: false;
|
|
219
|
+
hasRuntimeDefault: false;
|
|
220
|
+
enumValues: undefined;
|
|
221
|
+
baseColumn: never;
|
|
222
|
+
identity: undefined;
|
|
223
|
+
generated: undefined;
|
|
224
|
+
}, {}, {}>;
|
|
208
225
|
createdAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
209
226
|
name: "created_at";
|
|
210
227
|
tableName: "tasks";
|
|
@@ -482,3 +499,102 @@ export declare const pomodoroSessions: import("drizzle-orm/sqlite-core").SQLiteT
|
|
|
482
499
|
}>;
|
|
483
500
|
export type PomodoroSession = typeof pomodoroSessions.$inferSelect;
|
|
484
501
|
export type NewPomodoroSession = typeof pomodoroSessions.$inferInsert;
|
|
502
|
+
export declare const operationHistory: import("drizzle-orm/sqlite-core").SQLiteTableWithColumns<{
|
|
503
|
+
name: "operation_history";
|
|
504
|
+
schema: undefined;
|
|
505
|
+
columns: {
|
|
506
|
+
id: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
507
|
+
name: "id";
|
|
508
|
+
tableName: "operation_history";
|
|
509
|
+
dataType: "string";
|
|
510
|
+
columnType: "SQLiteText";
|
|
511
|
+
data: string;
|
|
512
|
+
driverParam: string;
|
|
513
|
+
notNull: true;
|
|
514
|
+
hasDefault: false;
|
|
515
|
+
isPrimaryKey: true;
|
|
516
|
+
isAutoincrement: false;
|
|
517
|
+
hasRuntimeDefault: false;
|
|
518
|
+
enumValues: [string, ...string[]];
|
|
519
|
+
baseColumn: never;
|
|
520
|
+
identity: undefined;
|
|
521
|
+
generated: undefined;
|
|
522
|
+
}, {}, {
|
|
523
|
+
length: number | undefined;
|
|
524
|
+
}>;
|
|
525
|
+
commandType: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
526
|
+
name: "command_type";
|
|
527
|
+
tableName: "operation_history";
|
|
528
|
+
dataType: "string";
|
|
529
|
+
columnType: "SQLiteText";
|
|
530
|
+
data: string;
|
|
531
|
+
driverParam: string;
|
|
532
|
+
notNull: true;
|
|
533
|
+
hasDefault: false;
|
|
534
|
+
isPrimaryKey: false;
|
|
535
|
+
isAutoincrement: false;
|
|
536
|
+
hasRuntimeDefault: false;
|
|
537
|
+
enumValues: [string, ...string[]];
|
|
538
|
+
baseColumn: never;
|
|
539
|
+
identity: undefined;
|
|
540
|
+
generated: undefined;
|
|
541
|
+
}, {}, {
|
|
542
|
+
length: number | undefined;
|
|
543
|
+
}>;
|
|
544
|
+
commandData: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
545
|
+
name: "command_data";
|
|
546
|
+
tableName: "operation_history";
|
|
547
|
+
dataType: "string";
|
|
548
|
+
columnType: "SQLiteText";
|
|
549
|
+
data: string;
|
|
550
|
+
driverParam: string;
|
|
551
|
+
notNull: true;
|
|
552
|
+
hasDefault: false;
|
|
553
|
+
isPrimaryKey: false;
|
|
554
|
+
isAutoincrement: false;
|
|
555
|
+
hasRuntimeDefault: false;
|
|
556
|
+
enumValues: [string, ...string[]];
|
|
557
|
+
baseColumn: never;
|
|
558
|
+
identity: undefined;
|
|
559
|
+
generated: undefined;
|
|
560
|
+
}, {}, {
|
|
561
|
+
length: number | undefined;
|
|
562
|
+
}>;
|
|
563
|
+
executedAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
564
|
+
name: "executed_at";
|
|
565
|
+
tableName: "operation_history";
|
|
566
|
+
dataType: "date";
|
|
567
|
+
columnType: "SQLiteTimestamp";
|
|
568
|
+
data: Date;
|
|
569
|
+
driverParam: number;
|
|
570
|
+
notNull: true;
|
|
571
|
+
hasDefault: false;
|
|
572
|
+
isPrimaryKey: false;
|
|
573
|
+
isAutoincrement: false;
|
|
574
|
+
hasRuntimeDefault: false;
|
|
575
|
+
enumValues: undefined;
|
|
576
|
+
baseColumn: never;
|
|
577
|
+
identity: undefined;
|
|
578
|
+
generated: undefined;
|
|
579
|
+
}, {}, {}>;
|
|
580
|
+
isUndone: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
581
|
+
name: "is_undone";
|
|
582
|
+
tableName: "operation_history";
|
|
583
|
+
dataType: "boolean";
|
|
584
|
+
columnType: "SQLiteBoolean";
|
|
585
|
+
data: boolean;
|
|
586
|
+
driverParam: number;
|
|
587
|
+
notNull: true;
|
|
588
|
+
hasDefault: true;
|
|
589
|
+
isPrimaryKey: false;
|
|
590
|
+
isAutoincrement: false;
|
|
591
|
+
hasRuntimeDefault: false;
|
|
592
|
+
enumValues: undefined;
|
|
593
|
+
baseColumn: never;
|
|
594
|
+
identity: undefined;
|
|
595
|
+
generated: undefined;
|
|
596
|
+
}, {}, {}>;
|
|
597
|
+
};
|
|
598
|
+
dialect: "sqlite";
|
|
599
|
+
}>;
|
|
600
|
+
export type OperationHistory = typeof operationHistory.$inferSelect;
|
package/dist/db/schema.js
CHANGED
|
@@ -11,6 +11,7 @@ export const tasks = sqliteTable('tasks', {
|
|
|
11
11
|
isFocused: integer('is_focused', { mode: 'boolean' }).notNull().default(false),
|
|
12
12
|
effort: text('effort'),
|
|
13
13
|
dueDate: integer('due_date', { mode: 'timestamp' }),
|
|
14
|
+
completedAt: integer('completed_at', { mode: 'timestamp' }),
|
|
14
15
|
createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
|
|
15
16
|
updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull(),
|
|
16
17
|
});
|
|
@@ -32,3 +33,10 @@ export const pomodoroSessions = sqliteTable('pomodoro_sessions', {
|
|
|
32
33
|
completedCount: integer('completed_count').notNull().default(0),
|
|
33
34
|
updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull(),
|
|
34
35
|
});
|
|
36
|
+
export const operationHistory = sqliteTable('operation_history', {
|
|
37
|
+
id: text('id').primaryKey(),
|
|
38
|
+
commandType: text('command_type').notNull(),
|
|
39
|
+
commandData: text('command_data').notNull(), // JSON serialized
|
|
40
|
+
executedAt: integer('executed_at', { mode: 'timestamp' }).notNull(),
|
|
41
|
+
isUndone: integer('is_undone', { mode: 'boolean' }).notNull().default(false),
|
|
42
|
+
});
|
package/dist/i18n/en.d.ts
CHANGED
|
@@ -243,6 +243,7 @@ export declare const en: {
|
|
|
243
243
|
taskDetailTitle: string;
|
|
244
244
|
taskDetailFooter: string;
|
|
245
245
|
taskDetailStatus: string;
|
|
246
|
+
taskDetailCompletedAt: string;
|
|
246
247
|
deleteConfirm: string;
|
|
247
248
|
deleted: string;
|
|
248
249
|
deleteCancelled: string;
|
|
@@ -670,6 +671,7 @@ export type TuiTranslations = {
|
|
|
670
671
|
taskDetailTitle: string;
|
|
671
672
|
taskDetailFooter: string;
|
|
672
673
|
taskDetailStatus: string;
|
|
674
|
+
taskDetailCompletedAt?: string;
|
|
673
675
|
deleteConfirm: string;
|
|
674
676
|
deleted: string;
|
|
675
677
|
deleteCancelled: string;
|
package/dist/i18n/en.js
CHANGED
|
@@ -256,6 +256,7 @@ export const en = {
|
|
|
256
256
|
taskDetailTitle: 'Task Details',
|
|
257
257
|
taskDetailFooter: 'j/k=select i=comment d=delete P=link b/Esc=back',
|
|
258
258
|
taskDetailStatus: 'Status',
|
|
259
|
+
taskDetailCompletedAt: 'Completed',
|
|
259
260
|
deleteConfirm: 'Delete "{title}"? (y/n)',
|
|
260
261
|
deleted: 'Deleted: "{title}"',
|
|
261
262
|
deleteCancelled: 'Delete cancelled',
|
package/dist/i18n/ja.js
CHANGED
|
@@ -256,6 +256,7 @@ export const ja = {
|
|
|
256
256
|
taskDetailTitle: 'タスク詳細',
|
|
257
257
|
taskDetailFooter: 'j/k=選択 i=コメント d=削除 P=紐づけ b/Esc=戻る',
|
|
258
258
|
taskDetailStatus: 'ステータス',
|
|
259
|
+
taskDetailCompletedAt: '完了日',
|
|
259
260
|
deleteConfirm: '「{title}」を削除しますか? (y/n)',
|
|
260
261
|
deleted: '削除しました: 「{title}」',
|
|
261
262
|
deleteCancelled: '削除をキャンセルしました',
|
package/dist/ui/App.js
CHANGED
|
@@ -407,6 +407,7 @@ function AppContent({ onOpenSettings }) {
|
|
|
407
407
|
toStatus: 'done',
|
|
408
408
|
fromWaitingFor: task.waitingFor,
|
|
409
409
|
toWaitingFor: null,
|
|
410
|
+
fromCompletedAt: task.completedAt,
|
|
410
411
|
description: fmt(i18n.tui.completed, { title: task.title }),
|
|
411
412
|
});
|
|
412
413
|
await history.execute(command);
|
|
@@ -420,6 +421,7 @@ function AppContent({ onOpenSettings }) {
|
|
|
420
421
|
toStatus: status,
|
|
421
422
|
fromWaitingFor: task.waitingFor,
|
|
422
423
|
toWaitingFor: null,
|
|
424
|
+
fromCompletedAt: task.completedAt,
|
|
423
425
|
description: fmt(i18n.tui.movedTo, { title: task.title, status: i18n.status[status] }),
|
|
424
426
|
});
|
|
425
427
|
await history.execute(command);
|
|
@@ -433,6 +435,7 @@ function AppContent({ onOpenSettings }) {
|
|
|
433
435
|
toStatus: 'waiting',
|
|
434
436
|
fromWaitingFor: task.waitingFor,
|
|
435
437
|
toWaitingFor: waitingFor.trim(),
|
|
438
|
+
fromCompletedAt: task.completedAt,
|
|
436
439
|
description: fmt(i18n.tui.movedToWaiting, { title: task.title, person: waitingFor.trim() }),
|
|
437
440
|
});
|
|
438
441
|
await history.execute(command);
|
|
@@ -1109,7 +1112,7 @@ function AppContent({ onOpenSettings }) {
|
|
|
1109
1112
|
return;
|
|
1110
1113
|
}
|
|
1111
1114
|
// Move to inbox
|
|
1112
|
-
if (input === 'i' && currentTasks.length > 0 && currentTab !== 'inbox' && currentTab !== 'projects'
|
|
1115
|
+
if (input === 'i' && currentTasks.length > 0 && currentTab !== 'inbox' && currentTab !== 'projects') {
|
|
1113
1116
|
const task = currentTasks[selectedTaskIndex];
|
|
1114
1117
|
moveTaskToStatus(task, 'inbox').then(() => {
|
|
1115
1118
|
if (selectedTaskIndex >= currentTasks.length - 1) {
|
|
@@ -1188,17 +1191,17 @@ function AppContent({ onOpenSettings }) {
|
|
|
1188
1191
|
const count = tasks[tab].length;
|
|
1189
1192
|
const label = `${index + 1}:${getTabLabel(tab)}(${count})`;
|
|
1190
1193
|
return (_jsx(Box, { marginRight: 1, children: _jsx(Text, { color: isActive ? theme.colors.textSelected : theme.colors.textMuted, bold: isActive, inverse: isActive && theme.style.tabActiveInverse, children: formatTabLabel(` ${label} `, isActive) }) }, tab));
|
|
1191
|
-
}) }), mode === 'project-detail' && selectedProject && (_jsxs(_Fragment, { children: [_jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { color: theme.colors.accent, bold: true, children: [theme.name === 'modern' ? '📁 ' : '>> ', selectedProject.title] }), _jsxs(Text, { color: theme.colors.textMuted, children: [" (Esc/b: ", i18n.tui.back || 'back', ")"] })] }), _jsx(Box, { marginBottom: 1, children: _jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.projectTasks || 'Tasks', " (", projectTasks.length, ")"] }) })] })), (mode === 'task-detail' || mode === 'add-comment') && selectedTask && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { color: theme.colors.accent, bold: true, children: [theme.name === 'modern' ? '📋 ' : '>> ', i18n.tui.taskDetailTitle || 'Task Details'] }), _jsxs(Text, { color: theme.colors.textMuted, children: [" (Esc/b: ", i18n.tui.back || 'back', ", ", i18n.tui.commentHint || 'i: add comment', ")"] })] }), _jsxs(Box, { flexDirection: "column", borderStyle: theme.borders.list, borderColor: theme.colors.border, paddingX: 1, paddingY: 1, marginBottom: 1, children: [_jsx(Text, { color: theme.colors.text, bold: true, children: selectedTask.title }), selectedTask.description && (_jsx(Text, { color: theme.colors.textMuted, children: selectedTask.description })), _jsxs(Box, { marginTop: 1, children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.taskDetailStatus, ": "] }), _jsxs(Text, { color: theme.colors.accent, children: [i18n.status[selectedTask.status], selectedTask.waitingFor && ` (${selectedTask.waitingFor})`] })] }), _jsxs(Box, { children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.context?.label || 'Context', ": "] }), _jsx(Text, { color: theme.colors.accent, children: selectedTask.context ? `@${selectedTask.context}` : (i18n.tui.context?.none || 'No context') })] }), _jsxs(Box, { children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.effort?.label || 'Effort', ": "] }), _jsx(Text, { color: theme.colors.accent, children: selectedTask.effort ? (i18n.tui.effort?.[selectedTask.effort] || selectedTask.effort) : '-' })] }), _jsxs(Box, { children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.focus?.label || 'Focus', ": "] }), _jsx(Text, { color: theme.colors.accent, children: selectedTask.isFocused ? '★' : '-' })] })] }), _jsx(Box, { marginBottom: 1, children: _jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.comments || 'Comments', " (", taskComments.length, ")"] }) }), _jsx(Box, { flexDirection: "column", borderStyle: theme.borders.list, borderColor: theme.colors.border, paddingX: 1, paddingY: 1, minHeight: 5, children: taskComments.length === 0 ? (_jsx(Text, { color: theme.colors.textMuted, italic: true, children: i18n.tui.noComments || 'No comments yet' })) : (taskComments.map((comment, index) => {
|
|
1194
|
+
}) }), mode === 'project-detail' && selectedProject && (_jsxs(_Fragment, { children: [_jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { color: theme.colors.accent, bold: true, children: [theme.name === 'modern' ? '📁 ' : '>> ', selectedProject.title] }), _jsxs(Text, { color: theme.colors.textMuted, children: [" (Esc/b: ", i18n.tui.back || 'back', ")"] })] }), _jsx(Box, { marginBottom: 1, children: _jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.projectTasks || 'Tasks', " (", projectTasks.length, ")"] }) })] })), (mode === 'task-detail' || mode === 'add-comment') && selectedTask && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { color: theme.colors.accent, bold: true, children: [theme.name === 'modern' ? '📋 ' : '>> ', i18n.tui.taskDetailTitle || 'Task Details'] }), _jsxs(Text, { color: theme.colors.textMuted, children: [" (Esc/b: ", i18n.tui.back || 'back', ", ", i18n.tui.commentHint || 'i: add comment', ")"] })] }), _jsxs(Box, { flexDirection: "column", borderStyle: theme.borders.list, borderColor: theme.colors.border, paddingX: 1, paddingY: 1, marginBottom: 1, children: [_jsx(Text, { color: theme.colors.text, bold: true, children: selectedTask.title }), selectedTask.description && (_jsx(Text, { color: theme.colors.textMuted, children: selectedTask.description })), _jsxs(Box, { marginTop: 1, children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.taskDetailStatus, ": "] }), _jsxs(Text, { color: theme.colors.accent, children: [i18n.status[selectedTask.status], selectedTask.waitingFor && ` (${selectedTask.waitingFor})`] })] }), _jsxs(Box, { children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.context?.label || 'Context', ": "] }), _jsx(Text, { color: theme.colors.accent, children: selectedTask.context ? `@${selectedTask.context}` : (i18n.tui.context?.none || 'No context') })] }), _jsxs(Box, { children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.effort?.label || 'Effort', ": "] }), _jsx(Text, { color: theme.colors.accent, children: selectedTask.effort ? (i18n.tui.effort?.[selectedTask.effort] || selectedTask.effort) : '-' })] }), _jsxs(Box, { children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.focus?.label || 'Focus', ": "] }), _jsx(Text, { color: theme.colors.accent, children: selectedTask.isFocused ? '★' : '-' })] }), selectedTask.status === 'done' && (_jsxs(Box, { children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.taskDetailCompletedAt || 'Completed', ": "] }), _jsx(Text, { color: theme.colors.accent, children: (selectedTask.completedAt ?? selectedTask.updatedAt).toLocaleString() })] }))] }), _jsx(Box, { marginBottom: 1, children: _jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.comments || 'Comments', " (", taskComments.length, ")"] }) }), _jsx(Box, { flexDirection: "column", borderStyle: theme.borders.list, borderColor: theme.colors.border, paddingX: 1, paddingY: 1, minHeight: 5, children: taskComments.length === 0 ? (_jsx(Text, { color: theme.colors.textMuted, italic: true, children: i18n.tui.noComments || 'No comments yet' })) : (taskComments.map((comment, index) => {
|
|
1192
1195
|
const isSelected = index === selectedCommentIndex && mode === 'task-detail';
|
|
1193
1196
|
return (_jsxs(Box, { flexDirection: "row", marginBottom: 1, children: [_jsx(Text, { color: isSelected ? theme.colors.textSelected : theme.colors.textMuted, children: isSelected ? theme.style.selectedPrefix : theme.style.unselectedPrefix }), _jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.textMuted, children: ["[", comment.createdAt.toLocaleString(), "]"] }), _jsx(Text, { color: isSelected ? theme.colors.textSelected : theme.colors.text, bold: isSelected, children: comment.content })] })] }, comment.id));
|
|
1194
1197
|
})) }), mode === 'add-comment' && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: theme.colors.secondary, bold: true, children: i18n.tui.addComment || 'New comment: ' }), _jsx(TextInput, { value: inputValue, onChange: setInputValue, onSubmit: handleInputSubmit, placeholder: "" }), _jsxs(Text, { color: theme.colors.textMuted, children: [" ", i18n.tui.inputHelp] })] }))] })), mode === 'search' && searchQuery && (_jsx(SearchResults, { results: searchResults, selectedIndex: searchResultIndex, query: searchQuery })), mode !== 'task-detail' && mode !== 'add-comment' && mode !== 'search' && (theme.uiStyle === 'titled-box' ? (_jsx(TitledBox, { title: mode === 'project-detail' && selectedProject ? selectedProject.title : getTabLabel(currentTab), borderColor: theme.colors.border, minHeight: 10, children: currentTasks.length === 0 ? (_jsx(Text, { color: theme.colors.textMuted, italic: true, children: i18n.tui.noTasks })) : (currentTasks.map((task, index) => {
|
|
1195
1198
|
const parentProject = getParentProject(task.parentId);
|
|
1196
1199
|
const progress = currentTab === 'projects' ? projectProgress[task.id] : undefined;
|
|
1197
|
-
return (_jsx(TaskItem, { task: task, isSelected: index === selectedTaskIndex, projectName: parentProject?.title, progress: progress }, task.id));
|
|
1200
|
+
return (_jsx(TaskItem, { task: task, isSelected: index === selectedTaskIndex, projectName: parentProject?.title, progress: progress, showStatus: mode === 'project-detail' }, task.id));
|
|
1198
1201
|
})) })) : (_jsx(Box, { flexDirection: "column", borderStyle: theme.borders.list, borderColor: theme.colors.border, paddingX: 1, paddingY: 1, minHeight: 10, children: currentTasks.length === 0 ? (_jsx(Text, { color: theme.colors.textMuted, italic: true, children: i18n.tui.noTasks })) : (currentTasks.map((task, index) => {
|
|
1199
1202
|
const parentProject = getParentProject(task.parentId);
|
|
1200
1203
|
const progress = currentTab === 'projects' ? projectProgress[task.id] : undefined;
|
|
1201
|
-
return (_jsx(TaskItem, { task: task, isSelected: index === selectedTaskIndex, projectName: parentProject?.title, progress: progress }, task.id));
|
|
1204
|
+
return (_jsx(TaskItem, { task: task, isSelected: index === selectedTaskIndex, projectName: parentProject?.title, progress: progress, showStatus: mode === 'project-detail' }, task.id));
|
|
1202
1205
|
})) }))), (mode === 'add' || mode === 'add-to-project') && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: theme.colors.secondary, bold: true, children: mode === 'add-to-project' && selectedProject
|
|
1203
1206
|
? `${i18n.tui.newTask}[${selectedProject.title}] `
|
|
1204
1207
|
: i18n.tui.newTask }), _jsx(TextInput, { value: inputValue, onChange: setInputValue, onSubmit: handleInputSubmit, placeholder: i18n.tui.placeholder }), _jsxs(Text, { color: theme.colors.textMuted, children: [" ", i18n.tui.inputHelp] })] })), mode === 'move-to-waiting' && taskToWaiting && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: theme.colors.secondary, bold: true, children: i18n.tui.waitingFor }), _jsx(TextInput, { value: inputValue, onChange: setInputValue, onSubmit: handleInputSubmit, placeholder: "" }), _jsxs(Text, { color: theme.colors.textMuted, children: [" ", i18n.tui.inputHelp] })] })), mode === 'select-project' && taskToLink && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.selectProject || 'Select project for', ": ", taskToLink.title] }), _jsx(Box, { flexDirection: "column", marginTop: 1, borderStyle: theme.borders.list, borderColor: theme.colors.borderActive, paddingX: 1, children: tasks.projects.map((project, index) => (_jsxs(Text, { color: index === projectSelectIndex ? theme.colors.textSelected : theme.colors.text, bold: index === projectSelectIndex, children: [index === projectSelectIndex ? theme.style.selectedPrefix : theme.style.unselectedPrefix, project.title] }, project.id))) }), _jsx(Text, { color: theme.colors.textMuted, children: i18n.tui.selectProjectHelp || 'j/k: select, Enter: confirm, Esc: cancel' })] })), mode === 'context-filter' && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: theme.colors.secondary, bold: true, children: i18n.tui.context?.filter || 'Filter by context' }), _jsx(Box, { flexDirection: "column", marginTop: 1, borderStyle: theme.borders.list, borderColor: theme.colors.borderActive, paddingX: 1, children: ['all', 'none', ...availableContexts].map((ctx, index) => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import React, { useState, useEffect, useCallback } from 'react';
|
|
3
3
|
import { Box, Text, useInput, useApp, useStdout } from 'ink';
|
|
4
4
|
import TextInput from 'ink-text-input';
|
|
@@ -14,6 +14,7 @@ import { SearchResults } from './SearchResults.js';
|
|
|
14
14
|
import { HelpModal } from './HelpModal.js';
|
|
15
15
|
import { InsightsModal } from './InsightsModal.js';
|
|
16
16
|
import { CalendarModal } from './CalendarModal.js';
|
|
17
|
+
import { StatusBadge } from './StatusBadge.js';
|
|
17
18
|
import { Clock } from './Clock.js';
|
|
18
19
|
import { CalendarEvents } from './CalendarEvents.js';
|
|
19
20
|
import { PomodoroBattleUI, BattleMessage, getBattleMessage, BATTLE_COMMANDS } from './PomodoroBattleUI.js';
|
|
@@ -346,6 +347,7 @@ export function GtdDQ({ onOpenSettings }) {
|
|
|
346
347
|
toStatus: 'done',
|
|
347
348
|
fromWaitingFor: task.waitingFor,
|
|
348
349
|
toWaitingFor: null,
|
|
350
|
+
fromCompletedAt: task.completedAt,
|
|
349
351
|
description: fmt(i18n.tui.completed, { title: task.title }),
|
|
350
352
|
});
|
|
351
353
|
await history.execute(command);
|
|
@@ -359,6 +361,7 @@ export function GtdDQ({ onOpenSettings }) {
|
|
|
359
361
|
toStatus: status,
|
|
360
362
|
fromWaitingFor: task.waitingFor,
|
|
361
363
|
toWaitingFor: null,
|
|
364
|
+
fromCompletedAt: task.completedAt,
|
|
362
365
|
description: fmt(i18n.tui.movedTo, { title: task.title, status: i18n.status[status] }),
|
|
363
366
|
});
|
|
364
367
|
await history.execute(command);
|
|
@@ -372,6 +375,7 @@ export function GtdDQ({ onOpenSettings }) {
|
|
|
372
375
|
toStatus: 'waiting',
|
|
373
376
|
fromWaitingFor: task.waitingFor,
|
|
374
377
|
toWaitingFor: waitingFor.trim(),
|
|
378
|
+
fromCompletedAt: task.completedAt,
|
|
375
379
|
description: fmt(i18n.tui.movedToWaiting, { title: task.title, person: waitingFor.trim() }),
|
|
376
380
|
});
|
|
377
381
|
await history.execute(command);
|
|
@@ -645,10 +649,20 @@ export function GtdDQ({ onOpenSettings }) {
|
|
|
645
649
|
// Handle task-detail mode
|
|
646
650
|
if (mode === 'task-detail') {
|
|
647
651
|
if (key.escape || input === 'b' || input === 'h' || key.leftArrow) {
|
|
652
|
+
// If came from project-detail, go back to project-detail
|
|
653
|
+
if (selectedProject) {
|
|
654
|
+
setMode('project-detail');
|
|
655
|
+
const taskIndex = projectTasks.findIndex(t => t.id === selectedTask?.id);
|
|
656
|
+
if (taskIndex >= 0) {
|
|
657
|
+
setSelectedTaskIndex(taskIndex);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
else {
|
|
661
|
+
setMode('normal');
|
|
662
|
+
}
|
|
648
663
|
setSelectedTask(null);
|
|
649
664
|
setTaskComments([]);
|
|
650
665
|
setSelectedCommentIndex(0);
|
|
651
|
-
setMode('normal');
|
|
652
666
|
return;
|
|
653
667
|
}
|
|
654
668
|
// Navigate comments
|
|
@@ -868,6 +882,14 @@ export function GtdDQ({ onOpenSettings }) {
|
|
|
868
882
|
});
|
|
869
883
|
return;
|
|
870
884
|
}
|
|
885
|
+
// Enter to view task details within project
|
|
886
|
+
if (key.return && projectTasks.length > 0) {
|
|
887
|
+
const task = projectTasks[selectedTaskIndex];
|
|
888
|
+
setSelectedTask(task);
|
|
889
|
+
loadTaskComments(task.id);
|
|
890
|
+
setMode('task-detail');
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
871
893
|
return;
|
|
872
894
|
}
|
|
873
895
|
if (message)
|
|
@@ -1006,7 +1028,7 @@ export function GtdDQ({ onOpenSettings }) {
|
|
|
1006
1028
|
return;
|
|
1007
1029
|
}
|
|
1008
1030
|
// Move to inbox
|
|
1009
|
-
if (input === 'i' && currentTab !== 'inbox' && currentTab !== 'projects'
|
|
1031
|
+
if (input === 'i' && currentTab !== 'inbox' && currentTab !== 'projects') {
|
|
1010
1032
|
moveTaskToStatus(task, 'inbox').then(() => {
|
|
1011
1033
|
if (selectedTaskIndex >= currentTasks.length - 1) {
|
|
1012
1034
|
setSelectedTaskIndex(Math.max(0, selectedTaskIndex - 1));
|
|
@@ -1157,7 +1179,7 @@ export function GtdDQ({ onOpenSettings }) {
|
|
|
1157
1179
|
}) })] })) : mode === 'set-context' && currentTasks.length > 0 ? (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.context?.setContext || 'Set context for', ": ", currentTasks[selectedTaskIndex]?.title] }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: ['clear', ...availableContexts, 'new'].map((ctx, index) => {
|
|
1158
1180
|
const label = ctx === 'clear' ? (i18n.tui.context?.none || 'Clear context') : ctx === 'new' ? (i18n.tui.context?.addNew || 'New context...') : `@${ctx}`;
|
|
1159
1181
|
return (_jsxs(Text, { color: index === contextSelectIndex ? theme.colors.textSelected : theme.colors.text, bold: index === contextSelectIndex, children: [index === contextSelectIndex ? '▶ ' : ' ', label] }, ctx));
|
|
1160
|
-
}) })] })) : mode === 'set-effort' && getCurrentTask() ? (_jsx(Box, { flexDirection: "column", children: _jsx(TitledBoxInline, { title: i18n.tui.effort?.set || 'Set Effort', width: Math.min(40, terminalWidth - 4), minHeight: EFFORT_OPTIONS.length, isActive: true, children: EFFORT_OPTIONS.map((option, index) => (_jsxs(Text, { color: index === effortSelectIndex ? theme.colors.textSelected : theme.colors.text, bold: index === effortSelectIndex, children: [index === effortSelectIndex ? '▶ ' : ' ', option.label] }, option.label))) }) })) : mode === 'select-project' && taskToLink ? (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.selectProject || 'Select project', ": ", taskToLink.title] }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: tasks.projects.map((project, index) => (_jsxs(Text, { color: index === projectSelectIndex ? theme.colors.textSelected : theme.colors.text, bold: index === projectSelectIndex, children: [index === projectSelectIndex ? '▶ ' : ' ', project.title] }, project.id))) })] })) : mode === 'confirm-delete' && taskToDelete ? (_jsx(Box, { flexDirection: "column", children: _jsx(Text, { color: theme.colors.accent, bold: true, children: fmt(i18n.tui.deleteConfirm || 'Delete "{title}"? (y/n)', { title: taskToDelete.title }) }) })) : (mode === 'task-detail' || mode === 'add-comment') && selectedTask ? (_jsxs(Box, { flexDirection: "column", children: [_jsxs(TitledBoxInline, { title: i18n.tui.taskDetailTitle || 'Task Details', width: terminalWidth - 4, minHeight: 4, isActive: true, children: [_jsx(Text, { color: theme.colors.text, bold: true, children: selectedTask.title }), selectedTask.description && (_jsx(Text, { color: theme.colors.textMuted, children: selectedTask.description })), _jsxs(Box, { children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.taskDetailStatus || 'Status', ": "] }), _jsxs(Text, { color: theme.colors.accent, children: [i18n.status[selectedTask.status], selectedTask.waitingFor && ` (${selectedTask.waitingFor})`] })] }), _jsxs(Box, { children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.context?.label || 'Context', ": "] }), _jsx(Text, { color: theme.colors.accent, children: selectedTask.context ? `@${selectedTask.context}` : (i18n.tui.context?.none || 'No context') })] }), _jsxs(Box, { children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.effort?.label || 'Effort', ": "] }), _jsx(Text, { color: theme.colors.accent, children: selectedTask.effort ? (i18n.tui.effort?.[selectedTask.effort] || selectedTask.effort) : '-' })] }), _jsxs(Box, { children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.focus?.label || 'Focus', ": "] }), _jsx(Text, { color: theme.colors.accent, children: selectedTask.isFocused ? '★' : '-' })] })] }), _jsx(Box, { marginTop: 1, children: _jsx(TitledBoxInline, { title: `${i18n.tui.comments || 'Comments'} (${taskComments.length})`, width: terminalWidth - 4, minHeight: 5, isActive: mode === 'task-detail', children: taskComments.length === 0 ? (_jsx(Text, { color: theme.colors.textMuted, italic: true, children: i18n.tui.noComments || 'No comments yet' })) : (taskComments.map((comment, index) => {
|
|
1182
|
+
}) })] })) : mode === 'set-effort' && getCurrentTask() ? (_jsx(Box, { flexDirection: "column", children: _jsx(TitledBoxInline, { title: i18n.tui.effort?.set || 'Set Effort', width: Math.min(40, terminalWidth - 4), minHeight: EFFORT_OPTIONS.length, isActive: true, children: EFFORT_OPTIONS.map((option, index) => (_jsxs(Text, { color: index === effortSelectIndex ? theme.colors.textSelected : theme.colors.text, bold: index === effortSelectIndex, children: [index === effortSelectIndex ? '▶ ' : ' ', option.label] }, option.label))) }) })) : mode === 'select-project' && taskToLink ? (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.selectProject || 'Select project', ": ", taskToLink.title] }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: tasks.projects.map((project, index) => (_jsxs(Text, { color: index === projectSelectIndex ? theme.colors.textSelected : theme.colors.text, bold: index === projectSelectIndex, children: [index === projectSelectIndex ? '▶ ' : ' ', project.title] }, project.id))) })] })) : mode === 'confirm-delete' && taskToDelete ? (_jsx(Box, { flexDirection: "column", children: _jsx(Text, { color: theme.colors.accent, bold: true, children: fmt(i18n.tui.deleteConfirm || 'Delete "{title}"? (y/n)', { title: taskToDelete.title }) }) })) : (mode === 'task-detail' || mode === 'add-comment') && selectedTask ? (_jsxs(Box, { flexDirection: "column", children: [_jsxs(TitledBoxInline, { title: i18n.tui.taskDetailTitle || 'Task Details', width: terminalWidth - 4, minHeight: 4, isActive: true, children: [_jsx(Text, { color: theme.colors.text, bold: true, children: selectedTask.title }), selectedTask.description && (_jsx(Text, { color: theme.colors.textMuted, children: selectedTask.description })), _jsxs(Box, { children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.taskDetailStatus || 'Status', ": "] }), _jsxs(Text, { color: theme.colors.accent, children: [i18n.status[selectedTask.status], selectedTask.waitingFor && ` (${selectedTask.waitingFor})`] })] }), _jsxs(Box, { children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.context?.label || 'Context', ": "] }), _jsx(Text, { color: theme.colors.accent, children: selectedTask.context ? `@${selectedTask.context}` : (i18n.tui.context?.none || 'No context') })] }), _jsxs(Box, { children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.effort?.label || 'Effort', ": "] }), _jsx(Text, { color: theme.colors.accent, children: selectedTask.effort ? (i18n.tui.effort?.[selectedTask.effort] || selectedTask.effort) : '-' })] }), _jsxs(Box, { children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.focus?.label || 'Focus', ": "] }), _jsx(Text, { color: theme.colors.accent, children: selectedTask.isFocused ? '★' : '-' })] }), selectedTask.status === 'done' && (_jsxs(Box, { children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.taskDetailCompletedAt || 'Completed', ": "] }), _jsx(Text, { color: theme.colors.accent, children: (selectedTask.completedAt ?? selectedTask.updatedAt).toLocaleString() })] }))] }), _jsx(Box, { marginTop: 1, children: _jsx(TitledBoxInline, { title: `${i18n.tui.comments || 'Comments'} (${taskComments.length})`, width: terminalWidth - 4, minHeight: 5, isActive: mode === 'task-detail', children: taskComments.length === 0 ? (_jsx(Text, { color: theme.colors.textMuted, italic: true, children: i18n.tui.noComments || 'No comments yet' })) : (taskComments.map((comment, index) => {
|
|
1161
1183
|
const isSelected = index === selectedCommentIndex && mode === 'task-detail';
|
|
1162
1184
|
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Text, { color: theme.colors.textMuted, children: [isSelected ? '▶ ' : ' ', "[", comment.createdAt.toLocaleString(), "]"] }), _jsxs(Text, { color: isSelected ? theme.colors.textSelected : theme.colors.text, bold: isSelected, children: [' ', comment.content] })] }, comment.id));
|
|
1163
1185
|
})) }) })] })) : (_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { marginRight: 2, children: _jsx(TitledBoxInline, { title: mode === 'project-detail' && selectedProject ? selectedProject.title : 'GTD', width: leftPaneWidth, minHeight: 8, isActive: paneFocus === 'tabs' && mode !== 'project-detail', children: mode === 'project-detail' ? (_jsx(Text, { color: theme.colors.textMuted, children: "\u2190 Esc/b: back" })) : (TABS.map((tab, index) => {
|
|
@@ -1180,7 +1202,7 @@ export function GtdDQ({ onOpenSettings }) {
|
|
|
1180
1202
|
].join('');
|
|
1181
1203
|
const availableWidth = rightPaneWidth - 6 - getDisplayWidth(prefix) - getDisplayWidth(focusPrefix) - getDisplayWidth(effortBadge) - getDisplayWidth(suffix);
|
|
1182
1204
|
const displayTitle = truncateString(task.title, availableWidth);
|
|
1183
|
-
return (_jsxs(Text, { color: isSelected ? theme.colors.textSelected : theme.colors.text, bold: isSelected, children: [prefix, focusPrefix, effortBadge, displayTitle, task.waitingFor && _jsxs(Text, { color: theme.colors.muted, children: [" (", task.waitingFor, ")"] }), task.context && _jsxs(Text, { color: theme.colors.muted, children: [" @", task.context] }), parentProject && _jsxs(Text, { color: theme.colors.muted, children: [" [", parentProject.title, "]"] }), progress && _jsxs(Text, { color: theme.colors.muted, children: [" [", progress.completed, "/", progress.total, "]"] })] }, task.id));
|
|
1205
|
+
return (_jsxs(Text, { color: isSelected ? theme.colors.textSelected : theme.colors.text, bold: isSelected, children: [prefix, mode === 'project-detail' && _jsxs(_Fragment, { children: [_jsx(StatusBadge, { status: task.status }), " "] }), focusPrefix, effortBadge, displayTitle, task.waitingFor && _jsxs(Text, { color: theme.colors.muted, children: [" (", task.waitingFor, ")"] }), task.context && _jsxs(Text, { color: theme.colors.muted, children: [" @", task.context] }), parentProject && _jsxs(Text, { color: theme.colors.muted, children: [" [", parentProject.title, "]"] }), progress && _jsxs(Text, { color: theme.colors.muted, children: [" [", progress.completed, "/", progress.total, "]"] })] }, task.id));
|
|
1184
1206
|
})) }) })] })), (mode === 'add' || mode === 'add-to-project') && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: theme.colors.secondary, bold: true, children: mode === 'add-to-project' && selectedProject
|
|
1185
1207
|
? `${i18n.tui.newTask}[${selectedProject.title}] `
|
|
1186
1208
|
: i18n.tui.newTask }), _jsx(TextInput, { value: inputValue, onChange: setInputValue, onSubmit: handleInputSubmit, placeholder: i18n.tui.placeholder })] })), mode === 'move-to-waiting' && taskToWaiting && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: theme.colors.secondary, bold: true, children: i18n.tui.waitingFor }), _jsx(TextInput, { value: inputValue, onChange: setInputValue, onSubmit: handleInputSubmit, placeholder: "" })] })), mode === 'add-context' && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: theme.colors.secondary, bold: true, children: "New context: " }), _jsx(TextInput, { value: inputValue, onChange: setInputValue, onSubmit: handleInputSubmit, placeholder: "Enter context name..." })] })), mode === 'add-comment' && selectedTask && (_jsxs(Box, { marginTop: 1, children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.addComment || 'Add comment', ": "] }), _jsx(TextInput, { value: inputValue, onChange: setInputValue, onSubmit: handleInputSubmit, placeholder: "Enter comment..." })] })), mode === 'search' && (_jsx(SearchBar, { value: searchQuery, onChange: handleSearchChange, onSubmit: handleInputSubmit })), mode === 'search' && searchQuery && (_jsx(SearchResults, { results: searchResults, selectedIndex: searchResultIndex, query: searchQuery })), message && mode === 'normal' && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.colors.textHighlight, children: message }) })), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: theme.colors.accent, children: "\u2328\uFE0F " }), _jsx(Text, { color: theme.colors.textMuted, children: mode === 'task-detail'
|