floq 0.0.1 → 0.1.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 +69 -21
- package/README.md +69 -21
- package/dist/cli.js +27 -1
- package/dist/commands/add.js +5 -6
- package/dist/commands/config.d.ts +3 -0
- package/dist/commands/config.js +43 -16
- package/dist/commands/done.js +4 -6
- package/dist/commands/list.js +9 -13
- package/dist/commands/move.js +4 -6
- package/dist/commands/project.js +18 -26
- package/dist/config.d.ts +10 -1
- package/dist/config.js +14 -0
- package/dist/db/index.d.ts +5 -4
- package/dist/db/index.js +107 -32
- package/dist/i18n/en.d.ts +21 -0
- package/dist/i18n/en.js +11 -0
- package/dist/i18n/ja.js +11 -0
- package/dist/index.js +23 -1
- package/dist/ui/App.js +106 -113
- package/dist/ui/ThemeSelector.d.ts +1 -1
- package/dist/ui/ThemeSelector.js +23 -10
- package/dist/ui/components/FunctionKeyBar.d.ts +5 -4
- package/dist/ui/components/FunctionKeyBar.js +19 -15
- package/dist/ui/theme/themes.d.ts +12 -0
- package/dist/ui/theme/themes.js +495 -3
- package/dist/ui/theme/types.d.ts +1 -1
- package/package.json +2 -1
package/dist/config.js
CHANGED
|
@@ -39,6 +39,16 @@ export function saveConfig(updates) {
|
|
|
39
39
|
// Ignore errors
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
|
+
export function getTursoConfig() {
|
|
43
|
+
return loadConfig().turso;
|
|
44
|
+
}
|
|
45
|
+
export function setTursoConfig(config) {
|
|
46
|
+
saveConfig({ turso: config });
|
|
47
|
+
}
|
|
48
|
+
export function isTursoEnabled() {
|
|
49
|
+
const turso = getTursoConfig();
|
|
50
|
+
return turso !== undefined && turso.url !== '' && turso.authToken !== '';
|
|
51
|
+
}
|
|
42
52
|
export function getDbPath() {
|
|
43
53
|
const config = loadConfig();
|
|
44
54
|
if (config.db_path) {
|
|
@@ -48,6 +58,10 @@ export function getDbPath() {
|
|
|
48
58
|
}
|
|
49
59
|
return join(DATA_DIR, config.db_path);
|
|
50
60
|
}
|
|
61
|
+
// Turso モードでは別のDBファイルを使用(embedded replica 用)
|
|
62
|
+
if (isTursoEnabled()) {
|
|
63
|
+
return join(DATA_DIR, 'gtd-turso.db');
|
|
64
|
+
}
|
|
51
65
|
return join(DATA_DIR, 'gtd.db');
|
|
52
66
|
}
|
|
53
67
|
export function getLocale() {
|
package/dist/db/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { drizzle } from 'drizzle-orm/libsql';
|
|
2
2
|
import * as schema from './schema.js';
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
export type DbInstance = ReturnType<typeof drizzle<typeof schema>>;
|
|
4
|
+
export declare function initDb(): Promise<void>;
|
|
5
|
+
export declare function getDb(): DbInstance;
|
|
6
|
+
export declare function syncDb(): Promise<void>;
|
|
6
7
|
export { schema };
|
package/dist/db/index.js
CHANGED
|
@@ -1,45 +1,83 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import { drizzle } from 'drizzle-orm/libsql';
|
|
2
|
+
import { createClient } from '@libsql/client';
|
|
3
3
|
import { mkdirSync, existsSync } from 'fs';
|
|
4
4
|
import { dirname } from 'path';
|
|
5
5
|
import * as schema from './schema.js';
|
|
6
|
-
import { getDbPath } from '../config.js';
|
|
6
|
+
import { getDbPath, isTursoEnabled, getTursoConfig } from '../config.js';
|
|
7
7
|
function ensureDbDir(dbPath) {
|
|
8
8
|
const dbDir = dirname(dbPath);
|
|
9
9
|
if (!existsSync(dbDir)) {
|
|
10
10
|
mkdirSync(dbDir, { recursive: true });
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
let client = null;
|
|
14
|
+
let dbInstance = null;
|
|
15
|
+
// リモート DB のスキーマを初期化
|
|
16
|
+
async function initializeRemoteSchema(tursoUrl, authToken) {
|
|
17
|
+
// リモート専用クライアントでスキーマを作成
|
|
18
|
+
const remoteClient = createClient({
|
|
19
|
+
url: tursoUrl,
|
|
20
|
+
authToken: authToken,
|
|
21
|
+
});
|
|
22
|
+
try {
|
|
23
|
+
const tableInfoResult = await remoteClient.execute("PRAGMA table_info(tasks)");
|
|
24
|
+
const tableInfo = tableInfoResult.rows;
|
|
25
|
+
const tableExists = tableInfo.length > 0;
|
|
26
|
+
if (!tableExists) {
|
|
27
|
+
// Fresh install: create new schema on remote
|
|
28
|
+
await remoteClient.execute(`
|
|
29
|
+
CREATE TABLE IF NOT EXISTS tasks (
|
|
30
|
+
id TEXT PRIMARY KEY,
|
|
31
|
+
title TEXT NOT NULL,
|
|
32
|
+
description TEXT,
|
|
33
|
+
status TEXT NOT NULL DEFAULT 'inbox' CHECK(status IN ('inbox', 'next', 'waiting', 'someday', 'done')),
|
|
34
|
+
is_project INTEGER NOT NULL DEFAULT 0,
|
|
35
|
+
parent_id TEXT,
|
|
36
|
+
waiting_for TEXT,
|
|
37
|
+
due_date INTEGER,
|
|
38
|
+
created_at INTEGER NOT NULL,
|
|
39
|
+
updated_at INTEGER NOT NULL
|
|
40
|
+
)
|
|
41
|
+
`);
|
|
42
|
+
await remoteClient.execute("CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status)");
|
|
43
|
+
await remoteClient.execute("CREATE INDEX IF NOT EXISTS idx_tasks_parent_id ON tasks(parent_id)");
|
|
44
|
+
await remoteClient.execute("CREATE INDEX IF NOT EXISTS idx_tasks_is_project ON tasks(is_project)");
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
remoteClient.close();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// ローカル DB のスキーマを初期化(ローカルモード用)
|
|
52
|
+
async function initializeLocalSchema() {
|
|
53
|
+
if (!client)
|
|
54
|
+
return;
|
|
55
|
+
const tableInfoResult = await client.execute("PRAGMA table_info(tasks)");
|
|
56
|
+
const tableInfo = tableInfoResult.rows;
|
|
16
57
|
const hasProjectId = tableInfo.some(col => col.name === 'project_id');
|
|
17
58
|
const hasIsProject = tableInfo.some(col => col.name === 'is_project');
|
|
18
59
|
const tableExists = tableInfo.length > 0;
|
|
19
60
|
if (tableExists && hasProjectId && !hasIsProject) {
|
|
20
61
|
// Migration: old schema -> new schema
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const projects = sqlite.prepare("SELECT * FROM projects").all();
|
|
26
|
-
const insertStmt = sqlite.prepare(`
|
|
27
|
-
INSERT INTO tasks (id, title, description, status, is_project, parent_id, waiting_for, due_date, created_at, updated_at)
|
|
28
|
-
VALUES (?, ?, ?, ?, 1, NULL, NULL, NULL, ?, ?)
|
|
29
|
-
`);
|
|
62
|
+
await client.execute("ALTER TABLE tasks ADD COLUMN is_project INTEGER NOT NULL DEFAULT 0");
|
|
63
|
+
await client.execute("ALTER TABLE tasks ADD COLUMN parent_id TEXT");
|
|
64
|
+
const projectsResult = await client.execute("SELECT * FROM projects");
|
|
65
|
+
const projects = projectsResult.rows;
|
|
30
66
|
for (const p of projects) {
|
|
31
67
|
const newStatus = p.status === 'active' ? 'next' : (p.status === 'completed' ? 'done' : p.status);
|
|
32
|
-
|
|
68
|
+
await client.execute({
|
|
69
|
+
sql: `INSERT INTO tasks (id, title, description, status, is_project, parent_id, waiting_for, due_date, created_at, updated_at)
|
|
70
|
+
VALUES (?, ?, ?, ?, 1, NULL, NULL, NULL, ?, ?)`,
|
|
71
|
+
args: [p.id, p.name, p.description, newStatus, p.created_at, p.updated_at]
|
|
72
|
+
});
|
|
33
73
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
sqlite.prepare("CREATE INDEX IF NOT EXISTS idx_tasks_parent_id ON tasks(parent_id)").run();
|
|
38
|
-
sqlite.prepare("CREATE INDEX IF NOT EXISTS idx_tasks_is_project ON tasks(is_project)").run();
|
|
74
|
+
await client.execute("UPDATE tasks SET parent_id = project_id WHERE project_id IS NOT NULL");
|
|
75
|
+
await client.execute("CREATE INDEX IF NOT EXISTS idx_tasks_parent_id ON tasks(parent_id)");
|
|
76
|
+
await client.execute("CREATE INDEX IF NOT EXISTS idx_tasks_is_project ON tasks(is_project)");
|
|
39
77
|
}
|
|
40
78
|
else if (!tableExists) {
|
|
41
79
|
// Fresh install: create new schema
|
|
42
|
-
|
|
80
|
+
await client.execute(`
|
|
43
81
|
CREATE TABLE IF NOT EXISTS tasks (
|
|
44
82
|
id TEXT PRIMARY KEY,
|
|
45
83
|
title TEXT NOT NULL,
|
|
@@ -52,21 +90,58 @@ function initializeSchema(sqlite) {
|
|
|
52
90
|
created_at INTEGER NOT NULL,
|
|
53
91
|
updated_at INTEGER NOT NULL
|
|
54
92
|
)
|
|
55
|
-
`)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
93
|
+
`);
|
|
94
|
+
await client.execute("CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status)");
|
|
95
|
+
await client.execute("CREATE INDEX IF NOT EXISTS idx_tasks_parent_id ON tasks(parent_id)");
|
|
96
|
+
await client.execute("CREATE INDEX IF NOT EXISTS idx_tasks_is_project ON tasks(is_project)");
|
|
59
97
|
}
|
|
60
98
|
}
|
|
61
|
-
|
|
99
|
+
// DB 初期化
|
|
100
|
+
export async function initDb() {
|
|
101
|
+
if (dbInstance)
|
|
102
|
+
return;
|
|
103
|
+
const dbPath = getDbPath();
|
|
104
|
+
ensureDbDir(dbPath);
|
|
105
|
+
if (isTursoEnabled()) {
|
|
106
|
+
// Turso embedded replica モード
|
|
107
|
+
const turso = getTursoConfig();
|
|
108
|
+
// 1. まずリモートにスキーマを作成
|
|
109
|
+
await initializeRemoteSchema(turso.url, turso.authToken);
|
|
110
|
+
// 2. embedded replica クライアントを作成
|
|
111
|
+
client = createClient({
|
|
112
|
+
url: `file:${dbPath}`,
|
|
113
|
+
syncUrl: turso.url,
|
|
114
|
+
authToken: turso.authToken,
|
|
115
|
+
syncInterval: 60, // 60秒ごとに自動同期
|
|
116
|
+
});
|
|
117
|
+
// 3. 初回同期(リモートからローカルにデータをプル)
|
|
118
|
+
await client.sync();
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
// ローカルモード(libsql でローカルファイルのみ)
|
|
122
|
+
client = createClient({
|
|
123
|
+
url: `file:${dbPath}`,
|
|
124
|
+
});
|
|
125
|
+
// ローカルモードではスキーマをローカルに作成
|
|
126
|
+
await initializeLocalSchema();
|
|
127
|
+
}
|
|
128
|
+
dbInstance = drizzle(client, { schema });
|
|
129
|
+
}
|
|
130
|
+
// DB インスタンス取得
|
|
62
131
|
export function getDb() {
|
|
63
132
|
if (!dbInstance) {
|
|
64
|
-
|
|
65
|
-
ensureDbDir(dbPath);
|
|
66
|
-
const sqlite = new Database(dbPath);
|
|
67
|
-
initializeSchema(sqlite);
|
|
68
|
-
dbInstance = drizzle(sqlite, { schema });
|
|
133
|
+
throw new Error('Database not initialized. Call initDb() first.');
|
|
69
134
|
}
|
|
70
135
|
return dbInstance;
|
|
71
136
|
}
|
|
137
|
+
// 手動同期(Turso モード時のみ有効)
|
|
138
|
+
export async function syncDb() {
|
|
139
|
+
if (!client) {
|
|
140
|
+
throw new Error('Database not initialized');
|
|
141
|
+
}
|
|
142
|
+
if (!isTursoEnabled()) {
|
|
143
|
+
throw new Error('Turso sync is not enabled');
|
|
144
|
+
}
|
|
145
|
+
await client.sync();
|
|
146
|
+
}
|
|
72
147
|
export { schema };
|
package/dist/i18n/en.d.ts
CHANGED
|
@@ -74,6 +74,16 @@ export declare const en: {
|
|
|
74
74
|
selectProject: string;
|
|
75
75
|
selectProjectHelp: string;
|
|
76
76
|
back: string;
|
|
77
|
+
keyBar: {
|
|
78
|
+
add: string;
|
|
79
|
+
done: string;
|
|
80
|
+
next: string;
|
|
81
|
+
someday: string;
|
|
82
|
+
inbox: string;
|
|
83
|
+
project: string;
|
|
84
|
+
help: string;
|
|
85
|
+
quit: string;
|
|
86
|
+
};
|
|
77
87
|
help: {
|
|
78
88
|
title: string;
|
|
79
89
|
navigation: string;
|
|
@@ -122,6 +132,16 @@ export type HelpTranslations = {
|
|
|
122
132
|
quit: string;
|
|
123
133
|
closeHint: string;
|
|
124
134
|
};
|
|
135
|
+
export type KeyBarTranslations = {
|
|
136
|
+
add: string;
|
|
137
|
+
done: string;
|
|
138
|
+
next: string;
|
|
139
|
+
someday: string;
|
|
140
|
+
inbox: string;
|
|
141
|
+
project: string;
|
|
142
|
+
help: string;
|
|
143
|
+
quit: string;
|
|
144
|
+
};
|
|
125
145
|
export type TuiTranslations = {
|
|
126
146
|
title: string;
|
|
127
147
|
helpHint: string;
|
|
@@ -144,6 +164,7 @@ export type TuiTranslations = {
|
|
|
144
164
|
selectProject: string;
|
|
145
165
|
selectProjectHelp: string;
|
|
146
166
|
back: string;
|
|
167
|
+
keyBar: KeyBarTranslations;
|
|
147
168
|
help: HelpTranslations;
|
|
148
169
|
};
|
|
149
170
|
export type Translations = {
|
package/dist/i18n/en.js
CHANGED
|
@@ -80,6 +80,17 @@ export const en = {
|
|
|
80
80
|
selectProject: 'Select project for',
|
|
81
81
|
selectProjectHelp: 'j/k: select, Enter: confirm, Esc: cancel',
|
|
82
82
|
back: 'back',
|
|
83
|
+
// Action key bar labels
|
|
84
|
+
keyBar: {
|
|
85
|
+
add: 'Add',
|
|
86
|
+
done: 'Done',
|
|
87
|
+
next: 'Next',
|
|
88
|
+
someday: 'Someday',
|
|
89
|
+
inbox: 'Inbox',
|
|
90
|
+
project: 'Project',
|
|
91
|
+
help: 'Help',
|
|
92
|
+
quit: 'Quit',
|
|
93
|
+
},
|
|
83
94
|
// Help modal
|
|
84
95
|
help: {
|
|
85
96
|
title: 'Keyboard Shortcuts',
|
package/dist/i18n/ja.js
CHANGED
|
@@ -80,6 +80,17 @@ export const ja = {
|
|
|
80
80
|
selectProject: 'プロジェクトを選択',
|
|
81
81
|
selectProjectHelp: 'j/k: 選択, Enter: 確定, Esc: キャンセル',
|
|
82
82
|
back: '戻る',
|
|
83
|
+
// Action key bar labels
|
|
84
|
+
keyBar: {
|
|
85
|
+
add: '追加',
|
|
86
|
+
done: '完了',
|
|
87
|
+
next: '次へ',
|
|
88
|
+
someday: 'いつか',
|
|
89
|
+
inbox: 'Inbox',
|
|
90
|
+
project: 'プロジェクト',
|
|
91
|
+
help: 'ヘルプ',
|
|
92
|
+
quit: '終了',
|
|
93
|
+
},
|
|
83
94
|
// Help modal
|
|
84
95
|
help: {
|
|
85
96
|
title: 'キーボードショートカット',
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,25 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { program } from './cli.js';
|
|
3
|
-
|
|
3
|
+
import { initDb } from './db/index.js';
|
|
4
|
+
import { isTursoEnabled, getTursoConfig } from './config.js';
|
|
5
|
+
async function main() {
|
|
6
|
+
// 起動時にモードを表示(TUI以外のコマンド時)
|
|
7
|
+
const args = process.argv.slice(2);
|
|
8
|
+
const isTuiMode = args.length === 0;
|
|
9
|
+
const isConfigCommand = args[0] === 'config';
|
|
10
|
+
const isSyncCommand = args[0] === 'sync';
|
|
11
|
+
// config/syncコマンド以外でTursoモードの場合は接続先を表示
|
|
12
|
+
if (!isTuiMode && !isConfigCommand && !isSyncCommand && isTursoEnabled()) {
|
|
13
|
+
const turso = getTursoConfig();
|
|
14
|
+
if (turso) {
|
|
15
|
+
const host = new URL(turso.url).host;
|
|
16
|
+
console.log(`🔄 Turso sync: ${host}`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
// configコマンドはDB不要なのでスキップ
|
|
20
|
+
if (!isConfigCommand) {
|
|
21
|
+
await initDb();
|
|
22
|
+
}
|
|
23
|
+
program.parse();
|
|
24
|
+
}
|
|
25
|
+
main().catch(console.error);
|