floq 0.2.2 → 0.3.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/README.ja.md +13 -0
- package/README.md +13 -0
- package/dist/i18n/en.d.ts +16 -0
- package/dist/i18n/en.js +10 -1
- package/dist/i18n/ja.js +10 -1
- package/dist/ui/App.js +132 -64
- package/dist/ui/SplashScreen.js +1 -1
- package/dist/ui/components/HelpModal.js +3 -2
- package/dist/ui/components/KanbanBoard.js +128 -46
- package/dist/ui/components/SearchResults.js +2 -2
- package/dist/ui/history/HistoryContext.d.ts +29 -0
- package/dist/ui/history/HistoryContext.js +44 -0
- package/dist/ui/history/HistoryManager.d.ts +56 -0
- package/dist/ui/history/HistoryManager.js +137 -0
- package/dist/ui/history/commands/ConvertToProjectCommand.d.ts +19 -0
- package/dist/ui/history/commands/ConvertToProjectCommand.js +37 -0
- package/dist/ui/history/commands/CreateCommentCommand.d.ts +18 -0
- package/dist/ui/history/commands/CreateCommentCommand.js +23 -0
- package/dist/ui/history/commands/CreateTaskCommand.d.ts +18 -0
- package/dist/ui/history/commands/CreateTaskCommand.js +24 -0
- package/dist/ui/history/commands/DeleteCommentCommand.d.ts +17 -0
- package/dist/ui/history/commands/DeleteCommentCommand.js +26 -0
- package/dist/ui/history/commands/DeleteTaskCommand.d.ts +18 -0
- package/dist/ui/history/commands/DeleteTaskCommand.js +51 -0
- package/dist/ui/history/commands/LinkTaskCommand.d.ts +20 -0
- package/dist/ui/history/commands/LinkTaskCommand.js +37 -0
- package/dist/ui/history/commands/MoveTaskCommand.d.ts +25 -0
- package/dist/ui/history/commands/MoveTaskCommand.js +43 -0
- package/dist/ui/history/commands/index.d.ts +7 -0
- package/dist/ui/history/commands/index.js +7 -0
- package/dist/ui/history/index.d.ts +6 -0
- package/dist/ui/history/index.js +8 -0
- package/dist/ui/history/types.d.ts +26 -0
- package/dist/ui/history/types.js +4 -0
- package/dist/ui/history/useHistory.d.ts +24 -0
- package/dist/ui/history/useHistory.js +31 -0
- package/package.json +1 -1
package/README.ja.md
CHANGED
|
@@ -61,9 +61,20 @@ floq
|
|
|
61
61
|
| `Esc/b` | 戻る |
|
|
62
62
|
| `/` | タスク検索 |
|
|
63
63
|
| `r` | 更新 |
|
|
64
|
+
| `u` | 元に戻す(Undo) |
|
|
65
|
+
| `Ctrl+r` | やり直し(Redo) |
|
|
64
66
|
| `?` | ヘルプ |
|
|
65
67
|
| `q` | 終了 |
|
|
66
68
|
|
|
69
|
+
#### 検索
|
|
70
|
+
|
|
71
|
+
| キー | アクション |
|
|
72
|
+
|------|-----------|
|
|
73
|
+
| `/` | 検索モード開始 |
|
|
74
|
+
| `↑/↓` または `Ctrl+j/k` | 検索結果をナビゲート |
|
|
75
|
+
| `Enter` | 選択したタスクのタブに移動して選択 |
|
|
76
|
+
| `Esc` | キャンセル |
|
|
77
|
+
|
|
67
78
|
#### プロジェクト詳細画面
|
|
68
79
|
|
|
69
80
|
| キー | アクション |
|
|
@@ -98,6 +109,8 @@ floq
|
|
|
98
109
|
| `Enter` | タスク詳細を開く |
|
|
99
110
|
| `/` | タスク検索 |
|
|
100
111
|
| `r` | 更新 |
|
|
112
|
+
| `u` | 元に戻す(Undo) |
|
|
113
|
+
| `Ctrl+r` | やり直し(Redo) |
|
|
101
114
|
| `?` | ヘルプ |
|
|
102
115
|
| `q` | 終了 |
|
|
103
116
|
|
package/README.md
CHANGED
|
@@ -61,9 +61,20 @@ floq
|
|
|
61
61
|
| `Esc/b` | Back |
|
|
62
62
|
| `/` | Search tasks |
|
|
63
63
|
| `r` | Refresh |
|
|
64
|
+
| `u` | Undo |
|
|
65
|
+
| `Ctrl+r` | Redo |
|
|
64
66
|
| `?` | Help |
|
|
65
67
|
| `q` | Quit |
|
|
66
68
|
|
|
69
|
+
#### Search
|
|
70
|
+
|
|
71
|
+
| Key | Action |
|
|
72
|
+
|-----|--------|
|
|
73
|
+
| `/` | Start search mode |
|
|
74
|
+
| `↑/↓` or `Ctrl+j/k` | Navigate search results |
|
|
75
|
+
| `Enter` | Jump to selected task's tab and select it |
|
|
76
|
+
| `Esc` | Cancel search |
|
|
77
|
+
|
|
67
78
|
#### Project Detail View
|
|
68
79
|
|
|
69
80
|
| Key | Action |
|
|
@@ -98,6 +109,8 @@ floq
|
|
|
98
109
|
| `Enter` | Open task detail |
|
|
99
110
|
| `/` | Search tasks |
|
|
100
111
|
| `r` | Refresh |
|
|
112
|
+
| `u` | Undo |
|
|
113
|
+
| `Ctrl+r` | Redo |
|
|
101
114
|
| `?` | Help |
|
|
102
115
|
| `q` | Quit |
|
|
103
116
|
|
package/dist/i18n/en.d.ts
CHANGED
|
@@ -136,6 +136,8 @@ export declare const en: {
|
|
|
136
136
|
other: string;
|
|
137
137
|
showHelp: string;
|
|
138
138
|
quit: string;
|
|
139
|
+
undo: string;
|
|
140
|
+
redo: string;
|
|
139
141
|
closeHint: string;
|
|
140
142
|
};
|
|
141
143
|
whatsNew: {
|
|
@@ -181,6 +183,12 @@ export declare const en: {
|
|
|
181
183
|
deleteConfirm: string;
|
|
182
184
|
deleted: string;
|
|
183
185
|
deleteCancelled: string;
|
|
186
|
+
undone: string;
|
|
187
|
+
redone: string;
|
|
188
|
+
nothingToUndo: string;
|
|
189
|
+
nothingToRedo: string;
|
|
190
|
+
undoFailed: string;
|
|
191
|
+
redoFailed: string;
|
|
184
192
|
comments: string;
|
|
185
193
|
projectTasks: string;
|
|
186
194
|
search: {
|
|
@@ -289,6 +297,8 @@ export type HelpTranslations = {
|
|
|
289
297
|
other: string;
|
|
290
298
|
showHelp: string;
|
|
291
299
|
quit: string;
|
|
300
|
+
undo: string;
|
|
301
|
+
redo: string;
|
|
292
302
|
closeHint: string;
|
|
293
303
|
};
|
|
294
304
|
export type WhatsNewTranslations = {
|
|
@@ -407,6 +417,12 @@ export type TuiTranslations = {
|
|
|
407
417
|
deleteConfirm: string;
|
|
408
418
|
deleted: string;
|
|
409
419
|
deleteCancelled: string;
|
|
420
|
+
undone: string;
|
|
421
|
+
redone: string;
|
|
422
|
+
nothingToUndo: string;
|
|
423
|
+
nothingToRedo: string;
|
|
424
|
+
undoFailed: string;
|
|
425
|
+
redoFailed: string;
|
|
410
426
|
comments: string;
|
|
411
427
|
projectTasks: string;
|
|
412
428
|
};
|
package/dist/i18n/en.js
CHANGED
|
@@ -145,6 +145,8 @@ export const en = {
|
|
|
145
145
|
other: 'Other',
|
|
146
146
|
showHelp: 'Show this help',
|
|
147
147
|
quit: 'Quit',
|
|
148
|
+
undo: 'Undo',
|
|
149
|
+
redo: 'Redo',
|
|
148
150
|
closeHint: 'Esc/q: close',
|
|
149
151
|
},
|
|
150
152
|
// What's New / Changelog
|
|
@@ -192,13 +194,20 @@ export const en = {
|
|
|
192
194
|
deleteConfirm: 'Delete "{title}"? (y/n)',
|
|
193
195
|
deleted: 'Deleted: "{title}"',
|
|
194
196
|
deleteCancelled: 'Delete cancelled',
|
|
197
|
+
// Undo/Redo
|
|
198
|
+
undone: 'Undone: {action}',
|
|
199
|
+
redone: 'Redone: {action}',
|
|
200
|
+
nothingToUndo: 'Nothing to undo',
|
|
201
|
+
nothingToRedo: 'Nothing to redo',
|
|
202
|
+
undoFailed: 'Undo failed',
|
|
203
|
+
redoFailed: 'Redo failed',
|
|
195
204
|
comments: 'Comments',
|
|
196
205
|
projectTasks: 'Tasks',
|
|
197
206
|
// Search
|
|
198
207
|
search: {
|
|
199
208
|
prefix: '/',
|
|
200
209
|
placeholder: 'Search tasks...',
|
|
201
|
-
help: '(Enter to select, Ctrl+j/k to navigate, Esc to cancel)',
|
|
210
|
+
help: '(Enter to select, ↑/↓ or Ctrl+j/k to navigate, Esc to cancel)',
|
|
202
211
|
noResults: 'No matching tasks',
|
|
203
212
|
resultsTitle: 'Search Results',
|
|
204
213
|
searchTasks: 'Search tasks',
|
package/dist/i18n/ja.js
CHANGED
|
@@ -145,6 +145,8 @@ export const ja = {
|
|
|
145
145
|
other: 'その他',
|
|
146
146
|
showHelp: 'このヘルプを表示',
|
|
147
147
|
quit: '終了',
|
|
148
|
+
undo: '元に戻す',
|
|
149
|
+
redo: 'やり直し',
|
|
148
150
|
closeHint: 'Esc/q: 閉じる',
|
|
149
151
|
},
|
|
150
152
|
// What's New / Changelog
|
|
@@ -192,13 +194,20 @@ export const ja = {
|
|
|
192
194
|
deleteConfirm: '「{title}」を削除しますか? (y/n)',
|
|
193
195
|
deleted: '削除しました: 「{title}」',
|
|
194
196
|
deleteCancelled: '削除をキャンセルしました',
|
|
197
|
+
// Undo/Redo
|
|
198
|
+
undone: '元に戻しました: {action}',
|
|
199
|
+
redone: 'やり直しました: {action}',
|
|
200
|
+
nothingToUndo: '元に戻す操作がありません',
|
|
201
|
+
nothingToRedo: 'やり直す操作がありません',
|
|
202
|
+
undoFailed: '元に戻す操作に失敗しました',
|
|
203
|
+
redoFailed: 'やり直しに失敗しました',
|
|
195
204
|
comments: 'コメント',
|
|
196
205
|
projectTasks: 'タスク一覧',
|
|
197
206
|
// Search
|
|
198
207
|
search: {
|
|
199
208
|
prefix: '/',
|
|
200
209
|
placeholder: 'タスクを検索...',
|
|
201
|
-
help: '(Enterで選択, Ctrl+j/kで移動, Escでキャンセル)',
|
|
210
|
+
help: '(Enterで選択, ↑/↓またはCtrl+j/kで移動, Escでキャンセル)',
|
|
202
211
|
noResults: '該当するタスクがありません',
|
|
203
212
|
resultsTitle: '検索結果',
|
|
204
213
|
searchTasks: 'タスク検索',
|
package/dist/ui/App.js
CHANGED
|
@@ -19,6 +19,7 @@ import { ThemeProvider, useTheme } from './theme/index.js';
|
|
|
19
19
|
import { getThemeName, getViewMode, setThemeName, setViewMode, setLocale, isTursoEnabled } from '../config.js';
|
|
20
20
|
import { KanbanBoard } from './components/KanbanBoard.js';
|
|
21
21
|
import { VERSION } from '../version.js';
|
|
22
|
+
import { HistoryProvider, useHistory, CreateTaskCommand, DeleteTaskCommand, MoveTaskCommand, LinkTaskCommand, ConvertToProjectCommand, CreateCommentCommand, DeleteCommentCommand, } from './history/index.js';
|
|
22
23
|
const TABS = ['inbox', 'next', 'waiting', 'someday', 'projects', 'done'];
|
|
23
24
|
export function App() {
|
|
24
25
|
const [themeName, setThemeNameState] = useState(getThemeName);
|
|
@@ -53,11 +54,12 @@ export function App() {
|
|
|
53
54
|
if (settingsMode === 'lang-select') {
|
|
54
55
|
return (_jsx(ThemeProvider, { themeName: themeName, children: _jsx(LanguageSelector, { onSelect: handleLocaleSelect, onCancel: handleSettingsCancel }) }));
|
|
55
56
|
}
|
|
56
|
-
return (_jsx(ThemeProvider, { themeName: themeName, children: viewMode === 'kanban' ? (_jsx(KanbanBoard, { onOpenSettings: setSettingsMode })) : (_jsx(AppContent, { onOpenSettings: setSettingsMode })) }));
|
|
57
|
+
return (_jsx(ThemeProvider, { themeName: themeName, children: _jsx(HistoryProvider, { children: viewMode === 'kanban' ? (_jsx(KanbanBoard, { onOpenSettings: setSettingsMode })) : (_jsx(AppContent, { onOpenSettings: setSettingsMode })) }) }));
|
|
57
58
|
}
|
|
58
59
|
function AppContent({ onOpenSettings }) {
|
|
59
60
|
const theme = useTheme();
|
|
60
61
|
const { exit } = useApp();
|
|
62
|
+
const history = useHistory();
|
|
61
63
|
const [mode, setMode] = useState('splash');
|
|
62
64
|
const [inputValue, setInputValue] = useState('');
|
|
63
65
|
const [currentListIndex, setCurrentListIndex] = useState(0);
|
|
@@ -174,42 +176,64 @@ function AppContent({ onOpenSettings }) {
|
|
|
174
176
|
setSearchResults(results);
|
|
175
177
|
setSearchResultIndex(0);
|
|
176
178
|
}, [searchTasks]);
|
|
179
|
+
// Navigate to a task from search results
|
|
180
|
+
const navigateToTask = useCallback((task) => {
|
|
181
|
+
const targetTab = task.isProject ? 'projects' : task.status;
|
|
182
|
+
const tabIndex = TABS.indexOf(targetTab);
|
|
183
|
+
const tabTasks = tasks[targetTab];
|
|
184
|
+
const taskIndex = tabTasks.findIndex(t => t.id === task.id);
|
|
185
|
+
if (tabIndex >= 0 && taskIndex >= 0) {
|
|
186
|
+
setCurrentListIndex(tabIndex);
|
|
187
|
+
setSelectedTaskIndex(taskIndex);
|
|
188
|
+
setMode('normal');
|
|
189
|
+
}
|
|
190
|
+
}, [tasks]);
|
|
177
191
|
const addTask = useCallback(async (title, parentId) => {
|
|
178
192
|
if (!title.trim())
|
|
179
193
|
return;
|
|
180
|
-
const db = getDb();
|
|
181
194
|
const now = new Date();
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
195
|
+
const taskId = uuidv4();
|
|
196
|
+
const command = new CreateTaskCommand({
|
|
197
|
+
task: {
|
|
198
|
+
id: taskId,
|
|
199
|
+
title: title.trim(),
|
|
200
|
+
status: parentId ? 'next' : 'inbox',
|
|
201
|
+
parentId: parentId || null,
|
|
202
|
+
createdAt: now,
|
|
203
|
+
updatedAt: now,
|
|
204
|
+
},
|
|
205
|
+
description: fmt(i18n.tui.added, { title: title.trim() }),
|
|
190
206
|
});
|
|
207
|
+
await history.execute(command);
|
|
191
208
|
setMessage(fmt(i18n.tui.added, { title: title.trim() }));
|
|
192
209
|
await loadTasks();
|
|
193
|
-
}, [i18n.tui.added, loadTasks]);
|
|
210
|
+
}, [i18n.tui.added, loadTasks, history]);
|
|
194
211
|
const addCommentToTask = useCallback(async (task, content) => {
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
212
|
+
const commentId = uuidv4();
|
|
213
|
+
const command = new CreateCommentCommand({
|
|
214
|
+
comment: {
|
|
215
|
+
id: commentId,
|
|
216
|
+
taskId: task.id,
|
|
217
|
+
content: content.trim(),
|
|
218
|
+
createdAt: new Date(),
|
|
219
|
+
},
|
|
220
|
+
description: i18n.tui.commentAdded || 'Comment added',
|
|
201
221
|
});
|
|
222
|
+
await history.execute(command);
|
|
202
223
|
setMessage(i18n.tui.commentAdded || 'Comment added');
|
|
203
224
|
await loadTaskComments(task.id);
|
|
204
|
-
}, [i18n.tui.commentAdded, loadTaskComments]);
|
|
225
|
+
}, [i18n.tui.commentAdded, loadTaskComments, history]);
|
|
205
226
|
const deleteComment = useCallback(async (comment) => {
|
|
206
|
-
const
|
|
207
|
-
|
|
227
|
+
const command = new DeleteCommentCommand({
|
|
228
|
+
comment,
|
|
229
|
+
description: i18n.tui.commentDeleted || 'Comment deleted',
|
|
230
|
+
});
|
|
231
|
+
await history.execute(command);
|
|
208
232
|
setMessage(i18n.tui.commentDeleted || 'Comment deleted');
|
|
209
233
|
if (selectedTask) {
|
|
210
234
|
await loadTaskComments(selectedTask.id);
|
|
211
235
|
}
|
|
212
|
-
}, [i18n.tui.commentDeleted, loadTaskComments, selectedTask]);
|
|
236
|
+
}, [i18n.tui.commentDeleted, loadTaskComments, selectedTask, history]);
|
|
213
237
|
const handleInputSubmit = async (value) => {
|
|
214
238
|
if (mode === 'move-to-waiting' && taskToWaiting) {
|
|
215
239
|
if (value.trim()) {
|
|
@@ -224,9 +248,7 @@ function AppContent({ onOpenSettings }) {
|
|
|
224
248
|
if (mode === 'search') {
|
|
225
249
|
if (searchResults.length > 0) {
|
|
226
250
|
const task = searchResults[searchResultIndex];
|
|
227
|
-
|
|
228
|
-
loadTaskComments(task.id);
|
|
229
|
-
setMode('task-detail');
|
|
251
|
+
navigateToTask(task);
|
|
230
252
|
}
|
|
231
253
|
else {
|
|
232
254
|
setMode('normal');
|
|
@@ -262,54 +284,74 @@ function AppContent({ onOpenSettings }) {
|
|
|
262
284
|
setInputValue('');
|
|
263
285
|
};
|
|
264
286
|
const linkTaskToProject = useCallback(async (task, project) => {
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
.
|
|
287
|
+
const command = new LinkTaskCommand({
|
|
288
|
+
taskId: task.id,
|
|
289
|
+
fromParentId: task.parentId,
|
|
290
|
+
toParentId: project.id,
|
|
291
|
+
description: fmt(i18n.tui.linkedToProject || 'Linked "{title}" to {project}', { title: task.title, project: project.title }),
|
|
292
|
+
});
|
|
293
|
+
await history.execute(command);
|
|
269
294
|
setMessage(fmt(i18n.tui.linkedToProject || 'Linked "{title}" to {project}', { title: task.title, project: project.title }));
|
|
270
295
|
await loadTasks();
|
|
271
|
-
}, [i18n.tui.linkedToProject, loadTasks]);
|
|
296
|
+
}, [i18n.tui.linkedToProject, loadTasks, history]);
|
|
272
297
|
const markTaskDone = useCallback(async (task) => {
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
.
|
|
276
|
-
|
|
298
|
+
const command = new MoveTaskCommand({
|
|
299
|
+
taskId: task.id,
|
|
300
|
+
fromStatus: task.status,
|
|
301
|
+
toStatus: 'done',
|
|
302
|
+
fromWaitingFor: task.waitingFor,
|
|
303
|
+
toWaitingFor: null,
|
|
304
|
+
description: fmt(i18n.tui.completed, { title: task.title }),
|
|
305
|
+
});
|
|
306
|
+
await history.execute(command);
|
|
277
307
|
setMessage(fmt(i18n.tui.completed, { title: task.title }));
|
|
278
308
|
await loadTasks();
|
|
279
|
-
}, [i18n.tui.completed, loadTasks]);
|
|
309
|
+
}, [i18n.tui.completed, loadTasks, history]);
|
|
280
310
|
const moveTaskToStatus = useCallback(async (task, status) => {
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
.
|
|
284
|
-
|
|
311
|
+
const command = new MoveTaskCommand({
|
|
312
|
+
taskId: task.id,
|
|
313
|
+
fromStatus: task.status,
|
|
314
|
+
toStatus: status,
|
|
315
|
+
fromWaitingFor: task.waitingFor,
|
|
316
|
+
toWaitingFor: null,
|
|
317
|
+
description: fmt(i18n.tui.movedTo, { title: task.title, status: i18n.status[status] }),
|
|
318
|
+
});
|
|
319
|
+
await history.execute(command);
|
|
285
320
|
setMessage(fmt(i18n.tui.movedTo, { title: task.title, status: i18n.status[status] }));
|
|
286
321
|
await loadTasks();
|
|
287
|
-
}, [i18n.tui.movedTo, i18n.status, loadTasks]);
|
|
322
|
+
}, [i18n.tui.movedTo, i18n.status, loadTasks, history]);
|
|
288
323
|
const moveTaskToWaiting = useCallback(async (task, waitingFor) => {
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
324
|
+
const command = new MoveTaskCommand({
|
|
325
|
+
taskId: task.id,
|
|
326
|
+
fromStatus: task.status,
|
|
327
|
+
toStatus: 'waiting',
|
|
328
|
+
fromWaitingFor: task.waitingFor,
|
|
329
|
+
toWaitingFor: waitingFor.trim(),
|
|
330
|
+
description: fmt(i18n.tui.movedToWaiting, { title: task.title, person: waitingFor.trim() }),
|
|
331
|
+
});
|
|
332
|
+
await history.execute(command);
|
|
293
333
|
setMessage(fmt(i18n.tui.movedToWaiting, { title: task.title, person: waitingFor.trim() }));
|
|
294
334
|
await loadTasks();
|
|
295
|
-
}, [i18n.tui.movedToWaiting, loadTasks]);
|
|
335
|
+
}, [i18n.tui.movedToWaiting, loadTasks, history]);
|
|
296
336
|
const makeTaskProject = useCallback(async (task) => {
|
|
297
|
-
const
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
337
|
+
const command = new ConvertToProjectCommand({
|
|
338
|
+
taskId: task.id,
|
|
339
|
+
originalStatus: task.status,
|
|
340
|
+
description: fmt(i18n.tui.madeProject || 'Made project: {title}', { title: task.title }),
|
|
341
|
+
});
|
|
342
|
+
await history.execute(command);
|
|
301
343
|
setMessage(fmt(i18n.tui.madeProject || 'Made project: {title}', { title: task.title }));
|
|
302
344
|
await loadTasks();
|
|
303
|
-
}, [i18n.tui.madeProject, loadTasks]);
|
|
345
|
+
}, [i18n.tui.madeProject, loadTasks, history]);
|
|
304
346
|
const deleteTask = useCallback(async (task) => {
|
|
305
|
-
const
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
await
|
|
347
|
+
const command = new DeleteTaskCommand({
|
|
348
|
+
task,
|
|
349
|
+
description: fmt(i18n.tui.deleted || 'Deleted: "{title}"', { title: task.title }),
|
|
350
|
+
});
|
|
351
|
+
await history.execute(command);
|
|
310
352
|
setMessage(fmt(i18n.tui.deleted || 'Deleted: "{title}"', { title: task.title }));
|
|
311
353
|
await loadTasks();
|
|
312
|
-
}, [i18n.tui.deleted, loadTasks]);
|
|
354
|
+
}, [i18n.tui.deleted, loadTasks, history]);
|
|
313
355
|
const getTabLabel = (tab) => {
|
|
314
356
|
switch (tab) {
|
|
315
357
|
case 'inbox':
|
|
@@ -332,10 +374,6 @@ function AppContent({ onOpenSettings }) {
|
|
|
332
374
|
setMode('normal');
|
|
333
375
|
return;
|
|
334
376
|
}
|
|
335
|
-
// Handle help mode - let HelpModal handle input
|
|
336
|
-
if (mode === 'help') {
|
|
337
|
-
return;
|
|
338
|
-
}
|
|
339
377
|
// Handle search mode
|
|
340
378
|
if (mode === 'search') {
|
|
341
379
|
if (key.escape) {
|
|
@@ -345,12 +383,12 @@ function AppContent({ onOpenSettings }) {
|
|
|
345
383
|
setMode('normal');
|
|
346
384
|
return;
|
|
347
385
|
}
|
|
348
|
-
// Navigate search results with Ctrl+j/k or Ctrl+n/p
|
|
349
|
-
if (key.ctrl && (input === 'j' || input === 'n')) {
|
|
386
|
+
// Navigate search results with arrow keys, Ctrl+j/k, or Ctrl+n/p
|
|
387
|
+
if (key.downArrow || (key.ctrl && (input === 'j' || input === 'n'))) {
|
|
350
388
|
setSearchResultIndex((prev) => prev < searchResults.length - 1 ? prev + 1 : 0);
|
|
351
389
|
return;
|
|
352
390
|
}
|
|
353
|
-
if (key.ctrl && (input === 'k' || input === 'p')) {
|
|
391
|
+
if (key.upArrow || (key.ctrl && (input === 'k' || input === 'p'))) {
|
|
354
392
|
setSearchResultIndex((prev) => prev > 0 ? prev - 1 : Math.max(0, searchResults.length - 1));
|
|
355
393
|
return;
|
|
356
394
|
}
|
|
@@ -750,12 +788,42 @@ function AppContent({ onOpenSettings }) {
|
|
|
750
788
|
return;
|
|
751
789
|
}
|
|
752
790
|
// Refresh
|
|
753
|
-
if (input === 'r') {
|
|
791
|
+
if (input === 'r' && !(key.ctrl)) {
|
|
754
792
|
loadTasks();
|
|
755
793
|
setMessage(i18n.tui.refreshed);
|
|
756
794
|
return;
|
|
757
795
|
}
|
|
758
|
-
|
|
796
|
+
// Undo (u key) - only in normal mode
|
|
797
|
+
if (input === 'u' && mode === 'normal') {
|
|
798
|
+
history.undo().then((didUndo) => {
|
|
799
|
+
if (didUndo) {
|
|
800
|
+
setMessage(fmt(i18n.tui.undone, { action: history.undoDescription || '' }));
|
|
801
|
+
loadTasks();
|
|
802
|
+
}
|
|
803
|
+
else {
|
|
804
|
+
setMessage(i18n.tui.nothingToUndo);
|
|
805
|
+
}
|
|
806
|
+
}).catch(() => {
|
|
807
|
+
setMessage(i18n.tui.undoFailed);
|
|
808
|
+
});
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
// Redo (Ctrl+r) - only in normal mode
|
|
812
|
+
if (key.ctrl && input === 'r' && mode === 'normal') {
|
|
813
|
+
history.redo().then((didRedo) => {
|
|
814
|
+
if (didRedo) {
|
|
815
|
+
setMessage(fmt(i18n.tui.redone, { action: history.redoDescription || '' }));
|
|
816
|
+
loadTasks();
|
|
817
|
+
}
|
|
818
|
+
else {
|
|
819
|
+
setMessage(i18n.tui.nothingToRedo);
|
|
820
|
+
}
|
|
821
|
+
}).catch(() => {
|
|
822
|
+
setMessage(i18n.tui.redoFailed);
|
|
823
|
+
});
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
}, { isActive: mode !== 'help' });
|
|
759
827
|
// Splash screen
|
|
760
828
|
if (mode === 'splash') {
|
|
761
829
|
return _jsx(SplashScreen, { onComplete: () => setMode('normal') });
|
package/dist/ui/SplashScreen.js
CHANGED
|
@@ -57,5 +57,5 @@ export function SplashScreen({ onComplete, duration = 1500 }) {
|
|
|
57
57
|
const visibleLines = Math.min(Math.floor(frame * logoLines.length / 10), logoLines.length);
|
|
58
58
|
return (_jsxs(Box, { flexDirection: "column", alignItems: "center", justifyContent: "center", padding: 2, children: [_jsx(Box, { flexDirection: "column", alignItems: "center", children: logoLines.slice(0, visibleLines).map((line, index) => (_jsx(Text, { color: theme.colors.secondary, bold: true, children: line }, index))) }), showTagline && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.colors.textMuted, italic: !isDosStyle, children: isDosStyle ? `[ ${TAGLINE.toUpperCase()} ]` : TAGLINE }) })), _jsx(Box, { marginTop: 2, children: _jsx(Text, { color: theme.colors.primary, children: frame < 10
|
|
59
59
|
? filled.repeat(frame) + empty.repeat(10 - frame)
|
|
60
|
-
: filled.repeat(10) }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.colors.textMuted
|
|
60
|
+
: filled.repeat(10) }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: isDosStyle ? theme.colors.textMuted : theme.colors.muted, children: isDosStyle ? `VER ${VERSION}` : `v${VERSION}` }) })] }));
|
|
61
61
|
}
|
|
@@ -87,6 +87,7 @@ export function HelpModal({ onClose, isKanban = false }) {
|
|
|
87
87
|
// Close modal
|
|
88
88
|
if (key.escape || key.return || input === 'q' || input === ' ') {
|
|
89
89
|
onClose();
|
|
90
|
+
return;
|
|
90
91
|
}
|
|
91
92
|
});
|
|
92
93
|
const formatTitle = (title) => theme.style.headerUppercase ? title.toUpperCase() : title;
|
|
@@ -99,14 +100,14 @@ function GTDKeybindingsContent() {
|
|
|
99
100
|
const help = i18n.tui.help;
|
|
100
101
|
const theme = useTheme();
|
|
101
102
|
const formatTitle = (title) => theme.style.headerUppercase ? title.toUpperCase() : title;
|
|
102
|
-
return (_jsxs(_Fragment, { children: [_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.navigation) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "1-6" }), " ", help.tabSwitch] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "h/l \u2190/\u2192" }), " ", help.prevNextTab] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "j/k \u2191/\u2193" }), " ", help.taskSelect] })] })] }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.actions) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "a" }), " ", help.addTask] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "d" }), " ", help.completeTask] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "n" }), " ", help.moveToNext] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "s" }), " ", help.moveToSomeday] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "w" }), " ", help.moveToWaiting] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "i" }), " ", help.moveToInbox] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "r" }), " ", help.refresh] })] })] }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.projects) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "p" }), " ", help.makeProject] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "P" }), " ", help.linkToProject] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "Enter" }), " ", help.openProject] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "Esc/b" }), " ", help.backFromProject] })] })] }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.settings) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "T" }), " ", help.changeTheme] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "V" }), " ", help.changeViewMode] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "L" }), " ", help.changeLanguage] })] })] }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.other) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "/" }), " ", help.searchTasks] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "?" }), " ", help.showHelp] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "q" }), " ", help.quit] })] })] })] }));
|
|
103
|
+
return (_jsxs(_Fragment, { children: [_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.navigation) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "1-6" }), " ", help.tabSwitch] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "h/l \u2190/\u2192" }), " ", help.prevNextTab] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "j/k \u2191/\u2193" }), " ", help.taskSelect] })] })] }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.actions) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "a" }), " ", help.addTask] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "d" }), " ", help.completeTask] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "n" }), " ", help.moveToNext] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "s" }), " ", help.moveToSomeday] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "w" }), " ", help.moveToWaiting] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "i" }), " ", help.moveToInbox] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "r" }), " ", help.refresh] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "u" }), " ", help.undo] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "Ctrl+r" }), " ", help.redo] })] })] }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.projects) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "p" }), " ", help.makeProject] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "P" }), " ", help.linkToProject] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "Enter" }), " ", help.openProject] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "Esc/b" }), " ", help.backFromProject] })] })] }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.settings) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "T" }), " ", help.changeTheme] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "V" }), " ", help.changeViewMode] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "L" }), " ", help.changeLanguage] })] })] }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.other) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "/" }), " ", help.searchTasks] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "?" }), " ", help.showHelp] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "q" }), " ", help.quit] })] })] })] }));
|
|
103
104
|
}
|
|
104
105
|
function KanbanKeybindingsContent() {
|
|
105
106
|
const i18n = t();
|
|
106
107
|
const help = i18n.tui.kanbanHelp;
|
|
107
108
|
const theme = useTheme();
|
|
108
109
|
const formatTitle = (title) => theme.style.headerUppercase ? title.toUpperCase() : title;
|
|
109
|
-
return (_jsxs(_Fragment, { children: [_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.navigation) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "h/l \u2190/\u2192" }), " ", help.columnSwitch] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "1-3" }), " ", help.columnDirect] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "j/k \u2191/\u2193" }), " ", help.taskSelect] })] })] }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.actions) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "a" }), " ", help.addTask] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "d" }), " ", help.completeTask] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "Enter" }), " ", help.moveRight] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "BS" }), " ", help.moveLeft] })] })] }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.settings) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "T" }), " ", help.changeTheme] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "V" }), " ", help.changeViewMode] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "L" }), " ", help.changeLanguage] })] })] }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.other) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "/" }), " ", help.searchTasks] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "?" }), " ", help.showHelp] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "q" }), " ", help.quit] })] })] })] }));
|
|
110
|
+
return (_jsxs(_Fragment, { children: [_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.navigation) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "h/l \u2190/\u2192" }), " ", help.columnSwitch] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "1-3" }), " ", help.columnDirect] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "j/k \u2191/\u2193" }), " ", help.taskSelect] })] })] }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.actions) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "a" }), " ", help.addTask] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "d" }), " ", help.completeTask] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "Enter" }), " ", help.moveRight] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "BS" }), " ", help.moveLeft] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "u" }), " Undo"] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "Ctrl+r" }), " Redo"] })] })] }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.settings) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "T" }), " ", help.changeTheme] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "V" }), " ", help.changeViewMode] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "L" }), " ", help.changeLanguage] })] })] }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.other) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "/" }), " ", help.searchTasks] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "?" }), " ", help.showHelp] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "q" }), " ", help.quit] })] })] })] }));
|
|
110
111
|
}
|
|
111
112
|
function InfoContent() {
|
|
112
113
|
const i18n = t();
|