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.
Files changed (120) hide show
  1. package/README.md +98 -9
  2. package/bin/collabmd.js +3 -2
  3. package/docker-compose.yml +13 -1
  4. package/package.json +2 -1
  5. package/public/assets/css/style.css +1 -1
  6. package/public/assets/js/chunks/{chunk-HEWVH67U.js → chunk-CYK7KO7W.js} +8 -8
  7. package/public/assets/js/chunks/chunk-GFDM7YCF.js +1 -0
  8. package/public/assets/js/chunks/editor-session-S5AT7BMV.js +22 -0
  9. package/public/assets/js/chunks/{preview-render-compiler-UZ4SZDQQ.js → preview-render-compiler-6Y7TKXFJ.js} +1 -1
  10. package/public/assets/js/chunks/{quick-switcher-controller-J7I3CUET.js → quick-switcher-controller-A6SKH5HI.js} +1 -1
  11. package/public/assets/js/{excalidraw-editor-ZDYKMZOL.js → excalidraw-editor-5YWMGLKF.js} +145 -62
  12. package/public/assets/js/excalidraw-editor.js +1 -1
  13. package/public/assets/js/main.js +503 -182
  14. package/public/assets/js/preview-render-worker.js +15 -15
  15. package/public/index.html +104 -84
  16. package/src/client/application/app-shell/git-feature.js +304 -27
  17. package/src/client/application/app-shell/ui-feature.js +69 -7
  18. package/src/client/application/app-shell/workspace-feature.js +46 -0
  19. package/src/client/application/app-shell-elements.js +2 -0
  20. package/src/client/application/mermaid-preview-hydrator.js +1 -1
  21. package/src/client/application/plantuml-preview-hydrator.js +1 -1
  22. package/src/client/application/workspace-coordinator.js +147 -18
  23. package/src/client/application/workspace-preview-controller.js +34 -0
  24. package/src/client/application/workspace-route-controller.js +104 -0
  25. package/src/client/bootstrap/collabmd-app-shell.js +107 -0
  26. package/src/client/domain/runtime-paths.js +5 -0
  27. package/src/client/domain/vault-utils.js +2 -2
  28. package/src/client/excalidraw-editor.js +52 -3
  29. package/src/client/infrastructure/auth-client.js +219 -52
  30. package/src/client/infrastructure/browser-navigation-port.js +20 -0
  31. package/src/client/infrastructure/editor-collaboration-client.js +4 -0
  32. package/src/client/infrastructure/editor-session.js +51 -7
  33. package/src/client/infrastructure/editor-view-adapter.js +297 -53
  34. package/src/client/infrastructure/runtime-config.js +66 -0
  35. package/src/client/infrastructure/workspace-sync-client.js +324 -0
  36. package/src/client/presentation/comment-ui-controller.js +274 -31
  37. package/src/client/presentation/comments-panel.js +2 -2
  38. package/src/client/presentation/excalidraw-embed-controller.js +60 -2
  39. package/src/client/presentation/file-explorer-controller.js +15 -3
  40. package/src/client/presentation/file-explorer-view.js +152 -11
  41. package/src/client/presentation/file-history-view-controller.js +806 -0
  42. package/src/client/presentation/git-diff-view-controller.js +718 -121
  43. package/src/client/presentation/git-panel-controller.js +458 -66
  44. package/src/client/presentation/layout-controller.js +1 -1
  45. package/src/client/styles/components/actions.css +89 -0
  46. package/src/client/styles/components/feedback.css +43 -0
  47. package/src/client/styles/components/navigation.css +68 -0
  48. package/src/client/styles/components/scrollbars.css +104 -0
  49. package/src/client/styles/components/states.css +289 -0
  50. package/src/client/styles/components/surfaces.css +155 -0
  51. package/src/client/styles/features/backlinks.css +213 -0
  52. package/src/client/styles/features/collaboration-chat.css +228 -0
  53. package/src/client/styles/features/collaboration-dialogs.css +154 -0
  54. package/src/client/styles/features/collaboration-presence.css +103 -0
  55. package/src/client/styles/features/collaboration-ui.css +3 -0
  56. package/src/client/styles/features/comment-card.css +236 -0
  57. package/src/client/styles/features/comment-markdown.css +127 -0
  58. package/src/client/styles/features/comment-overlays.css +60 -0
  59. package/src/client/styles/features/comments-drawer.css +120 -0
  60. package/src/client/styles/features/comments.css +4 -0
  61. package/src/client/styles/features/diagram-preview.css +227 -0
  62. package/src/client/styles/features/diff-commit.css +268 -0
  63. package/src/client/styles/features/diff-lines.css +160 -0
  64. package/src/client/styles/features/diff-toolbar.css +98 -0
  65. package/src/client/styles/features/diff.css +3 -0
  66. package/src/client/styles/features/embedded-preview.css +218 -0
  67. package/src/client/styles/features/git-branch.css +155 -0
  68. package/src/client/styles/features/git-files.css +105 -0
  69. package/src/client/styles/features/git-history.css +339 -0
  70. package/src/client/styles/features/git.css +3 -0
  71. package/src/client/styles/features/media-lightbox.css +157 -0
  72. package/src/client/styles/features/outline.css +115 -0
  73. package/src/client/styles/features/preview-content.css +3 -0
  74. package/src/client/styles/features/preview-markdown.css +201 -0
  75. package/src/client/styles/features/preview-sidebars.css +2 -0
  76. package/src/client/styles/features/quick-switcher.css +167 -0
  77. package/src/client/styles/foundation/themes.css +347 -0
  78. package/src/client/styles/foundation/tokens.css +66 -0
  79. package/src/client/styles/foundation/utilities.css +36 -0
  80. package/src/client/styles/layout/editor-page.css +203 -0
  81. package/src/client/styles/layout/overlays.css +132 -0
  82. package/src/client/styles/layout/responsive.css +251 -0
  83. package/src/client/styles/layout/shell.css +314 -0
  84. package/src/client/styles/layout/sidebar.css +176 -0
  85. package/src/client/styles/layout/view-modes.css +92 -0
  86. package/src/client/styles/overrides/codemirror.css +53 -0
  87. package/src/client/styles/overrides/highlightjs.css +7 -0
  88. package/src/client/styles/overrides/vendor.css +2 -0
  89. package/src/client/styles/primitives/controls.css +276 -0
  90. package/src/client/styles/style.css +25 -4901
  91. package/src/domain/wiki-link-resolver.js +16 -5
  92. package/src/domain/workspace-change.js +68 -0
  93. package/src/domain/workspace-room.js +3 -0
  94. package/src/server/auth/create-auth-service.js +373 -15
  95. package/src/server/auth/session-cookie.js +30 -4
  96. package/src/server/config/env.js +121 -5
  97. package/src/server/create-app-server.js +49 -10
  98. package/src/server/domain/backlink-index.js +94 -1
  99. package/src/server/domain/collaboration/collaboration-room.js +281 -69
  100. package/src/server/domain/collaboration/room-registry.js +64 -10
  101. package/src/server/index.js +3 -1
  102. package/src/server/infrastructure/git/command-runner.js +2 -1
  103. package/src/server/infrastructure/git/git-service.js +48 -2
  104. package/src/server/infrastructure/git/history-service.js +683 -0
  105. package/src/server/infrastructure/git/parsers.js +30 -0
  106. package/src/server/infrastructure/git/responses.js +85 -19
  107. package/src/server/infrastructure/http/create-auth-api-handler.js +30 -2
  108. package/src/server/infrastructure/http/create-git-api-command-handler.js +55 -43
  109. package/src/server/infrastructure/http/create-git-api-handler.js +4 -0
  110. package/src/server/infrastructure/http/create-git-api-query-handler.js +61 -0
  111. package/src/server/infrastructure/http/create-request-handler.js +22 -0
  112. package/src/server/infrastructure/http/create-vault-api-command-handler.js +58 -14
  113. package/src/server/infrastructure/http/create-vault-api-handler.js +3 -0
  114. package/src/server/infrastructure/http/create-vault-api-query-handler.js +3 -1
  115. package/src/server/infrastructure/http/http-response.js +65 -28
  116. package/src/server/infrastructure/persistence/vault-file-store.js +163 -52
  117. package/src/server/infrastructure/workspace/file-system-sync-service.js +488 -0
  118. package/src/server/infrastructure/workspace/workspace-mutation-coordinator.js +551 -0
  119. package/public/assets/js/chunks/chunk-R3DDMJHH.js +0 -1
  120. package/public/assets/js/chunks/editor-session-TAV2VXTA.js +0 -22
@@ -0,0 +1,324 @@
1
+ import { WebsocketProvider } from 'y-websocket';
2
+ import * as Y from 'yjs';
3
+
4
+ import { normalizeWorkspaceEvent } from '../../domain/workspace-change.js';
5
+ import { WORKSPACE_ROOM_NAME } from '../../domain/workspace-room.js';
6
+ import { resolveWsBaseUrl } from '../domain/runtime-paths.js';
7
+ import { stopReconnectOnControlledClose } from './yjs-provider-reset-guard.js';
8
+
9
+ function createNode(entry) {
10
+ if (!entry?.path || !entry?.type) {
11
+ return null;
12
+ }
13
+
14
+ if (entry.nodeType === 'directory' || entry.type === 'directory') {
15
+ return {
16
+ children: [],
17
+ name: entry.name,
18
+ path: entry.path,
19
+ type: 'directory',
20
+ };
21
+ }
22
+
23
+ return {
24
+ name: entry.name,
25
+ path: entry.path,
26
+ type: entry.type,
27
+ };
28
+ }
29
+
30
+ function sortNodes(nodes = []) {
31
+ nodes.sort((left, right) => {
32
+ if (left.type === 'directory' && right.type !== 'directory') return -1;
33
+ if (left.type !== 'directory' && right.type === 'directory') return 1;
34
+ return left.name.localeCompare(right.name, undefined, { sensitivity: 'base' });
35
+ });
36
+
37
+ nodes.forEach((node) => {
38
+ if (Array.isArray(node.children)) {
39
+ sortNodes(node.children);
40
+ }
41
+ });
42
+
43
+ return nodes;
44
+ }
45
+
46
+ function toEntryMap(value) {
47
+ if (value instanceof Map) {
48
+ return value;
49
+ }
50
+
51
+ return new Map(Object.entries(value ?? {}));
52
+ }
53
+
54
+ function sortPathsByDepth(values = [], direction = 'asc') {
55
+ const factor = direction === 'desc' ? -1 : 1;
56
+ return [...values].sort((left, right) => {
57
+ const depthDelta = left.split('/').length - right.split('/').length;
58
+ if (depthDelta !== 0) {
59
+ return depthDelta * factor;
60
+ }
61
+
62
+ return left.localeCompare(right, undefined, { sensitivity: 'base' }) * factor;
63
+ });
64
+ }
65
+
66
+ class WorkspaceTreeModel {
67
+ constructor() {
68
+ this.entriesByPath = new Map();
69
+ this.nodesByPath = new Map();
70
+ this.nodeParentPathByPath = new Map();
71
+ this.roots = [];
72
+ }
73
+
74
+ reset(rawEntries) {
75
+ this.entriesByPath = toEntryMap(rawEntries);
76
+ this.nodesByPath = new Map();
77
+ this.nodeParentPathByPath = new Map();
78
+ this.roots = [];
79
+
80
+ this.entriesByPath.forEach((entry) => {
81
+ const node = createNode(entry);
82
+ if (node) {
83
+ this.nodesByPath.set(entry.path, node);
84
+ }
85
+ });
86
+
87
+ sortPathsByDepth(Array.from(this.nodesByPath.keys())).forEach((pathValue) => {
88
+ const node = this.nodesByPath.get(pathValue);
89
+ if (node) {
90
+ this.attachNode(pathValue, node, this.entriesByPath.get(pathValue)?.parentPath || '');
91
+ }
92
+ });
93
+
94
+ sortNodes(this.roots);
95
+ return this.roots;
96
+ }
97
+
98
+ getTree() {
99
+ return this.roots;
100
+ }
101
+
102
+ applyMapChanges(changes, entriesMap) {
103
+ const deletePaths = [];
104
+ const upsertPaths = [];
105
+
106
+ changes.forEach((change, pathValue) => {
107
+ if (change.action === 'delete') {
108
+ deletePaths.push(pathValue);
109
+ } else {
110
+ upsertPaths.push(pathValue);
111
+ }
112
+ });
113
+
114
+ sortPathsByDepth(deletePaths, 'desc').forEach((pathValue) => {
115
+ this.removeEntry(pathValue);
116
+ });
117
+
118
+ sortPathsByDepth(upsertPaths).forEach((pathValue) => {
119
+ this.upsertEntry(pathValue, entriesMap.get(pathValue));
120
+ });
121
+
122
+ sortNodes(this.roots);
123
+ return this.roots;
124
+ }
125
+
126
+ removeEntry(pathValue) {
127
+ const node = this.nodesByPath.get(pathValue);
128
+ if (!node) {
129
+ this.entriesByPath.delete(pathValue);
130
+ this.nodeParentPathByPath.delete(pathValue);
131
+ return;
132
+ }
133
+
134
+ this.detachNode(pathValue, node);
135
+ this.nodesByPath.delete(pathValue);
136
+ this.entriesByPath.delete(pathValue);
137
+ this.nodeParentPathByPath.delete(pathValue);
138
+ }
139
+
140
+ upsertEntry(pathValue, entry) {
141
+ if (!entry?.path || !entry?.type) {
142
+ this.removeEntry(pathValue);
143
+ return;
144
+ }
145
+
146
+ const nextNode = createNode(entry);
147
+ if (!nextNode) {
148
+ this.removeEntry(pathValue);
149
+ return;
150
+ }
151
+
152
+ const existingNode = this.nodesByPath.get(pathValue);
153
+ let node = existingNode;
154
+ if (!node || (node.type === 'directory') !== (nextNode.type === 'directory')) {
155
+ if (node) {
156
+ this.detachNode(pathValue, node);
157
+ }
158
+ node = nextNode;
159
+ this.nodesByPath.set(pathValue, node);
160
+ } else {
161
+ node.name = nextNode.name;
162
+ node.path = nextNode.path;
163
+ node.type = nextNode.type;
164
+ if (node.type === 'directory' && !Array.isArray(node.children)) {
165
+ node.children = [];
166
+ }
167
+ if (node.type !== 'directory' && Array.isArray(node.children)) {
168
+ delete node.children;
169
+ }
170
+ }
171
+
172
+ this.entriesByPath.set(pathValue, entry);
173
+ this.attachNode(pathValue, node, entry.parentPath || '');
174
+
175
+ if (node.type === 'directory') {
176
+ this.rehomeChildren(pathValue);
177
+ }
178
+ }
179
+
180
+ detachNode(pathValue, node) {
181
+ const currentParentPath = this.nodeParentPathByPath.get(pathValue) || '';
182
+ const siblings = currentParentPath
183
+ ? this.nodesByPath.get(currentParentPath)?.children
184
+ : this.roots;
185
+ const index = siblings?.indexOf?.(node) ?? -1;
186
+ if (index >= 0) {
187
+ siblings.splice(index, 1);
188
+ }
189
+ this.nodeParentPathByPath.delete(pathValue);
190
+ }
191
+
192
+ attachNode(pathValue, node, requestedParentPath = '') {
193
+ const parentPath = this.nodesByPath.get(requestedParentPath)?.type === 'directory'
194
+ ? requestedParentPath
195
+ : '';
196
+ const currentParentPath = this.nodeParentPathByPath.get(pathValue);
197
+
198
+ if (currentParentPath === parentPath) {
199
+ return;
200
+ }
201
+
202
+ if (currentParentPath !== undefined) {
203
+ this.detachNode(pathValue, node);
204
+ }
205
+
206
+ const siblings = parentPath
207
+ ? this.nodesByPath.get(parentPath)?.children
208
+ : this.roots;
209
+ if (!siblings.includes(node)) {
210
+ siblings.push(node);
211
+ }
212
+ this.nodeParentPathByPath.set(pathValue, parentPath);
213
+ }
214
+
215
+ rehomeChildren(parentPath) {
216
+ this.entriesByPath.forEach((entry, pathValue) => {
217
+ if (entry?.parentPath !== parentPath) {
218
+ return;
219
+ }
220
+
221
+ const node = this.nodesByPath.get(pathValue);
222
+ if (node) {
223
+ this.attachNode(pathValue, node, parentPath);
224
+ }
225
+ });
226
+ }
227
+ }
228
+
229
+ export class WorkspaceSyncClient {
230
+ constructor({
231
+ onTreeChange = () => {},
232
+ onWorkspaceEvent = () => {},
233
+ } = {}) {
234
+ this.onTreeChange = onTreeChange;
235
+ this.onWorkspaceEvent = onWorkspaceEvent;
236
+ this.ydoc = new Y.Doc();
237
+ this.entries = this.ydoc.getMap('entries');
238
+ this.events = this.ydoc.getArray('events');
239
+ this.provider = null;
240
+ this._didInitialSync = false;
241
+ this.seenEventIds = new Set();
242
+ this.treeModel = new WorkspaceTreeModel();
243
+
244
+ this.handleEntriesChange = (event) => {
245
+ if (!this._didInitialSync || !event) {
246
+ return;
247
+ }
248
+
249
+ this.onTreeChange(this.treeModel.applyMapChanges(event.changes.keys, this.entries), {
250
+ changedPaths: Array.from(event.changes.keys.keys()),
251
+ reset: false,
252
+ });
253
+ };
254
+ this.handleEventsChange = () => {
255
+ if (!this._didInitialSync) {
256
+ this.primeEventCache();
257
+ return;
258
+ }
259
+
260
+ this.events.toArray().forEach((event) => {
261
+ const normalized = normalizeWorkspaceEvent(event);
262
+ if (!normalized || this.seenEventIds.has(normalized.id)) {
263
+ return;
264
+ }
265
+
266
+ this.seenEventIds.add(normalized.id);
267
+ this.onWorkspaceEvent(normalized);
268
+ });
269
+ };
270
+ }
271
+
272
+ connect() {
273
+ if (this.provider) {
274
+ return;
275
+ }
276
+
277
+ this._didInitialSync = false;
278
+ this.provider = new WebsocketProvider(resolveWsBaseUrl(), WORKSPACE_ROOM_NAME, this.ydoc, {
279
+ disableBc: true,
280
+ maxBackoffTime: 5000,
281
+ });
282
+ stopReconnectOnControlledClose(this.provider);
283
+
284
+ this.entries.observe(this.handleEntriesChange);
285
+ this.events.observe(this.handleEventsChange);
286
+ this.provider.on('sync', (isSynced) => {
287
+ if (!isSynced || this._didInitialSync) {
288
+ return;
289
+ }
290
+
291
+ this._didInitialSync = true;
292
+ this.primeEventCache();
293
+ this.onTreeChange(this.treeModel.reset(this.entries.toJSON()), {
294
+ changedPaths: [],
295
+ reset: true,
296
+ });
297
+ });
298
+ }
299
+
300
+ primeEventCache() {
301
+ this.events.toArray().forEach((event) => {
302
+ const normalized = normalizeWorkspaceEvent(event);
303
+ if (normalized) {
304
+ this.seenEventIds.add(normalized.id);
305
+ }
306
+ });
307
+ }
308
+
309
+ disconnect() {
310
+ this.entries.unobserve(this.handleEntriesChange);
311
+ this.events.unobserve(this.handleEventsChange);
312
+ this.provider?.disconnect();
313
+ this.provider?.destroy();
314
+ this.provider = null;
315
+ this._didInitialSync = false;
316
+ this.seenEventIds.clear();
317
+ this.treeModel.reset(new Map());
318
+ }
319
+
320
+ destroy() {
321
+ this.disconnect();
322
+ this.ydoc.destroy();
323
+ }
324
+ }