floq 0.4.0 → 0.6.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 +16 -3
- package/README.md +16 -3
- package/dist/cli.js +12 -1
- package/dist/commands/config.d.ts +2 -0
- package/dist/commands/config.js +51 -1
- package/dist/commands/list.js +17 -5
- package/dist/config.d.ts +3 -0
- package/dist/config.js +13 -0
- package/dist/i18n/en.d.ts +13 -0
- package/dist/i18n/en.js +7 -0
- package/dist/i18n/ja.js +7 -0
- package/dist/ui/App.js +39 -20
- package/dist/ui/SplashScreen.d.ts +2 -1
- package/dist/ui/SplashScreen.js +109 -10
- package/dist/ui/components/DQLayout.d.ts +36 -0
- package/dist/ui/components/DQLayout.js +53 -0
- package/dist/ui/components/DQTaskList.d.ts +53 -0
- package/dist/ui/components/DQTaskList.js +48 -0
- package/dist/ui/components/DQWindow.d.ts +19 -0
- package/dist/ui/components/DQWindow.js +33 -0
- package/dist/ui/components/GtdDQ.d.ts +7 -0
- package/dist/ui/components/GtdDQ.js +773 -0
- package/dist/ui/components/HelpModal.js +136 -102
- package/dist/ui/components/KanbanBoard.js +10 -6
- package/dist/ui/components/KanbanColumn.js +53 -1
- package/dist/ui/components/KanbanDQ.d.ts +7 -0
- package/dist/ui/components/KanbanDQ.js +470 -0
- package/dist/ui/components/SearchResults.d.ts +2 -1
- package/dist/ui/components/SearchResults.js +22 -2
- package/dist/ui/components/TitledBox.d.ts +11 -0
- package/dist/ui/components/TitledBox.js +66 -0
- package/dist/ui/theme/themes.d.ts +1 -0
- package/dist/ui/theme/themes.js +43 -1
- package/dist/ui/theme/types.d.ts +3 -1
- package/package.json +1 -1
package/README.ja.md
CHANGED
|
@@ -4,17 +4,20 @@
|
|
|
4
4
|
|
|
5
5
|
MS-DOSスタイルのテーマを備えたターミナルベースのGTD(Getting Things Done)タスクマネージャー。
|
|
6
6
|
|
|
7
|
+

|
|
8
|
+
|
|
7
9
|
## 特徴
|
|
8
10
|
|
|
9
11
|
- **TUIインターフェース**: Ink(CLI用React)で構築されたインタラクティブなターミナルUI
|
|
10
|
-
- **GTDワークフロー**: Inbox、Next Actions、Waiting For、Someday/Maybe、Done
|
|
12
|
+
- **GTDワークフロー**: Inbox、Next Actions、Waiting For、Someday/Maybe、Done(過去7日間表示)
|
|
11
13
|
- **カンバンモード**: 3カラムのカンバンボード表示(TODO、Doing、Done)
|
|
12
14
|
- **プロジェクト**: タスクをプロジェクトに整理(進捗バー表示付き)
|
|
13
|
-
- **コンテキスト**: タスクにコンテキスト(@work、@home
|
|
15
|
+
- **コンテキスト**: タスクにコンテキスト(@work、@homeなど)を設定してフィルタリング。タスク追加時は現在のフィルターを自動継承
|
|
14
16
|
- **タスク検索**: `/` キーで全タスクを素早く検索
|
|
15
17
|
- **コメント**: タスクにメモやコメントを追加
|
|
16
18
|
- **クラウド同期**: [Turso](https://turso.tech/)のembedded replicasによるオプションの同期機能
|
|
17
|
-
- **テーマ**: MS-DOS
|
|
19
|
+
- **テーマ**: MS-DOSノスタルジックスタイルやドラクエRPG風を含む複数テーマ
|
|
20
|
+
- **スプラッシュ画面**: 起動時のスプラッシュ画面(レトロテーマではドラクエ風)
|
|
18
21
|
- **多言語対応**: 英語・日本語サポート
|
|
19
22
|
- **Vimスタイルナビゲーション**: hjklまたは矢印キーで操作
|
|
20
23
|
- **セットアップウィザード**: 初回起動時の簡単設定
|
|
@@ -209,6 +212,12 @@ floq config mode kanban # カンバンボード
|
|
|
209
212
|
floq config db /path/to/custom.db
|
|
210
213
|
floq config db # デフォルトに戻す
|
|
211
214
|
|
|
215
|
+
# スプラッシュ画面設定
|
|
216
|
+
floq config splash # 現在の設定を表示
|
|
217
|
+
floq config splash 3000 # 3秒に設定
|
|
218
|
+
floq config splash off # スプラッシュ無効化
|
|
219
|
+
floq config splash key # キー入力待ちモード
|
|
220
|
+
|
|
212
221
|
# データベースリセット(全データ削除)
|
|
213
222
|
floq db reset # 確認あり
|
|
214
223
|
floq db reset --force # 確認なし
|
|
@@ -254,6 +263,10 @@ floq config turso --disable
|
|
|
254
263
|
|
|
255
264
|
26種類のテーマが利用可能。`floq config theme` でインタラクティブに選択(j/kで移動)。
|
|
256
265
|
|
|
266
|
+
一部のテーマは**ドラクエRPG風UI**を採用しており、タイトル付きメッセージボックス、2カラムレイアウト、レトロなスプラッシュ画面が表示されます。DQ風テーマ: `turbo-pascal`、`msx`、`pc-98`
|
|
267
|
+
|
|
268
|
+

|
|
269
|
+
|
|
257
270
|
| テーマ | 説明 |
|
|
258
271
|
|--------|------|
|
|
259
272
|
| `modern` | シンプルでクリーン(デフォルト) |
|
package/README.md
CHANGED
|
@@ -4,17 +4,20 @@
|
|
|
4
4
|
|
|
5
5
|
A terminal-based GTD (Getting Things Done) task manager with MS-DOS style themes.
|
|
6
6
|
|
|
7
|
+

|
|
8
|
+
|
|
7
9
|
## Features
|
|
8
10
|
|
|
9
11
|
- **TUI Interface**: Interactive terminal UI built with Ink (React for CLI)
|
|
10
|
-
- **GTD Workflow**: Inbox, Next Actions, Waiting For, Someday/Maybe, Done
|
|
12
|
+
- **GTD Workflow**: Inbox, Next Actions, Waiting For, Someday/Maybe, Done (shows last 7 days)
|
|
11
13
|
- **Kanban Mode**: 3-column kanban board view (TODO, Doing, Done)
|
|
12
14
|
- **Projects**: Organize tasks into projects with progress tracking
|
|
13
|
-
- **Contexts**: Tag tasks with contexts (@work, @home, etc.) and filter by context
|
|
15
|
+
- **Contexts**: Tag tasks with contexts (@work, @home, etc.) and filter by context. New tasks inherit the active context filter
|
|
14
16
|
- **Task Search**: Quick search across all tasks with `/`
|
|
15
17
|
- **Comments**: Add notes and comments to tasks
|
|
16
18
|
- **Cloud Sync**: Optional sync with [Turso](https://turso.tech/) using embedded replicas
|
|
17
|
-
- **Themes**: Multiple themes including MS-DOS nostalgic styles
|
|
19
|
+
- **Themes**: Multiple themes including MS-DOS nostalgic styles and Dragon Quest RPG style
|
|
20
|
+
- **Splash Screen**: Configurable startup splash with Dragon Quest style for retro themes
|
|
18
21
|
- **i18n**: English and Japanese support
|
|
19
22
|
- **Vim-style Navigation**: Use hjkl or arrow keys
|
|
20
23
|
- **Setup Wizard**: First-run wizard for easy configuration
|
|
@@ -209,6 +212,12 @@ floq config mode kanban # Kanban board
|
|
|
209
212
|
floq config db /path/to/custom.db
|
|
210
213
|
floq config db # Reset to default
|
|
211
214
|
|
|
215
|
+
# Splash screen settings
|
|
216
|
+
floq config splash # Show current setting
|
|
217
|
+
floq config splash 3000 # Set to 3 seconds
|
|
218
|
+
floq config splash off # Disable splash screen
|
|
219
|
+
floq config splash key # Wait for key press
|
|
220
|
+
|
|
212
221
|
# Reset database (delete all data)
|
|
213
222
|
floq db reset # With confirmation
|
|
214
223
|
floq db reset --force # Skip confirmation
|
|
@@ -254,6 +263,10 @@ floq config turso --disable
|
|
|
254
263
|
|
|
255
264
|
26 themes available. Use `floq config theme` for interactive selection (j/k to navigate).
|
|
256
265
|
|
|
266
|
+
Some themes feature a **Dragon Quest RPG style UI** with titled message boxes, 2-column layouts, and retro splash screens. Themes with DQ-style: `turbo-pascal`, `msx`, `pc-98`.
|
|
267
|
+
|
|
268
|
+

|
|
269
|
+
|
|
257
270
|
| Theme | Description |
|
|
258
271
|
|-------|-------------|
|
|
259
272
|
| `modern` | Clean, minimal style (default) |
|
package/dist/cli.js
CHANGED
|
@@ -7,7 +7,7 @@ import { listTasks, listProjects } from './commands/list.js';
|
|
|
7
7
|
import { moveTask } from './commands/move.js';
|
|
8
8
|
import { markDone } from './commands/done.js';
|
|
9
9
|
import { addProject, listProjectsCommand, showProject, completeProject, } from './commands/project.js';
|
|
10
|
-
import { showConfig, setLanguage, setDbPath, resetDbPath, setTheme, selectTheme, setViewModeCommand, selectMode, setTurso, disableTurso, syncCommand, resetDatabase } from './commands/config.js';
|
|
10
|
+
import { showConfig, setLanguage, setDbPath, resetDbPath, setTheme, selectTheme, setViewModeCommand, selectMode, setTurso, disableTurso, syncCommand, resetDatabase, setSplashCommand, showSplash } from './commands/config.js';
|
|
11
11
|
import { addComment, listComments } from './commands/comment.js';
|
|
12
12
|
import { listContexts, addContextCommand, removeContextCommand } from './commands/context.js';
|
|
13
13
|
import { runSetupWizard } from './commands/setup.js';
|
|
@@ -155,6 +155,17 @@ configCmd
|
|
|
155
155
|
process.exit(1);
|
|
156
156
|
}
|
|
157
157
|
});
|
|
158
|
+
configCmd
|
|
159
|
+
.command('splash [duration]')
|
|
160
|
+
.description('Set splash screen duration (ms, off=disable, key=wait for key)')
|
|
161
|
+
.action(async (duration) => {
|
|
162
|
+
if (duration !== undefined) {
|
|
163
|
+
await setSplashCommand(duration);
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
await showSplash();
|
|
167
|
+
}
|
|
168
|
+
});
|
|
158
169
|
// Sync command
|
|
159
170
|
program
|
|
160
171
|
.command('sync')
|
|
@@ -9,5 +9,7 @@ export declare function setViewModeCommand(mode: string): Promise<void>;
|
|
|
9
9
|
export declare function selectMode(): Promise<void>;
|
|
10
10
|
export declare function setTurso(url: string, token: string): Promise<void>;
|
|
11
11
|
export declare function disableTurso(): Promise<void>;
|
|
12
|
+
export declare function setSplashCommand(duration: string): Promise<void>;
|
|
13
|
+
export declare function showSplash(): Promise<void>;
|
|
12
14
|
export declare function syncCommand(): Promise<void>;
|
|
13
15
|
export declare function resetDatabase(force: boolean): Promise<void>;
|
package/dist/commands/config.js
CHANGED
|
@@ -3,7 +3,7 @@ import React from 'react';
|
|
|
3
3
|
import { createInterface } from 'readline';
|
|
4
4
|
import { unlinkSync, existsSync, readdirSync } from 'fs';
|
|
5
5
|
import { dirname, basename, join } from 'path';
|
|
6
|
-
import { loadConfig, saveConfig, getDbPath, getViewMode, setViewMode, isTursoEnabled, getTursoConfig, setTursoConfig } from '../config.js';
|
|
6
|
+
import { loadConfig, saveConfig, getDbPath, getViewMode, setViewMode, isTursoEnabled, getTursoConfig, setTursoConfig, getSplashDuration, setSplashDuration } from '../config.js';
|
|
7
7
|
import { CONFIG_FILE } from '../paths.js';
|
|
8
8
|
import { ThemeSelector } from '../ui/ThemeSelector.js';
|
|
9
9
|
import { ModeSelector } from '../ui/ModeSelector.js';
|
|
@@ -13,6 +13,7 @@ const VALID_LOCALES = ['en', 'ja'];
|
|
|
13
13
|
const VALID_VIEW_MODES = ['gtd', 'kanban'];
|
|
14
14
|
export async function showConfig() {
|
|
15
15
|
const config = loadConfig();
|
|
16
|
+
const splashDuration = getSplashDuration();
|
|
16
17
|
console.log('GTD CLI Configuration');
|
|
17
18
|
console.log('─'.repeat(40));
|
|
18
19
|
console.log(`Config file: ${CONFIG_FILE}`);
|
|
@@ -20,6 +21,7 @@ export async function showConfig() {
|
|
|
20
21
|
console.log(`Database: ${getDbPath()}`);
|
|
21
22
|
console.log(`Theme: ${config.theme || 'modern'}`);
|
|
22
23
|
console.log(`View Mode: ${config.viewMode || 'gtd'}`);
|
|
24
|
+
console.log(`Splash: ${splashDuration === 0 ? 'disabled' : splashDuration === -1 ? 'wait for key' : `${splashDuration}ms`}`);
|
|
23
25
|
console.log(`Turso: ${isTursoEnabled() ? 'enabled' : 'disabled'}`);
|
|
24
26
|
if (config.db_path) {
|
|
25
27
|
console.log(` (custom: ${config.db_path})`);
|
|
@@ -115,6 +117,54 @@ export async function disableTurso() {
|
|
|
115
117
|
setTursoConfig(undefined);
|
|
116
118
|
console.log('Turso sync disabled');
|
|
117
119
|
}
|
|
120
|
+
export async function setSplashCommand(duration) {
|
|
121
|
+
let value;
|
|
122
|
+
// Handle special keywords
|
|
123
|
+
if (duration === 'key' || duration === 'wait') {
|
|
124
|
+
value = -1;
|
|
125
|
+
}
|
|
126
|
+
else if (duration === 'off' || duration === 'disable') {
|
|
127
|
+
value = 0;
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
value = parseInt(duration, 10);
|
|
131
|
+
if (isNaN(value)) {
|
|
132
|
+
console.error(`Invalid duration: ${duration}`);
|
|
133
|
+
console.error('Usage: floq config splash <milliseconds|key|off>');
|
|
134
|
+
console.error(' off/0 = disable splash screen');
|
|
135
|
+
console.error(' key = wait for key press');
|
|
136
|
+
console.error(' positive number = duration in milliseconds');
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
if (value < 0) {
|
|
140
|
+
console.error('Duration must be 0 (disabled) or positive number');
|
|
141
|
+
console.error('Use "key" for wait-for-keypress mode');
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
setSplashDuration(value);
|
|
146
|
+
if (value === 0) {
|
|
147
|
+
console.log('Splash screen disabled');
|
|
148
|
+
}
|
|
149
|
+
else if (value === -1) {
|
|
150
|
+
console.log('Splash screen set to wait for key press');
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
console.log(`Splash screen duration set to ${value}ms`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
export async function showSplash() {
|
|
157
|
+
const duration = getSplashDuration();
|
|
158
|
+
if (duration === 0) {
|
|
159
|
+
console.log('Splash screen: disabled');
|
|
160
|
+
}
|
|
161
|
+
else if (duration === -1) {
|
|
162
|
+
console.log('Splash screen: wait for key press');
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
console.log(`Splash screen: ${duration}ms`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
118
168
|
export async function syncCommand() {
|
|
119
169
|
if (!isTursoEnabled()) {
|
|
120
170
|
console.error('Turso sync is not enabled.');
|
package/dist/commands/list.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { eq } from 'drizzle-orm';
|
|
1
|
+
import { eq, and, gte } from 'drizzle-orm';
|
|
2
2
|
import { getDb, schema } from '../db/index.js';
|
|
3
3
|
import { t, fmt } from '../i18n/index.js';
|
|
4
4
|
export async function listTasks(status) {
|
|
@@ -11,10 +11,22 @@ export async function listTasks(status) {
|
|
|
11
11
|
console.error(fmt(i18n.commands.list.validStatuses, { statuses: validStatuses.join(', ') }));
|
|
12
12
|
process.exit(1);
|
|
13
13
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
// For done status, only show tasks from the last week by default
|
|
15
|
+
let tasks;
|
|
16
|
+
if (status === 'done') {
|
|
17
|
+
const oneWeekAgo = new Date();
|
|
18
|
+
oneWeekAgo.setDate(oneWeekAgo.getDate() - 7);
|
|
19
|
+
tasks = await db
|
|
20
|
+
.select()
|
|
21
|
+
.from(schema.tasks)
|
|
22
|
+
.where(and(eq(schema.tasks.status, 'done'), gte(schema.tasks.updatedAt, oneWeekAgo)));
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
tasks = await db
|
|
26
|
+
.select()
|
|
27
|
+
.from(schema.tasks)
|
|
28
|
+
.where(eq(schema.tasks.status, status));
|
|
29
|
+
}
|
|
18
30
|
console.log(`\n${i18n.status[status]} (${tasks.length})`);
|
|
19
31
|
console.log('─'.repeat(40));
|
|
20
32
|
if (tasks.length === 0) {
|
package/dist/config.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ export interface Config {
|
|
|
13
13
|
viewMode: ViewMode;
|
|
14
14
|
turso?: TursoConfig;
|
|
15
15
|
contexts?: string[];
|
|
16
|
+
splashDuration?: number;
|
|
16
17
|
}
|
|
17
18
|
export declare function loadConfig(): Config;
|
|
18
19
|
export declare function saveConfig(updates: Partial<Config>): void;
|
|
@@ -30,3 +31,5 @@ export declare function isFirstRun(): boolean;
|
|
|
30
31
|
export declare function getContexts(): string[];
|
|
31
32
|
export declare function addContext(context: string): boolean;
|
|
32
33
|
export declare function removeContext(context: string): boolean;
|
|
34
|
+
export declare function getSplashDuration(): number;
|
|
35
|
+
export declare function setSplashDuration(duration: number): void;
|
package/dist/config.js
CHANGED
|
@@ -138,3 +138,16 @@ export function removeContext(context) {
|
|
|
138
138
|
saveConfig({ contexts: newContexts });
|
|
139
139
|
return true;
|
|
140
140
|
}
|
|
141
|
+
const DEFAULT_SPLASH_DURATION = 2500; // 2.5 seconds
|
|
142
|
+
export function getSplashDuration() {
|
|
143
|
+
const duration = loadConfig().splashDuration;
|
|
144
|
+
// undefined means use default, 0 means disabled
|
|
145
|
+
if (duration === undefined) {
|
|
146
|
+
return DEFAULT_SPLASH_DURATION;
|
|
147
|
+
}
|
|
148
|
+
return duration;
|
|
149
|
+
}
|
|
150
|
+
export function setSplashDuration(duration) {
|
|
151
|
+
// Allow -1 (wait for key), 0 (disabled), or positive values
|
|
152
|
+
saveConfig({ splashDuration: duration >= 0 ? duration : -1 });
|
|
153
|
+
}
|
package/dist/i18n/en.d.ts
CHANGED
|
@@ -240,6 +240,12 @@ export declare const en: {
|
|
|
240
240
|
turso: string;
|
|
241
241
|
};
|
|
242
242
|
};
|
|
243
|
+
splash: {
|
|
244
|
+
welcome: string;
|
|
245
|
+
subtitle: string;
|
|
246
|
+
subtitleKanban: string;
|
|
247
|
+
pressKey: string;
|
|
248
|
+
};
|
|
243
249
|
setup: {
|
|
244
250
|
welcome: {
|
|
245
251
|
title: string;
|
|
@@ -517,6 +523,12 @@ export type SetupTranslations = {
|
|
|
517
523
|
confirm: string;
|
|
518
524
|
};
|
|
519
525
|
};
|
|
526
|
+
export type SplashTranslations = {
|
|
527
|
+
welcome: string;
|
|
528
|
+
subtitle: string;
|
|
529
|
+
subtitleKanban: string;
|
|
530
|
+
pressKey: string;
|
|
531
|
+
};
|
|
520
532
|
export type Translations = {
|
|
521
533
|
status: Record<string, string>;
|
|
522
534
|
kanban: KanbanTranslations;
|
|
@@ -531,5 +543,6 @@ export type Translations = {
|
|
|
531
543
|
context: Record<string, string>;
|
|
532
544
|
};
|
|
533
545
|
tui: TuiTranslations;
|
|
546
|
+
splash?: SplashTranslations;
|
|
534
547
|
setup: SetupTranslations;
|
|
535
548
|
};
|
package/dist/i18n/en.js
CHANGED
|
@@ -255,6 +255,13 @@ export const en = {
|
|
|
255
255
|
turso: 'turso',
|
|
256
256
|
},
|
|
257
257
|
},
|
|
258
|
+
// Splash screen (Dragon Quest style)
|
|
259
|
+
splash: {
|
|
260
|
+
welcome: 'Welcome!',
|
|
261
|
+
subtitle: 'Your GTD adventure begins',
|
|
262
|
+
subtitleKanban: 'Your Kanban adventure begins',
|
|
263
|
+
pressKey: '▼ Press any key',
|
|
264
|
+
},
|
|
258
265
|
// Setup wizard
|
|
259
266
|
setup: {
|
|
260
267
|
welcome: {
|
package/dist/i18n/ja.js
CHANGED
|
@@ -255,6 +255,13 @@ export const ja = {
|
|
|
255
255
|
turso: 'turso',
|
|
256
256
|
},
|
|
257
257
|
},
|
|
258
|
+
// Splash screen (Dragon Quest style)
|
|
259
|
+
splash: {
|
|
260
|
+
welcome: 'ようこそ!',
|
|
261
|
+
subtitle: 'GTDの冒険がはじまる',
|
|
262
|
+
subtitleKanban: 'Kanbanの冒険がはじまる',
|
|
263
|
+
pressKey: '▼ なにかキーをおしてください',
|
|
264
|
+
},
|
|
258
265
|
// Setup wizard
|
|
259
266
|
setup: {
|
|
260
267
|
welcome: {
|
package/dist/ui/App.js
CHANGED
|
@@ -2,22 +2,25 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import { useState, useEffect, useCallback } from 'react';
|
|
3
3
|
import { Box, Text, useInput, useApp } from 'ink';
|
|
4
4
|
import TextInput from 'ink-text-input';
|
|
5
|
-
import { eq, and } from 'drizzle-orm';
|
|
5
|
+
import { eq, and, gte } from 'drizzle-orm';
|
|
6
6
|
import { v4 as uuidv4 } from 'uuid';
|
|
7
7
|
import { TaskItem } from './components/TaskItem.js';
|
|
8
8
|
import { HelpModal } from './components/HelpModal.js';
|
|
9
9
|
import { FunctionKeyBar } from './components/FunctionKeyBar.js';
|
|
10
10
|
import { SearchBar } from './components/SearchBar.js';
|
|
11
11
|
import { SearchResults } from './components/SearchResults.js';
|
|
12
|
+
import { TitledBox } from './components/TitledBox.js';
|
|
12
13
|
import { SplashScreen } from './SplashScreen.js';
|
|
13
14
|
import { ThemeSelector } from './ThemeSelector.js';
|
|
14
15
|
import { ModeSelector } from './ModeSelector.js';
|
|
15
16
|
import { LanguageSelector } from './LanguageSelector.js';
|
|
16
17
|
import { getDb, schema } from '../db/index.js';
|
|
17
18
|
import { t, fmt } from '../i18n/index.js';
|
|
18
|
-
import { ThemeProvider, useTheme } from './theme/index.js';
|
|
19
|
-
import { getThemeName, getViewMode, setThemeName, setViewMode, setLocale, isTursoEnabled, getContexts, addContext } from '../config.js';
|
|
19
|
+
import { ThemeProvider, useTheme, getTheme } from './theme/index.js';
|
|
20
|
+
import { getThemeName, getViewMode, setThemeName, setViewMode, setLocale, isTursoEnabled, getContexts, addContext, getSplashDuration } from '../config.js';
|
|
20
21
|
import { KanbanBoard } from './components/KanbanBoard.js';
|
|
22
|
+
import { KanbanDQ } from './components/KanbanDQ.js';
|
|
23
|
+
import { GtdDQ } from './components/GtdDQ.js';
|
|
21
24
|
import { VERSION } from '../version.js';
|
|
22
25
|
import { HistoryProvider, useHistory, CreateTaskCommand, DeleteTaskCommand, MoveTaskCommand, LinkTaskCommand, ConvertToProjectCommand, CreateCommentCommand, DeleteCommentCommand, SetContextCommand, } from './history/index.js';
|
|
23
26
|
const TABS = ['inbox', 'next', 'waiting', 'someday', 'projects', 'done'];
|
|
@@ -25,6 +28,8 @@ export function App() {
|
|
|
25
28
|
const [themeName, setThemeNameState] = useState(getThemeName);
|
|
26
29
|
const [viewMode, setViewModeState] = useState(getViewMode);
|
|
27
30
|
const [settingsMode, setSettingsMode] = useState('none');
|
|
31
|
+
const splashDuration = getSplashDuration();
|
|
32
|
+
const [showSplash, setShowSplash] = useState(splashDuration !== 0);
|
|
28
33
|
const [, forceUpdate] = useState({});
|
|
29
34
|
const handleThemeSelect = (theme) => {
|
|
30
35
|
setThemeName(theme);
|
|
@@ -44,6 +49,12 @@ export function App() {
|
|
|
44
49
|
const handleSettingsCancel = () => {
|
|
45
50
|
setSettingsMode('none');
|
|
46
51
|
};
|
|
52
|
+
const currentTheme = getTheme(themeName);
|
|
53
|
+
const useDQStyle = currentTheme.uiStyle === 'titled-box';
|
|
54
|
+
// Show splash screen (all themes, configurable duration)
|
|
55
|
+
if (showSplash) {
|
|
56
|
+
return (_jsx(ThemeProvider, { themeName: themeName, children: _jsx(SplashScreen, { onComplete: () => setShowSplash(false), duration: splashDuration, viewMode: viewMode }) }));
|
|
57
|
+
}
|
|
47
58
|
// Settings selector screens
|
|
48
59
|
if (settingsMode === 'theme-select') {
|
|
49
60
|
return (_jsx(ThemeProvider, { themeName: themeName, children: _jsx(ThemeSelector, { onSelect: handleThemeSelect, onCancel: handleSettingsCancel }) }));
|
|
@@ -54,13 +65,13 @@ export function App() {
|
|
|
54
65
|
if (settingsMode === 'lang-select') {
|
|
55
66
|
return (_jsx(ThemeProvider, { themeName: themeName, children: _jsx(LanguageSelector, { onSelect: handleLocaleSelect, onCancel: handleSettingsCancel }) }));
|
|
56
67
|
}
|
|
57
|
-
return (_jsx(ThemeProvider, { themeName: themeName, children: _jsx(HistoryProvider, { children: viewMode === 'kanban' ? (_jsx(KanbanBoard, { onOpenSettings: setSettingsMode })) : (_jsx(AppContent, { onOpenSettings: setSettingsMode })) }) }));
|
|
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 }))) }) }));
|
|
58
69
|
}
|
|
59
70
|
function AppContent({ onOpenSettings }) {
|
|
60
71
|
const theme = useTheme();
|
|
61
72
|
const { exit } = useApp();
|
|
62
73
|
const history = useHistory();
|
|
63
|
-
const [mode, setMode] = useState('
|
|
74
|
+
const [mode, setMode] = useState('normal');
|
|
64
75
|
const [inputValue, setInputValue] = useState('');
|
|
65
76
|
const [currentListIndex, setCurrentListIndex] = useState(0);
|
|
66
77
|
const [selectedTaskIndex, setSelectedTaskIndex] = useState(0);
|
|
@@ -105,10 +116,21 @@ function AppContent({ onOpenSettings }) {
|
|
|
105
116
|
// Load all tasks (including project children) by status
|
|
106
117
|
const statusList = ['inbox', 'next', 'waiting', 'someday', 'done'];
|
|
107
118
|
for (const status of statusList) {
|
|
119
|
+
// For done tasks, only show those completed in the last week
|
|
120
|
+
const oneWeekAgo = new Date();
|
|
121
|
+
oneWeekAgo.setDate(oneWeekAgo.getDate() - 7);
|
|
122
|
+
const conditions = [
|
|
123
|
+
eq(schema.tasks.status, status),
|
|
124
|
+
eq(schema.tasks.isProject, false),
|
|
125
|
+
];
|
|
126
|
+
// Filter done tasks to last week only
|
|
127
|
+
if (status === 'done') {
|
|
128
|
+
conditions.push(gte(schema.tasks.updatedAt, oneWeekAgo));
|
|
129
|
+
}
|
|
108
130
|
let allTasks = await db
|
|
109
131
|
.select()
|
|
110
132
|
.from(schema.tasks)
|
|
111
|
-
.where(and(
|
|
133
|
+
.where(and(...conditions));
|
|
112
134
|
// Apply context filter
|
|
113
135
|
if (contextFilter !== null) {
|
|
114
136
|
if (contextFilter === '') {
|
|
@@ -205,7 +227,7 @@ function AppContent({ onOpenSettings }) {
|
|
|
205
227
|
setMode('normal');
|
|
206
228
|
}
|
|
207
229
|
}, [tasks]);
|
|
208
|
-
const addTask = useCallback(async (title, parentId) => {
|
|
230
|
+
const addTask = useCallback(async (title, parentId, context) => {
|
|
209
231
|
if (!title.trim())
|
|
210
232
|
return;
|
|
211
233
|
const now = new Date();
|
|
@@ -216,6 +238,7 @@ function AppContent({ onOpenSettings }) {
|
|
|
216
238
|
title: title.trim(),
|
|
217
239
|
status: parentId ? 'next' : 'inbox',
|
|
218
240
|
parentId: parentId || null,
|
|
241
|
+
context: context || null,
|
|
219
242
|
createdAt: now,
|
|
220
243
|
updatedAt: now,
|
|
221
244
|
},
|
|
@@ -298,12 +321,13 @@ function AppContent({ onOpenSettings }) {
|
|
|
298
321
|
setMode('task-detail');
|
|
299
322
|
}
|
|
300
323
|
else if (mode === 'add-to-project' && selectedProject) {
|
|
301
|
-
await addTask(value, selectedProject.id);
|
|
324
|
+
await addTask(value, selectedProject.id, contextFilter && contextFilter !== '' ? contextFilter : null);
|
|
302
325
|
await loadProjectTasks(selectedProject.id);
|
|
303
326
|
setMode('project-detail');
|
|
304
327
|
}
|
|
305
328
|
else {
|
|
306
|
-
|
|
329
|
+
// Pass contextFilter when adding a task, so it inherits the current filter context
|
|
330
|
+
await addTask(value, undefined, contextFilter && contextFilter !== '' ? contextFilter : null);
|
|
307
331
|
setMode('normal');
|
|
308
332
|
}
|
|
309
333
|
}
|
|
@@ -417,11 +441,6 @@ function AppContent({ onOpenSettings }) {
|
|
|
417
441
|
}
|
|
418
442
|
};
|
|
419
443
|
useInput((input, key) => {
|
|
420
|
-
// Skip splash screen on any key
|
|
421
|
-
if (mode === 'splash') {
|
|
422
|
-
setMode('normal');
|
|
423
|
-
return;
|
|
424
|
-
}
|
|
425
444
|
// Handle search mode
|
|
426
445
|
if (mode === 'search') {
|
|
427
446
|
if (key.escape) {
|
|
@@ -965,10 +984,6 @@ function AppContent({ onOpenSettings }) {
|
|
|
965
984
|
return;
|
|
966
985
|
}
|
|
967
986
|
}, { isActive: mode !== 'help' });
|
|
968
|
-
// Splash screen
|
|
969
|
-
if (mode === 'splash') {
|
|
970
|
-
return _jsx(SplashScreen, { onComplete: () => setMode('normal') });
|
|
971
|
-
}
|
|
972
987
|
// Help modal overlay
|
|
973
988
|
if (mode === 'help') {
|
|
974
989
|
return (_jsx(Box, { flexDirection: "column", padding: 1, children: _jsx(HelpModal, { onClose: () => setMode('normal') }) }));
|
|
@@ -990,11 +1005,15 @@ function AppContent({ onOpenSettings }) {
|
|
|
990
1005
|
}) }), mode === 'project-detail' && selectedProject && (_jsxs(_Fragment, { children: [_jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { color: theme.colors.accent, bold: true, children: [theme.name === 'modern' ? '📁 ' : '>> ', selectedProject.title] }), _jsxs(Text, { color: theme.colors.textMuted, children: [" (Esc/b: ", i18n.tui.back || 'back', ")"] })] }), _jsx(Box, { marginBottom: 1, children: _jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.projectTasks || 'Tasks', " (", projectTasks.length, ")"] }) })] })), (mode === 'task-detail' || mode === 'add-comment') && selectedTask && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { color: theme.colors.accent, bold: true, children: [theme.name === 'modern' ? '📋 ' : '>> ', i18n.tui.taskDetailTitle || 'Task Details'] }), _jsxs(Text, { color: theme.colors.textMuted, children: [" (Esc/b: ", i18n.tui.back || 'back', ", ", i18n.tui.commentHint || 'i: add comment', ")"] })] }), _jsxs(Box, { flexDirection: "column", borderStyle: theme.borders.list, borderColor: theme.colors.border, paddingX: 1, paddingY: 1, marginBottom: 1, children: [_jsx(Text, { color: theme.colors.text, bold: true, children: selectedTask.title }), selectedTask.description && (_jsx(Text, { color: theme.colors.textMuted, children: selectedTask.description })), _jsxs(Box, { marginTop: 1, children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.taskDetailStatus, ": "] }), _jsxs(Text, { color: theme.colors.accent, children: [i18n.status[selectedTask.status], selectedTask.waitingFor && ` (${selectedTask.waitingFor})`] })] }), _jsxs(Box, { children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.context?.label || 'Context', ": "] }), _jsx(Text, { color: theme.colors.accent, children: selectedTask.context ? `@${selectedTask.context}` : (i18n.tui.context?.none || 'No context') })] })] }), _jsx(Box, { marginBottom: 1, children: _jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.comments || 'Comments', " (", taskComments.length, ")"] }) }), _jsx(Box, { flexDirection: "column", borderStyle: theme.borders.list, borderColor: theme.colors.border, paddingX: 1, paddingY: 1, minHeight: 5, children: taskComments.length === 0 ? (_jsx(Text, { color: theme.colors.textMuted, italic: true, children: i18n.tui.noComments || 'No comments yet' })) : (taskComments.map((comment, index) => {
|
|
991
1006
|
const isSelected = index === selectedCommentIndex && mode === 'task-detail';
|
|
992
1007
|
return (_jsxs(Box, { flexDirection: "row", marginBottom: 1, children: [_jsx(Text, { color: isSelected ? theme.colors.textSelected : theme.colors.textMuted, children: isSelected ? theme.style.selectedPrefix : theme.style.unselectedPrefix }), _jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.textMuted, children: ["[", comment.createdAt.toLocaleString(), "]"] }), _jsx(Text, { color: isSelected ? theme.colors.textSelected : theme.colors.text, bold: isSelected, children: comment.content })] })] }, comment.id));
|
|
993
|
-
})) }), mode === 'add-comment' && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: theme.colors.secondary, bold: true, children: i18n.tui.addComment || 'New comment: ' }), _jsx(TextInput, { value: inputValue, onChange: setInputValue, onSubmit: handleInputSubmit, placeholder: "" }), _jsxs(Text, { color: theme.colors.textMuted, children: [" ", i18n.tui.inputHelp] })] }))] })), mode === 'search' && searchQuery && (_jsx(SearchResults, { results: searchResults, selectedIndex: searchResultIndex, query: searchQuery })), mode !== 'task-detail' && mode !== 'add-comment' && mode !== 'search' && (_jsx(
|
|
1008
|
+
})) }), mode === 'add-comment' && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: theme.colors.secondary, bold: true, children: i18n.tui.addComment || 'New comment: ' }), _jsx(TextInput, { value: inputValue, onChange: setInputValue, onSubmit: handleInputSubmit, placeholder: "" }), _jsxs(Text, { color: theme.colors.textMuted, children: [" ", i18n.tui.inputHelp] })] }))] })), mode === 'search' && searchQuery && (_jsx(SearchResults, { results: searchResults, selectedIndex: searchResultIndex, query: searchQuery })), mode !== 'task-detail' && mode !== 'add-comment' && mode !== 'search' && (theme.uiStyle === 'titled-box' ? (_jsx(TitledBox, { title: mode === 'project-detail' && selectedProject ? selectedProject.title : getTabLabel(currentTab), borderColor: theme.colors.border, minHeight: 10, children: currentTasks.length === 0 ? (_jsx(Text, { color: theme.colors.textMuted, italic: true, children: i18n.tui.noTasks })) : (currentTasks.map((task, index) => {
|
|
1009
|
+
const parentProject = getParentProject(task.parentId);
|
|
1010
|
+
const progress = currentTab === 'projects' ? projectProgress[task.id] : undefined;
|
|
1011
|
+
return (_jsx(TaskItem, { task: task, isSelected: index === selectedTaskIndex, projectName: parentProject?.title, progress: progress }, task.id));
|
|
1012
|
+
})) })) : (_jsx(Box, { flexDirection: "column", borderStyle: theme.borders.list, borderColor: theme.colors.border, paddingX: 1, paddingY: 1, minHeight: 10, children: currentTasks.length === 0 ? (_jsx(Text, { color: theme.colors.textMuted, italic: true, children: i18n.tui.noTasks })) : (currentTasks.map((task, index) => {
|
|
994
1013
|
const parentProject = getParentProject(task.parentId);
|
|
995
1014
|
const progress = currentTab === 'projects' ? projectProgress[task.id] : undefined;
|
|
996
1015
|
return (_jsx(TaskItem, { task: task, isSelected: index === selectedTaskIndex, projectName: parentProject?.title, progress: progress }, task.id));
|
|
997
|
-
})) })), (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
|
|
1016
|
+
})) }))), (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
|
|
998
1017
|
? `${i18n.tui.newTask}[${selectedProject.title}] `
|
|
999
1018
|
: i18n.tui.newTask }), _jsx(TextInput, { value: inputValue, onChange: setInputValue, onSubmit: handleInputSubmit, placeholder: i18n.tui.placeholder }), _jsxs(Text, { color: theme.colors.textMuted, children: [" ", i18n.tui.inputHelp] })] })), mode === 'move-to-waiting' && taskToWaiting && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: theme.colors.secondary, bold: true, children: i18n.tui.waitingFor }), _jsx(TextInput, { value: inputValue, onChange: setInputValue, onSubmit: handleInputSubmit, placeholder: "" }), _jsxs(Text, { color: theme.colors.textMuted, children: [" ", i18n.tui.inputHelp] })] })), mode === 'select-project' && taskToLink && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.selectProject || 'Select project for', ": ", taskToLink.title] }), _jsx(Box, { flexDirection: "column", marginTop: 1, borderStyle: theme.borders.list, borderColor: theme.colors.borderActive, paddingX: 1, children: tasks.projects.map((project, index) => (_jsxs(Text, { color: index === projectSelectIndex ? theme.colors.textSelected : theme.colors.text, bold: index === projectSelectIndex, children: [index === projectSelectIndex ? theme.style.selectedPrefix : theme.style.unselectedPrefix, project.title] }, project.id))) }), _jsx(Text, { color: theme.colors.textMuted, children: i18n.tui.selectProjectHelp || 'j/k: select, Enter: confirm, Esc: cancel' })] })), mode === 'context-filter' && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: theme.colors.secondary, bold: true, children: i18n.tui.context?.filter || 'Filter by context' }), _jsx(Box, { flexDirection: "column", marginTop: 1, borderStyle: theme.borders.list, borderColor: theme.colors.borderActive, paddingX: 1, children: ['all', 'none', ...availableContexts].map((ctx, index) => {
|
|
1000
1019
|
const label = ctx === 'all'
|
|
@@ -2,6 +2,7 @@ import React from 'react';
|
|
|
2
2
|
interface SplashScreenProps {
|
|
3
3
|
onComplete: () => void;
|
|
4
4
|
duration?: number;
|
|
5
|
+
viewMode?: 'gtd' | 'kanban';
|
|
5
6
|
}
|
|
6
|
-
export declare function SplashScreen({ onComplete, duration }: SplashScreenProps): React.ReactElement;
|
|
7
|
+
export declare function SplashScreen({ onComplete, duration, viewMode }: SplashScreenProps): React.ReactElement;
|
|
7
8
|
export {};
|