floq 0.6.0 → 0.7.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/i18n/en.d.ts +20 -0
- package/dist/i18n/en.js +12 -1
- package/dist/i18n/ja.js +12 -1
- package/dist/ui/App.js +4 -1
- package/dist/ui/SplashScreen.js +47 -4
- package/dist/ui/components/GtdDQ.js +168 -13
- package/dist/ui/components/GtdMario.d.ts +7 -0
- package/dist/ui/components/GtdMario.js +831 -0
- package/dist/ui/components/KanbanDQ.js +39 -5
- package/dist/ui/components/KanbanMario.d.ts +7 -0
- package/dist/ui/components/KanbanMario.js +426 -0
- package/dist/ui/components/MarioBox.d.ts +19 -0
- package/dist/ui/components/MarioBox.js +83 -0
- package/dist/ui/components/SearchResults.js +4 -1
- package/dist/ui/theme/themes.d.ts +1 -0
- package/dist/ui/theme/themes.js +42 -0
- package/dist/ui/theme/types.d.ts +2 -2
- package/package.json +1 -1
package/dist/i18n/en.d.ts
CHANGED
|
@@ -85,6 +85,16 @@ export declare const en: {
|
|
|
85
85
|
waitingFor: string;
|
|
86
86
|
refreshed: string;
|
|
87
87
|
footer: string;
|
|
88
|
+
dqFooter: {
|
|
89
|
+
tabs: string;
|
|
90
|
+
tasks: string;
|
|
91
|
+
projectDetail: string;
|
|
92
|
+
taskDetail: string;
|
|
93
|
+
};
|
|
94
|
+
kanbanFooter: {
|
|
95
|
+
category: string;
|
|
96
|
+
tasks: string;
|
|
97
|
+
};
|
|
88
98
|
noTasks: string;
|
|
89
99
|
tabInbox: string;
|
|
90
100
|
tabNext: string;
|
|
@@ -435,6 +445,16 @@ export type TuiTranslations = {
|
|
|
435
445
|
waitingFor: string;
|
|
436
446
|
refreshed: string;
|
|
437
447
|
footer: string;
|
|
448
|
+
dqFooter: {
|
|
449
|
+
tabs: string;
|
|
450
|
+
tasks: string;
|
|
451
|
+
projectDetail: string;
|
|
452
|
+
taskDetail: string;
|
|
453
|
+
};
|
|
454
|
+
kanbanFooter: {
|
|
455
|
+
category: string;
|
|
456
|
+
tasks: string;
|
|
457
|
+
};
|
|
438
458
|
noTasks: string;
|
|
439
459
|
tabInbox: string;
|
|
440
460
|
tabNext: string;
|
package/dist/i18n/en.js
CHANGED
|
@@ -89,7 +89,18 @@ export const en = {
|
|
|
89
89
|
movedToWaiting: 'Moved "{title}" to Waiting (for {person})',
|
|
90
90
|
waitingFor: 'Waiting for: ',
|
|
91
91
|
refreshed: 'Refreshed',
|
|
92
|
-
footer: 'a=add d=done D=delete n=next s=someday w=waiting i=inbox p=project P=link',
|
|
92
|
+
footer: 'a=add d=done D=delete n=next s=someday w=waiting i=inbox p=project P=link c=context',
|
|
93
|
+
// DQ/Mario style footers
|
|
94
|
+
dqFooter: {
|
|
95
|
+
tabs: 'j/k=select l/Enter=tasks 1-6=tab a=add @=filter /=search',
|
|
96
|
+
tasks: 'j/k=select Enter=detail h/Esc=back d=done n=next s=someday w=wait i=inbox c=context p=project P=link D=delete u=undo /=search',
|
|
97
|
+
projectDetail: 'j/k=select a=add d=done Esc/b=back /=search',
|
|
98
|
+
taskDetail: 'j/k=select c/i=add comment P=link D=delete comment Esc/b=back',
|
|
99
|
+
},
|
|
100
|
+
kanbanFooter: {
|
|
101
|
+
category: 'j/k=select l/Enter=tasks a=add @=filter /=search',
|
|
102
|
+
tasks: 'j/k=select h/Esc=back d=done m=move a=add u=undo /=search',
|
|
103
|
+
},
|
|
93
104
|
noTasks: 'No tasks',
|
|
94
105
|
// Tab labels
|
|
95
106
|
tabInbox: 'Inbox',
|
package/dist/i18n/ja.js
CHANGED
|
@@ -89,7 +89,18 @@ export const ja = {
|
|
|
89
89
|
movedToWaiting: '「{title}」を連絡待ち({person})に移動しました',
|
|
90
90
|
waitingFor: '待機相手: ',
|
|
91
91
|
refreshed: '更新しました',
|
|
92
|
-
footer: 'a=追加 d=完了 D=削除 n=次 s=いつか w=待ち i=Inbox p=プロジェクト化 P=紐づけ',
|
|
92
|
+
footer: 'a=追加 d=完了 D=削除 n=次 s=いつか w=待ち i=Inbox p=プロジェクト化 P=紐づけ c=コンテキスト',
|
|
93
|
+
// DQ/Mario style footers
|
|
94
|
+
dqFooter: {
|
|
95
|
+
tabs: 'j/k=選択 l/Enter=タスク 1-6=タブ a=追加 @=フィルター /=検索',
|
|
96
|
+
tasks: 'j/k=選択 Enter=詳細 h/Esc=戻る d=完了 n=次 s=いつか w=待ち i=inbox c=コンテキスト p=プロジェクト化 P=紐づけ D=削除 u=戻す /=検索',
|
|
97
|
+
projectDetail: 'j/k=選択 a=追加 d=完了 Esc/b=戻る /=検索',
|
|
98
|
+
taskDetail: 'j/k=選択 c/i=コメント追加 P=紐づけ D=コメント削除 Esc/b=戻る',
|
|
99
|
+
},
|
|
100
|
+
kanbanFooter: {
|
|
101
|
+
category: 'j/k=選択 l/Enter=タスク a=追加 @=フィルター /=検索',
|
|
102
|
+
tasks: 'j/k=選択 h/Esc=戻る d=完了 m=移動 a=追加 u=戻す /=検索',
|
|
103
|
+
},
|
|
93
104
|
noTasks: 'タスクなし',
|
|
94
105
|
// Tab labels
|
|
95
106
|
tabInbox: 'Inbox',
|
package/dist/ui/App.js
CHANGED
|
@@ -20,7 +20,9 @@ import { ThemeProvider, useTheme, getTheme } from './theme/index.js';
|
|
|
20
20
|
import { getThemeName, getViewMode, setThemeName, setViewMode, setLocale, isTursoEnabled, getContexts, addContext, getSplashDuration } from '../config.js';
|
|
21
21
|
import { KanbanBoard } from './components/KanbanBoard.js';
|
|
22
22
|
import { KanbanDQ } from './components/KanbanDQ.js';
|
|
23
|
+
import { KanbanMario } from './components/KanbanMario.js';
|
|
23
24
|
import { GtdDQ } from './components/GtdDQ.js';
|
|
25
|
+
import { GtdMario } from './components/GtdMario.js';
|
|
24
26
|
import { VERSION } from '../version.js';
|
|
25
27
|
import { HistoryProvider, useHistory, CreateTaskCommand, DeleteTaskCommand, MoveTaskCommand, LinkTaskCommand, ConvertToProjectCommand, CreateCommentCommand, DeleteCommentCommand, SetContextCommand, } from './history/index.js';
|
|
26
28
|
const TABS = ['inbox', 'next', 'waiting', 'someday', 'projects', 'done'];
|
|
@@ -51,6 +53,7 @@ export function App() {
|
|
|
51
53
|
};
|
|
52
54
|
const currentTheme = getTheme(themeName);
|
|
53
55
|
const useDQStyle = currentTheme.uiStyle === 'titled-box';
|
|
56
|
+
const useMarioStyle = currentTheme.uiStyle === 'mario-block';
|
|
54
57
|
// Show splash screen (all themes, configurable duration)
|
|
55
58
|
if (showSplash) {
|
|
56
59
|
return (_jsx(ThemeProvider, { themeName: themeName, children: _jsx(SplashScreen, { onComplete: () => setShowSplash(false), duration: splashDuration, viewMode: viewMode }) }));
|
|
@@ -65,7 +68,7 @@ export function App() {
|
|
|
65
68
|
if (settingsMode === 'lang-select') {
|
|
66
69
|
return (_jsx(ThemeProvider, { themeName: themeName, children: _jsx(LanguageSelector, { onSelect: handleLocaleSelect, onCancel: handleSettingsCancel }) }));
|
|
67
70
|
}
|
|
68
|
-
return (_jsx(ThemeProvider, { themeName: themeName, children: _jsx(HistoryProvider, { children: viewMode === 'kanban' ? (useDQStyle ? (_jsx(KanbanDQ, { onOpenSettings: setSettingsMode })) : (_jsx(KanbanBoard, { onOpenSettings: setSettingsMode }))) : (useDQStyle ? (_jsx(GtdDQ, { onOpenSettings: setSettingsMode })) : (_jsx(AppContent, { onOpenSettings: setSettingsMode }))) }) }));
|
|
71
|
+
return (_jsx(ThemeProvider, { themeName: themeName, children: _jsx(HistoryProvider, { children: viewMode === 'kanban' ? (useMarioStyle ? (_jsx(KanbanMario, { onOpenSettings: setSettingsMode })) : useDQStyle ? (_jsx(KanbanDQ, { onOpenSettings: setSettingsMode })) : (_jsx(KanbanBoard, { onOpenSettings: setSettingsMode }))) : (useMarioStyle ? (_jsx(GtdMario, { onOpenSettings: setSettingsMode })) : useDQStyle ? (_jsx(GtdDQ, { onOpenSettings: setSettingsMode })) : (_jsx(AppContent, { onOpenSettings: setSettingsMode }))) }) }));
|
|
69
72
|
}
|
|
70
73
|
function AppContent({ onOpenSettings }) {
|
|
71
74
|
const theme = useTheme();
|
package/dist/ui/SplashScreen.js
CHANGED
|
@@ -52,6 +52,40 @@ const DQ_BORDER = {
|
|
|
52
52
|
vertical: '│',
|
|
53
53
|
};
|
|
54
54
|
const TAGLINE = 'Flow your tasks, clear your mind';
|
|
55
|
+
// Mario/Nintendo style splash - SFC boot screen inspired
|
|
56
|
+
const MARIO_LOGO = [
|
|
57
|
+
' ★ ★ ★ ★ ★ ★ ★ ★ ★',
|
|
58
|
+
'',
|
|
59
|
+
' ████████ ██ ██████ ██████',
|
|
60
|
+
' ██ ██ ██ ██ ██ ██',
|
|
61
|
+
' ██████ ██ ██ ██ ██ ██',
|
|
62
|
+
' ██ ██ ██ ██ ██ ▄▄ ██',
|
|
63
|
+
' ██ ███████ ██████ ██████',
|
|
64
|
+
'',
|
|
65
|
+
' ★ ★ ★ ★ ★ ★ ★ ★ ★',
|
|
66
|
+
];
|
|
67
|
+
const MARIO_QUOTES_JA = [
|
|
68
|
+
'イッツァミー!タスクマネージャー!',
|
|
69
|
+
'マンマミーア!タスクがいっぱいだ!',
|
|
70
|
+
'レッツァゴー!',
|
|
71
|
+
'ヤッフー!',
|
|
72
|
+
'スーパータスククリア!',
|
|
73
|
+
'コインをゲット!',
|
|
74
|
+
'1UPキノコ!',
|
|
75
|
+
'ファイアフラワー!',
|
|
76
|
+
'スターパワー!',
|
|
77
|
+
];
|
|
78
|
+
const MARIO_QUOTES_EN = [
|
|
79
|
+
"It's-a me! Task Manager!",
|
|
80
|
+
'Mamma mia! So many tasks!',
|
|
81
|
+
"Let's-a go!",
|
|
82
|
+
'Yahoo!',
|
|
83
|
+
'Super Task Clear!',
|
|
84
|
+
'Got a coin!',
|
|
85
|
+
'1-UP Mushroom!',
|
|
86
|
+
'Fire Flower!',
|
|
87
|
+
'Star Power!',
|
|
88
|
+
];
|
|
55
89
|
// Dragon Quest famous quotes for splash screen
|
|
56
90
|
const DQ_QUOTES_JA = [
|
|
57
91
|
'へんじがない。ただのしかばねのようだ。',
|
|
@@ -84,12 +118,17 @@ export function SplashScreen({ onComplete, duration = 1500, viewMode = 'gtd' })
|
|
|
84
118
|
const theme = useTheme();
|
|
85
119
|
const i18n = t();
|
|
86
120
|
const isDqStyle = theme.uiStyle === 'titled-box';
|
|
87
|
-
const
|
|
121
|
+
const isMarioStyle = theme.uiStyle === 'mario-block';
|
|
122
|
+
const isDosStyle = theme.name !== 'modern' && !isMarioStyle;
|
|
88
123
|
const logo = isDosStyle ? LOGO_DOS : LOGO_MODERN;
|
|
89
124
|
const [filled, empty] = theme.style.loadingChars;
|
|
90
125
|
// Pick a random quote (stable across re-renders)
|
|
91
126
|
const [randomQuote] = useState(() => {
|
|
92
127
|
const isJapanese = i18n.splash?.welcome === 'ようこそ!';
|
|
128
|
+
if (isMarioStyle) {
|
|
129
|
+
const quotes = isJapanese ? MARIO_QUOTES_JA : MARIO_QUOTES_EN;
|
|
130
|
+
return quotes[Math.floor(Math.random() * quotes.length)];
|
|
131
|
+
}
|
|
93
132
|
const quotes = isJapanese ? DQ_QUOTES_JA : DQ_QUOTES_EN;
|
|
94
133
|
return quotes[Math.floor(Math.random() * quotes.length)];
|
|
95
134
|
});
|
|
@@ -118,9 +157,9 @@ export function SplashScreen({ onComplete, duration = 1500, viewMode = 'gtd' })
|
|
|
118
157
|
const taglineTimer = setTimeout(() => {
|
|
119
158
|
setShowTagline(true);
|
|
120
159
|
}, 600);
|
|
121
|
-
// Blink effect for DQ style or wait-for-key mode
|
|
160
|
+
// Blink effect for DQ style, Mario style, or wait-for-key mode
|
|
122
161
|
let blinkInterval = null;
|
|
123
|
-
if (isDqStyle || waitForKeyPress) {
|
|
162
|
+
if (isDqStyle || isMarioStyle || waitForKeyPress) {
|
|
124
163
|
blinkInterval = setInterval(() => {
|
|
125
164
|
setBlinkVisible((prev) => !prev);
|
|
126
165
|
}, 500);
|
|
@@ -140,7 +179,11 @@ export function SplashScreen({ onComplete, duration = 1500, viewMode = 'gtd' })
|
|
|
140
179
|
if (blinkInterval)
|
|
141
180
|
clearInterval(blinkInterval);
|
|
142
181
|
};
|
|
143
|
-
}, [onComplete, duration, isDqStyle, waitForKeyPress]);
|
|
182
|
+
}, [onComplete, duration, isDqStyle, isMarioStyle, waitForKeyPress]);
|
|
183
|
+
// Mario / Nintendo style splash (SFC boot screen inspired)
|
|
184
|
+
if (isMarioStyle) {
|
|
185
|
+
return (_jsxs(Box, { flexDirection: "column", alignItems: "center", justifyContent: "center", padding: 2, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: theme.colors.secondary, children: "\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501" }) }), _jsx(Box, { flexDirection: "column", alignItems: "center", marginBottom: 1, children: MARIO_LOGO.map((line, index) => (_jsx(Text, { color: index === 0 || index === 8 ? theme.colors.secondary : theme.colors.primary, bold: true, children: line }, index))) }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: theme.colors.text, bold: true, children: "\uFF5E SUPER TASK MANAGER \uFF5E" }) }), _jsx(Box, { marginBottom: 1, children: _jsxs(Text, { color: theme.colors.accent, children: ["\uD83C\uDF44 ", randomQuote] }) }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: theme.colors.secondary, children: "\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501" }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.colors.textMuted, children: blinkVisible ? '- PRESS START -' : ' ' }) }), _jsxs(Box, { marginTop: 2, flexDirection: "column", alignItems: "center", children: [_jsxs(Text, { color: theme.colors.textMuted, children: ["VER ", VERSION] }), _jsx(Text, { color: theme.colors.textMuted, children: "\u00A9 2026 polidog/PartyHard Inc." })] })] }));
|
|
186
|
+
}
|
|
144
187
|
// Dragon Quest style splash
|
|
145
188
|
if (isDqStyle) {
|
|
146
189
|
const boxWidth = 40;
|
|
@@ -7,13 +7,41 @@ import { v4 as uuidv4 } from 'uuid';
|
|
|
7
7
|
import { getDb, schema } from '../../db/index.js';
|
|
8
8
|
import { t, fmt } from '../../i18n/index.js';
|
|
9
9
|
import { useTheme } from '../theme/index.js';
|
|
10
|
-
import { isTursoEnabled, getContexts, addContext } from '../../config.js';
|
|
11
|
-
import {
|
|
12
|
-
import { useHistory, CreateTaskCommand, DeleteTaskCommand, MoveTaskCommand, LinkTaskCommand, ConvertToProjectCommand, } from '../history/index.js';
|
|
10
|
+
import { isTursoEnabled, getContexts, addContext, getLocale } from '../../config.js';
|
|
11
|
+
import { useHistory, CreateTaskCommand, DeleteTaskCommand, MoveTaskCommand, LinkTaskCommand, ConvertToProjectCommand, CreateCommentCommand, DeleteCommentCommand, SetContextCommand, } from '../history/index.js';
|
|
13
12
|
import { SearchBar } from './SearchBar.js';
|
|
14
13
|
import { SearchResults } from './SearchResults.js';
|
|
15
14
|
import { HelpModal } from './HelpModal.js';
|
|
16
15
|
const TABS = ['inbox', 'next', 'waiting', 'someday', 'projects', 'done'];
|
|
16
|
+
// Dragon Quest job classes
|
|
17
|
+
const DQ_JOBS_JA = [
|
|
18
|
+
'ゆうしゃ',
|
|
19
|
+
'せんし',
|
|
20
|
+
'まほうつかい',
|
|
21
|
+
'そうりょ',
|
|
22
|
+
'ぶとうか',
|
|
23
|
+
'とうぞく',
|
|
24
|
+
'あそびにん',
|
|
25
|
+
'けんじゃ',
|
|
26
|
+
'バトルマスター',
|
|
27
|
+
'パラディン',
|
|
28
|
+
'レンジャー',
|
|
29
|
+
'スーパースター',
|
|
30
|
+
];
|
|
31
|
+
const DQ_JOBS_EN = [
|
|
32
|
+
'Hero',
|
|
33
|
+
'Warrior',
|
|
34
|
+
'Mage',
|
|
35
|
+
'Priest',
|
|
36
|
+
'Martial Artist',
|
|
37
|
+
'Thief',
|
|
38
|
+
'Gadabout',
|
|
39
|
+
'Sage',
|
|
40
|
+
'Battle Master',
|
|
41
|
+
'Paladin',
|
|
42
|
+
'Ranger',
|
|
43
|
+
'Superstar',
|
|
44
|
+
];
|
|
17
45
|
// Round border characters
|
|
18
46
|
const BORDER = {
|
|
19
47
|
topLeft: '╭',
|
|
@@ -81,6 +109,12 @@ export function GtdDQ({ onOpenSettings }) {
|
|
|
81
109
|
const { stdout } = useStdout();
|
|
82
110
|
const history = useHistory();
|
|
83
111
|
const i18n = t();
|
|
112
|
+
// Random job class (stable across re-renders)
|
|
113
|
+
const [jobClass] = useState(() => {
|
|
114
|
+
const isJapanese = getLocale() === 'ja';
|
|
115
|
+
const jobs = isJapanese ? DQ_JOBS_JA : DQ_JOBS_EN;
|
|
116
|
+
return jobs[Math.floor(Math.random() * jobs.length)];
|
|
117
|
+
});
|
|
84
118
|
const [mode, setMode] = useState('normal');
|
|
85
119
|
const [paneFocus, setPaneFocus] = useState('tabs');
|
|
86
120
|
const [currentTabIndex, setCurrentTabIndex] = useState(0);
|
|
@@ -325,6 +359,46 @@ export function GtdDQ({ onOpenSettings }) {
|
|
|
325
359
|
setMessage(fmt(i18n.tui.linkedToProject || 'Linked "{title}" to {project}', { title: task.title, project: project.title }));
|
|
326
360
|
await loadTasks();
|
|
327
361
|
}, [i18n.tui.linkedToProject, loadTasks, history]);
|
|
362
|
+
const addCommentToTask = useCallback(async (task, content) => {
|
|
363
|
+
const commentId = uuidv4();
|
|
364
|
+
const command = new CreateCommentCommand({
|
|
365
|
+
comment: {
|
|
366
|
+
id: commentId,
|
|
367
|
+
taskId: task.id,
|
|
368
|
+
content: content.trim(),
|
|
369
|
+
createdAt: new Date(),
|
|
370
|
+
},
|
|
371
|
+
description: i18n.tui.commentAdded || 'Comment added',
|
|
372
|
+
});
|
|
373
|
+
await history.execute(command);
|
|
374
|
+
setMessage(i18n.tui.commentAdded || 'Comment added');
|
|
375
|
+
await loadTaskComments(task.id);
|
|
376
|
+
}, [history, i18n.tui.commentAdded, loadTaskComments]);
|
|
377
|
+
const deleteComment = useCallback(async (comment) => {
|
|
378
|
+
const command = new DeleteCommentCommand({
|
|
379
|
+
comment,
|
|
380
|
+
description: i18n.tui.commentDeleted || 'Comment deleted',
|
|
381
|
+
});
|
|
382
|
+
await history.execute(command);
|
|
383
|
+
setMessage(i18n.tui.commentDeleted || 'Comment deleted');
|
|
384
|
+
if (selectedTask) {
|
|
385
|
+
await loadTaskComments(selectedTask.id);
|
|
386
|
+
}
|
|
387
|
+
}, [i18n.tui.commentDeleted, loadTaskComments, selectedTask, history]);
|
|
388
|
+
const setTaskContext = useCallback(async (task, context) => {
|
|
389
|
+
const description = context
|
|
390
|
+
? fmt(i18n.tui.context?.contextSet || 'Set context @{context} for "{title}"', { context, title: task.title })
|
|
391
|
+
: fmt(i18n.tui.context?.contextCleared || 'Cleared context for "{title}"', { title: task.title });
|
|
392
|
+
const command = new SetContextCommand({
|
|
393
|
+
taskId: task.id,
|
|
394
|
+
fromContext: task.context,
|
|
395
|
+
toContext: context,
|
|
396
|
+
description,
|
|
397
|
+
});
|
|
398
|
+
await history.execute(command);
|
|
399
|
+
setMessage(description);
|
|
400
|
+
await loadTasks();
|
|
401
|
+
}, [i18n.tui.context, loadTasks, history]);
|
|
328
402
|
const handleInputSubmit = async (value) => {
|
|
329
403
|
// Handle search mode submit
|
|
330
404
|
if (mode === 'search') {
|
|
@@ -340,6 +414,15 @@ export function GtdDQ({ onOpenSettings }) {
|
|
|
340
414
|
setSearchResultIndex(0);
|
|
341
415
|
return;
|
|
342
416
|
}
|
|
417
|
+
// Handle add-comment mode
|
|
418
|
+
if (mode === 'add-comment' && selectedTask) {
|
|
419
|
+
if (value.trim()) {
|
|
420
|
+
await addCommentToTask(selectedTask, value);
|
|
421
|
+
}
|
|
422
|
+
setInputValue('');
|
|
423
|
+
setMode('task-detail');
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
343
426
|
if (mode === 'move-to-waiting' && taskToWaiting) {
|
|
344
427
|
if (value.trim()) {
|
|
345
428
|
await moveTaskToWaiting(taskToWaiting, value);
|
|
@@ -394,12 +477,57 @@ export function GtdDQ({ onOpenSettings }) {
|
|
|
394
477
|
else if (mode === 'add-context') {
|
|
395
478
|
setMode('set-context');
|
|
396
479
|
}
|
|
480
|
+
else if (mode === 'add-comment') {
|
|
481
|
+
setMode('task-detail');
|
|
482
|
+
}
|
|
397
483
|
else {
|
|
398
484
|
setMode('normal');
|
|
399
485
|
}
|
|
400
486
|
}
|
|
401
487
|
return;
|
|
402
488
|
}
|
|
489
|
+
// Handle task-detail mode
|
|
490
|
+
if (mode === 'task-detail') {
|
|
491
|
+
if (key.escape || input === 'b' || input === 'h' || key.leftArrow) {
|
|
492
|
+
setSelectedTask(null);
|
|
493
|
+
setTaskComments([]);
|
|
494
|
+
setSelectedCommentIndex(0);
|
|
495
|
+
setMode('normal');
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
// Navigate comments
|
|
499
|
+
if (key.upArrow || input === 'k') {
|
|
500
|
+
setSelectedCommentIndex((prev) => (prev > 0 ? prev - 1 : Math.max(0, taskComments.length - 1)));
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
if (key.downArrow || input === 'j') {
|
|
504
|
+
setSelectedCommentIndex((prev) => (prev < taskComments.length - 1 ? prev + 1 : 0));
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
// Add comment
|
|
508
|
+
if (input === 'c' || input === 'i') {
|
|
509
|
+
setMode('add-comment');
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
// Delete comment
|
|
513
|
+
if (input === 'D' && taskComments.length > 0) {
|
|
514
|
+
const comment = taskComments[selectedCommentIndex];
|
|
515
|
+
deleteComment(comment).then(() => {
|
|
516
|
+
if (selectedCommentIndex >= taskComments.length - 1) {
|
|
517
|
+
setSelectedCommentIndex(Math.max(0, selectedCommentIndex - 1));
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
// Link to project (P key)
|
|
523
|
+
if (input === 'P' && tasks.projects.length > 0) {
|
|
524
|
+
setTaskToLink(selectedTask);
|
|
525
|
+
setProjectSelectIndex(0);
|
|
526
|
+
setMode('select-project');
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
403
531
|
// Handle search mode
|
|
404
532
|
if (mode === 'search') {
|
|
405
533
|
if (key.escape) {
|
|
@@ -493,7 +621,13 @@ export function GtdDQ({ onOpenSettings }) {
|
|
|
493
621
|
setInputValue('');
|
|
494
622
|
return;
|
|
495
623
|
}
|
|
496
|
-
|
|
624
|
+
const task = currentTasks[selectedTaskIndex];
|
|
625
|
+
if (selected === 'clear') {
|
|
626
|
+
setTaskContext(task, null);
|
|
627
|
+
}
|
|
628
|
+
else {
|
|
629
|
+
setTaskContext(task, selected);
|
|
630
|
+
}
|
|
497
631
|
setContextSelectIndex(0);
|
|
498
632
|
setMode('normal');
|
|
499
633
|
return;
|
|
@@ -503,9 +637,10 @@ export function GtdDQ({ onOpenSettings }) {
|
|
|
503
637
|
// Handle select-project mode
|
|
504
638
|
if (mode === 'select-project') {
|
|
505
639
|
if (key.escape) {
|
|
640
|
+
const wasFromTaskDetail = selectedTask !== null;
|
|
506
641
|
setTaskToLink(null);
|
|
507
642
|
setProjectSelectIndex(0);
|
|
508
|
-
setMode('normal');
|
|
643
|
+
setMode(wasFromTaskDetail ? 'task-detail' : 'normal');
|
|
509
644
|
return;
|
|
510
645
|
}
|
|
511
646
|
if (key.upArrow || input === 'k') {
|
|
@@ -518,10 +653,11 @@ export function GtdDQ({ onOpenSettings }) {
|
|
|
518
653
|
}
|
|
519
654
|
if (key.return && taskToLink && tasks.projects.length > 0) {
|
|
520
655
|
const project = tasks.projects[projectSelectIndex];
|
|
656
|
+
const wasFromTaskDetail = selectedTask !== null;
|
|
521
657
|
linkTaskToProject(taskToLink, project);
|
|
522
658
|
setTaskToLink(null);
|
|
523
659
|
setProjectSelectIndex(0);
|
|
524
|
-
setMode('normal');
|
|
660
|
+
setMode(wasFromTaskDetail ? 'task-detail' : 'normal');
|
|
525
661
|
return;
|
|
526
662
|
}
|
|
527
663
|
return;
|
|
@@ -641,6 +777,14 @@ export function GtdDQ({ onOpenSettings }) {
|
|
|
641
777
|
setSelectedTaskIndex((prev) => (prev < currentTasks.length - 1 ? prev + 1 : 0));
|
|
642
778
|
return;
|
|
643
779
|
}
|
|
780
|
+
// Enter to view task details (on non-projects tabs)
|
|
781
|
+
if (key.return && currentTab !== 'projects' && currentTasks.length > 0) {
|
|
782
|
+
const task = currentTasks[selectedTaskIndex];
|
|
783
|
+
setSelectedTask(task);
|
|
784
|
+
loadTaskComments(task.id);
|
|
785
|
+
setMode('task-detail');
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
644
788
|
// Task actions
|
|
645
789
|
if (currentTasks.length > 0) {
|
|
646
790
|
const task = currentTasks[selectedTaskIndex];
|
|
@@ -708,6 +852,12 @@ export function GtdDQ({ onOpenSettings }) {
|
|
|
708
852
|
setMode('confirm-delete');
|
|
709
853
|
return;
|
|
710
854
|
}
|
|
855
|
+
// Set context
|
|
856
|
+
if (input === 'c' && currentTab !== 'projects') {
|
|
857
|
+
setContextSelectIndex(0);
|
|
858
|
+
setMode('set-context');
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
711
861
|
}
|
|
712
862
|
// Undo
|
|
713
863
|
if (input === 'u') {
|
|
@@ -741,10 +891,13 @@ export function GtdDQ({ onOpenSettings }) {
|
|
|
741
891
|
if (mode === 'help') {
|
|
742
892
|
return (_jsx(Box, { flexDirection: "column", padding: 1, children: _jsx(HelpModal, { onClose: () => setMode('normal') }) }));
|
|
743
893
|
}
|
|
744
|
-
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsxs(Box, { marginBottom: 1, justifyContent: "space-between", children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, color: theme.colors.
|
|
894
|
+
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsxs(Box, { marginBottom: 1, justifyContent: "space-between", children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: jobClass }), _jsx(Text, { color: theme.colors.text, children: " Lv." }), _jsx(Text, { bold: true, color: theme.colors.secondary, children: Math.floor(tasks.done.length / 5) + 1 }), _jsx(Text, { color: theme.colors.text, children: " HP " }), _jsx(Text, { bold: true, color: theme.colors.statusNext, children: tasks.inbox.length + tasks.next.length }), _jsxs(Text, { color: theme.colors.textMuted, children: ["/", tasks.inbox.length + tasks.next.length + tasks.waiting.length + tasks.someday.length] }), _jsx(Text, { color: theme.colors.text, children: " MP " }), _jsx(Text, { bold: true, color: theme.colors.statusWaiting, children: tasks.projects.length }), _jsx(Text, { color: tursoEnabled ? theme.colors.accent : theme.colors.textMuted, children: tursoEnabled ? ' [TURSO]' : '' }), contextFilter !== null && (_jsxs(Text, { color: theme.colors.accent, children: [' ', "@", contextFilter === '' ? 'none' : contextFilter] }))] }), _jsx(Text, { color: theme.colors.textMuted, children: "?=help q=quit" })] }), mode === 'context-filter' ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: theme.colors.secondary, bold: true, children: "Filter by context" }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: ['all', 'none', ...availableContexts].map((ctx, index) => {
|
|
745
895
|
const label = ctx === 'all' ? 'All' : ctx === 'none' ? 'No context' : `@${ctx}`;
|
|
746
896
|
return (_jsxs(Text, { color: index === contextSelectIndex ? theme.colors.textSelected : theme.colors.text, bold: index === contextSelectIndex, children: [index === contextSelectIndex ? '▶ ' : ' ', label] }, ctx));
|
|
747
|
-
}) })] })) : 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 }) }) })) : (_jsxs(Box, { flexDirection: "
|
|
897
|
+
}) })] })) : 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') })] })] }), _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) => {
|
|
898
|
+
const isSelected = index === selectedCommentIndex && mode === 'task-detail';
|
|
899
|
+
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));
|
|
900
|
+
})) }) })] })) : (_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) => {
|
|
748
901
|
const isSelected = index === currentTabIndex;
|
|
749
902
|
const count = tasks[tab].length;
|
|
750
903
|
return (_jsxs(Text, { color: isSelected && paneFocus === 'tabs' ? theme.colors.textSelected : theme.colors.text, bold: isSelected, children: [isSelected ? '▶ ' : ' ', index + 1, ":", getTabLabel(tab), " (", count, ")"] }, tab));
|
|
@@ -765,9 +918,11 @@ export function GtdDQ({ onOpenSettings }) {
|
|
|
765
918
|
return (_jsxs(Text, { color: isSelected ? theme.colors.textSelected : theme.colors.text, bold: isSelected, children: [prefix, 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));
|
|
766
919
|
})) }) })] })), (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
|
|
767
920
|
? `${i18n.tui.newTask}[${selectedProject.title}] `
|
|
768
|
-
: 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 === '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 }) })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.colors.textMuted, children: mode === '
|
|
769
|
-
?
|
|
770
|
-
:
|
|
771
|
-
?
|
|
772
|
-
:
|
|
921
|
+
: 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 }) })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.colors.textMuted, children: mode === 'task-detail'
|
|
922
|
+
? i18n.tui.dqFooter.taskDetail
|
|
923
|
+
: mode === 'project-detail'
|
|
924
|
+
? i18n.tui.dqFooter.projectDetail
|
|
925
|
+
: paneFocus === 'tabs'
|
|
926
|
+
? i18n.tui.dqFooter.tabs
|
|
927
|
+
: i18n.tui.dqFooter.tasks }) })] }));
|
|
773
928
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
type SettingsMode = 'none' | 'theme-select' | 'mode-select' | 'lang-select';
|
|
3
|
+
interface GtdMarioProps {
|
|
4
|
+
onOpenSettings?: (mode: SettingsMode) => void;
|
|
5
|
+
}
|
|
6
|
+
export declare function GtdMario({ onOpenSettings }: GtdMarioProps): React.ReactElement;
|
|
7
|
+
export {};
|