collabmd 0.1.19 → 0.1.21

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.
Files changed (53) hide show
  1. package/README.md +13 -11
  2. package/docker-compose.yml +1 -1
  3. package/package.json +1 -1
  4. package/public/assets/css/style.css +1 -1
  5. package/public/assets/js/chunks/chunk-GFDM7YCF.js +1 -0
  6. package/public/assets/js/chunks/editor-session-EAUBJCHH.js +22 -0
  7. package/public/assets/js/chunks/{preview-render-compiler-UZ4SZDQQ.js → preview-render-compiler-6Y7TKXFJ.js} +1 -1
  8. package/public/assets/js/chunks/{quick-switcher-controller-J7I3CUET.js → quick-switcher-controller-A6SKH5HI.js} +1 -1
  9. package/public/assets/js/{excalidraw-editor-ZDYKMZOL.js → excalidraw-editor-TCIH6GJL.js} +1 -1
  10. package/public/assets/js/excalidraw-editor.js +1 -1
  11. package/public/assets/js/main.js +102 -75
  12. package/public/assets/js/preview-render-worker.js +15 -15
  13. package/public/index.html +14 -4
  14. package/src/client/application/app-shell/git-feature.js +86 -19
  15. package/src/client/application/app-shell/ui-feature.js +4 -0
  16. package/src/client/application/app-shell-elements.js +1 -0
  17. package/src/client/bootstrap/collabmd-app-shell.js +16 -0
  18. package/src/client/domain/vault-utils.js +2 -2
  19. package/src/client/excalidraw-editor.js +2 -1
  20. package/src/client/infrastructure/editor-session.js +4 -0
  21. package/src/client/infrastructure/editor-view-adapter.js +245 -7
  22. package/src/client/infrastructure/workspace-sync-client.js +324 -0
  23. package/src/client/presentation/backlinks-panel.js +157 -101
  24. package/src/client/presentation/comment-ui-controller.js +233 -10
  25. package/src/client/presentation/file-explorer-controller.js +15 -3
  26. package/src/client/presentation/file-explorer-view.js +152 -11
  27. package/src/client/presentation/git-panel-controller.js +73 -0
  28. package/src/client/presentation/outline-controller.js +25 -0
  29. package/src/client/styles/style.css +184 -9
  30. package/src/domain/wiki-link-resolver.js +16 -5
  31. package/src/domain/workspace-change.js +68 -0
  32. package/src/domain/workspace-room.js +3 -0
  33. package/src/server/create-app-server.js +41 -10
  34. package/src/server/domain/backlink-index.js +94 -1
  35. package/src/server/domain/collaboration/collaboration-room.js +191 -22
  36. package/src/server/domain/collaboration/room-registry.js +64 -10
  37. package/src/server/infrastructure/git/errors.js +4 -1
  38. package/src/server/infrastructure/git/git-service.js +215 -1
  39. package/src/server/infrastructure/git/responses.js +12 -19
  40. package/src/server/infrastructure/http/create-git-api-command-handler.js +58 -43
  41. package/src/server/infrastructure/http/create-git-api-handler.js +2 -0
  42. package/src/server/infrastructure/http/create-git-api-query-handler.js +16 -1
  43. package/src/server/infrastructure/http/create-request-handler.js +7 -0
  44. package/src/server/infrastructure/http/create-vault-api-command-handler.js +58 -14
  45. package/src/server/infrastructure/http/create-vault-api-handler.js +3 -0
  46. package/src/server/infrastructure/http/create-vault-api-query-handler.js +3 -1
  47. package/src/server/infrastructure/http/http-response.js +65 -28
  48. package/src/server/infrastructure/persistence/pull-backup-store.js +283 -0
  49. package/src/server/infrastructure/persistence/vault-file-store.js +179 -52
  50. package/src/server/infrastructure/workspace/file-system-sync-service.js +488 -0
  51. package/src/server/infrastructure/workspace/workspace-mutation-coordinator.js +551 -0
  52. package/public/assets/js/chunks/chunk-R3DDMJHH.js +0 -1
  53. package/public/assets/js/chunks/editor-session-AH6Z3MXW.js +0 -22
@@ -0,0 +1,551 @@
1
+ import { stat } from 'node:fs/promises';
2
+ import { basename, dirname } from 'node:path';
3
+
4
+ import { getVaultTreeNodeType, isVaultFilePath, supportsBacklinksForFilePath } from '../../../domain/file-kind.js';
5
+ import {
6
+ createEmptyWorkspaceChange,
7
+ createWorkspaceChange,
8
+ normalizeWorkspaceEvent,
9
+ } from '../../../domain/workspace-change.js';
10
+ import { WORKSPACE_ROOM_NAME } from '../../../domain/workspace-room.js';
11
+ import { sanitizeVaultPath } from '../persistence/path-utils.js';
12
+
13
+ function createEventId() {
14
+ if (globalThis.crypto?.randomUUID) {
15
+ return globalThis.crypto.randomUUID();
16
+ }
17
+
18
+ return `workspace-${Date.now()}-${Math.random().toString(16).slice(2, 10)}`;
19
+ }
20
+
21
+ function countWorkspacePaths(workspaceChange = {}) {
22
+ return (
23
+ (workspaceChange.changedPaths?.length ?? 0)
24
+ + (workspaceChange.deletedPaths?.length ?? 0)
25
+ + (workspaceChange.renamedPaths?.length ?? 0)
26
+ );
27
+ }
28
+
29
+ function normalizePaths(paths = []) {
30
+ return Array.from(new Set((paths ?? []).filter(Boolean)));
31
+ }
32
+
33
+ function normalizeWorkspacePath(pathValue = '') {
34
+ return String(pathValue ?? '').replace(/\\/g, '/').trim();
35
+ }
36
+
37
+ function getParentDirectoryPath(pathValue = '') {
38
+ const parentPath = dirname(normalizeWorkspacePath(pathValue)).replace(/\\/g, '/');
39
+ return parentPath === '.' ? '' : parentPath;
40
+ }
41
+
42
+ function createWorkspaceEntry(pathValue, nodeType) {
43
+ const normalizedPath = normalizeWorkspacePath(pathValue);
44
+ return {
45
+ fileKind: nodeType === 'directory' ? null : getVaultTreeNodeType(normalizedPath),
46
+ name: basename(normalizedPath),
47
+ nodeType,
48
+ parentPath: getParentDirectoryPath(normalizedPath),
49
+ path: normalizedPath,
50
+ type: nodeType === 'directory' ? 'directory' : getVaultTreeNodeType(normalizedPath),
51
+ };
52
+ }
53
+
54
+ function createWorkspaceMetadata(pathValue, type, info) {
55
+ return {
56
+ inode: Number(info.ino || 0),
57
+ mtimeMs: Number(info.mtimeMs || 0),
58
+ path: pathValue,
59
+ size: type === 'directory' ? 0 : Number(info.size || 0),
60
+ type,
61
+ };
62
+ }
63
+
64
+ function compareWorkspaceTreeNodes(left = {}, right = {}) {
65
+ const leftIsDirectory = left.type === 'directory';
66
+ const rightIsDirectory = right.type === 'directory';
67
+ if (leftIsDirectory && !rightIsDirectory) {
68
+ return -1;
69
+ }
70
+ if (!leftIsDirectory && rightIsDirectory) {
71
+ return 1;
72
+ }
73
+
74
+ return String(left.name ?? '').localeCompare(String(right.name ?? ''), undefined, { sensitivity: 'base' });
75
+ }
76
+
77
+ function sortWorkspaceTree(nodes = []) {
78
+ nodes.sort(compareWorkspaceTreeNodes);
79
+ nodes.forEach((node) => {
80
+ if (node.type === 'directory') {
81
+ sortWorkspaceTree(node.children);
82
+ }
83
+ });
84
+ return nodes;
85
+ }
86
+
87
+ function createWorkspaceTree(entries = new Map()) {
88
+ const nodesByPath = new Map();
89
+ const rootNodes = [];
90
+
91
+ entries.forEach((entry, pathValue) => {
92
+ const normalizedPath = normalizeWorkspacePath(pathValue);
93
+ if (!normalizedPath) {
94
+ return;
95
+ }
96
+
97
+ if (entry?.type === 'directory' || entry?.nodeType === 'directory') {
98
+ nodesByPath.set(normalizedPath, {
99
+ children: [],
100
+ name: entry?.name ?? basename(normalizedPath),
101
+ path: normalizedPath,
102
+ type: 'directory',
103
+ });
104
+ return;
105
+ }
106
+
107
+ nodesByPath.set(normalizedPath, {
108
+ name: entry?.name ?? basename(normalizedPath),
109
+ path: normalizedPath,
110
+ type: entry?.type ?? getVaultTreeNodeType(normalizedPath),
111
+ });
112
+ });
113
+
114
+ nodesByPath.forEach((node, pathValue) => {
115
+ const parentPath = getParentDirectoryPath(pathValue);
116
+ const parentNode = parentPath ? nodesByPath.get(parentPath) : null;
117
+ if (parentNode?.type === 'directory') {
118
+ parentNode.children.push(node);
119
+ return;
120
+ }
121
+
122
+ rootNodes.push(node);
123
+ });
124
+
125
+ return sortWorkspaceTree(rootNodes);
126
+ }
127
+
128
+ function workspaceEntriesEqual(left = {}, right = {}) {
129
+ return (
130
+ left.fileKind === right.fileKind
131
+ && left.name === right.name
132
+ && left.nodeType === right.nodeType
133
+ && left.parentPath === right.parentPath
134
+ && left.path === right.path
135
+ && left.type === right.type
136
+ );
137
+ }
138
+
139
+ function diffWorkspaceEntries(previousEntries = new Map(), nextEntries = new Map()) {
140
+ const upserts = new Map();
141
+ const deletes = [];
142
+
143
+ previousEntries.forEach((previousEntry, pathValue) => {
144
+ const nextEntry = nextEntries.get(pathValue);
145
+ if (!nextEntry) {
146
+ deletes.push(pathValue);
147
+ return;
148
+ }
149
+
150
+ if (!workspaceEntriesEqual(previousEntry, nextEntry)) {
151
+ upserts.set(pathValue, nextEntry);
152
+ }
153
+ });
154
+
155
+ nextEntries.forEach((nextEntry, pathValue) => {
156
+ if (!previousEntries.has(pathValue)) {
157
+ upserts.set(pathValue, nextEntry);
158
+ }
159
+ });
160
+
161
+ return { deletes, upserts };
162
+ }
163
+
164
+ export class WorkspaceMutationCoordinator {
165
+ constructor({
166
+ backlinkIndex,
167
+ roomRegistry,
168
+ vaultFileStore,
169
+ managedWriteWindowMs = 1200,
170
+ }) {
171
+ this.backlinkIndex = backlinkIndex ?? null;
172
+ this.roomRegistry = roomRegistry;
173
+ this.vaultFileStore = vaultFileStore;
174
+ this.managedWriteWindowMs = managedWriteWindowMs;
175
+ this.managedPathExpiry = new Map();
176
+ this.globalSuppressionUntil = 0;
177
+ this.workspaceState = null;
178
+ this.workspaceTree = [];
179
+ }
180
+
181
+ replaceWorkspaceState(nextState) {
182
+ this.workspaceState = nextState ?? null;
183
+ this.workspaceTree = createWorkspaceTree(nextState?.entries ?? new Map());
184
+ return this.workspaceState;
185
+ }
186
+
187
+ getWorkspaceTree() {
188
+ return this.workspaceTree;
189
+ }
190
+
191
+ isIncrementalApiAction(action) {
192
+ return action === 'create-directory'
193
+ || action === 'create-file'
194
+ || action === 'delete-file'
195
+ || action === 'rename-file'
196
+ || action === 'upload-attachment'
197
+ || action === 'write-file';
198
+ }
199
+
200
+ async readWorkspacePathState(pathValue, {
201
+ expectDirectory = false,
202
+ } = {}) {
203
+ const normalizedPath = normalizeWorkspacePath(pathValue);
204
+ if (!normalizedPath) {
205
+ return null;
206
+ }
207
+
208
+ const absolutePath = sanitizeVaultPath(this.vaultFileStore?.vaultDir, normalizedPath);
209
+ if (!absolutePath) {
210
+ return null;
211
+ }
212
+
213
+ try {
214
+ const info = await stat(absolutePath);
215
+ if (expectDirectory) {
216
+ if (!info.isDirectory()) {
217
+ return null;
218
+ }
219
+
220
+ return {
221
+ entry: createWorkspaceEntry(normalizedPath, 'directory'),
222
+ metadata: createWorkspaceMetadata(normalizedPath, 'directory', info),
223
+ };
224
+ }
225
+
226
+ if (!info.isFile() || !isVaultFilePath(normalizedPath)) {
227
+ return null;
228
+ }
229
+
230
+ return {
231
+ entry: createWorkspaceEntry(normalizedPath, 'file'),
232
+ metadata: createWorkspaceMetadata(normalizedPath, 'file', info),
233
+ };
234
+ } catch (error) {
235
+ if (error?.code === 'ENOENT' || error?.code === 'ENOTDIR') {
236
+ return null;
237
+ }
238
+
239
+ throw error;
240
+ }
241
+ }
242
+
243
+ async ensureDirectoryEntries(nextEntries, nextMetadata, pathValue, {
244
+ includeSelf = false,
245
+ } = {}) {
246
+ const rootPath = includeSelf ? normalizeWorkspacePath(pathValue) : getParentDirectoryPath(pathValue);
247
+ if (!rootPath) {
248
+ return true;
249
+ }
250
+
251
+ const segments = rootPath.split('/').filter(Boolean);
252
+ let currentPath = '';
253
+ for (const segment of segments) {
254
+ currentPath = currentPath ? `${currentPath}/${segment}` : segment;
255
+ if (nextEntries.has(currentPath) && nextMetadata.has(currentPath)) {
256
+ continue;
257
+ }
258
+
259
+ const directoryState = await this.readWorkspacePathState(currentPath, {
260
+ expectDirectory: true,
261
+ });
262
+ if (!directoryState) {
263
+ return false;
264
+ }
265
+
266
+ nextEntries.set(currentPath, directoryState.entry);
267
+ nextMetadata.set(currentPath, directoryState.metadata);
268
+ }
269
+
270
+ return true;
271
+ }
272
+
273
+ async deriveNextWorkspaceStateForApiMutation(action, workspaceChange = {}) {
274
+ if (!this.isIncrementalApiAction(action)) {
275
+ return null;
276
+ }
277
+
278
+ const previousState = this.workspaceState;
279
+ if (!previousState?.entries || !previousState?.metadata) {
280
+ return null;
281
+ }
282
+
283
+ const nextEntries = new Map(previousState.entries);
284
+ const nextMetadata = new Map(previousState.metadata);
285
+
286
+ for (const pathValue of workspaceChange.deletedPaths ?? []) {
287
+ nextEntries.delete(pathValue);
288
+ nextMetadata.delete(pathValue);
289
+ }
290
+
291
+ for (const entry of workspaceChange.renamedPaths ?? []) {
292
+ nextEntries.delete(entry.oldPath);
293
+ nextMetadata.delete(entry.oldPath);
294
+
295
+ if (!(await this.ensureDirectoryEntries(nextEntries, nextMetadata, entry.newPath))) {
296
+ return null;
297
+ }
298
+
299
+ const nextPathState = await this.readWorkspacePathState(entry.newPath);
300
+ if (!nextPathState) {
301
+ return null;
302
+ }
303
+
304
+ nextEntries.set(entry.newPath, nextPathState.entry);
305
+ nextMetadata.set(entry.newPath, nextPathState.metadata);
306
+ }
307
+
308
+ const changedPaths = normalizePaths(workspaceChange.changedPaths ?? []);
309
+ for (const pathValue of changedPaths) {
310
+ const expectsDirectory = action === 'create-directory';
311
+ if (expectsDirectory) {
312
+ if (!(await this.ensureDirectoryEntries(nextEntries, nextMetadata, pathValue, { includeSelf: true }))) {
313
+ return null;
314
+ }
315
+
316
+ const directoryState = await this.readWorkspacePathState(pathValue, {
317
+ expectDirectory: true,
318
+ });
319
+ if (!directoryState) {
320
+ return null;
321
+ }
322
+
323
+ nextEntries.set(pathValue, directoryState.entry);
324
+ nextMetadata.set(pathValue, directoryState.metadata);
325
+ continue;
326
+ }
327
+
328
+ if (!(await this.ensureDirectoryEntries(nextEntries, nextMetadata, pathValue))) {
329
+ return null;
330
+ }
331
+
332
+ const nextPathState = await this.readWorkspacePathState(pathValue);
333
+ if (!nextPathState) {
334
+ return null;
335
+ }
336
+
337
+ nextEntries.set(pathValue, nextPathState.entry);
338
+ nextMetadata.set(pathValue, nextPathState.metadata);
339
+ }
340
+
341
+ return {
342
+ entries: nextEntries,
343
+ metadata: nextMetadata,
344
+ scannedAt: Date.now(),
345
+ };
346
+ }
347
+
348
+ getWorkspaceRoom() {
349
+ return this.roomRegistry?.getOrCreate?.(WORKSPACE_ROOM_NAME) ?? null;
350
+ }
351
+
352
+ syncWorkspaceEntries(nextState, {
353
+ previousState = this.workspaceState,
354
+ } = {}) {
355
+ const room = this.getWorkspaceRoom();
356
+ if (!room || !nextState) {
357
+ return false;
358
+ }
359
+
360
+ const patch = diffWorkspaceEntries(
361
+ previousState?.entries ?? new Map(),
362
+ nextState.entries ?? new Map(),
363
+ );
364
+ return room.applyWorkspaceEntryPatch(patch, {
365
+ generatedAt: nextState.scannedAt,
366
+ });
367
+ }
368
+
369
+ async initialize() {
370
+ const snapshot = await this.vaultFileStore.scanWorkspaceState();
371
+ this.replaceWorkspaceState(snapshot);
372
+ this.getWorkspaceRoom()?.replaceWorkspaceEntries(snapshot.entries, {
373
+ generatedAt: snapshot.scannedAt,
374
+ });
375
+ return snapshot;
376
+ }
377
+
378
+ markManagedPaths(paths = [], { durationMs = this.managedWriteWindowMs } = {}) {
379
+ const expiresAt = Date.now() + durationMs;
380
+ normalizePaths(paths).forEach((pathValue) => {
381
+ this.managedPathExpiry.set(pathValue, expiresAt);
382
+ });
383
+ }
384
+
385
+ runManagedWrite(paths = [], operation) {
386
+ this.markManagedPaths(paths);
387
+ return Promise.resolve(operation()).finally(() => {
388
+ this.markManagedPaths(paths);
389
+ });
390
+ }
391
+
392
+ async runManagedWorkspaceMutation(operation) {
393
+ this.globalSuppressionUntil = Math.max(this.globalSuppressionUntil, Date.now() + this.managedWriteWindowMs);
394
+ try {
395
+ return await operation();
396
+ } finally {
397
+ this.globalSuppressionUntil = Math.max(this.globalSuppressionUntil, Date.now() + this.managedWriteWindowMs);
398
+ }
399
+ }
400
+
401
+ isGloballySuppressed() {
402
+ return Date.now() <= this.globalSuppressionUntil;
403
+ }
404
+
405
+ cleanupExpiredManagedPaths() {
406
+ const now = Date.now();
407
+ Array.from(this.managedPathExpiry.entries()).forEach(([pathValue, expiresAt]) => {
408
+ if (expiresAt <= now) {
409
+ this.managedPathExpiry.delete(pathValue);
410
+ }
411
+ });
412
+ }
413
+
414
+ isManagedPath(pathValue) {
415
+ this.cleanupExpiredManagedPaths();
416
+ const expiresAt = this.managedPathExpiry.get(pathValue);
417
+ return Number.isFinite(expiresAt) && expiresAt > Date.now();
418
+ }
419
+
420
+ filterManagedWorkspaceChange(workspaceChange = {}) {
421
+ if (this.isGloballySuppressed()) {
422
+ return null;
423
+ }
424
+
425
+ const filtered = createWorkspaceChange({
426
+ changedPaths: (workspaceChange.changedPaths ?? []).filter((pathValue) => !this.isManagedPath(pathValue)),
427
+ deletedPaths: (workspaceChange.deletedPaths ?? []).filter((pathValue) => !this.isManagedPath(pathValue)),
428
+ renamedPaths: (workspaceChange.renamedPaths ?? []).filter((entry) => (
429
+ entry?.oldPath
430
+ && entry?.newPath
431
+ && !this.isManagedPath(entry.oldPath)
432
+ && !this.isManagedPath(entry.newPath)
433
+ )),
434
+ refreshExplorer: workspaceChange.refreshExplorer !== false,
435
+ });
436
+
437
+ if (countWorkspacePaths(filtered) === 0) {
438
+ return null;
439
+ }
440
+
441
+ return filtered;
442
+ }
443
+
444
+ async reconcileBacklinks(workspaceChange, nextState, {
445
+ forceRebuild = false,
446
+ } = {}) {
447
+ if (!this.backlinkIndex) {
448
+ return;
449
+ }
450
+
451
+ const previousEntries = this.workspaceState?.entries ?? new Map();
452
+ if (
453
+ forceRebuild
454
+ || countWorkspacePaths(workspaceChange) > 25
455
+ ) {
456
+ this.backlinkIndex.scheduleBuild?.();
457
+ return;
458
+ }
459
+
460
+ for (const pathValue of workspaceChange.deletedPaths ?? []) {
461
+ if (supportsBacklinksForFilePath(pathValue)) {
462
+ this.backlinkIndex.onFileDeleted(pathValue);
463
+ }
464
+ }
465
+
466
+ for (const entry of workspaceChange.renamedPaths ?? []) {
467
+ if (supportsBacklinksForFilePath(entry.oldPath) || supportsBacklinksForFilePath(entry.newPath)) {
468
+ this.backlinkIndex.onFileRenamed(entry.oldPath, entry.newPath);
469
+ }
470
+ }
471
+
472
+ for (const pathValue of workspaceChange.changedPaths ?? []) {
473
+ if (!supportsBacklinksForFilePath(pathValue)) {
474
+ continue;
475
+ }
476
+
477
+ const existsNow = nextState.entries.has(pathValue);
478
+ const existedBefore = previousEntries.has(pathValue);
479
+ if (!existsNow) {
480
+ if (existedBefore) {
481
+ this.backlinkIndex.onFileDeleted(pathValue);
482
+ }
483
+ continue;
484
+ }
485
+
486
+ const content = await this.vaultFileStore.readMarkdownFile(pathValue);
487
+ if (content === null) {
488
+ continue;
489
+ }
490
+
491
+ if (existedBefore) {
492
+ this.backlinkIndex.updateFile(pathValue, content);
493
+ } else {
494
+ this.backlinkIndex.onFileCreated(pathValue, content);
495
+ }
496
+ }
497
+ }
498
+
499
+ async apply({
500
+ action = 'workspace',
501
+ origin = 'api',
502
+ publishEvent = true,
503
+ requestId = null,
504
+ sourceRef = null,
505
+ workspaceChange = createEmptyWorkspaceChange(),
506
+ nextState = null,
507
+ forceBacklinkRebuild = false,
508
+ } = {}) {
509
+ const normalizedChange = createWorkspaceChange(workspaceChange);
510
+ const previousState = this.workspaceState;
511
+ const derivedState = nextState
512
+ ? null
513
+ : await this.deriveNextWorkspaceStateForApiMutation(action, normalizedChange);
514
+ const resolvedState = nextState ?? derivedState ?? await this.vaultFileStore.scanWorkspaceState();
515
+
516
+ await this.vaultFileStore.reconcileSidecars?.(normalizedChange);
517
+ await this.vaultFileStore.reconcileCollaborationSnapshots?.(normalizedChange);
518
+ await this.reconcileBacklinks(normalizedChange, resolvedState, {
519
+ forceRebuild: forceBacklinkRebuild,
520
+ });
521
+
522
+ const roomEffects = await this.roomRegistry?.reconcileWorkspaceChange?.(normalizedChange) ?? {};
523
+ const highlightRanges = normalizePaths(
524
+ (roomEffects.highlightRanges ?? []).map((entry) => entry?.path),
525
+ ).map((pathValue) => roomEffects.highlightRanges.find((entry) => entry.path === pathValue));
526
+ const reloadRequiredPaths = normalizePaths(roomEffects.reloadRequiredPaths ?? []);
527
+
528
+ this.syncWorkspaceEntries(resolvedState, {
529
+ previousState,
530
+ });
531
+ this.replaceWorkspaceState(resolvedState);
532
+
533
+ if (!publishEvent) {
534
+ return null;
535
+ }
536
+
537
+ const event = normalizeWorkspaceEvent({
538
+ action,
539
+ createdAt: Date.now(),
540
+ highlightRanges,
541
+ id: createEventId(),
542
+ origin,
543
+ reloadRequiredPaths,
544
+ requestId,
545
+ sourceRef,
546
+ workspaceChange: normalizedChange,
547
+ });
548
+ this.getWorkspaceRoom()?.publishWorkspaceEvent(event);
549
+ return event;
550
+ }
551
+ }
@@ -1 +0,0 @@
1
- function x(t){let e=String(t??"").trim();return e?e.endsWith(".md")?e:`${e}.md`:null}function l(t,e){let n=x(t);if(!n||!Array.isArray(e)||e.length===0)return null;let o=String(t??"").trim();return e.find(r=>r===n||r.endsWith(`/${n}`)||r.replace(/\.md$/i,"")===o)??null}function N(t){return String(t).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;")}function k(t,e){return l(t,e)??void 0}function M(t,e,n){return Math.min(Math.max(t,e),n)}function h(t){return String(t??"").replace(/\\/g,"/").split("/").filter(Boolean)}function _(t,e){let n=h(t),o=String(e??"").split("/").map(u=>{try{return decodeURIComponent(u)}catch{return u}});if(o.length===0)return"";n.pop();let r=[...n];for(let u of o){let c=String(u??"").trim();if(!(!c||c===".")){if(c===".."){if(r.length===0)return"";r.pop();continue}r.push(c)}}return r.join("/")}var s=Object.freeze([".md",".markdown",".mdx"]),m=".excalidraw",p=Object.freeze([".mmd",".mermaid"]),f=Object.freeze([".puml",".plantuml"]),g=Object.freeze([".png",".jpg",".jpeg",".webp",".gif",".svg"]),E=Object.freeze([m,...p,...f]),S=Object.freeze([...s,...E,...g]),T=/\.(?:md|markdown|mdx|excalidraw|mmd|mermaid|puml|plantuml|png|jpe?g|webp|gif|svg)$/i;function d(t){return String(t??"").trim().toLowerCase()}function a(t,e){let n=d(t);return e.some(o=>n.endsWith(o))}function i(t){return a(t,s)?"markdown":a(t,[m])?"excalidraw":a(t,p)?"mermaid":a(t,f)?"plantuml":a(t,g)?"image":null}function P(t){let e=i(t);return e?e==="image"?"image":e==="markdown"?"file":e:null}function O(t){let e=d(t);return S.find(n=>e.endsWith(n))??""}function w(t){return i(t)==="markdown"}function W(t){return i(t)==="excalidraw"}function z(t){return i(t)==="mermaid"}function L(t){return i(t)==="plantuml"}function j(t){return i(t)==="image"}function v(t){let e=i(t);return e==="excalidraw"||e==="mermaid"||e==="plantuml"}function X(t){let e=i(t);return e==="markdown"||e==="mermaid"||e==="plantuml"}function b(t){return w(t)}function R(t){return String(t??"").replace(T,"")}export{N as a,k as b,M as c,_ as d,i as e,P as f,O as g,w as h,W as i,z as j,L as k,j as l,v as m,X as n,b as o,R as p};