floq 1.4.1 → 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 +4 -1
- package/dist/ui/components/GtdMario.js +4 -1
- 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
|
@@ -244,6 +244,7 @@ export function KanbanMario({ onOpenSettings }) {
|
|
|
244
244
|
toStatus: newStatus,
|
|
245
245
|
fromWaitingFor: task.waitingFor,
|
|
246
246
|
toWaitingFor: null,
|
|
247
|
+
fromCompletedAt: task.completedAt,
|
|
247
248
|
description: fmt(i18n.tui.movedTo, { title: task.title, status: i18n.status[newStatus] }),
|
|
248
249
|
});
|
|
249
250
|
await history.execute(command);
|
|
@@ -257,6 +258,7 @@ export function KanbanMario({ onOpenSettings }) {
|
|
|
257
258
|
toStatus: 'done',
|
|
258
259
|
fromWaitingFor: task.waitingFor,
|
|
259
260
|
toWaitingFor: null,
|
|
261
|
+
fromCompletedAt: task.completedAt,
|
|
260
262
|
description: fmt(i18n.tui.completed, { title: task.title }),
|
|
261
263
|
});
|
|
262
264
|
await history.execute(command);
|
|
@@ -9,6 +9,7 @@ interface TaskItemProps {
|
|
|
9
9
|
isSelected: boolean;
|
|
10
10
|
projectName?: string;
|
|
11
11
|
progress?: ProjectProgress;
|
|
12
|
+
showStatus?: boolean;
|
|
12
13
|
}
|
|
13
|
-
export declare function TaskItem({ task, isSelected, projectName, progress }: TaskItemProps): React.ReactElement;
|
|
14
|
+
export declare function TaskItem({ task, isSelected, projectName, progress, showStatus }: TaskItemProps): React.ReactElement;
|
|
14
15
|
export {};
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
3
|
import { t } from '../../i18n/index.js';
|
|
4
4
|
import { useTheme } from '../theme/index.js';
|
|
5
5
|
import { ProgressBar } from './ProgressBar.js';
|
|
6
|
+
import { StatusBadge } from './StatusBadge.js';
|
|
6
7
|
const EFFORT_LABELS = {
|
|
7
8
|
small: 'S',
|
|
8
9
|
medium: 'M',
|
|
9
10
|
large: 'L',
|
|
10
11
|
};
|
|
11
|
-
export function TaskItem({ task, isSelected, projectName, progress }) {
|
|
12
|
+
export function TaskItem({ task, isSelected, projectName, progress, showStatus }) {
|
|
12
13
|
const shortId = task.id.slice(0, 8);
|
|
13
14
|
const i18n = t();
|
|
14
15
|
const theme = useTheme();
|
|
15
|
-
return (_jsx(Box, { children: _jsxs(Text, { color: isSelected ? theme.colors.textSelected : theme.colors.text, bold: isSelected, children: [isSelected ? theme.style.selectedPrefix : theme.style.unselectedPrefix, task.isFocused && _jsx(Text, { color: theme.colors.accent, children: "\u2605 " }), "[", shortId, "] ", task.effort && _jsxs(Text, { color: theme.colors.secondary, children: ["[", EFFORT_LABELS[task.effort], "] "] }), task.title, task.context && (_jsxs(Text, { color: theme.colors.accent, children: [" @", task.context] })), projectName && (_jsxs(Text, { color: theme.colors.statusSomeday, children: [" [", projectName, "]"] })), progress && progress.total > 0 && (_jsx(ProgressBar, { completed: progress.completed, total: progress.total })), task.waitingFor && (_jsxs(Text, { color: theme.colors.statusWaiting, children: [" (", i18n.status.waiting.toLowerCase(), ": ", task.waitingFor, ")"] })), task.dueDate && (_jsxs(Text, { color: theme.colors.accent, children: [" (due: ", task.dueDate.toLocaleDateString(), ")"] }))] }) }));
|
|
16
|
+
return (_jsx(Box, { children: _jsxs(Text, { color: isSelected ? theme.colors.textSelected : theme.colors.text, bold: isSelected, children: [isSelected ? theme.style.selectedPrefix : theme.style.unselectedPrefix, task.isFocused && _jsx(Text, { color: theme.colors.accent, children: "\u2605 " }), "[", shortId, "] ", showStatus && _jsxs(_Fragment, { children: [_jsx(StatusBadge, { status: task.status }), _jsx(Text, { children: " " })] }), task.effort && _jsxs(Text, { color: theme.colors.secondary, children: ["[", EFFORT_LABELS[task.effort], "] "] }), task.title, task.context && (_jsxs(Text, { color: theme.colors.accent, children: [" @", task.context] })), projectName && (_jsxs(Text, { color: theme.colors.statusSomeday, children: [" [", projectName, "]"] })), progress && progress.total > 0 && (_jsx(ProgressBar, { completed: progress.completed, total: progress.total })), task.waitingFor && (_jsxs(Text, { color: theme.colors.statusWaiting, children: [" (", i18n.status.waiting.toLowerCase(), ": ", task.waitingFor, ")"] })), task.dueDate && (_jsxs(Text, { color: theme.colors.accent, children: [" (due: ", task.dueDate.toLocaleDateString(), ")"] }))] }) }));
|
|
16
17
|
}
|
|
@@ -5,6 +5,14 @@ const HistoryContext = createContext(null);
|
|
|
5
5
|
export function HistoryProvider({ children }) {
|
|
6
6
|
const manager = useMemo(() => getHistoryManager(), []);
|
|
7
7
|
const [state, setState] = useState(manager.getState());
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
// Load persisted history from DB on mount
|
|
10
|
+
manager.init().then(() => {
|
|
11
|
+
setState(manager.getState());
|
|
12
|
+
}).catch(() => {
|
|
13
|
+
// Ignore init errors - in-memory still works
|
|
14
|
+
});
|
|
15
|
+
}, [manager]);
|
|
8
16
|
useEffect(() => {
|
|
9
17
|
const unsubscribe = manager.subscribe(() => {
|
|
10
18
|
setState(manager.getState());
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import type { UndoableCommand, HistoryState } from './types.js';
|
|
2
2
|
/**
|
|
3
3
|
* Manages undo/redo history using the Command Pattern
|
|
4
|
+
* with DB persistence for crash-safe undo
|
|
4
5
|
*/
|
|
5
6
|
export declare class HistoryManager {
|
|
6
7
|
private undoStack;
|
|
7
8
|
private redoStack;
|
|
8
9
|
private listeners;
|
|
10
|
+
private initialized;
|
|
11
|
+
/**
|
|
12
|
+
* Initialize by loading history from DB
|
|
13
|
+
*/
|
|
14
|
+
init(): Promise<void>;
|
|
9
15
|
/**
|
|
10
16
|
* Execute a command and add it to the undo stack
|
|
11
17
|
*/
|
|
@@ -43,11 +49,13 @@ export declare class HistoryManager {
|
|
|
43
49
|
/**
|
|
44
50
|
* Clear all history
|
|
45
51
|
*/
|
|
46
|
-
clear(): void
|
|
52
|
+
clear(): Promise<void>;
|
|
47
53
|
/**
|
|
48
54
|
* Subscribe to history changes
|
|
49
55
|
*/
|
|
50
56
|
subscribe(listener: () => void): () => void;
|
|
57
|
+
private loadFromDb;
|
|
58
|
+
private clearRedoFromDb;
|
|
51
59
|
private notifyListeners;
|
|
52
60
|
}
|
|
53
61
|
/**
|
|
@@ -1,23 +1,70 @@
|
|
|
1
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
2
|
+
import { eq, desc, asc } from 'drizzle-orm';
|
|
3
|
+
import { getDb, schema } from '../../db/index.js';
|
|
4
|
+
import { deserializeCommand } from './commands/registry.js';
|
|
1
5
|
import { MAX_HISTORY_SIZE } from './types.js';
|
|
2
6
|
/**
|
|
3
7
|
* Manages undo/redo history using the Command Pattern
|
|
8
|
+
* with DB persistence for crash-safe undo
|
|
4
9
|
*/
|
|
5
10
|
export class HistoryManager {
|
|
6
11
|
undoStack = [];
|
|
7
12
|
redoStack = [];
|
|
8
13
|
listeners = new Set();
|
|
14
|
+
initialized = false;
|
|
15
|
+
/**
|
|
16
|
+
* Initialize by loading history from DB
|
|
17
|
+
*/
|
|
18
|
+
async init() {
|
|
19
|
+
if (this.initialized)
|
|
20
|
+
return;
|
|
21
|
+
try {
|
|
22
|
+
await this.loadFromDb();
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// DB may not have the table yet - that's OK, just use in-memory
|
|
26
|
+
}
|
|
27
|
+
this.initialized = true;
|
|
28
|
+
}
|
|
9
29
|
/**
|
|
10
30
|
* Execute a command and add it to the undo stack
|
|
11
31
|
*/
|
|
12
32
|
async execute(command) {
|
|
13
33
|
await command.execute();
|
|
34
|
+
const dbId = uuidv4();
|
|
35
|
+
// Persist to DB
|
|
36
|
+
try {
|
|
37
|
+
const serialized = command.toJSON();
|
|
38
|
+
const db = getDb();
|
|
39
|
+
await db.insert(schema.operationHistory).values({
|
|
40
|
+
id: dbId,
|
|
41
|
+
commandType: serialized.type,
|
|
42
|
+
commandData: JSON.stringify(serialized.data),
|
|
43
|
+
executedAt: new Date(),
|
|
44
|
+
isUndone: false,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// If DB write fails, in-memory undo still works
|
|
49
|
+
}
|
|
14
50
|
// Add to undo stack
|
|
15
|
-
this.undoStack.push(command);
|
|
51
|
+
this.undoStack.push({ dbId, command });
|
|
16
52
|
// Clear redo stack (new action invalidates redo history)
|
|
53
|
+
// Also clean up DB records for redo items
|
|
54
|
+
await this.clearRedoFromDb();
|
|
17
55
|
this.redoStack = [];
|
|
18
56
|
// Enforce max history size
|
|
19
57
|
if (this.undoStack.length > MAX_HISTORY_SIZE) {
|
|
20
|
-
this.undoStack.shift();
|
|
58
|
+
const removed = this.undoStack.shift();
|
|
59
|
+
if (removed) {
|
|
60
|
+
try {
|
|
61
|
+
const db = getDb();
|
|
62
|
+
await db.delete(schema.operationHistory).where(eq(schema.operationHistory.id, removed.dbId));
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// ignore
|
|
66
|
+
}
|
|
67
|
+
}
|
|
21
68
|
}
|
|
22
69
|
this.notifyListeners();
|
|
23
70
|
}
|
|
@@ -26,19 +73,29 @@ export class HistoryManager {
|
|
|
26
73
|
* @returns true if undo was performed, false if nothing to undo
|
|
27
74
|
*/
|
|
28
75
|
async undo() {
|
|
29
|
-
const
|
|
30
|
-
if (!
|
|
76
|
+
const tracked = this.undoStack.pop();
|
|
77
|
+
if (!tracked) {
|
|
31
78
|
return false;
|
|
32
79
|
}
|
|
33
80
|
try {
|
|
34
|
-
await command.undo();
|
|
35
|
-
|
|
81
|
+
await tracked.command.undo();
|
|
82
|
+
// Mark as undone in DB
|
|
83
|
+
try {
|
|
84
|
+
const db = getDb();
|
|
85
|
+
await db.update(schema.operationHistory)
|
|
86
|
+
.set({ isUndone: true })
|
|
87
|
+
.where(eq(schema.operationHistory.id, tracked.dbId));
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// ignore DB errors
|
|
91
|
+
}
|
|
92
|
+
this.redoStack.push(tracked);
|
|
36
93
|
this.notifyListeners();
|
|
37
94
|
return true;
|
|
38
95
|
}
|
|
39
96
|
catch (error) {
|
|
40
97
|
// Re-add command to undo stack if undo fails
|
|
41
|
-
this.undoStack.push(
|
|
98
|
+
this.undoStack.push(tracked);
|
|
42
99
|
throw error;
|
|
43
100
|
}
|
|
44
101
|
}
|
|
@@ -47,19 +104,29 @@ export class HistoryManager {
|
|
|
47
104
|
* @returns true if redo was performed, false if nothing to redo
|
|
48
105
|
*/
|
|
49
106
|
async redo() {
|
|
50
|
-
const
|
|
51
|
-
if (!
|
|
107
|
+
const tracked = this.redoStack.pop();
|
|
108
|
+
if (!tracked) {
|
|
52
109
|
return false;
|
|
53
110
|
}
|
|
54
111
|
try {
|
|
55
|
-
await command.execute();
|
|
56
|
-
|
|
112
|
+
await tracked.command.execute();
|
|
113
|
+
// Mark as not undone in DB
|
|
114
|
+
try {
|
|
115
|
+
const db = getDb();
|
|
116
|
+
await db.update(schema.operationHistory)
|
|
117
|
+
.set({ isUndone: false })
|
|
118
|
+
.where(eq(schema.operationHistory.id, tracked.dbId));
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
// ignore DB errors
|
|
122
|
+
}
|
|
123
|
+
this.undoStack.push(tracked);
|
|
57
124
|
this.notifyListeners();
|
|
58
125
|
return true;
|
|
59
126
|
}
|
|
60
127
|
catch (error) {
|
|
61
128
|
// Re-add command to redo stack if redo fails
|
|
62
|
-
this.redoStack.push(
|
|
129
|
+
this.redoStack.push(tracked);
|
|
63
130
|
throw error;
|
|
64
131
|
}
|
|
65
132
|
}
|
|
@@ -71,7 +138,7 @@ export class HistoryManager {
|
|
|
71
138
|
undoCount: this.undoStack.length,
|
|
72
139
|
redoCount: this.redoStack.length,
|
|
73
140
|
lastCommandDescription: this.undoStack.length > 0
|
|
74
|
-
? this.undoStack[this.undoStack.length - 1].description
|
|
141
|
+
? this.undoStack[this.undoStack.length - 1].command.description
|
|
75
142
|
: null,
|
|
76
143
|
};
|
|
77
144
|
}
|
|
@@ -92,7 +159,7 @@ export class HistoryManager {
|
|
|
92
159
|
*/
|
|
93
160
|
getUndoDescription() {
|
|
94
161
|
return this.undoStack.length > 0
|
|
95
|
-
? this.undoStack[this.undoStack.length - 1].description
|
|
162
|
+
? this.undoStack[this.undoStack.length - 1].command.description
|
|
96
163
|
: null;
|
|
97
164
|
}
|
|
98
165
|
/**
|
|
@@ -100,15 +167,22 @@ export class HistoryManager {
|
|
|
100
167
|
*/
|
|
101
168
|
getRedoDescription() {
|
|
102
169
|
return this.redoStack.length > 0
|
|
103
|
-
? this.redoStack[this.redoStack.length - 1].description
|
|
170
|
+
? this.redoStack[this.redoStack.length - 1].command.description
|
|
104
171
|
: null;
|
|
105
172
|
}
|
|
106
173
|
/**
|
|
107
174
|
* Clear all history
|
|
108
175
|
*/
|
|
109
|
-
clear() {
|
|
176
|
+
async clear() {
|
|
110
177
|
this.undoStack = [];
|
|
111
178
|
this.redoStack = [];
|
|
179
|
+
try {
|
|
180
|
+
const db = getDb();
|
|
181
|
+
await db.delete(schema.operationHistory);
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
// ignore DB errors
|
|
185
|
+
}
|
|
112
186
|
this.notifyListeners();
|
|
113
187
|
}
|
|
114
188
|
/**
|
|
@@ -118,6 +192,56 @@ export class HistoryManager {
|
|
|
118
192
|
this.listeners.add(listener);
|
|
119
193
|
return () => this.listeners.delete(listener);
|
|
120
194
|
}
|
|
195
|
+
async loadFromDb() {
|
|
196
|
+
const db = getDb();
|
|
197
|
+
// Load undo stack: non-undone operations, oldest first
|
|
198
|
+
const undoRows = await db
|
|
199
|
+
.select()
|
|
200
|
+
.from(schema.operationHistory)
|
|
201
|
+
.where(eq(schema.operationHistory.isUndone, false))
|
|
202
|
+
.orderBy(asc(schema.operationHistory.executedAt))
|
|
203
|
+
.limit(MAX_HISTORY_SIZE);
|
|
204
|
+
for (const row of undoRows) {
|
|
205
|
+
try {
|
|
206
|
+
const data = JSON.parse(row.commandData);
|
|
207
|
+
const command = deserializeCommand(row.commandType, data);
|
|
208
|
+
this.undoStack.push({ dbId: row.id, command });
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
// Skip commands that can't be deserialized
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
// Load redo stack: undone operations, most recent first (so most recent undo is on top)
|
|
215
|
+
const redoRows = await db
|
|
216
|
+
.select()
|
|
217
|
+
.from(schema.operationHistory)
|
|
218
|
+
.where(eq(schema.operationHistory.isUndone, true))
|
|
219
|
+
.orderBy(desc(schema.operationHistory.executedAt))
|
|
220
|
+
.limit(MAX_HISTORY_SIZE);
|
|
221
|
+
for (const row of redoRows) {
|
|
222
|
+
try {
|
|
223
|
+
const data = JSON.parse(row.commandData);
|
|
224
|
+
const command = deserializeCommand(row.commandType, data);
|
|
225
|
+
this.redoStack.push({ dbId: row.id, command });
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
// Skip commands that can't be deserialized
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
async clearRedoFromDb() {
|
|
233
|
+
if (this.redoStack.length === 0)
|
|
234
|
+
return;
|
|
235
|
+
try {
|
|
236
|
+
const db = getDb();
|
|
237
|
+
for (const tracked of this.redoStack) {
|
|
238
|
+
await db.delete(schema.operationHistory).where(eq(schema.operationHistory.id, tracked.dbId));
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
catch {
|
|
242
|
+
// ignore
|
|
243
|
+
}
|
|
244
|
+
}
|
|
121
245
|
notifyListeners() {
|
|
122
246
|
for (const listener of this.listeners) {
|
|
123
247
|
listener();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { UndoableCommand } from '../types.js';
|
|
1
|
+
import type { UndoableCommand, SerializedCommand } from '../types.js';
|
|
2
2
|
import type { TaskStatus } from '../../../db/schema.js';
|
|
3
3
|
interface ConvertToProjectParams {
|
|
4
4
|
taskId: string;
|
|
@@ -15,5 +15,9 @@ export declare class ConvertToProjectCommand implements UndoableCommand {
|
|
|
15
15
|
constructor(params: ConvertToProjectParams);
|
|
16
16
|
execute(): Promise<void>;
|
|
17
17
|
undo(): Promise<void>;
|
|
18
|
+
toJSON(): SerializedCommand;
|
|
19
|
+
static fromJSON(json: {
|
|
20
|
+
data: Record<string, unknown>;
|
|
21
|
+
}): ConvertToProjectCommand;
|
|
18
22
|
}
|
|
19
23
|
export {};
|
|
@@ -34,4 +34,17 @@ export class ConvertToProjectCommand {
|
|
|
34
34
|
})
|
|
35
35
|
.where(eq(schema.tasks.id, this.taskId));
|
|
36
36
|
}
|
|
37
|
+
toJSON() {
|
|
38
|
+
return {
|
|
39
|
+
type: 'convert_to_project',
|
|
40
|
+
data: {
|
|
41
|
+
taskId: this.taskId,
|
|
42
|
+
originalStatus: this.originalStatus,
|
|
43
|
+
description: this.description,
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
static fromJSON(json) {
|
|
48
|
+
return new ConvertToProjectCommand(json.data);
|
|
49
|
+
}
|
|
37
50
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { UndoableCommand } from '../types.js';
|
|
1
|
+
import type { UndoableCommand, SerializedCommand } from '../types.js';
|
|
2
2
|
import type { NewComment } from '../../../db/schema.js';
|
|
3
3
|
interface CreateCommentParams {
|
|
4
4
|
comment: NewComment;
|
|
@@ -14,5 +14,9 @@ export declare class CreateCommentCommand implements UndoableCommand {
|
|
|
14
14
|
constructor(params: CreateCommentParams);
|
|
15
15
|
execute(): Promise<void>;
|
|
16
16
|
undo(): Promise<void>;
|
|
17
|
+
toJSON(): SerializedCommand;
|
|
18
|
+
static fromJSON(json: {
|
|
19
|
+
data: Record<string, unknown>;
|
|
20
|
+
}): CreateCommentCommand;
|
|
17
21
|
}
|
|
18
22
|
export {};
|
|
@@ -20,4 +20,26 @@ export class CreateCommentCommand {
|
|
|
20
20
|
const db = getDb();
|
|
21
21
|
await db.delete(schema.comments).where(eq(schema.comments.id, this.createdCommentId));
|
|
22
22
|
}
|
|
23
|
+
toJSON() {
|
|
24
|
+
return {
|
|
25
|
+
type: 'create_comment',
|
|
26
|
+
data: {
|
|
27
|
+
comment: {
|
|
28
|
+
...this.comment,
|
|
29
|
+
createdAt: this.comment.createdAt instanceof Date ? this.comment.createdAt.toISOString() : this.comment.createdAt,
|
|
30
|
+
},
|
|
31
|
+
description: this.description,
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
static fromJSON(json) {
|
|
36
|
+
const commentData = json.data.comment;
|
|
37
|
+
return new CreateCommentCommand({
|
|
38
|
+
comment: {
|
|
39
|
+
...commentData,
|
|
40
|
+
createdAt: new Date(commentData.createdAt),
|
|
41
|
+
},
|
|
42
|
+
description: json.data.description,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
23
45
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { UndoableCommand } from '../types.js';
|
|
1
|
+
import type { UndoableCommand, SerializedCommand } from '../types.js';
|
|
2
2
|
import type { NewTask } from '../../../db/schema.js';
|
|
3
3
|
interface CreateTaskParams {
|
|
4
4
|
task: NewTask;
|
|
@@ -14,5 +14,9 @@ export declare class CreateTaskCommand implements UndoableCommand {
|
|
|
14
14
|
constructor(params: CreateTaskParams);
|
|
15
15
|
execute(): Promise<void>;
|
|
16
16
|
undo(): Promise<void>;
|
|
17
|
+
toJSON(): SerializedCommand;
|
|
18
|
+
static fromJSON(json: {
|
|
19
|
+
data: Record<string, unknown>;
|
|
20
|
+
}): CreateTaskCommand;
|
|
17
21
|
}
|
|
18
22
|
export {};
|
|
@@ -18,7 +18,32 @@ export class CreateTaskCommand {
|
|
|
18
18
|
}
|
|
19
19
|
async undo() {
|
|
20
20
|
const db = getDb();
|
|
21
|
-
// Delete the created task
|
|
22
21
|
await db.delete(schema.tasks).where(eq(schema.tasks.id, this.createdTaskId));
|
|
23
22
|
}
|
|
23
|
+
toJSON() {
|
|
24
|
+
return {
|
|
25
|
+
type: 'create_task',
|
|
26
|
+
data: {
|
|
27
|
+
task: {
|
|
28
|
+
...this.task,
|
|
29
|
+
dueDate: this.task.dueDate instanceof Date ? this.task.dueDate.toISOString() : this.task.dueDate ?? null,
|
|
30
|
+
createdAt: this.task.createdAt instanceof Date ? this.task.createdAt.toISOString() : this.task.createdAt,
|
|
31
|
+
updatedAt: this.task.updatedAt instanceof Date ? this.task.updatedAt.toISOString() : this.task.updatedAt,
|
|
32
|
+
},
|
|
33
|
+
description: this.description,
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
static fromJSON(json) {
|
|
38
|
+
const taskData = json.data.task;
|
|
39
|
+
return new CreateTaskCommand({
|
|
40
|
+
task: {
|
|
41
|
+
...taskData,
|
|
42
|
+
dueDate: taskData.dueDate ? new Date(taskData.dueDate) : null,
|
|
43
|
+
createdAt: new Date(taskData.createdAt),
|
|
44
|
+
updatedAt: new Date(taskData.updatedAt),
|
|
45
|
+
},
|
|
46
|
+
description: json.data.description,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
24
49
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { UndoableCommand } from '../types.js';
|
|
1
|
+
import type { UndoableCommand, SerializedCommand } from '../types.js';
|
|
2
2
|
import type { Comment } from '../../../db/schema.js';
|
|
3
3
|
interface DeleteCommentParams {
|
|
4
4
|
comment: Comment;
|
|
@@ -13,5 +13,9 @@ export declare class DeleteCommentCommand implements UndoableCommand {
|
|
|
13
13
|
constructor(params: DeleteCommentParams);
|
|
14
14
|
execute(): Promise<void>;
|
|
15
15
|
undo(): Promise<void>;
|
|
16
|
+
toJSON(): SerializedCommand;
|
|
17
|
+
static fromJSON(json: {
|
|
18
|
+
data: Record<string, unknown>;
|
|
19
|
+
}): DeleteCommentCommand;
|
|
16
20
|
}
|
|
17
21
|
export {};
|
|
@@ -23,4 +23,26 @@ export class DeleteCommentCommand {
|
|
|
23
23
|
createdAt: this.comment.createdAt,
|
|
24
24
|
});
|
|
25
25
|
}
|
|
26
|
+
toJSON() {
|
|
27
|
+
return {
|
|
28
|
+
type: 'delete_comment',
|
|
29
|
+
data: {
|
|
30
|
+
comment: {
|
|
31
|
+
...this.comment,
|
|
32
|
+
createdAt: this.comment.createdAt.toISOString(),
|
|
33
|
+
},
|
|
34
|
+
description: this.description,
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
static fromJSON(json) {
|
|
39
|
+
const commentData = json.data.comment;
|
|
40
|
+
return new DeleteCommentCommand({
|
|
41
|
+
comment: {
|
|
42
|
+
...commentData,
|
|
43
|
+
createdAt: new Date(commentData.createdAt),
|
|
44
|
+
},
|
|
45
|
+
description: json.data.description,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
26
48
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import type { UndoableCommand } from '../types.js';
|
|
2
|
-
import type { Task } from '../../../db/schema.js';
|
|
1
|
+
import type { UndoableCommand, SerializedCommand } from '../types.js';
|
|
2
|
+
import type { Task, Comment } from '../../../db/schema.js';
|
|
3
3
|
interface DeleteTaskParams {
|
|
4
4
|
task: Task;
|
|
5
5
|
description: string;
|
|
6
|
+
savedComments?: Comment[];
|
|
6
7
|
}
|
|
7
8
|
/**
|
|
8
9
|
* Command to delete a task (and its comments)
|
|
@@ -14,5 +15,9 @@ export declare class DeleteTaskCommand implements UndoableCommand {
|
|
|
14
15
|
constructor(params: DeleteTaskParams);
|
|
15
16
|
execute(): Promise<void>;
|
|
16
17
|
undo(): Promise<void>;
|
|
18
|
+
toJSON(): SerializedCommand;
|
|
19
|
+
static fromJSON(json: {
|
|
20
|
+
data: Record<string, unknown>;
|
|
21
|
+
}): DeleteTaskCommand;
|
|
17
22
|
}
|
|
18
23
|
export {};
|
|
@@ -10,6 +10,9 @@ export class DeleteTaskCommand {
|
|
|
10
10
|
constructor(params) {
|
|
11
11
|
this.task = params.task;
|
|
12
12
|
this.description = params.description;
|
|
13
|
+
if (params.savedComments) {
|
|
14
|
+
this.savedComments = params.savedComments;
|
|
15
|
+
}
|
|
13
16
|
}
|
|
14
17
|
async execute() {
|
|
15
18
|
const db = getDb();
|
|
@@ -35,6 +38,7 @@ export class DeleteTaskCommand {
|
|
|
35
38
|
parentId: this.task.parentId,
|
|
36
39
|
waitingFor: this.task.waitingFor,
|
|
37
40
|
dueDate: this.task.dueDate,
|
|
41
|
+
completedAt: this.task.completedAt,
|
|
38
42
|
createdAt: this.task.createdAt,
|
|
39
43
|
updatedAt: this.task.updatedAt,
|
|
40
44
|
});
|
|
@@ -48,4 +52,41 @@ export class DeleteTaskCommand {
|
|
|
48
52
|
});
|
|
49
53
|
}
|
|
50
54
|
}
|
|
55
|
+
toJSON() {
|
|
56
|
+
return {
|
|
57
|
+
type: 'delete_task',
|
|
58
|
+
data: {
|
|
59
|
+
task: {
|
|
60
|
+
...this.task,
|
|
61
|
+
dueDate: this.task.dueDate ? this.task.dueDate.toISOString() : null,
|
|
62
|
+
completedAt: this.task.completedAt ? this.task.completedAt.toISOString() : null,
|
|
63
|
+
createdAt: this.task.createdAt.toISOString(),
|
|
64
|
+
updatedAt: this.task.updatedAt.toISOString(),
|
|
65
|
+
},
|
|
66
|
+
savedComments: this.savedComments.map(c => ({
|
|
67
|
+
...c,
|
|
68
|
+
createdAt: c.createdAt.toISOString(),
|
|
69
|
+
})),
|
|
70
|
+
description: this.description,
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
static fromJSON(json) {
|
|
75
|
+
const taskData = json.data.task;
|
|
76
|
+
const commentsData = json.data.savedComments || [];
|
|
77
|
+
return new DeleteTaskCommand({
|
|
78
|
+
task: {
|
|
79
|
+
...taskData,
|
|
80
|
+
dueDate: taskData.dueDate ? new Date(taskData.dueDate) : null,
|
|
81
|
+
completedAt: taskData.completedAt ? new Date(taskData.completedAt) : null,
|
|
82
|
+
createdAt: new Date(taskData.createdAt),
|
|
83
|
+
updatedAt: new Date(taskData.updatedAt),
|
|
84
|
+
},
|
|
85
|
+
savedComments: commentsData.map(c => ({
|
|
86
|
+
...c,
|
|
87
|
+
createdAt: new Date(c.createdAt),
|
|
88
|
+
})),
|
|
89
|
+
description: json.data.description,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
51
92
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { UndoableCommand } from '../types.js';
|
|
1
|
+
import type { UndoableCommand, SerializedCommand } from '../types.js';
|
|
2
2
|
interface LinkTaskParams {
|
|
3
3
|
taskId: string;
|
|
4
4
|
fromParentId: string | null;
|
|
@@ -16,5 +16,9 @@ export declare class LinkTaskCommand implements UndoableCommand {
|
|
|
16
16
|
constructor(params: LinkTaskParams);
|
|
17
17
|
execute(): Promise<void>;
|
|
18
18
|
undo(): Promise<void>;
|
|
19
|
+
toJSON(): SerializedCommand;
|
|
20
|
+
static fromJSON(json: {
|
|
21
|
+
data: Record<string, unknown>;
|
|
22
|
+
}): LinkTaskCommand;
|
|
19
23
|
}
|
|
20
24
|
export {};
|
|
@@ -34,4 +34,18 @@ export class LinkTaskCommand {
|
|
|
34
34
|
})
|
|
35
35
|
.where(eq(schema.tasks.id, this.taskId));
|
|
36
36
|
}
|
|
37
|
+
toJSON() {
|
|
38
|
+
return {
|
|
39
|
+
type: 'link_task',
|
|
40
|
+
data: {
|
|
41
|
+
taskId: this.taskId,
|
|
42
|
+
fromParentId: this.fromParentId,
|
|
43
|
+
toParentId: this.toParentId,
|
|
44
|
+
description: this.description,
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
static fromJSON(json) {
|
|
49
|
+
return new LinkTaskCommand(json.data);
|
|
50
|
+
}
|
|
37
51
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { UndoableCommand } from '../types.js';
|
|
1
|
+
import type { UndoableCommand, SerializedCommand } from '../types.js';
|
|
2
2
|
import type { TaskStatus } from '../../../db/schema.js';
|
|
3
3
|
interface MoveTaskParams {
|
|
4
4
|
taskId: string;
|
|
@@ -6,6 +6,7 @@ interface MoveTaskParams {
|
|
|
6
6
|
toStatus: TaskStatus;
|
|
7
7
|
fromWaitingFor: string | null;
|
|
8
8
|
toWaitingFor: string | null;
|
|
9
|
+
fromCompletedAt: Date | null;
|
|
9
10
|
description: string;
|
|
10
11
|
}
|
|
11
12
|
/**
|
|
@@ -18,8 +19,13 @@ export declare class MoveTaskCommand implements UndoableCommand {
|
|
|
18
19
|
private readonly toStatus;
|
|
19
20
|
private readonly fromWaitingFor;
|
|
20
21
|
private readonly toWaitingFor;
|
|
22
|
+
private readonly fromCompletedAt;
|
|
21
23
|
constructor(params: MoveTaskParams);
|
|
22
24
|
execute(): Promise<void>;
|
|
23
25
|
undo(): Promise<void>;
|
|
26
|
+
toJSON(): SerializedCommand;
|
|
27
|
+
static fromJSON(json: {
|
|
28
|
+
data: Record<string, unknown>;
|
|
29
|
+
}): MoveTaskCommand;
|
|
24
30
|
}
|
|
25
31
|
export {};
|