collabmd 0.1.20 → 0.1.23
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 +98 -9
- package/bin/collabmd.js +3 -2
- package/docker-compose.yml +13 -1
- package/package.json +2 -1
- package/public/assets/css/style.css +1 -1
- package/public/assets/js/chunks/{chunk-HEWVH67U.js → chunk-CYK7KO7W.js} +8 -8
- package/public/assets/js/chunks/chunk-GFDM7YCF.js +1 -0
- package/public/assets/js/chunks/editor-session-S5AT7BMV.js +22 -0
- package/public/assets/js/chunks/{preview-render-compiler-UZ4SZDQQ.js → preview-render-compiler-6Y7TKXFJ.js} +1 -1
- package/public/assets/js/chunks/{quick-switcher-controller-J7I3CUET.js → quick-switcher-controller-A6SKH5HI.js} +1 -1
- package/public/assets/js/{excalidraw-editor-ZDYKMZOL.js → excalidraw-editor-5YWMGLKF.js} +145 -62
- package/public/assets/js/excalidraw-editor.js +1 -1
- package/public/assets/js/main.js +503 -182
- package/public/assets/js/preview-render-worker.js +15 -15
- package/public/index.html +104 -84
- package/src/client/application/app-shell/git-feature.js +304 -27
- package/src/client/application/app-shell/ui-feature.js +69 -7
- package/src/client/application/app-shell/workspace-feature.js +46 -0
- package/src/client/application/app-shell-elements.js +2 -0
- package/src/client/application/mermaid-preview-hydrator.js +1 -1
- package/src/client/application/plantuml-preview-hydrator.js +1 -1
- package/src/client/application/workspace-coordinator.js +147 -18
- package/src/client/application/workspace-preview-controller.js +34 -0
- package/src/client/application/workspace-route-controller.js +104 -0
- package/src/client/bootstrap/collabmd-app-shell.js +107 -0
- package/src/client/domain/runtime-paths.js +5 -0
- package/src/client/domain/vault-utils.js +2 -2
- package/src/client/excalidraw-editor.js +52 -3
- package/src/client/infrastructure/auth-client.js +219 -52
- package/src/client/infrastructure/browser-navigation-port.js +20 -0
- package/src/client/infrastructure/editor-collaboration-client.js +4 -0
- package/src/client/infrastructure/editor-session.js +51 -7
- package/src/client/infrastructure/editor-view-adapter.js +297 -53
- package/src/client/infrastructure/runtime-config.js +66 -0
- package/src/client/infrastructure/workspace-sync-client.js +324 -0
- package/src/client/presentation/comment-ui-controller.js +274 -31
- package/src/client/presentation/comments-panel.js +2 -2
- package/src/client/presentation/excalidraw-embed-controller.js +60 -2
- package/src/client/presentation/file-explorer-controller.js +15 -3
- package/src/client/presentation/file-explorer-view.js +152 -11
- package/src/client/presentation/file-history-view-controller.js +806 -0
- package/src/client/presentation/git-diff-view-controller.js +718 -121
- package/src/client/presentation/git-panel-controller.js +458 -66
- package/src/client/presentation/layout-controller.js +1 -1
- package/src/client/styles/components/actions.css +89 -0
- package/src/client/styles/components/feedback.css +43 -0
- package/src/client/styles/components/navigation.css +68 -0
- package/src/client/styles/components/scrollbars.css +104 -0
- package/src/client/styles/components/states.css +289 -0
- package/src/client/styles/components/surfaces.css +155 -0
- package/src/client/styles/features/backlinks.css +213 -0
- package/src/client/styles/features/collaboration-chat.css +228 -0
- package/src/client/styles/features/collaboration-dialogs.css +154 -0
- package/src/client/styles/features/collaboration-presence.css +103 -0
- package/src/client/styles/features/collaboration-ui.css +3 -0
- package/src/client/styles/features/comment-card.css +236 -0
- package/src/client/styles/features/comment-markdown.css +127 -0
- package/src/client/styles/features/comment-overlays.css +60 -0
- package/src/client/styles/features/comments-drawer.css +120 -0
- package/src/client/styles/features/comments.css +4 -0
- package/src/client/styles/features/diagram-preview.css +227 -0
- package/src/client/styles/features/diff-commit.css +268 -0
- package/src/client/styles/features/diff-lines.css +160 -0
- package/src/client/styles/features/diff-toolbar.css +98 -0
- package/src/client/styles/features/diff.css +3 -0
- package/src/client/styles/features/embedded-preview.css +218 -0
- package/src/client/styles/features/git-branch.css +155 -0
- package/src/client/styles/features/git-files.css +105 -0
- package/src/client/styles/features/git-history.css +339 -0
- package/src/client/styles/features/git.css +3 -0
- package/src/client/styles/features/media-lightbox.css +157 -0
- package/src/client/styles/features/outline.css +115 -0
- package/src/client/styles/features/preview-content.css +3 -0
- package/src/client/styles/features/preview-markdown.css +201 -0
- package/src/client/styles/features/preview-sidebars.css +2 -0
- package/src/client/styles/features/quick-switcher.css +167 -0
- package/src/client/styles/foundation/themes.css +347 -0
- package/src/client/styles/foundation/tokens.css +66 -0
- package/src/client/styles/foundation/utilities.css +36 -0
- package/src/client/styles/layout/editor-page.css +203 -0
- package/src/client/styles/layout/overlays.css +132 -0
- package/src/client/styles/layout/responsive.css +251 -0
- package/src/client/styles/layout/shell.css +314 -0
- package/src/client/styles/layout/sidebar.css +176 -0
- package/src/client/styles/layout/view-modes.css +92 -0
- package/src/client/styles/overrides/codemirror.css +53 -0
- package/src/client/styles/overrides/highlightjs.css +7 -0
- package/src/client/styles/overrides/vendor.css +2 -0
- package/src/client/styles/primitives/controls.css +276 -0
- package/src/client/styles/style.css +25 -4901
- package/src/domain/wiki-link-resolver.js +16 -5
- package/src/domain/workspace-change.js +68 -0
- package/src/domain/workspace-room.js +3 -0
- package/src/server/auth/create-auth-service.js +373 -15
- package/src/server/auth/session-cookie.js +30 -4
- package/src/server/config/env.js +121 -5
- package/src/server/create-app-server.js +49 -10
- package/src/server/domain/backlink-index.js +94 -1
- package/src/server/domain/collaboration/collaboration-room.js +281 -69
- package/src/server/domain/collaboration/room-registry.js +64 -10
- package/src/server/index.js +3 -1
- package/src/server/infrastructure/git/command-runner.js +2 -1
- package/src/server/infrastructure/git/git-service.js +48 -2
- package/src/server/infrastructure/git/history-service.js +683 -0
- package/src/server/infrastructure/git/parsers.js +30 -0
- package/src/server/infrastructure/git/responses.js +85 -19
- package/src/server/infrastructure/http/create-auth-api-handler.js +30 -2
- package/src/server/infrastructure/http/create-git-api-command-handler.js +55 -43
- package/src/server/infrastructure/http/create-git-api-handler.js +4 -0
- package/src/server/infrastructure/http/create-git-api-query-handler.js +61 -0
- package/src/server/infrastructure/http/create-request-handler.js +22 -0
- package/src/server/infrastructure/http/create-vault-api-command-handler.js +58 -14
- package/src/server/infrastructure/http/create-vault-api-handler.js +3 -0
- package/src/server/infrastructure/http/create-vault-api-query-handler.js +3 -1
- package/src/server/infrastructure/http/http-response.js +65 -28
- package/src/server/infrastructure/persistence/vault-file-store.js +163 -52
- package/src/server/infrastructure/workspace/file-system-sync-service.js +488 -0
- package/src/server/infrastructure/workspace/workspace-mutation-coordinator.js +551 -0
- package/public/assets/js/chunks/chunk-R3DDMJHH.js +0 -1
- package/public/assets/js/chunks/editor-session-TAV2VXTA.js +0 -22
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
brotliCompress,
|
|
3
|
+
constants as zlibConstants,
|
|
4
|
+
gzip,
|
|
5
|
+
} from 'node:zlib';
|
|
2
6
|
|
|
3
7
|
const COMPRESSIBLE_CONTENT_TYPE_PATTERN = /^(?:text\/|application\/(?:javascript|json|xml)|image\/svg\+xml)/i;
|
|
4
8
|
const MIN_COMPRESSIBLE_BYTES = 1024;
|
|
@@ -41,9 +45,9 @@ function resolveCompressionEncoding(acceptEncodingHeader) {
|
|
|
41
45
|
return null;
|
|
42
46
|
}
|
|
43
47
|
|
|
44
|
-
function
|
|
48
|
+
function prepareBody(req, body, contentType) {
|
|
45
49
|
if (body === undefined || body === null) {
|
|
46
|
-
return { body: null,
|
|
50
|
+
return { body: null, encoding: null };
|
|
47
51
|
}
|
|
48
52
|
|
|
49
53
|
const bodyBuffer = Buffer.isBuffer(body)
|
|
@@ -54,27 +58,48 @@ function maybeCompressBody(req, body, contentType) {
|
|
|
54
58
|
bodyBuffer.byteLength < MIN_COMPRESSIBLE_BYTES
|
|
55
59
|
|| !COMPRESSIBLE_CONTENT_TYPE_PATTERN.test(String(contentType || ''))
|
|
56
60
|
) {
|
|
57
|
-
return { body: bodyBuffer,
|
|
61
|
+
return { body: bodyBuffer, encoding: null };
|
|
58
62
|
}
|
|
59
63
|
|
|
60
64
|
const encoding = resolveCompressionEncoding(req.headers['accept-encoding']);
|
|
61
65
|
if (!encoding) {
|
|
62
|
-
return { body: bodyBuffer,
|
|
66
|
+
return { body: bodyBuffer, encoding: null };
|
|
63
67
|
}
|
|
64
68
|
|
|
65
|
-
|
|
66
|
-
|
|
69
|
+
return { body: bodyBuffer, encoding };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function compressBody(bodyBuffer, encoding, callback) {
|
|
73
|
+
if (encoding === 'br') {
|
|
74
|
+
brotliCompress(bodyBuffer, {
|
|
67
75
|
params: {
|
|
68
76
|
[zlibConstants.BROTLI_PARAM_QUALITY]: 5,
|
|
69
77
|
},
|
|
70
|
-
})
|
|
71
|
-
|
|
78
|
+
}, callback);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
gzip(bodyBuffer, { level: 6 }, callback);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function writeResponseHead(res, statusCode, headers, bodyBuffer) {
|
|
86
|
+
const responseHeaders = { ...headers };
|
|
87
|
+
if (bodyBuffer) {
|
|
88
|
+
responseHeaders['Content-Length'] = String(bodyBuffer.byteLength);
|
|
89
|
+
}
|
|
72
90
|
|
|
73
|
-
|
|
74
|
-
|
|
91
|
+
res.writeHead(statusCode, responseHeaders);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function writePreparedBody(req, res, statusCode, headers, bodyBuffer) {
|
|
95
|
+
writeResponseHead(res, statusCode, headers, bodyBuffer);
|
|
96
|
+
|
|
97
|
+
if (req.method === 'HEAD' || statusCode === 204 || statusCode === 304) {
|
|
98
|
+
res.end();
|
|
99
|
+
return;
|
|
75
100
|
}
|
|
76
101
|
|
|
77
|
-
|
|
102
|
+
res.end(bodyBuffer ?? undefined);
|
|
78
103
|
}
|
|
79
104
|
|
|
80
105
|
export function setHeaders(res, headers) {
|
|
@@ -116,30 +141,42 @@ export function sendResponse(req, res, {
|
|
|
116
141
|
statusCode = 200,
|
|
117
142
|
} = {}) {
|
|
118
143
|
const contentType = headers['Content-Type'] || headers['content-type'] || '';
|
|
119
|
-
const prepared =
|
|
144
|
+
const prepared = prepareBody(req, body, contentType);
|
|
120
145
|
|
|
121
146
|
if (body !== null) {
|
|
122
147
|
appendVaryHeader(res, 'Accept-Encoding');
|
|
123
148
|
}
|
|
124
149
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
if (prepared.compressed && prepared.encoding) {
|
|
128
|
-
responseHeaders['Content-Encoding'] = prepared.encoding;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if (prepared.body) {
|
|
132
|
-
responseHeaders['Content-Length'] = String(prepared.body.byteLength);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
res.writeHead(statusCode, responseHeaders);
|
|
136
|
-
|
|
137
|
-
if (req.method === 'HEAD' || statusCode === 204 || statusCode === 304) {
|
|
138
|
-
res.end();
|
|
150
|
+
if (!prepared.body || !prepared.encoding || req.method === 'HEAD' || statusCode === 204 || statusCode === 304) {
|
|
151
|
+
writePreparedBody(req, res, statusCode, headers, prepared.body);
|
|
139
152
|
return;
|
|
140
153
|
}
|
|
141
154
|
|
|
142
|
-
|
|
155
|
+
compressBody(prepared.body, prepared.encoding, (error, compressedBody) => {
|
|
156
|
+
if (res.writableEnded || res.destroyed) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (error) {
|
|
161
|
+
console.error('[http] Failed to compress response body:', error.message);
|
|
162
|
+
if (!res.headersSent) {
|
|
163
|
+
writePreparedBody(req, res, statusCode, headers, prepared.body);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
res.destroy(error);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (!compressedBody || compressedBody.byteLength >= prepared.body.byteLength) {
|
|
171
|
+
writePreparedBody(req, res, statusCode, headers, prepared.body);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
writePreparedBody(req, res, statusCode, {
|
|
176
|
+
...headers,
|
|
177
|
+
'Content-Encoding': prepared.encoding,
|
|
178
|
+
}, compressedBody);
|
|
179
|
+
});
|
|
143
180
|
}
|
|
144
181
|
|
|
145
182
|
export function jsonResponse(req, res, statusCode, data) {
|
|
@@ -60,6 +60,19 @@ async function pathExists(pathValue) {
|
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
function createWorkspaceEntry(relativePath, type) {
|
|
64
|
+
return {
|
|
65
|
+
fileKind: type === 'directory' ? null : getVaultTreeNodeType(relativePath),
|
|
66
|
+
name: basename(relativePath),
|
|
67
|
+
nodeType: type,
|
|
68
|
+
parentPath: dirname(relativePath).replace(/\\/g, '/') === '.'
|
|
69
|
+
? ''
|
|
70
|
+
: dirname(relativePath).replace(/\\/g, '/'),
|
|
71
|
+
path: relativePath,
|
|
72
|
+
type: type === 'directory' ? 'directory' : getVaultTreeNodeType(relativePath),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
63
76
|
async function cleanupPaths(paths = []) {
|
|
64
77
|
await Promise.allSettled(paths.filter(Boolean).map((pathValue) => rm(pathValue, { force: true })));
|
|
65
78
|
}
|
|
@@ -150,6 +163,19 @@ export class VaultFileStore {
|
|
|
150
163
|
constructor({ vaultDir }) {
|
|
151
164
|
this.vaultDir = resolve(vaultDir);
|
|
152
165
|
this.sidecarStore = new SidecarStore({ vaultDir: this.vaultDir });
|
|
166
|
+
this.managedWriteTracker = null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
setManagedWriteTracker(tracker) {
|
|
170
|
+
this.managedWriteTracker = tracker ?? null;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async runManagedWrite(paths, operation) {
|
|
174
|
+
if (!this.managedWriteTracker?.runManagedWrite) {
|
|
175
|
+
return operation();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return this.managedWriteTracker.runManagedWrite(paths, operation);
|
|
153
179
|
}
|
|
154
180
|
|
|
155
181
|
async tree() {
|
|
@@ -251,11 +277,13 @@ export class VaultFileStore {
|
|
|
251
277
|
}
|
|
252
278
|
|
|
253
279
|
try {
|
|
254
|
-
await
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
280
|
+
await this.runManagedWrite([filePath], async () => {
|
|
281
|
+
await mkdir(dirname(resolved.absolute), { recursive: true });
|
|
282
|
+
await writeFile(resolved.absolute, content, 'utf-8');
|
|
283
|
+
if (invalidateCollaborationSnapshot) {
|
|
284
|
+
await this.deleteCollaborationSnapshot(filePath);
|
|
285
|
+
}
|
|
286
|
+
});
|
|
259
287
|
return { ok: true };
|
|
260
288
|
} catch (error) {
|
|
261
289
|
return { ok: false, error: error.message };
|
|
@@ -356,8 +384,10 @@ export class VaultFileStore {
|
|
|
356
384
|
}
|
|
357
385
|
|
|
358
386
|
try {
|
|
359
|
-
await
|
|
360
|
-
|
|
387
|
+
await this.runManagedWrite([storedPath], async () => {
|
|
388
|
+
await mkdir(dirname(absolutePath), { recursive: true });
|
|
389
|
+
await writeFile(absolutePath, content);
|
|
390
|
+
});
|
|
361
391
|
} catch (error) {
|
|
362
392
|
return { ok: false, error: error.message };
|
|
363
393
|
}
|
|
@@ -412,49 +442,51 @@ export class VaultFileStore {
|
|
|
412
442
|
const committedOperations = [];
|
|
413
443
|
|
|
414
444
|
try {
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
445
|
+
await this.runManagedWrite([filePath], async () => {
|
|
446
|
+
for (const operation of operations) {
|
|
447
|
+
if (operation.kind !== 'write') {
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
419
450
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
451
|
+
const tempPath = createTransactionalPath(operation.targetPath, 'tmp');
|
|
452
|
+
await mkdir(dirname(operation.targetPath), { recursive: true });
|
|
453
|
+
await writeFile(tempPath, operation.value, operation.writeOptions);
|
|
454
|
+
operation.tempPath = tempPath;
|
|
455
|
+
stagedWrites.push(tempPath);
|
|
456
|
+
}
|
|
426
457
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
458
|
+
for (const operation of operations) {
|
|
459
|
+
const hadExistingTarget = await pathExists(operation.targetPath);
|
|
460
|
+
const backupPath = hadExistingTarget
|
|
461
|
+
? createTransactionalPath(operation.targetPath, 'bak')
|
|
462
|
+
: null;
|
|
432
463
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
464
|
+
if (backupPath) {
|
|
465
|
+
await rename(operation.targetPath, backupPath);
|
|
466
|
+
}
|
|
436
467
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
468
|
+
try {
|
|
469
|
+
if (operation.kind === 'write') {
|
|
470
|
+
await rename(operation.tempPath, operation.targetPath);
|
|
471
|
+
const stagedIndex = stagedWrites.indexOf(operation.tempPath);
|
|
472
|
+
if (stagedIndex >= 0) {
|
|
473
|
+
stagedWrites.splice(stagedIndex, 1);
|
|
474
|
+
}
|
|
443
475
|
}
|
|
476
|
+
} catch (error) {
|
|
477
|
+
if (backupPath) {
|
|
478
|
+
await rename(backupPath, operation.targetPath);
|
|
479
|
+
}
|
|
480
|
+
throw error;
|
|
444
481
|
}
|
|
445
|
-
} catch (error) {
|
|
446
|
-
if (backupPath) {
|
|
447
|
-
await rename(backupPath, operation.targetPath);
|
|
448
|
-
}
|
|
449
|
-
throw error;
|
|
450
|
-
}
|
|
451
482
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
483
|
+
committedOperations.push({
|
|
484
|
+
backupPath,
|
|
485
|
+
kind: operation.kind,
|
|
486
|
+
targetPath: operation.targetPath,
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
});
|
|
458
490
|
|
|
459
491
|
await cleanupPaths(committedOperations.map((operation) => operation.backupPath));
|
|
460
492
|
return { ok: true };
|
|
@@ -510,9 +542,11 @@ export class VaultFileStore {
|
|
|
510
542
|
}
|
|
511
543
|
}
|
|
512
544
|
|
|
513
|
-
await
|
|
514
|
-
|
|
515
|
-
|
|
545
|
+
await this.runManagedWrite([filePath], async () => {
|
|
546
|
+
await mkdir(dirname(absolute), { recursive: true });
|
|
547
|
+
await writeFile(absolute, content, 'utf-8');
|
|
548
|
+
await this.deleteCollaborationSnapshot(filePath);
|
|
549
|
+
});
|
|
516
550
|
return { ok: true };
|
|
517
551
|
}
|
|
518
552
|
|
|
@@ -523,8 +557,10 @@ export class VaultFileStore {
|
|
|
523
557
|
}
|
|
524
558
|
|
|
525
559
|
try {
|
|
526
|
-
await
|
|
527
|
-
|
|
560
|
+
await this.runManagedWrite([filePath], async () => {
|
|
561
|
+
await rm(absolute, { force: true });
|
|
562
|
+
await this.sidecarStore.deleteAllForFile(filePath);
|
|
563
|
+
});
|
|
528
564
|
return { ok: true };
|
|
529
565
|
} catch (error) {
|
|
530
566
|
return { ok: false, error: error.message };
|
|
@@ -538,9 +574,11 @@ export class VaultFileStore {
|
|
|
538
574
|
}
|
|
539
575
|
|
|
540
576
|
try {
|
|
541
|
-
await
|
|
542
|
-
|
|
543
|
-
|
|
577
|
+
await this.runManagedWrite([oldPath, newPath], async () => {
|
|
578
|
+
await mkdir(dirname(absoluteNew), { recursive: true });
|
|
579
|
+
await rename(absoluteOld, absoluteNew);
|
|
580
|
+
await this.sidecarStore.renameAllForFile(oldPath, newPath);
|
|
581
|
+
});
|
|
544
582
|
return { ok: true };
|
|
545
583
|
} catch (error) {
|
|
546
584
|
return { ok: false, error: error.message };
|
|
@@ -554,7 +592,7 @@ export class VaultFileStore {
|
|
|
554
592
|
}
|
|
555
593
|
|
|
556
594
|
try {
|
|
557
|
-
await mkdir(absolute, { recursive: true });
|
|
595
|
+
await this.runManagedWrite([dirPath], () => mkdir(absolute, { recursive: true }));
|
|
558
596
|
return { ok: true };
|
|
559
597
|
} catch (error) {
|
|
560
598
|
return { ok: false, error: error.message };
|
|
@@ -633,4 +671,77 @@ export class VaultFileStore {
|
|
|
633
671
|
|
|
634
672
|
return toVaultRelativePath(this.vaultDir, absolute);
|
|
635
673
|
}
|
|
674
|
+
|
|
675
|
+
async scanWorkspaceState() {
|
|
676
|
+
const entries = new Map();
|
|
677
|
+
const metadata = new Map();
|
|
678
|
+
|
|
679
|
+
const visitDirectory = async (dirPath) => {
|
|
680
|
+
let dirEntries;
|
|
681
|
+
try {
|
|
682
|
+
dirEntries = await readdir(dirPath, { withFileTypes: true });
|
|
683
|
+
} catch {
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
const sorted = dirEntries.sort((left, right) => {
|
|
688
|
+
if (left.isDirectory() && !right.isDirectory()) return -1;
|
|
689
|
+
if (!left.isDirectory() && right.isDirectory()) return 1;
|
|
690
|
+
return left.name.localeCompare(right.name, undefined, { sensitivity: 'base' });
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
for (const entry of sorted) {
|
|
694
|
+
if (isIgnoredVaultEntry(entry.name)) {
|
|
695
|
+
continue;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
const fullPath = join(dirPath, entry.name);
|
|
699
|
+
const relativePath = toVaultRelativePath(this.vaultDir, fullPath).replace(/\\/g, '/');
|
|
700
|
+
|
|
701
|
+
if (entry.isDirectory()) {
|
|
702
|
+
entries.set(relativePath, createWorkspaceEntry(relativePath, 'directory'));
|
|
703
|
+
try {
|
|
704
|
+
const info = await stat(fullPath);
|
|
705
|
+
metadata.set(relativePath, {
|
|
706
|
+
inode: Number(info.ino || 0),
|
|
707
|
+
mtimeMs: Number(info.mtimeMs || 0),
|
|
708
|
+
path: relativePath,
|
|
709
|
+
size: 0,
|
|
710
|
+
type: 'directory',
|
|
711
|
+
});
|
|
712
|
+
} catch {
|
|
713
|
+
// Ignore transient directories that disappear during scans.
|
|
714
|
+
}
|
|
715
|
+
await visitDirectory(fullPath);
|
|
716
|
+
continue;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
if (!isVaultFilePath(entry.name)) {
|
|
720
|
+
continue;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
entries.set(relativePath, createWorkspaceEntry(relativePath, 'file'));
|
|
724
|
+
try {
|
|
725
|
+
const info = await stat(fullPath);
|
|
726
|
+
metadata.set(relativePath, {
|
|
727
|
+
inode: Number(info.ino || 0),
|
|
728
|
+
mtimeMs: Number(info.mtimeMs || 0),
|
|
729
|
+
path: relativePath,
|
|
730
|
+
size: Number(info.size || 0),
|
|
731
|
+
type: 'file',
|
|
732
|
+
});
|
|
733
|
+
} catch {
|
|
734
|
+
// Ignore transient files that disappear during scans.
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
};
|
|
738
|
+
|
|
739
|
+
await visitDirectory(this.vaultDir);
|
|
740
|
+
|
|
741
|
+
return {
|
|
742
|
+
entries,
|
|
743
|
+
metadata,
|
|
744
|
+
scannedAt: Date.now(),
|
|
745
|
+
};
|
|
746
|
+
}
|
|
636
747
|
}
|