@ycniuqton/devlens 0.1.7 → 0.1.8
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 +11 -2
- package/dist/routes/browser.js +3 -1
- package/dist/routes/browser.js.map +1 -1
- package/dist/routes/settings.d.ts +1 -0
- package/dist/routes/settings.js +53 -0
- package/dist/routes/settings.js.map +1 -0
- package/dist/server.js +20 -3
- package/dist/server.js.map +1 -1
- package/dist/services/files.d.ts +1 -1
- package/dist/services/files.js +4 -3
- package/dist/services/files.js.map +1 -1
- package/dist/services/settings.d.ts +13 -0
- package/dist/services/settings.js +104 -0
- package/dist/services/settings.js.map +1 -0
- package/dist/services/watcher.d.ts +2 -2
- package/dist/services/watcher.js +8 -8
- package/dist/services/watcher.js.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/package.json +2 -2
- package/public/css/style.css +69 -0
- package/public/index.html +37 -4
- package/public/js/app.js +1 -1
- package/public/js/diff.js +44 -19
- package/public/js/history.js +31 -15
- package/public/js/settings.js +85 -0
package/README.md
CHANGED
|
@@ -21,15 +21,24 @@ Devlens gives you a web-based UI to monitor file changes, manage Claude Code tas
|
|
|
21
21
|
|
|
22
22
|
## Installation
|
|
23
23
|
|
|
24
|
-
###
|
|
24
|
+
### One-liner (install + init in current project)
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install @ycniuqton/devlens@latest && npx devlens init
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Run this in any project directory — installs the latest version locally and sets up Devlens hooks for that project. Re-run anytime to upgrade.
|
|
31
|
+
|
|
32
|
+
### Global install
|
|
25
33
|
|
|
26
34
|
```bash
|
|
27
35
|
npm install -g @ycniuqton/devlens
|
|
36
|
+
devlens init
|
|
28
37
|
```
|
|
29
38
|
|
|
30
39
|
### Without installing — use `npx`
|
|
31
40
|
|
|
32
|
-
> **Note:** the package is scoped
|
|
41
|
+
> **Note:** the package is scoped. Plain `npx devlens` will install an unrelated package with the same name. Use the `--package` form:
|
|
33
42
|
|
|
34
43
|
```bash
|
|
35
44
|
npx --package=@ycniuqton/devlens -- devlens init
|
package/dist/routes/browser.js
CHANGED
|
@@ -42,8 +42,10 @@ exports.browserRouter.get('/commit/:hash', async (req, res) => {
|
|
|
42
42
|
// GET /api/browser/files?path=... — list directory contents
|
|
43
43
|
exports.browserRouter.get('/files', (req, res) => {
|
|
44
44
|
const projectDir = req.app.locals.projectDir;
|
|
45
|
+
const settings = req.app.locals.settingsService;
|
|
45
46
|
const relPath = req.query.path || '';
|
|
46
|
-
const
|
|
47
|
+
const ignoreSet = settings ? settings.getIgnoreNameSet() : undefined;
|
|
48
|
+
const entries = (0, files_1.listDirectory)(projectDir, relPath, ignoreSet);
|
|
47
49
|
res.json({ path: relPath, entries });
|
|
48
50
|
});
|
|
49
51
|
// GET /api/browser/file?path=... — read file content
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser.js","sourceRoot":"","sources":["../../src/routes/browser.ts"],"names":[],"mappings":";;;AAAA,qCAAoD;AAEpD,6CAA4D;AAE/C,QAAA,aAAa,GAAG,IAAA,gBAAM,GAAE,CAAC;AAEtC,gDAAgD;AAChD,qBAAa,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,EAAE;IACjE,MAAM,GAAG,GAAe,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC;IAClD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,gBAAgB,EAAE,CAAC;QAC5C,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;IACvB,CAAC;IAAC,OAAO,GAAQ,EAAE,CAAC;QAClB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;IAC/C,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,uEAAuE;AACvE,qBAAa,CAAC,GAAG,CAAC,UAAU,EAAE,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,EAAE;IAClE,MAAM,GAAG,GAAe,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC;IAClD,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,KAAe,CAAC,IAAI,EAAE,CAAC;QACxD,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACpC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAChB,CAAC;IAAC,OAAO,GAAQ,EAAE,CAAC;QAClB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;IAC/C,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,qEAAqE;AACrE,qBAAa,CAAC,GAAG,CAAC,eAAe,EAAE,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,EAAE;IACvE,MAAM,GAAG,GAAe,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC;IAClD,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,GAAG,CAAC,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACxD,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,aAAa,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACtD,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACnD,CAAC;IAAC,OAAO,GAAQ,EAAE,CAAC;QAClB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;IAC/C,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,4DAA4D;AAC5D,qBAAa,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,GAAY,EAAE,GAAa,EAAE,EAAE;IAC1D,MAAM,UAAU,GAAW,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC;IACrD,MAAM,OAAO,GAAI,GAAG,CAAC,KAAK,CAAC,IAAe,IAAI,EAAE,CAAC;IACjD,MAAM,OAAO,GAAG,IAAA,qBAAa,EAAC,UAAU,EAAE,OAAO,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"browser.js","sourceRoot":"","sources":["../../src/routes/browser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const settingsRouter: import("express-serve-static-core").Router;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.settingsRouter = void 0;
|
|
4
|
+
const express_1 = require("express");
|
|
5
|
+
exports.settingsRouter = (0, express_1.Router)();
|
|
6
|
+
const PRESET_PATTERNS = [
|
|
7
|
+
'node_modules',
|
|
8
|
+
'.git',
|
|
9
|
+
'.devlens',
|
|
10
|
+
'dist',
|
|
11
|
+
'build',
|
|
12
|
+
'.next',
|
|
13
|
+
'.nuxt',
|
|
14
|
+
'.cache',
|
|
15
|
+
'.turbo',
|
|
16
|
+
'_bmad',
|
|
17
|
+
'venv',
|
|
18
|
+
'.venv',
|
|
19
|
+
'env',
|
|
20
|
+
'__pycache__',
|
|
21
|
+
'.pytest_cache',
|
|
22
|
+
'target',
|
|
23
|
+
'vendor',
|
|
24
|
+
'coverage',
|
|
25
|
+
'.idea',
|
|
26
|
+
'.vscode',
|
|
27
|
+
];
|
|
28
|
+
// GET /api/settings — current settings + presets
|
|
29
|
+
exports.settingsRouter.get('/', (req, res) => {
|
|
30
|
+
const settings = req.app.locals.settingsService;
|
|
31
|
+
res.json({
|
|
32
|
+
ignorePatterns: settings.getIgnorePatterns(),
|
|
33
|
+
presets: PRESET_PATTERNS,
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
// PUT /api/settings — replace settings
|
|
37
|
+
exports.settingsRouter.put('/', (req, res) => {
|
|
38
|
+
const settings = req.app.locals.settingsService;
|
|
39
|
+
const broadcast = req.app.locals.broadcast;
|
|
40
|
+
const reloadWatcher = req.app.locals.reloadWatcher;
|
|
41
|
+
const { ignorePatterns } = req.body || {};
|
|
42
|
+
if (!Array.isArray(ignorePatterns)) {
|
|
43
|
+
return res.status(400).json({ error: 'ignorePatterns must be an array' });
|
|
44
|
+
}
|
|
45
|
+
const updated = settings.updateSettings({ ignorePatterns });
|
|
46
|
+
// Restart the watcher with new patterns
|
|
47
|
+
if (reloadWatcher)
|
|
48
|
+
reloadWatcher();
|
|
49
|
+
if (broadcast)
|
|
50
|
+
broadcast({ type: 'settings-update', payload: updated });
|
|
51
|
+
res.json(updated);
|
|
52
|
+
});
|
|
53
|
+
//# sourceMappingURL=settings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings.js","sourceRoot":"","sources":["../../src/routes/settings.ts"],"names":[],"mappings":";;;AAAA,qCAAoD;AAIvC,QAAA,cAAc,GAAG,IAAA,gBAAM,GAAE,CAAC;AAEvC,MAAM,eAAe,GAAG;IACtB,cAAc;IACd,MAAM;IACN,UAAU;IACV,MAAM;IACN,OAAO;IACP,OAAO;IACP,OAAO;IACP,QAAQ;IACR,QAAQ;IACR,OAAO;IACP,MAAM;IACN,OAAO;IACP,KAAK;IACL,aAAa;IACb,eAAe;IACf,QAAQ;IACR,QAAQ;IACR,UAAU;IACV,OAAO;IACP,SAAS;CACV,CAAC;AAEF,iDAAiD;AACjD,sBAAc,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAY,EAAE,GAAa,EAAE,EAAE;IACtD,MAAM,QAAQ,GAAoB,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,eAAe,CAAC;IACjE,GAAG,CAAC,IAAI,CAAC;QACP,cAAc,EAAE,QAAQ,CAAC,iBAAiB,EAAE;QAC5C,OAAO,EAAE,eAAe;KACzB,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,uCAAuC;AACvC,sBAAc,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAY,EAAE,GAAa,EAAE,EAAE;IACtD,MAAM,QAAQ,GAAoB,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,eAAe,CAAC;IACjE,MAAM,SAAS,GAA6B,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC;IACrE,MAAM,aAAa,GAA6B,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,aAAa,CAAC;IAE7E,MAAM,EAAE,cAAc,EAAE,GAAG,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC;IAC1C,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,cAAc,CAAC,EAAE,CAAC;QACnC,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,iCAAiC,EAAE,CAAC,CAAC;IAC5E,CAAC;IAED,MAAM,OAAO,GAAG,QAAQ,CAAC,cAAc,CAAC,EAAE,cAAc,EAAE,CAAC,CAAC;IAE5D,wCAAwC;IACxC,IAAI,aAAa;QAAE,aAAa,EAAE,CAAC;IAEnC,IAAI,SAAS;QAAE,SAAS,CAAC,EAAE,IAAI,EAAE,iBAAiB,EAAE,OAAO,EAAE,OAAO,EAAS,CAAC,CAAC;IAC/E,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AACpB,CAAC,CAAC,CAAC"}
|
package/dist/server.js
CHANGED
|
@@ -13,11 +13,13 @@ const git_1 = require("./services/git");
|
|
|
13
13
|
const watcher_1 = require("./services/watcher");
|
|
14
14
|
const taskStore_1 = require("./services/taskStore");
|
|
15
15
|
const rules_1 = require("./services/rules");
|
|
16
|
+
const settings_1 = require("./services/settings");
|
|
16
17
|
const diff_1 = require("./routes/diff");
|
|
17
18
|
const tasks_1 = require("./routes/tasks");
|
|
18
19
|
const integrations_1 = require("./routes/integrations");
|
|
19
20
|
const rules_2 = require("./routes/rules");
|
|
20
21
|
const browser_1 = require("./routes/browser");
|
|
22
|
+
const settings_2 = require("./routes/settings");
|
|
21
23
|
function createServer(options) {
|
|
22
24
|
const app = (0, express_1.default)();
|
|
23
25
|
const httpServer = http_1.default.createServer(app);
|
|
@@ -28,11 +30,13 @@ function createServer(options) {
|
|
|
28
30
|
const gitService = (0, git_1.createGitService)(options.projectDir);
|
|
29
31
|
const taskStore = (0, taskStore_1.createTaskStore)(options.projectDir);
|
|
30
32
|
const rulesService = (0, rules_1.createRulesService)(options.projectDir);
|
|
33
|
+
const settingsService = (0, settings_1.createSettingsService)(options.projectDir);
|
|
31
34
|
rulesService.ensureDefault();
|
|
32
35
|
// Attach to app.locals for route access
|
|
33
36
|
app.locals.gitService = gitService;
|
|
34
37
|
app.locals.taskStore = taskStore;
|
|
35
38
|
app.locals.rulesService = rulesService;
|
|
39
|
+
app.locals.settingsService = settingsService;
|
|
36
40
|
app.locals.projectDir = options.projectDir;
|
|
37
41
|
app.locals.port = options.port;
|
|
38
42
|
// API routes
|
|
@@ -48,6 +52,7 @@ function createServer(options) {
|
|
|
48
52
|
app.use('/api/integrations', integrations_1.integrationsRouter);
|
|
49
53
|
app.use('/api/rules', rules_2.rulesRouter);
|
|
50
54
|
app.use('/api/browser', browser_1.browserRouter);
|
|
55
|
+
app.use('/api/settings', settings_2.settingsRouter);
|
|
51
56
|
// Static files
|
|
52
57
|
const publicDir = path_1.default.resolve(__dirname, '../public');
|
|
53
58
|
app.use(express_1.default.static(publicDir));
|
|
@@ -64,8 +69,9 @@ function createServer(options) {
|
|
|
64
69
|
}
|
|
65
70
|
});
|
|
66
71
|
}
|
|
67
|
-
// File watcher -> WebSocket broadcast
|
|
68
|
-
|
|
72
|
+
// File watcher -> WebSocket broadcast (rebuildable when settings change)
|
|
73
|
+
let watcher = null;
|
|
74
|
+
const onWatcherChange = async () => {
|
|
69
75
|
try {
|
|
70
76
|
const diff = await gitService.getDiff();
|
|
71
77
|
const status = await gitService.getStatus();
|
|
@@ -75,7 +81,18 @@ function createServer(options) {
|
|
|
75
81
|
catch {
|
|
76
82
|
// Git service may fail if not a git repo
|
|
77
83
|
}
|
|
78
|
-
}
|
|
84
|
+
};
|
|
85
|
+
function buildWatcher() {
|
|
86
|
+
watcher = (0, watcher_1.createWatcher)(options.projectDir, onWatcherChange, settingsService.getChokidarIgnoreGlobs());
|
|
87
|
+
}
|
|
88
|
+
buildWatcher();
|
|
89
|
+
function reloadWatcher() {
|
|
90
|
+
if (watcher) {
|
|
91
|
+
watcher.close().catch(() => { });
|
|
92
|
+
}
|
|
93
|
+
buildWatcher();
|
|
94
|
+
}
|
|
95
|
+
app.locals.reloadWatcher = reloadWatcher;
|
|
79
96
|
// Watch rules.md for external changes
|
|
80
97
|
const rulesPath = path_1.default.join(options.projectDir, '.devlens', 'rules.md');
|
|
81
98
|
const rulesWatcher = chokidar_1.default.watch(rulesPath, { ignoreInitial: true });
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/dist/services/files.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export interface FileEntry {
|
|
|
4
4
|
type: 'file' | 'dir';
|
|
5
5
|
size?: number;
|
|
6
6
|
}
|
|
7
|
-
export declare function listDirectory(projectDir: string, relPath: string): FileEntry[];
|
|
7
|
+
export declare function listDirectory(projectDir: string, relPath: string, ignoreSet?: Set<string>): FileEntry[];
|
|
8
8
|
export declare function readFile(projectDir: string, relPath: string, maxBytes?: number): {
|
|
9
9
|
content: string;
|
|
10
10
|
truncated: boolean;
|
package/dist/services/files.js
CHANGED
|
@@ -7,13 +7,13 @@ exports.listDirectory = listDirectory;
|
|
|
7
7
|
exports.readFile = readFile;
|
|
8
8
|
const fs_1 = __importDefault(require("fs"));
|
|
9
9
|
const path_1 = __importDefault(require("path"));
|
|
10
|
-
const
|
|
10
|
+
const FALLBACK_IGNORE = new Set(['.git', 'node_modules', '.devlens', 'dist', '.next', '.nuxt', '.cache']);
|
|
11
11
|
function isSafePath(projectDir, target) {
|
|
12
12
|
const abs = path_1.default.resolve(projectDir, target);
|
|
13
13
|
const root = path_1.default.resolve(projectDir);
|
|
14
14
|
return abs === root || abs.startsWith(root + path_1.default.sep);
|
|
15
15
|
}
|
|
16
|
-
function listDirectory(projectDir, relPath) {
|
|
16
|
+
function listDirectory(projectDir, relPath, ignoreSet) {
|
|
17
17
|
if (!isSafePath(projectDir, relPath))
|
|
18
18
|
return [];
|
|
19
19
|
const abs = path_1.default.resolve(projectDir, relPath);
|
|
@@ -21,8 +21,9 @@ function listDirectory(projectDir, relPath) {
|
|
|
21
21
|
return [];
|
|
22
22
|
const entries = fs_1.default.readdirSync(abs, { withFileTypes: true });
|
|
23
23
|
const result = [];
|
|
24
|
+
const ignored = ignoreSet || FALLBACK_IGNORE;
|
|
24
25
|
for (const entry of entries) {
|
|
25
|
-
if (
|
|
26
|
+
if (ignored.has(entry.name))
|
|
26
27
|
continue;
|
|
27
28
|
if (entry.name.startsWith('.') && entry.name !== '.gitignore' && entry.name !== '.env.example')
|
|
28
29
|
continue;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"files.js","sourceRoot":"","sources":["../../src/services/files.ts"],"names":[],"mappings":";;;;;AAkBA,
|
|
1
|
+
{"version":3,"file":"files.js","sourceRoot":"","sources":["../../src/services/files.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface DevlensSettings {
|
|
2
|
+
ignorePatterns: string[];
|
|
3
|
+
}
|
|
4
|
+
export interface SettingsService {
|
|
5
|
+
getSettings(): DevlensSettings;
|
|
6
|
+
updateSettings(input: Partial<DevlensSettings>): DevlensSettings;
|
|
7
|
+
getIgnorePatterns(): string[];
|
|
8
|
+
/** Convert ignore patterns to chokidar-compatible globs */
|
|
9
|
+
getChokidarIgnoreGlobs(): (string | RegExp)[];
|
|
10
|
+
/** Convert ignore patterns to a Set of names for the file explorer */
|
|
11
|
+
getIgnoreNameSet(): Set<string>;
|
|
12
|
+
}
|
|
13
|
+
export declare function createSettingsService(projectDir: string): SettingsService;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createSettingsService = createSettingsService;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
// Common heavy/noisy directories that should be ignored by default
|
|
10
|
+
const DEFAULT_IGNORE_PATTERNS = [
|
|
11
|
+
'node_modules',
|
|
12
|
+
'.git',
|
|
13
|
+
'.devlens',
|
|
14
|
+
'dist',
|
|
15
|
+
'build',
|
|
16
|
+
'.next',
|
|
17
|
+
'.nuxt',
|
|
18
|
+
'.cache',
|
|
19
|
+
'.turbo',
|
|
20
|
+
'_bmad',
|
|
21
|
+
'venv',
|
|
22
|
+
'.venv',
|
|
23
|
+
'env',
|
|
24
|
+
'__pycache__',
|
|
25
|
+
'.pytest_cache',
|
|
26
|
+
'target', // Rust / Java
|
|
27
|
+
'vendor', // Go / PHP
|
|
28
|
+
'coverage',
|
|
29
|
+
'.idea',
|
|
30
|
+
'.vscode',
|
|
31
|
+
];
|
|
32
|
+
function createSettingsService(projectDir) {
|
|
33
|
+
const devlensDir = path_1.default.join(projectDir, '.devlens');
|
|
34
|
+
const settingsFile = path_1.default.join(devlensDir, 'settings.json');
|
|
35
|
+
function ensureFile() {
|
|
36
|
+
if (!fs_1.default.existsSync(devlensDir)) {
|
|
37
|
+
fs_1.default.mkdirSync(devlensDir, { recursive: true });
|
|
38
|
+
}
|
|
39
|
+
if (!fs_1.default.existsSync(settingsFile)) {
|
|
40
|
+
const initial = { ignorePatterns: DEFAULT_IGNORE_PATTERNS };
|
|
41
|
+
fs_1.default.writeFileSync(settingsFile, JSON.stringify(initial, null, 2));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function load() {
|
|
45
|
+
ensureFile();
|
|
46
|
+
try {
|
|
47
|
+
const data = JSON.parse(fs_1.default.readFileSync(settingsFile, 'utf-8'));
|
|
48
|
+
return {
|
|
49
|
+
ignorePatterns: Array.isArray(data.ignorePatterns) ? data.ignorePatterns : DEFAULT_IGNORE_PATTERNS,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return { ignorePatterns: DEFAULT_IGNORE_PATTERNS };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function save(settings) {
|
|
57
|
+
ensureFile();
|
|
58
|
+
fs_1.default.writeFileSync(settingsFile, JSON.stringify(settings, null, 2));
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
getSettings() {
|
|
62
|
+
return load();
|
|
63
|
+
},
|
|
64
|
+
updateSettings(input) {
|
|
65
|
+
const current = load();
|
|
66
|
+
const merged = {
|
|
67
|
+
ignorePatterns: input.ignorePatterns ?? current.ignorePatterns,
|
|
68
|
+
};
|
|
69
|
+
// Dedupe + trim + drop empties
|
|
70
|
+
merged.ignorePatterns = Array.from(new Set(merged.ignorePatterns.map(p => (p || '').trim()).filter(Boolean)));
|
|
71
|
+
save(merged);
|
|
72
|
+
return merged;
|
|
73
|
+
},
|
|
74
|
+
getIgnorePatterns() {
|
|
75
|
+
return load().ignorePatterns;
|
|
76
|
+
},
|
|
77
|
+
getChokidarIgnoreGlobs() {
|
|
78
|
+
const patterns = load().ignorePatterns;
|
|
79
|
+
// Always ignore dotfiles
|
|
80
|
+
const result = [/(^|[\/\\])\../];
|
|
81
|
+
for (const p of patterns) {
|
|
82
|
+
// If pattern contains glob chars, use as-is. Otherwise treat as a directory or filename.
|
|
83
|
+
if (p.includes('*') || p.includes('?') || p.includes('[')) {
|
|
84
|
+
result.push(p);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
// Match anywhere in tree: ignore the dir/file at any depth
|
|
88
|
+
result.push(`**/${p}/**`, `**/${p}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return result;
|
|
92
|
+
},
|
|
93
|
+
getIgnoreNameSet() {
|
|
94
|
+
const patterns = load().ignorePatterns;
|
|
95
|
+
const set = new Set();
|
|
96
|
+
for (const p of patterns) {
|
|
97
|
+
if (!p.includes('*') && !p.includes('/'))
|
|
98
|
+
set.add(p);
|
|
99
|
+
}
|
|
100
|
+
return set;
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=settings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings.js","sourceRoot":"","sources":["../../src/services/settings.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import
|
|
2
|
-
export declare function createWatcher(projectDir: string, onChange: () => void):
|
|
1
|
+
import { FSWatcher } from 'chokidar';
|
|
2
|
+
export declare function createWatcher(projectDir: string, onChange: () => void, ignored?: (string | RegExp)[]): FSWatcher;
|
package/dist/services/watcher.js
CHANGED
|
@@ -5,22 +5,22 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.createWatcher = createWatcher;
|
|
7
7
|
const chokidar_1 = __importDefault(require("chokidar"));
|
|
8
|
-
function createWatcher(projectDir, onChange
|
|
8
|
+
function createWatcher(projectDir, onChange, ignored = [
|
|
9
|
+
/(^|[\/\\])\../,
|
|
10
|
+
'**/node_modules/**',
|
|
11
|
+
'**/.git/**',
|
|
12
|
+
'**/.devlens/**',
|
|
13
|
+
]) {
|
|
9
14
|
let debounceTimer = null;
|
|
10
15
|
const watcher = chokidar_1.default.watch(projectDir, {
|
|
11
|
-
ignored
|
|
12
|
-
/(^|[\/\\])\../, // dotfiles
|
|
13
|
-
'**/node_modules/**',
|
|
14
|
-
'**/.git/**',
|
|
15
|
-
'**/.devlens/**',
|
|
16
|
-
],
|
|
16
|
+
ignored,
|
|
17
17
|
persistent: true,
|
|
18
18
|
ignoreInitial: true,
|
|
19
19
|
});
|
|
20
20
|
const debouncedOnChange = () => {
|
|
21
21
|
if (debounceTimer)
|
|
22
22
|
clearTimeout(debounceTimer);
|
|
23
|
-
debounceTimer = setTimeout(onChange,
|
|
23
|
+
debounceTimer = setTimeout(onChange, 500);
|
|
24
24
|
};
|
|
25
25
|
watcher.on('change', debouncedOnChange);
|
|
26
26
|
watcher.on('add', debouncedOnChange);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"watcher.js","sourceRoot":"","sources":["../../src/services/watcher.ts"],"names":[],"mappings":";;;;;AAEA,
|
|
1
|
+
{"version":3,"file":"watcher.js","sourceRoot":"","sources":["../../src/services/watcher.ts"],"names":[],"mappings":";;;;;AAEA,sCA4BC;AA9BD,wDAA+C;AAE/C,SAAgB,aAAa,CAC3B,UAAkB,EAClB,QAAoB,EACpB,UAA+B;IAC7B,eAAe;IACf,oBAAoB;IACpB,YAAY;IACZ,gBAAgB;CACjB;IAED,IAAI,aAAa,GAA0B,IAAI,CAAC;IAEhD,MAAM,OAAO,GAAG,kBAAQ,CAAC,KAAK,CAAC,UAAU,EAAE;QACzC,OAAO;QACP,UAAU,EAAE,IAAI;QAChB,aAAa,EAAE,IAAI;KACpB,CAAC,CAAC;IAEH,MAAM,iBAAiB,GAAG,GAAG,EAAE;QAC7B,IAAI,aAAa;YAAE,YAAY,CAAC,aAAa,CAAC,CAAC;QAC/C,aAAa,GAAG,UAAU,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IAC5C,CAAC,CAAC;IAEF,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,iBAAiB,CAAC,CAAC;IACxC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,CAAC,CAAC;IACrC,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,iBAAiB,CAAC,CAAC;IAExC,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -82,6 +82,6 @@ export interface DevlensConfig {
|
|
|
82
82
|
};
|
|
83
83
|
}
|
|
84
84
|
export interface WsMessage {
|
|
85
|
-
type: 'file-changed' | 'diff-update' | 'status-update' | 'task-update' | 'todo-update' | 'claude-tasks-update' | 'rules-update' | 'commit-approval-update';
|
|
85
|
+
type: 'file-changed' | 'diff-update' | 'status-update' | 'task-update' | 'todo-update' | 'claude-tasks-update' | 'rules-update' | 'commit-approval-update' | 'settings-update';
|
|
86
86
|
payload: unknown;
|
|
87
87
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ycniuqton/devlens",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
},
|
|
30
30
|
"homepage": "https://github.com/ycniuqton/Devlens#readme",
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@ycniuqton/devlens": "^0.1.
|
|
32
|
+
"@ycniuqton/devlens": "^0.1.7",
|
|
33
33
|
"better-sqlite3": "^12.8.0",
|
|
34
34
|
"chokidar": "^3.6.0",
|
|
35
35
|
"commander": "^12.0.0",
|
package/public/css/style.css
CHANGED
|
@@ -2011,6 +2011,75 @@ body {
|
|
|
2011
2011
|
font-weight: 500;
|
|
2012
2012
|
}
|
|
2013
2013
|
|
|
2014
|
+
/* ============================================================
|
|
2015
|
+
Settings Tab
|
|
2016
|
+
============================================================ */
|
|
2017
|
+
.settings-container { max-width: 800px; width: 100%; }
|
|
2018
|
+
|
|
2019
|
+
.settings-section {
|
|
2020
|
+
background: var(--color-surface);
|
|
2021
|
+
border: 1px solid var(--color-border);
|
|
2022
|
+
border-radius: var(--radius-lg);
|
|
2023
|
+
padding: var(--sp-5) var(--sp-6);
|
|
2024
|
+
}
|
|
2025
|
+
|
|
2026
|
+
.settings-section h2 {
|
|
2027
|
+
font-size: var(--text-md);
|
|
2028
|
+
font-weight: 600;
|
|
2029
|
+
margin-bottom: var(--sp-2);
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
.settings-subheader {
|
|
2033
|
+
font-size: var(--text-xs);
|
|
2034
|
+
font-weight: 700;
|
|
2035
|
+
text-transform: uppercase;
|
|
2036
|
+
letter-spacing: 0.08em;
|
|
2037
|
+
color: var(--color-text-muted);
|
|
2038
|
+
margin: var(--sp-5) 0 var(--sp-3);
|
|
2039
|
+
}
|
|
2040
|
+
|
|
2041
|
+
.settings-presets {
|
|
2042
|
+
display: grid;
|
|
2043
|
+
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
|
2044
|
+
gap: var(--sp-2);
|
|
2045
|
+
}
|
|
2046
|
+
|
|
2047
|
+
.preset-checkbox {
|
|
2048
|
+
display: flex;
|
|
2049
|
+
align-items: center;
|
|
2050
|
+
gap: var(--sp-2);
|
|
2051
|
+
padding: var(--sp-2) var(--sp-3);
|
|
2052
|
+
background: var(--color-bg);
|
|
2053
|
+
border: 1px solid var(--color-border-subtle);
|
|
2054
|
+
border-radius: var(--radius-sm);
|
|
2055
|
+
cursor: pointer;
|
|
2056
|
+
transition: all var(--duration-fast) var(--ease);
|
|
2057
|
+
font-size: var(--text-sm);
|
|
2058
|
+
}
|
|
2059
|
+
|
|
2060
|
+
.preset-checkbox:hover { border-color: var(--color-text-muted); }
|
|
2061
|
+
.preset-checkbox input { cursor: pointer; }
|
|
2062
|
+
.preset-name { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--color-text); }
|
|
2063
|
+
|
|
2064
|
+
.settings-textarea {
|
|
2065
|
+
width: 100%;
|
|
2066
|
+
background: var(--color-bg);
|
|
2067
|
+
border: 1px solid var(--color-border);
|
|
2068
|
+
border-radius: var(--radius-md);
|
|
2069
|
+
color: var(--color-text);
|
|
2070
|
+
font-family: var(--font-mono);
|
|
2071
|
+
font-size: var(--text-sm);
|
|
2072
|
+
padding: var(--sp-3) var(--sp-4);
|
|
2073
|
+
resize: vertical;
|
|
2074
|
+
min-height: 120px;
|
|
2075
|
+
}
|
|
2076
|
+
|
|
2077
|
+
.settings-textarea:focus {
|
|
2078
|
+
outline: none;
|
|
2079
|
+
border-color: var(--color-primary);
|
|
2080
|
+
box-shadow: 0 0 0 3px var(--color-primary-subtle);
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2014
2083
|
/* ============================================================
|
|
2015
2084
|
Responsive
|
|
2016
2085
|
============================================================ */
|
package/public/index.html
CHANGED
|
@@ -63,6 +63,12 @@
|
|
|
63
63
|
</svg>
|
|
64
64
|
<span>Integrations</span>
|
|
65
65
|
</button>
|
|
66
|
+
<button class="nav-item" data-tab="settings" aria-label="Settings">
|
|
67
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
68
|
+
<circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/>
|
|
69
|
+
</svg>
|
|
70
|
+
<span>Settings</span>
|
|
71
|
+
</button>
|
|
66
72
|
</nav>
|
|
67
73
|
|
|
68
74
|
<div class="sidebar-footer">
|
|
@@ -88,11 +94,11 @@
|
|
|
88
94
|
</div>
|
|
89
95
|
<div class="divider-v"></div>
|
|
90
96
|
<div class="btn-group">
|
|
91
|
-
<button class="btn btn-ghost" data-view="side-by-side">
|
|
97
|
+
<button class="btn btn-ghost active" data-view="side-by-side">
|
|
92
98
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="8" height="18" rx="1"/><rect x="13" y="3" width="8" height="18" rx="1"/></svg>
|
|
93
99
|
Split
|
|
94
100
|
</button>
|
|
95
|
-
<button class="btn btn-ghost
|
|
101
|
+
<button class="btn btn-ghost" data-view="line-by-line">
|
|
96
102
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="1"/><line x1="3" y1="12" x2="21" y2="12"/></svg>
|
|
97
103
|
Unified
|
|
98
104
|
</button>
|
|
@@ -118,10 +124,10 @@
|
|
|
118
124
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="18 15 12 9 6 15"/></svg>
|
|
119
125
|
</button>
|
|
120
126
|
<div class="file-list-divider"></div>
|
|
121
|
-
<button class="btn-icon btn-icon-sm" id="file-view-tree" title="Folder view" aria-label="Folder view">
|
|
127
|
+
<button class="btn-icon btn-icon-sm active" id="file-view-tree" title="Folder view" aria-label="Folder view">
|
|
122
128
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>
|
|
123
129
|
</button>
|
|
124
|
-
<button class="btn-icon btn-icon-sm
|
|
130
|
+
<button class="btn-icon btn-icon-sm" id="file-view-flat" title="Flat list" aria-label="Flat list">
|
|
125
131
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/></svg>
|
|
126
132
|
</button>
|
|
127
133
|
<span class="file-count" id="file-count">0</span>
|
|
@@ -378,6 +384,32 @@
|
|
|
378
384
|
|
|
379
385
|
</div>
|
|
380
386
|
</section>
|
|
387
|
+
|
|
388
|
+
<!-- Settings View -->
|
|
389
|
+
<section id="settings-view" class="tab-content">
|
|
390
|
+
<header class="view-header">
|
|
391
|
+
<h1>Settings</h1>
|
|
392
|
+
<div class="header-actions">
|
|
393
|
+
<button class="btn btn-primary" id="save-settings-btn">Save Settings</button>
|
|
394
|
+
</div>
|
|
395
|
+
</header>
|
|
396
|
+
|
|
397
|
+
<div class="settings-container">
|
|
398
|
+
<div class="settings-section">
|
|
399
|
+
<h2>Ignored Patterns</h2>
|
|
400
|
+
<p class="section-desc">Patterns ignored by the file watcher and the file explorer. Reduces lag for projects with large dependency folders. Changes apply immediately on save.</p>
|
|
401
|
+
|
|
402
|
+
<h3 class="settings-subheader">Common presets</h3>
|
|
403
|
+
<div id="settings-presets" class="settings-presets">
|
|
404
|
+
<p class="panel-empty">Loading...</p>
|
|
405
|
+
</div>
|
|
406
|
+
|
|
407
|
+
<h3 class="settings-subheader">Custom patterns</h3>
|
|
408
|
+
<p class="section-desc">One pattern per line. Plain names match anywhere in the tree (e.g. <code>node_modules</code>). Globs supported (e.g. <code>**/*.log</code>).</p>
|
|
409
|
+
<textarea id="settings-custom" class="settings-textarea" rows="6" placeholder="my-cache **/*.log"></textarea>
|
|
410
|
+
</div>
|
|
411
|
+
</div>
|
|
412
|
+
</section>
|
|
381
413
|
</main>
|
|
382
414
|
|
|
383
415
|
<!-- Task Modal -->
|
|
@@ -482,6 +514,7 @@
|
|
|
482
514
|
<script src="/js/rules.js"></script>
|
|
483
515
|
<script src="/js/browser.js"></script>
|
|
484
516
|
<script src="/js/history.js"></script>
|
|
517
|
+
<script src="/js/settings.js"></script>
|
|
485
518
|
<script src="/js/integrations.js"></script>
|
|
486
519
|
</body>
|
|
487
520
|
</html>
|
package/public/js/app.js
CHANGED
|
@@ -40,7 +40,7 @@ window.addEventListener('popstate', () => {
|
|
|
40
40
|
// Load initial tab from URL — redirect / to /diff
|
|
41
41
|
(function() {
|
|
42
42
|
const path = location.pathname.replace('/', '');
|
|
43
|
-
const tab = ['diff', 'tasks', 'browser', 'history', 'rules', 'integrations'].includes(path) ? path : 'diff';
|
|
43
|
+
const tab = ['diff', 'tasks', 'browser', 'history', 'rules', 'integrations', 'settings'].includes(path) ? path : 'diff';
|
|
44
44
|
if (!path || path === '') {
|
|
45
45
|
history.replaceState(null, '', '/diff');
|
|
46
46
|
}
|
package/public/js/diff.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
// Diff viewer
|
|
2
2
|
var currentFilter = 'all';
|
|
3
|
-
var currentViewMode = '
|
|
4
|
-
var fileListMode = localStorage.getItem('devlens-file-view') || '
|
|
3
|
+
var currentViewMode = localStorage.getItem('devlens-diff-view') || 'side-by-side';
|
|
4
|
+
var fileListMode = localStorage.getItem('devlens-file-view') || 'tree';
|
|
5
5
|
var diffFiles = [];
|
|
6
6
|
var currentFiles = [];
|
|
7
7
|
|
|
8
|
-
// Restore file list
|
|
9
|
-
(function
|
|
8
|
+
// Restore view mode + file list mode from localStorage
|
|
9
|
+
(function restoreDiffSettings() {
|
|
10
10
|
const flatBtn = document.getElementById('file-view-flat');
|
|
11
11
|
const treeBtn = document.getElementById('file-view-tree');
|
|
12
12
|
if (fileListMode === 'tree') {
|
|
@@ -16,6 +16,12 @@ var currentFiles = [];
|
|
|
16
16
|
flatBtn?.classList.add('active');
|
|
17
17
|
treeBtn?.classList.remove('active');
|
|
18
18
|
}
|
|
19
|
+
|
|
20
|
+
// Restore active button for diff view mode (split / unified)
|
|
21
|
+
document.querySelectorAll('[data-view]').forEach(b => {
|
|
22
|
+
if (b.dataset.view === currentViewMode) b.classList.add('active');
|
|
23
|
+
else b.classList.remove('active');
|
|
24
|
+
});
|
|
19
25
|
})();
|
|
20
26
|
|
|
21
27
|
// Filter and view toggle
|
|
@@ -34,6 +40,7 @@ document.querySelector('.header-actions')?.addEventListener('click', (e) => {
|
|
|
34
40
|
document.querySelectorAll('[data-view]').forEach(b => b.classList.remove('active'));
|
|
35
41
|
btn.classList.add('active');
|
|
36
42
|
currentViewMode = btn.dataset.view;
|
|
43
|
+
localStorage.setItem('devlens-diff-view', currentViewMode);
|
|
37
44
|
renderAllFiles();
|
|
38
45
|
}
|
|
39
46
|
});
|
|
@@ -56,11 +63,12 @@ document.getElementById('file-list-items').addEventListener('click', (e) => {
|
|
|
56
63
|
document.querySelectorAll('#file-list-items li').forEach(l => l.classList.remove('selected'));
|
|
57
64
|
li.classList.add('selected');
|
|
58
65
|
|
|
59
|
-
// Collapse all, expand only the clicked file
|
|
66
|
+
// Collapse all, expand only the clicked file (lazy-render its body)
|
|
60
67
|
const sections = document.querySelectorAll('.diff-file-section');
|
|
61
68
|
for (const section of sections) {
|
|
62
69
|
if (section.dataset.file === fileName) {
|
|
63
70
|
section.classList.add('expanded');
|
|
71
|
+
renderFileBodyIfNeeded(section);
|
|
64
72
|
} else {
|
|
65
73
|
section.classList.remove('expanded');
|
|
66
74
|
}
|
|
@@ -198,21 +206,12 @@ function renderAllFiles() {
|
|
|
198
206
|
return;
|
|
199
207
|
}
|
|
200
208
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
// First file expanded, rest collapsed
|
|
209
|
+
// Render only headers — diffs are computed lazily on expand
|
|
210
|
+
// diffFiles[i].diff holds the raw unified diff string for this file
|
|
204
211
|
container.innerHTML = diffFiles.map((file, i) => {
|
|
205
|
-
const diffHtml = Diff2Html.html(file.diff, {
|
|
206
|
-
drawFileList: false,
|
|
207
|
-
matching: 'lines',
|
|
208
|
-
outputFormat: outputFormat,
|
|
209
|
-
colorScheme: 'dark',
|
|
210
|
-
});
|
|
211
|
-
|
|
212
212
|
const shortName = file.name.split('/').pop();
|
|
213
|
-
|
|
214
213
|
return `
|
|
215
|
-
<div class="diff-file-section ${i === 0 ? 'expanded' : ''}" data-file="${file.name}">
|
|
214
|
+
<div class="diff-file-section ${i === 0 ? 'expanded' : ''}" data-file="${file.name}" data-idx="${i}">
|
|
216
215
|
<div class="diff-file-header" onclick="toggleFileSection(this)">
|
|
217
216
|
<svg class="chevron" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
218
217
|
<polyline points="9 18 15 12 9 6"/>
|
|
@@ -220,16 +219,42 @@ function renderAllFiles() {
|
|
|
220
219
|
<span class="diff-file-name">${file.name}</span>
|
|
221
220
|
<span class="diff-file-badge">${shortName}</span>
|
|
222
221
|
</div>
|
|
223
|
-
<div class="diff-file-body"
|
|
222
|
+
<div class="diff-file-body" data-rendered="false"></div>
|
|
224
223
|
</div>
|
|
225
224
|
`;
|
|
226
225
|
}).join('');
|
|
226
|
+
|
|
227
|
+
// Pre-render only the first (expanded by default) file
|
|
228
|
+
const first = container.querySelector('.diff-file-section.expanded');
|
|
229
|
+
if (first) renderFileBodyIfNeeded(first);
|
|
227
230
|
}
|
|
228
231
|
|
|
229
|
-
//
|
|
232
|
+
// Render the diff HTML for a file section if not already done
|
|
233
|
+
function renderFileBodyIfNeeded(section) {
|
|
234
|
+
const body = section.querySelector('.diff-file-body');
|
|
235
|
+
if (!body || body.dataset.rendered === 'true') return;
|
|
236
|
+
|
|
237
|
+
const idx = parseInt(section.dataset.idx, 10);
|
|
238
|
+
const file = diffFiles[idx];
|
|
239
|
+
if (!file) return;
|
|
240
|
+
|
|
241
|
+
const outputFormat = currentViewMode === 'side-by-side' ? 'side-by-side' : 'line-by-line';
|
|
242
|
+
body.innerHTML = Diff2Html.html(file.diff, {
|
|
243
|
+
drawFileList: false,
|
|
244
|
+
matching: 'lines',
|
|
245
|
+
outputFormat: outputFormat,
|
|
246
|
+
colorScheme: 'dark',
|
|
247
|
+
});
|
|
248
|
+
body.dataset.rendered = 'true';
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Chevron click — toggle just this file, lazy-render on first expand
|
|
230
252
|
function toggleFileSection(headerEl) {
|
|
231
253
|
const section = headerEl.closest('.diff-file-section');
|
|
232
254
|
section.classList.toggle('expanded');
|
|
255
|
+
if (section.classList.contains('expanded')) {
|
|
256
|
+
renderFileBodyIfNeeded(section);
|
|
257
|
+
}
|
|
233
258
|
}
|
|
234
259
|
|
|
235
260
|
var STATUS_LABELS = { modified: 'M', added: 'A', deleted: 'D', untracked: 'U', renamed: 'R' };
|
package/public/js/history.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
var historyLoaded = false;
|
|
4
4
|
var historySelectedHash = null;
|
|
5
|
-
var historyViewMode = localStorage.getItem('devlens-history-view') || '
|
|
5
|
+
var historyViewMode = localStorage.getItem('devlens-history-view') || 'side-by-side';
|
|
6
6
|
var historyCachedData = null; // last loaded commit { hash, files, diff }
|
|
7
7
|
|
|
8
8
|
async function loadBranchInfo() {
|
|
@@ -133,33 +133,49 @@ function renderCommitDetails(hash, data) {
|
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
const files = historySplitDiffByFile(data.diff);
|
|
136
|
-
|
|
137
|
-
|
|
136
|
+
|
|
137
|
+
// Stash raw diffs on a closure-accessible array indexed by data-idx
|
|
138
|
+
const renderCommitFileBody = (section) => {
|
|
139
|
+
const body = section.querySelector('.commit-file-body');
|
|
140
|
+
if (!body || body.dataset.rendered === 'true') return;
|
|
141
|
+
const idx = parseInt(section.dataset.idx, 10);
|
|
142
|
+
const f = files[idx];
|
|
143
|
+
if (!f) return;
|
|
144
|
+
body.innerHTML = Diff2Html.html(f.diff, {
|
|
138
145
|
drawFileList: false,
|
|
139
146
|
matching: 'lines',
|
|
140
147
|
outputFormat: historyViewMode,
|
|
141
148
|
colorScheme: 'dark',
|
|
142
149
|
});
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
<
|
|
150
|
+
body.dataset.rendered = 'true';
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
diffEl.innerHTML = files.map((file, i) => `
|
|
154
|
+
<div class="commit-file-section" data-file="${escapeAttrHistory(file.name)}" data-idx="${i}">
|
|
155
|
+
<div class="commit-file-header">
|
|
156
|
+
<svg class="commit-file-chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg>
|
|
157
|
+
<span class="commit-file-name">${escapeHtmlHistory(file.name)}</span>
|
|
150
158
|
</div>
|
|
151
|
-
|
|
152
|
-
|
|
159
|
+
<div class="commit-file-body" data-rendered="false"></div>
|
|
160
|
+
</div>
|
|
161
|
+
`).join('');
|
|
153
162
|
|
|
154
|
-
// Wire
|
|
163
|
+
// Wire collapse/expand with lazy render on first expand
|
|
155
164
|
diffEl.querySelectorAll('.commit-file-header').forEach(h => {
|
|
156
165
|
h.addEventListener('click', () => {
|
|
157
|
-
h.closest('.commit-file-section')
|
|
166
|
+
const section = h.closest('.commit-file-section');
|
|
167
|
+
section.classList.toggle('expanded');
|
|
168
|
+
if (section.classList.contains('expanded')) {
|
|
169
|
+
renderCommitFileBody(section);
|
|
170
|
+
}
|
|
158
171
|
});
|
|
159
172
|
});
|
|
160
173
|
|
|
161
174
|
document.getElementById('commit-expand-all')?.addEventListener('click', () => {
|
|
162
|
-
diffEl.querySelectorAll('.commit-file-section').forEach(s =>
|
|
175
|
+
diffEl.querySelectorAll('.commit-file-section').forEach(s => {
|
|
176
|
+
s.classList.add('expanded');
|
|
177
|
+
renderCommitFileBody(s);
|
|
178
|
+
});
|
|
163
179
|
});
|
|
164
180
|
document.getElementById('commit-collapse-all')?.addEventListener('click', () => {
|
|
165
181
|
diffEl.querySelectorAll('.commit-file-section').forEach(s => s.classList.remove('expanded'));
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// Settings tab — manage .devlens/settings.json (ignore patterns)
|
|
2
|
+
|
|
3
|
+
var settingsLoaded = false;
|
|
4
|
+
|
|
5
|
+
async function loadSettings() {
|
|
6
|
+
try {
|
|
7
|
+
const res = await fetch('/api/settings');
|
|
8
|
+
const data = await res.json();
|
|
9
|
+
renderSettings(data);
|
|
10
|
+
settingsLoaded = true;
|
|
11
|
+
} catch {
|
|
12
|
+
showToast('Failed to load settings', 'error');
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function renderSettings(data) {
|
|
17
|
+
const presetsEl = document.getElementById('settings-presets');
|
|
18
|
+
const customEl = document.getElementById('settings-custom');
|
|
19
|
+
if (!presetsEl || !customEl) return;
|
|
20
|
+
|
|
21
|
+
const active = new Set(data.ignorePatterns || []);
|
|
22
|
+
const presets = data.presets || [];
|
|
23
|
+
|
|
24
|
+
// Render preset checkboxes
|
|
25
|
+
presetsEl.innerHTML = presets.map(p => `
|
|
26
|
+
<label class="preset-checkbox">
|
|
27
|
+
<input type="checkbox" data-preset="${escapeAttrSettings(p)}" ${active.has(p) ? 'checked' : ''}>
|
|
28
|
+
<span class="preset-name">${escapeHtmlSettings(p)}</span>
|
|
29
|
+
</label>
|
|
30
|
+
`).join('');
|
|
31
|
+
|
|
32
|
+
// Custom = active patterns NOT in presets
|
|
33
|
+
const customPatterns = (data.ignorePatterns || []).filter(p => !presets.includes(p));
|
|
34
|
+
customEl.value = customPatterns.join('\n');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function collectCurrentPatterns() {
|
|
38
|
+
const checked = Array.from(document.querySelectorAll('#settings-presets input[type="checkbox"]:checked'))
|
|
39
|
+
.map(cb => cb.dataset.preset);
|
|
40
|
+
const custom = (document.getElementById('settings-custom')?.value || '')
|
|
41
|
+
.split('\n')
|
|
42
|
+
.map(l => l.trim())
|
|
43
|
+
.filter(Boolean);
|
|
44
|
+
// Dedupe
|
|
45
|
+
return Array.from(new Set([...checked, ...custom]));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function saveSettings() {
|
|
49
|
+
const patterns = collectCurrentPatterns();
|
|
50
|
+
try {
|
|
51
|
+
const res = await fetch('/api/settings', {
|
|
52
|
+
method: 'PUT',
|
|
53
|
+
headers: { 'Content-Type': 'application/json' },
|
|
54
|
+
body: JSON.stringify({ ignorePatterns: patterns }),
|
|
55
|
+
});
|
|
56
|
+
if (!res.ok) {
|
|
57
|
+
showToast('Failed to save settings', 'error');
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
showToast('Settings saved — watcher restarted', 'success');
|
|
61
|
+
} catch {
|
|
62
|
+
showToast('Failed to save settings', 'error');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function escapeHtmlSettings(str) {
|
|
67
|
+
if (str == null) return '';
|
|
68
|
+
const div = document.createElement('div');
|
|
69
|
+
div.textContent = str;
|
|
70
|
+
return div.innerHTML;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function escapeAttrSettings(str) {
|
|
74
|
+
return String(str).replace(/"/g, '"');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
document.getElementById('save-settings-btn')?.addEventListener('click', saveSettings);
|
|
78
|
+
|
|
79
|
+
document.querySelector('[data-tab="settings"]')?.addEventListener('click', () => {
|
|
80
|
+
if (!settingsLoaded) loadSettings();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
if (location.pathname === '/settings') {
|
|
84
|
+
loadSettings();
|
|
85
|
+
}
|