@mattli/dotmd 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.md +77 -0
- package/dist/cli/commands/init.d.ts +4 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +23 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/install-hook.d.ts +2 -0
- package/dist/cli/commands/install-hook.d.ts.map +1 -0
- package/dist/cli/commands/install-hook.js +31 -0
- package/dist/cli/commands/install-hook.js.map +1 -0
- package/dist/cli/commands/scan.d.ts +5 -0
- package/dist/cli/commands/scan.d.ts.map +1 -0
- package/dist/cli/commands/scan.js +75 -0
- package/dist/cli/commands/scan.js.map +1 -0
- package/dist/cli/commands/serve.d.ts +4 -0
- package/dist/cli/commands/serve.d.ts.map +1 -0
- package/dist/cli/commands/serve.js +8 -0
- package/dist/cli/commands/serve.js.map +1 -0
- package/dist/cli/commands/status.d.ts +2 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/status.js +70 -0
- package/dist/cli/commands/status.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +38 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/config/defaults.d.ts +13 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +36 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/loader.d.ts +4 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +28 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/dashboard/layout.d.ts +6 -0
- package/dist/dashboard/layout.d.ts.map +1 -0
- package/dist/dashboard/layout.js +47 -0
- package/dist/dashboard/layout.js.map +1 -0
- package/dist/dashboard/server.d.ts +5 -0
- package/dist/dashboard/server.d.ts.map +1 -0
- package/dist/dashboard/server.js +305 -0
- package/dist/dashboard/server.js.map +1 -0
- package/dist/dashboard/settings-client.d.ts +2 -0
- package/dist/dashboard/settings-client.d.ts.map +1 -0
- package/dist/dashboard/settings-client.js +310 -0
- package/dist/dashboard/settings-client.js.map +1 -0
- package/dist/dashboard/settings.d.ts +3 -0
- package/dist/dashboard/settings.d.ts.map +1 -0
- package/dist/dashboard/settings.js +99 -0
- package/dist/dashboard/settings.js.map +1 -0
- package/dist/dashboard/views.d.ts +8 -0
- package/dist/dashboard/views.d.ts.map +1 -0
- package/dist/dashboard/views.js +694 -0
- package/dist/dashboard/views.js.map +1 -0
- package/dist/dashboard/wizard-client.d.ts +2 -0
- package/dist/dashboard/wizard-client.d.ts.map +1 -0
- package/dist/dashboard/wizard-client.js +266 -0
- package/dist/dashboard/wizard-client.js.map +1 -0
- package/dist/dashboard/wizard.d.ts +9 -0
- package/dist/dashboard/wizard.d.ts.map +1 -0
- package/dist/dashboard/wizard.js +236 -0
- package/dist/dashboard/wizard.js.map +1 -0
- package/dist/scanner/git.d.ts +8 -0
- package/dist/scanner/git.d.ts.map +1 -0
- package/dist/scanner/git.js +34 -0
- package/dist/scanner/git.js.map +1 -0
- package/dist/scanner/index.d.ts +10 -0
- package/dist/scanner/index.d.ts.map +1 -0
- package/dist/scanner/index.js +60 -0
- package/dist/scanner/index.js.map +1 -0
- package/dist/storage/db.d.ts +3 -0
- package/dist/storage/db.d.ts.map +1 -0
- package/dist/storage/db.js +52 -0
- package/dist/storage/db.js.map +1 -0
- package/dist/storage/snapshots.d.ts +43 -0
- package/dist/storage/snapshots.d.ts.map +1 -0
- package/dist/storage/snapshots.js +102 -0
- package/dist/storage/snapshots.js.map +1 -0
- package/package.json +60 -0
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import { execFile } from "child_process";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import os from "os";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { Hono } from "hono";
|
|
6
|
+
import { serve } from "@hono/node-server";
|
|
7
|
+
import { getDb } from "../storage/db.js";
|
|
8
|
+
import { getTrackedFiles, getFileSnapshots, getRecentChanges, categorizeFile, } from "../storage/snapshots.js";
|
|
9
|
+
import { loadConfig, saveConfig } from "../config/loader.js";
|
|
10
|
+
import { scanFiles, discoverFiles } from "../scanner/index.js";
|
|
11
|
+
import { CONFIG_PATH, DEFAULT_CONFIG, SUGGESTED_ROOTS, SUGGESTED_PATTERNS, expandHome, } from "../config/defaults.js";
|
|
12
|
+
import { layout } from "./layout.js";
|
|
13
|
+
import { fileListPage, fileDetailPage, timelinePage } from "./views.js";
|
|
14
|
+
import { setupFoldersPage, setupPatternsPage, setupPreviewPage, setupCompletePage, } from "./wizard.js";
|
|
15
|
+
import { WIZARD_CLIENT_SCRIPT } from "./wizard-client.js";
|
|
16
|
+
import { settingsPage } from "./settings.js";
|
|
17
|
+
import { SETTINGS_CLIENT_SCRIPT } from "./settings-client.js";
|
|
18
|
+
export function createApp(db) {
|
|
19
|
+
const app = new Hono();
|
|
20
|
+
// Prevent back-forward cache so unseen indicators stay accurate
|
|
21
|
+
app.use("*", async (c, next) => {
|
|
22
|
+
await next();
|
|
23
|
+
c.header("Cache-Control", "no-store");
|
|
24
|
+
});
|
|
25
|
+
// Redirect to setup if no config exists
|
|
26
|
+
app.get("/", (c) => {
|
|
27
|
+
if (!fs.existsSync(CONFIG_PATH)) {
|
|
28
|
+
return c.redirect("/setup");
|
|
29
|
+
}
|
|
30
|
+
return c.redirect("/timeline");
|
|
31
|
+
});
|
|
32
|
+
// Home — file list
|
|
33
|
+
app.get("/files", (c) => {
|
|
34
|
+
const config = loadConfig();
|
|
35
|
+
scanFiles(db, config);
|
|
36
|
+
const files = getTrackedFiles(db);
|
|
37
|
+
const recent = getRecentChanges(db, undefined, 100);
|
|
38
|
+
// All last-updated times (for displaying timestamps)
|
|
39
|
+
const lastUpdated = new Map();
|
|
40
|
+
// Unseen changes only (for the yellow dot)
|
|
41
|
+
const unseenChanges = new Set();
|
|
42
|
+
const viewedTimes = new Map();
|
|
43
|
+
for (const file of files) {
|
|
44
|
+
viewedTimes.set(file.id, file.last_viewed_at || null);
|
|
45
|
+
}
|
|
46
|
+
for (const change of recent) {
|
|
47
|
+
if (change.diff && !lastUpdated.has(change.file_id)) {
|
|
48
|
+
lastUpdated.set(change.file_id, change.created_at);
|
|
49
|
+
const lastViewed = viewedTimes.get(change.file_id);
|
|
50
|
+
if (!lastViewed || change.created_at > lastViewed) {
|
|
51
|
+
unseenChanges.add(change.file_id);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return c.html(layout("Files", fileListPage(files, lastUpdated, unseenChanges)));
|
|
56
|
+
});
|
|
57
|
+
// File detail
|
|
58
|
+
app.get("/files/:id", (c) => {
|
|
59
|
+
const config = loadConfig();
|
|
60
|
+
scanFiles(db, config);
|
|
61
|
+
const id = parseInt(c.req.param("id"), 10);
|
|
62
|
+
const file = db
|
|
63
|
+
.prepare("SELECT * FROM tracked_files WHERE id = ?")
|
|
64
|
+
.get(id);
|
|
65
|
+
if (!file) {
|
|
66
|
+
return c.html(layout("Not Found", "<p>File not found.</p>"), 404);
|
|
67
|
+
}
|
|
68
|
+
// Mark file as viewed
|
|
69
|
+
db.prepare("UPDATE tracked_files SET last_viewed_at = datetime('now') WHERE id = ?").run(id);
|
|
70
|
+
const PAGE_SIZE = 50;
|
|
71
|
+
const snapshots = getFileSnapshots(db, id, PAGE_SIZE + 1);
|
|
72
|
+
const hasMore = snapshots.length > PAGE_SIZE;
|
|
73
|
+
if (hasMore)
|
|
74
|
+
snapshots.pop();
|
|
75
|
+
return c.html(layout(file.path, fileDetailPage(file, snapshots, hasMore)));
|
|
76
|
+
});
|
|
77
|
+
// Timeline
|
|
78
|
+
app.get("/timeline", (c) => {
|
|
79
|
+
const config = loadConfig();
|
|
80
|
+
scanFiles(db, config);
|
|
81
|
+
const PAGE_SIZE = 50;
|
|
82
|
+
const changes = getRecentChanges(db, undefined, PAGE_SIZE + 1);
|
|
83
|
+
const hasMore = changes.length > PAGE_SIZE;
|
|
84
|
+
if (hasMore)
|
|
85
|
+
changes.pop();
|
|
86
|
+
// Build unseen set using last_viewed_at per file
|
|
87
|
+
const unseenChanges = new Set();
|
|
88
|
+
const files = getTrackedFiles(db);
|
|
89
|
+
const viewedTimes = new Map();
|
|
90
|
+
for (const file of files) {
|
|
91
|
+
viewedTimes.set(file.id, file.last_viewed_at || null);
|
|
92
|
+
}
|
|
93
|
+
for (const change of changes) {
|
|
94
|
+
if (change.diff) {
|
|
95
|
+
const lastViewed = viewedTimes.get(change.file_id);
|
|
96
|
+
if (!lastViewed || change.created_at > lastViewed) {
|
|
97
|
+
unseenChanges.add(change.id);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return c.html(layout("Timeline", timelinePage(changes, unseenChanges, hasMore)));
|
|
102
|
+
});
|
|
103
|
+
// API: trigger scan
|
|
104
|
+
app.post("/api/scan", (c) => {
|
|
105
|
+
const config = loadConfig();
|
|
106
|
+
const results = scanFiles(db, config);
|
|
107
|
+
const changed = results.filter((r) => r.result.changed).length;
|
|
108
|
+
return c.json({ scanned: results.length, changed });
|
|
109
|
+
});
|
|
110
|
+
// API: list files
|
|
111
|
+
app.get("/api/files", (c) => {
|
|
112
|
+
const files = getTrackedFiles(db);
|
|
113
|
+
return c.json(files);
|
|
114
|
+
});
|
|
115
|
+
// API: file detail
|
|
116
|
+
app.get("/api/files/:id", (c) => {
|
|
117
|
+
const id = parseInt(c.req.param("id"), 10);
|
|
118
|
+
const file = db
|
|
119
|
+
.prepare("SELECT * FROM tracked_files WHERE id = ?")
|
|
120
|
+
.get(id);
|
|
121
|
+
if (!file)
|
|
122
|
+
return c.json({ error: "Not found" }, 404);
|
|
123
|
+
const limit = Math.min(Math.max(parseInt(c.req.query("limit") || "50", 10) || 50, 1), 500);
|
|
124
|
+
const offset = Math.max(parseInt(c.req.query("offset") || "0", 10) || 0, 0);
|
|
125
|
+
const snapshots = getFileSnapshots(db, id, limit, offset);
|
|
126
|
+
return c.json({ file, snapshots });
|
|
127
|
+
});
|
|
128
|
+
// API: timeline
|
|
129
|
+
app.get("/api/timeline", (c) => {
|
|
130
|
+
const limit = Math.min(Math.max(parseInt(c.req.query("limit") || "50", 10) || 50, 1), 500);
|
|
131
|
+
const offset = Math.max(parseInt(c.req.query("offset") || "0", 10) || 0, 0);
|
|
132
|
+
const changes = getRecentChanges(db, undefined, limit, offset);
|
|
133
|
+
// Compute unseen IDs for this batch
|
|
134
|
+
const files = getTrackedFiles(db);
|
|
135
|
+
const viewedTimes = new Map();
|
|
136
|
+
for (const file of files) {
|
|
137
|
+
viewedTimes.set(file.id, file.last_viewed_at || null);
|
|
138
|
+
}
|
|
139
|
+
const unseenIds = [];
|
|
140
|
+
for (const change of changes) {
|
|
141
|
+
if (change.diff) {
|
|
142
|
+
const lastViewed = viewedTimes.get(change.file_id);
|
|
143
|
+
if (!lastViewed || change.created_at > lastViewed) {
|
|
144
|
+
unseenIds.push(change.id);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return c.json({ changes, unseenIds });
|
|
149
|
+
});
|
|
150
|
+
// API: open file in editor
|
|
151
|
+
app.post("/api/open/:id", (c) => {
|
|
152
|
+
const id = parseInt(c.req.param("id"), 10);
|
|
153
|
+
const file = db
|
|
154
|
+
.prepare("SELECT path FROM tracked_files WHERE id = ?")
|
|
155
|
+
.get(id);
|
|
156
|
+
if (!file)
|
|
157
|
+
return c.json({ error: "File not found" }, 404);
|
|
158
|
+
const filePath = file.path;
|
|
159
|
+
if (!fs.existsSync(filePath)) {
|
|
160
|
+
return c.json({ error: "File no longer exists on disk" }, 404);
|
|
161
|
+
}
|
|
162
|
+
const editor = process.env.VISUAL || process.env.EDITOR;
|
|
163
|
+
const [cmd, ...args] = editor ? editor.split(/\s+/) : ["open", "-t"];
|
|
164
|
+
execFile(cmd, [...args, filePath], (err) => {
|
|
165
|
+
if (err)
|
|
166
|
+
console.error("Failed to open file:", err.message);
|
|
167
|
+
});
|
|
168
|
+
return c.json({ success: true });
|
|
169
|
+
});
|
|
170
|
+
// Settings page
|
|
171
|
+
app.get("/settings", (c) => {
|
|
172
|
+
const config = loadConfig();
|
|
173
|
+
const html = settingsPage(config, SUGGESTED_ROOTS, SUGGESTED_PATTERNS);
|
|
174
|
+
return c.html(layout("Settings", html, { scripts: SETTINGS_CLIENT_SCRIPT }));
|
|
175
|
+
});
|
|
176
|
+
// --- Setup wizard pages ---
|
|
177
|
+
app.get("/setup", (c) => {
|
|
178
|
+
const existingDirs = SUGGESTED_ROOTS.filter((r) => fs.existsSync(expandHome(r)));
|
|
179
|
+
const html = setupFoldersPage(SUGGESTED_ROOTS, existingDirs);
|
|
180
|
+
return c.html(layout("Setup", html, { hideNav: true, scripts: WIZARD_CLIENT_SCRIPT }));
|
|
181
|
+
});
|
|
182
|
+
app.get("/setup/step2", (c) => {
|
|
183
|
+
const roots = c.req.queries("roots") ?? [];
|
|
184
|
+
const html = setupPatternsPage(roots, SUGGESTED_PATTERNS, DEFAULT_CONFIG.patterns);
|
|
185
|
+
return c.html(layout("Setup — Patterns", html, {
|
|
186
|
+
hideNav: true,
|
|
187
|
+
scripts: WIZARD_CLIENT_SCRIPT,
|
|
188
|
+
}));
|
|
189
|
+
});
|
|
190
|
+
app.get("/setup/step3", (c) => {
|
|
191
|
+
const roots = c.req.queries("roots") ?? [];
|
|
192
|
+
const patterns = c.req.queries("patterns") ?? [];
|
|
193
|
+
const tempConfig = {
|
|
194
|
+
scan_roots: roots,
|
|
195
|
+
patterns,
|
|
196
|
+
exclude: DEFAULT_CONFIG.exclude,
|
|
197
|
+
};
|
|
198
|
+
const filePaths = discoverFiles(tempConfig);
|
|
199
|
+
const expandedRoots = roots.map((r) => ({ original: r, expanded: expandHome(r) }));
|
|
200
|
+
expandedRoots.sort((a, b) => b.expanded.length - a.expanded.length);
|
|
201
|
+
const files = filePaths.map((p) => {
|
|
202
|
+
let root = "Other";
|
|
203
|
+
for (const r of expandedRoots) {
|
|
204
|
+
if (p.startsWith(r.expanded + "/") || p === r.expanded) {
|
|
205
|
+
root = r.original;
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return { path: p, category: categorizeFile(p), root };
|
|
210
|
+
});
|
|
211
|
+
const html = setupPreviewPage(files, roots, patterns);
|
|
212
|
+
return c.html(layout("Setup — Preview", html, {
|
|
213
|
+
hideNav: true,
|
|
214
|
+
scripts: WIZARD_CLIENT_SCRIPT,
|
|
215
|
+
}));
|
|
216
|
+
});
|
|
217
|
+
app.get("/setup/complete", (c) => {
|
|
218
|
+
const count = parseInt(c.req.query("count") || "0", 10);
|
|
219
|
+
const html = setupCompletePage(count);
|
|
220
|
+
return c.html(layout("Setup Complete", html, { hideNav: true }));
|
|
221
|
+
});
|
|
222
|
+
// --- Setup wizard API ---
|
|
223
|
+
app.get("/api/setup/browse", (c) => {
|
|
224
|
+
const dir = c.req.query("dir") || os.homedir();
|
|
225
|
+
const expanded = expandHome(dir);
|
|
226
|
+
try {
|
|
227
|
+
const entries = fs.readdirSync(expanded, { withFileTypes: true });
|
|
228
|
+
const dirs = entries
|
|
229
|
+
.filter((e) => e.isDirectory() && !e.name.startsWith("."))
|
|
230
|
+
.map((e) => ({
|
|
231
|
+
name: e.name,
|
|
232
|
+
path: path.join(dir, e.name).replace(os.homedir(), "~"),
|
|
233
|
+
}))
|
|
234
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
235
|
+
const parent = expanded === os.homedir()
|
|
236
|
+
? null
|
|
237
|
+
: path.dirname(expanded).replace(os.homedir(), "~");
|
|
238
|
+
return c.json({ current: dir.replace(os.homedir(), "~"), parent, dirs });
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
return c.json({ current: dir, parent: null, dirs: [], error: "Cannot read directory" });
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
app.post("/api/setup/discover", async (c) => {
|
|
245
|
+
const body = await c.req.json();
|
|
246
|
+
const tempConfig = {
|
|
247
|
+
scan_roots: body.roots,
|
|
248
|
+
patterns: body.patterns,
|
|
249
|
+
exclude: DEFAULT_CONFIG.exclude,
|
|
250
|
+
};
|
|
251
|
+
const filePaths = discoverFiles(tempConfig);
|
|
252
|
+
// Match each file to its scan root
|
|
253
|
+
const expandedRoots = body.roots.map((r) => ({ original: r, expanded: expandHome(r) }));
|
|
254
|
+
// Sort longest first for correct matching
|
|
255
|
+
expandedRoots.sort((a, b) => b.expanded.length - a.expanded.length);
|
|
256
|
+
const files = filePaths.map((p) => {
|
|
257
|
+
let root = "Other";
|
|
258
|
+
for (const r of expandedRoots) {
|
|
259
|
+
if (p.startsWith(r.expanded + "/") || p === r.expanded) {
|
|
260
|
+
root = r.original;
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return { path: p, category: categorizeFile(p), root };
|
|
265
|
+
});
|
|
266
|
+
return c.json({ files });
|
|
267
|
+
});
|
|
268
|
+
app.post("/api/setup/confirm", async (c) => {
|
|
269
|
+
const body = await c.req.json();
|
|
270
|
+
const config = {
|
|
271
|
+
scan_roots: body.roots,
|
|
272
|
+
patterns: body.patterns,
|
|
273
|
+
exclude: DEFAULT_CONFIG.exclude,
|
|
274
|
+
};
|
|
275
|
+
saveConfig(config);
|
|
276
|
+
const results = scanFiles(db, config);
|
|
277
|
+
// Build set of files that should be tracked
|
|
278
|
+
const keepPaths = new Set(results.map((r) => r.path));
|
|
279
|
+
// Remove user-excluded files from the keep set
|
|
280
|
+
for (const excluded of body.excludedFiles) {
|
|
281
|
+
keepPaths.delete(excluded);
|
|
282
|
+
}
|
|
283
|
+
// Remove all tracked files not in the keep set
|
|
284
|
+
const allTracked = db.prepare("SELECT id, path FROM tracked_files").all();
|
|
285
|
+
for (const tracked of allTracked) {
|
|
286
|
+
if (!keepPaths.has(tracked.path)) {
|
|
287
|
+
db.prepare("DELETE FROM snapshots WHERE file_id = ?").run(tracked.id);
|
|
288
|
+
db.prepare("DELETE FROM tracked_files WHERE id = ?").run(tracked.id);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
const trackedCount = keepPaths.size;
|
|
292
|
+
return c.json({ success: true, trackedCount });
|
|
293
|
+
});
|
|
294
|
+
return app;
|
|
295
|
+
}
|
|
296
|
+
export function startServer(port = 3333) {
|
|
297
|
+
const db = getDb();
|
|
298
|
+
// Run a scan on startup
|
|
299
|
+
const config = loadConfig();
|
|
300
|
+
scanFiles(db, config);
|
|
301
|
+
const app = createApp(db);
|
|
302
|
+
console.log(`dotmd dashboard running at http://localhost:${port}`);
|
|
303
|
+
serve({ fetch: app.fetch, port });
|
|
304
|
+
}
|
|
305
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/dashboard/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const SETTINGS_CLIENT_SCRIPT = "<script>\ndocument.addEventListener('DOMContentLoaded', function() {\n var debounceTimer = null;\n var currentBrowseDir = null;\n\n function escapeHtml(str) {\n var div = document.createElement('div');\n div.textContent = str;\n return div.innerHTML;\n }\n\n // --- Chip toggling ---\n function initChipToggling(container) {\n if (!container) return;\n container.querySelectorAll('[data-chip]').forEach(function(chip) {\n chip.addEventListener('click', function() {\n var isSelected = chip.getAttribute('data-selected') === 'true';\n chip.setAttribute('data-selected', isSelected ? 'false' : 'true');\n if (isSelected) {\n chip.classList.remove('bg-blue-100', 'text-blue-800', 'border-blue-300');\n chip.classList.add('bg-gray-100', 'text-gray-600', 'border-gray-300');\n } else {\n chip.classList.remove('bg-gray-100', 'text-gray-600', 'border-gray-300');\n chip.classList.add('bg-blue-100', 'text-blue-800', 'border-blue-300');\n }\n scheduleRefresh();\n });\n });\n }\n\n initChipToggling(document.getElementById('roots-list'));\n initChipToggling(document.getElementById('patterns-list'));\n\n // --- Collect selected values ---\n function getSelectedRoots() {\n var values = [];\n document.getElementById('roots-list').querySelectorAll('[data-chip][data-selected=\"true\"]').forEach(function(chip) {\n values.push(chip.getAttribute('data-value'));\n });\n document.getElementById('roots-list').querySelectorAll('[data-custom-value]').forEach(function(tag) {\n values.push(tag.getAttribute('data-custom-value'));\n });\n return values;\n }\n\n function getSelectedPatterns() {\n var values = [];\n document.getElementById('patterns-list').querySelectorAll('[data-chip][data-selected=\"true\"]').forEach(function(chip) {\n values.push(chip.getAttribute('data-value'));\n });\n document.getElementById('patterns-list').querySelectorAll('[data-custom-value]').forEach(function(tag) {\n values.push(tag.getAttribute('data-custom-value'));\n });\n return values;\n }\n\n // --- Auto-refresh file preview ---\n function scheduleRefresh() {\n clearTimeout(debounceTimer);\n debounceTimer = setTimeout(refreshPreview, 300);\n }\n\n function refreshPreview() {\n var roots = getSelectedRoots();\n var patterns = getSelectedPatterns();\n var preview = document.getElementById('file-preview');\n\n if (roots.length === 0 || patterns.length === 0) {\n preview.innerHTML = '<p class=\"text-gray-400 text-sm\">Select at least one folder and one pattern.</p>';\n return;\n }\n\n preview.style.minHeight = preview.offsetHeight + 'px';\n\n fetch('/api/setup/discover', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ roots: roots, patterns: patterns })\n })\n .then(function(res) { return res.json(); })\n .then(function(data) {\n renderFilePreview(data.files || []);\n })\n .catch(function() {\n preview.innerHTML = '<p class=\"text-red-500 text-sm\">Error scanning files.</p>';\n });\n }\n\n function renderFilePreview(files) {\n var preview = document.getElementById('file-preview');\n\n if (files.length === 0) {\n preview.innerHTML = '<p class=\"text-gray-400 text-sm\">No matching files found.</p>';\n return;\n }\n\n // Group by root (provided by server)\n var groups = {};\n var rootOrder = [];\n files.forEach(function(f) {\n var root = f.root || 'Other';\n if (!groups[root]) {\n groups[root] = [];\n rootOrder.push(root);\n }\n groups[root].push(f);\n });\n\n var html = '<div class=\"flex gap-3 mb-4\">' +\n '<button type=\"button\" id=\"preview-select-all\" class=\"text-sm text-blue-600 hover:underline\">Select all</button>' +\n '<button type=\"button\" id=\"preview-deselect-all\" class=\"text-sm text-blue-600 hover:underline\">Deselect all</button>' +\n '<span class=\"text-sm text-gray-400\">' + files.length + ' file' + (files.length === 1 ? '' : 's') + '</span>' +\n '</div>';\n\n rootOrder.forEach(function(root) {\n var rootFiles = groups[root];\n if (!rootFiles || rootFiles.length === 0) return;\n\n html += '<div class=\"mb-4\">';\n html += '<h3 class=\"text-sm font-semibold text-gray-500 uppercase tracking-wide mb-2\">' + escapeHtml(root) + '</h3>';\n html += '<div class=\"space-y-1\">';\n\n rootFiles.forEach(function(file) {\n // Show path relative to the root for cleaner display\n var displayPath = file.path;\n if (root !== 'Other' && displayPath.indexOf(root) === 0) {\n displayPath = displayPath.slice(root.length + 1);\n }\n html += '<label class=\"flex items-center gap-3 px-3 py-2 rounded-lg hover:bg-gray-50 cursor-pointer\">' +\n '<input type=\"checkbox\" name=\"files\" value=\"' + escapeHtml(file.path) + '\" checked ' +\n 'class=\"rounded border-gray-300 text-blue-600 focus:ring-blue-500\">' +\n '<span class=\"font-mono text-sm\">' + escapeHtml(displayPath) + '</span>' +\n '</label>';\n });\n\n html += '</div></div>';\n });\n\n preview.innerHTML = html;\n preview.style.minHeight = '';\n\n // Wire up select/deselect all\n var selectAll = document.getElementById('preview-select-all');\n var deselectAll = document.getElementById('preview-deselect-all');\n if (selectAll) {\n selectAll.addEventListener('click', function() {\n preview.querySelectorAll('input[name=\"files\"]').forEach(function(cb) { cb.checked = true; });\n });\n }\n if (deselectAll) {\n deselectAll.addEventListener('click', function() {\n preview.querySelectorAll('input[name=\"files\"]').forEach(function(cb) { cb.checked = false; });\n });\n }\n }\n\n // --- Folder browser ---\n var openBrowserBtn = document.getElementById('open-browser');\n var folderBrowser = document.getElementById('folder-browser');\n var browserPath = document.getElementById('browser-path');\n var browserList = document.getElementById('browser-list');\n var browserUp = document.getElementById('browser-up');\n var browserAdd = document.getElementById('browser-add');\n var rootsList = document.getElementById('roots-list');\n\n if (browserAdd) {\n browserAdd.addEventListener('click', function() {\n if (currentBrowseDir) {\n addCustomChip(rootsList, currentBrowseDir);\n scheduleRefresh();\n }\n });\n }\n\n function addCustomChip(container, value) {\n // Don't add duplicates\n var existing = container.querySelectorAll('[data-custom-value]');\n for (var i = 0; i < existing.length; i++) {\n if (existing[i].getAttribute('data-custom-value') === value) return;\n }\n var chips = container.querySelectorAll('[data-chip]');\n for (var i = 0; i < chips.length; i++) {\n if (chips[i].getAttribute('data-value') === value) return;\n }\n\n var tag = document.createElement('span');\n tag.setAttribute('data-custom-value', value);\n tag.className = 'inline-flex items-center gap-1 px-3 py-1.5 rounded-full text-sm font-medium bg-blue-100 text-blue-800 border border-blue-300';\n tag.innerHTML = escapeHtml(value) + ' <button type=\"button\" class=\"ml-1 text-blue-500 hover:text-blue-700 font-bold\">×</button>';\n tag.querySelector('button').addEventListener('click', function() {\n tag.remove();\n scheduleRefresh();\n });\n container.appendChild(tag);\n }\n\n function loadBrowserDir(dir) {\n fetch('/api/setup/browse?dir=' + encodeURIComponent(dir || '~'))\n .then(function(res) { return res.json(); })\n .then(function(data) {\n if (data.error) return;\n currentBrowseDir = data.current;\n if (browserPath) browserPath.textContent = data.current;\n if (browserUp) {\n browserUp.style.display = data.parent ? '' : 'none';\n browserUp.onclick = function() { loadBrowserDir(data.parent); };\n }\n if (!browserList) return;\n browserList.innerHTML = '';\n\n data.dirs.forEach(function(entry) {\n var row = document.createElement('div');\n row.className = 'px-4 py-2 hover:bg-gray-50 cursor-pointer flex items-center gap-2 text-sm';\n row.innerHTML = '<span class=\"text-gray-400\">📁</span> ' + escapeHtml(entry.name);\n row.addEventListener('click', function() {\n loadBrowserDir(entry.path);\n });\n browserList.appendChild(row);\n });\n });\n }\n\n if (openBrowserBtn && folderBrowser) {\n openBrowserBtn.addEventListener('click', function() {\n var isHidden = folderBrowser.classList.contains('hidden');\n if (isHidden) {\n folderBrowser.classList.remove('hidden');\n openBrowserBtn.textContent = 'Hide browser';\n loadBrowserDir('~');\n } else {\n folderBrowser.classList.add('hidden');\n openBrowserBtn.textContent = 'Browse for folder...';\n }\n });\n }\n\n // --- Custom pattern input ---\n var addBtn = document.getElementById('add-custom');\n var customInput = document.getElementById('custom-input');\n var patternsList = document.getElementById('patterns-list');\n\n function addCustomPattern() {\n if (!customInput || !patternsList) return;\n var value = customInput.value.trim();\n if (!value) return;\n addCustomChip(patternsList, value);\n customInput.value = '';\n scheduleRefresh();\n }\n\n if (addBtn) {\n addBtn.addEventListener('click', addCustomPattern);\n }\n if (customInput) {\n customInput.addEventListener('keydown', function(e) {\n if (e.key === 'Enter') {\n e.preventDefault();\n addCustomPattern();\n }\n });\n }\n\n // --- Save ---\n var saveBtn = document.getElementById('save-btn');\n var saveStatus = document.getElementById('save-status');\n\n if (saveBtn) {\n saveBtn.addEventListener('click', function() {\n saveBtn.disabled = true;\n saveBtn.textContent = 'Saving...';\n if (saveStatus) saveStatus.classList.add('hidden');\n\n var roots = getSelectedRoots();\n var patterns = getSelectedPatterns();\n\n var excludedFiles = [];\n document.querySelectorAll('#file-preview input[name=\"files\"]').forEach(function(cb) {\n if (!cb.checked) {\n excludedFiles.push(cb.value);\n }\n });\n\n fetch('/api/setup/confirm', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ roots: roots, patterns: patterns, excludedFiles: excludedFiles })\n })\n .then(function(res) { return res.json(); })\n .then(function(data) {\n saveBtn.disabled = false;\n saveBtn.textContent = 'Save';\n if (data.success && saveStatus) {\n saveStatus.textContent = 'Settings saved. Tracking ' + data.trackedCount + ' file' + (data.trackedCount === 1 ? '' : 's') + '.';\n saveStatus.classList.remove('hidden');\n setTimeout(function() { saveStatus.classList.add('hidden'); }, 3000);\n }\n })\n .catch(function() {\n saveBtn.disabled = false;\n saveBtn.textContent = 'Save';\n alert('Error saving settings. Please try again.');\n });\n });\n }\n\n // --- Initial load ---\n refreshPreview();\n});\n</script>";
|
|
2
|
+
//# sourceMappingURL=settings-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings-client.d.ts","sourceRoot":"","sources":["../../src/dashboard/settings-client.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,sBAAsB,sxWAoTzB,CAAC"}
|