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,14 +1,16 @@
|
|
|
1
|
+
import { createWorkspaceChange } from '../../../domain/workspace-change.js';
|
|
1
2
|
import { resolveApiUrl } from '../../domain/runtime-paths.js';
|
|
2
3
|
|
|
3
4
|
function normalizeWorkspaceChange(workspaceChange = {}) {
|
|
4
|
-
return
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
return createWorkspaceChange(workspaceChange);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function createWorkspaceRequestId() {
|
|
9
|
+
if (globalThis.crypto?.randomUUID) {
|
|
10
|
+
return globalThis.crypto.randomUUID();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return `workspace-${Date.now()}-${Math.random().toString(16).slice(2, 10)}`;
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
export const gitFeature = {
|
|
@@ -51,6 +53,46 @@ export const gitFeature = {
|
|
|
51
53
|
this.navigation.navigateToGitDiff({ filePath, scope });
|
|
52
54
|
},
|
|
53
55
|
|
|
56
|
+
handleGitCommitSelection(hash, { closeSidebarOnMobile = false, historyFilePath = null, path = null } = {}) {
|
|
57
|
+
if (closeSidebarOnMobile) {
|
|
58
|
+
this.closeSidebarOnMobile();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
this.navigation.navigateToGitCommit({ hash, historyFilePath, path });
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
handleGitHistorySelection({ closeSidebarOnMobile = false } = {}) {
|
|
65
|
+
if (closeSidebarOnMobile) {
|
|
66
|
+
this.closeSidebarOnMobile();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
this.navigation.navigateToGitHistory();
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
handleGitFileHistorySelection(filePath = this.currentFilePath, { closeSidebarOnMobile = false } = {}) {
|
|
73
|
+
if (closeSidebarOnMobile) {
|
|
74
|
+
this.closeSidebarOnMobile();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!filePath) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
this.navigation.navigateToGitFileHistory({ filePath });
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
handleGitFilePreviewSelection({ hash, path, currentFilePath = null, closeSidebarOnMobile = false } = {}) {
|
|
85
|
+
if (closeSidebarOnMobile) {
|
|
86
|
+
this.closeSidebarOnMobile();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (!hash || !path) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
this.navigation.navigateToGitFilePreview({ hash, path, currentFilePath });
|
|
94
|
+
},
|
|
95
|
+
|
|
54
96
|
handleGitRepoChange(isGitRepo, status = null) {
|
|
55
97
|
this.gitRepoAvailable = Boolean(isGitRepo);
|
|
56
98
|
this.elements.sidebarTabs?.classList.toggle('hidden', !this.gitRepoAvailable);
|
|
@@ -62,47 +104,225 @@ export const gitFeature = {
|
|
|
62
104
|
this.gitDiffView.setRepoStatus(this.gitRepoAvailable ? status : null);
|
|
63
105
|
|
|
64
106
|
if (!this.gitRepoAvailable && this.activeSidebarTab === 'git') {
|
|
107
|
+
this.syncFileHistoryButton({ mode: this.navigation.getHashRoute().type === 'git-file-preview' ? 'history-preview' : 'editor' });
|
|
65
108
|
this.setSidebarTab('files');
|
|
66
109
|
return;
|
|
67
110
|
}
|
|
68
111
|
|
|
69
|
-
|
|
112
|
+
const routeType = this.navigation.getHashRoute().type;
|
|
113
|
+
if (
|
|
114
|
+
this.gitRepoAvailable
|
|
115
|
+
&& (routeType === 'git-diff' || routeType === 'git-commit' || routeType === 'git-history')
|
|
116
|
+
&& this.activeSidebarTab !== 'git'
|
|
117
|
+
) {
|
|
70
118
|
this.setSidebarTab('git');
|
|
71
119
|
}
|
|
120
|
+
|
|
121
|
+
this.syncFileHistoryButton({
|
|
122
|
+
filePath: this.currentFilePath,
|
|
123
|
+
mode: routeType === 'git-file-preview'
|
|
124
|
+
? 'history-preview'
|
|
125
|
+
: routeType === 'file'
|
|
126
|
+
? 'editor'
|
|
127
|
+
: 'empty',
|
|
128
|
+
});
|
|
72
129
|
},
|
|
73
130
|
|
|
74
|
-
|
|
75
|
-
const
|
|
76
|
-
this.elements.
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
131
|
+
syncFileHistoryButton({ filePath = this.currentFilePath, mode = 'editor' } = {}) {
|
|
132
|
+
const button = this.elements.fileHistoryButton;
|
|
133
|
+
const label = this.elements.fileHistoryButtonLabel;
|
|
134
|
+
if (!button || !label) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const supported = this.gitRepoAvailable && this.supportsFileHistory?.(filePath);
|
|
139
|
+
if (!supported) {
|
|
140
|
+
button.classList.add('hidden');
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
button.classList.remove('hidden');
|
|
145
|
+
if (mode === 'history-preview') {
|
|
146
|
+
label.textContent = 'Back to History';
|
|
147
|
+
button.setAttribute('aria-label', 'Back to file history');
|
|
148
|
+
button.title = 'Back to file history';
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (mode !== 'editor') {
|
|
153
|
+
button.classList.add('hidden');
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
label.textContent = 'History';
|
|
158
|
+
button.setAttribute('aria-label', 'Open file history');
|
|
159
|
+
button.title = 'View file history';
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
syncMainChrome({ badgeLabel = '', mode, title = null } = {}) {
|
|
163
|
+
const isSpecialMode = mode === 'diff' || mode === 'history' || mode === 'history-preview';
|
|
164
|
+
this.elements.toolbarCenter?.classList.toggle('hidden', isSpecialMode);
|
|
165
|
+
this.elements.mobileViewToggle?.classList.toggle('hidden', isSpecialMode);
|
|
166
|
+
this.elements.userCount?.classList.toggle('hidden', isSpecialMode);
|
|
167
|
+
this.elements.toolbarDiffBadge?.classList.toggle('hidden', !badgeLabel);
|
|
168
|
+
if (this.elements.toolbarDiffBadge) {
|
|
169
|
+
this.elements.toolbarDiffBadge.textContent = badgeLabel;
|
|
170
|
+
}
|
|
80
171
|
|
|
81
172
|
if (title && this.elements.activeFileName) {
|
|
82
173
|
this.elements.activeFileName.textContent = title;
|
|
83
174
|
}
|
|
175
|
+
|
|
176
|
+
this.syncFileHistoryButton({ filePath: this.currentFilePath, mode });
|
|
84
177
|
},
|
|
85
178
|
|
|
86
179
|
async showGitDiff({ filePath = null, scope = 'all' } = {}) {
|
|
87
|
-
this.
|
|
180
|
+
this.clearStaticPreviewDocument?.();
|
|
181
|
+
this.gitPanel.setSelection(filePath ? { path: filePath, scope, source: 'workspace' } : {});
|
|
182
|
+
this.gitPanel.setMode('changes');
|
|
88
183
|
this.showDiffState();
|
|
89
184
|
this.syncMainChrome({
|
|
185
|
+
badgeLabel: 'Diff',
|
|
90
186
|
mode: 'diff',
|
|
91
187
|
title: this.gitDiffView.getToolbarTitle({ filePath, scope }),
|
|
92
188
|
});
|
|
93
|
-
|
|
189
|
+
this.syncFileHistoryButton({ filePath, mode: 'diff' });
|
|
190
|
+
await this.gitDiffView.openWorkspaceDiff({ filePath, scope });
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
async showGitCommit({ hash, path = null, historyFilePath = null } = {}) {
|
|
194
|
+
this.clearStaticPreviewDocument?.();
|
|
195
|
+
this.gitPanel.setMode('history');
|
|
196
|
+
this.gitPanel.setSelection(hash ? { commitHash: hash, path, source: 'commit' } : { source: 'commit' });
|
|
197
|
+
this.showDiffState();
|
|
198
|
+
this.syncMainChrome({
|
|
199
|
+
badgeLabel: 'Diff',
|
|
200
|
+
mode: 'diff',
|
|
201
|
+
title: this.gitDiffView.getToolbarTitle({ commitHash: hash, path, source: 'commit' }),
|
|
202
|
+
});
|
|
203
|
+
this.syncFileHistoryButton({ filePath: path, mode: 'diff' });
|
|
204
|
+
await this.gitDiffView.openCommitDiff({ hash, historyFilePath, path });
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
async showGitHistory() {
|
|
208
|
+
this.clearStaticPreviewDocument?.();
|
|
209
|
+
this.gitPanel.setMode('history');
|
|
210
|
+
this.gitPanel.setSelection({ source: 'commit' });
|
|
211
|
+
this.workspaceRouteController?.showEmptyState?.();
|
|
212
|
+
this.syncMainChrome({
|
|
213
|
+
mode: 'empty',
|
|
214
|
+
title: 'Git History',
|
|
215
|
+
});
|
|
216
|
+
this.syncFileHistoryButton({ mode: 'empty' });
|
|
217
|
+
},
|
|
218
|
+
|
|
219
|
+
async showGitFileHistory({ filePath = null } = {}) {
|
|
220
|
+
if (!filePath) {
|
|
221
|
+
this.workspaceRouteController?.showEmptyState?.();
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
this.clearStaticPreviewDocument?.();
|
|
226
|
+
this.commentUi?.attachSession?.(null);
|
|
227
|
+
this.commentUi?.setCurrentFile?.(filePath, {
|
|
228
|
+
fileKind: this.getCommentFileKind(filePath),
|
|
229
|
+
supported: false,
|
|
230
|
+
});
|
|
231
|
+
this.handleCommentThreadsChange?.([]);
|
|
232
|
+
this.handleCommentSelectionChange?.(null);
|
|
233
|
+
this.workspaceRouteController?.showFileHistoryState?.(filePath);
|
|
234
|
+
this.syncMainChrome({
|
|
235
|
+
badgeLabel: 'History',
|
|
236
|
+
mode: 'history',
|
|
237
|
+
title: this.getDisplayName(filePath),
|
|
238
|
+
});
|
|
239
|
+
this.syncFileHistoryButton({ filePath, mode: 'history' });
|
|
240
|
+
await this.fileHistoryView.openFileHistory({ filePath });
|
|
241
|
+
},
|
|
242
|
+
|
|
243
|
+
async showGitFilePreview({ hash, filePath = null, currentFilePath = null } = {}) {
|
|
244
|
+
const resolvedCurrentFilePath = String(currentFilePath ?? '').trim() || filePath;
|
|
245
|
+
if (!hash || !filePath || !resolvedCurrentFilePath) {
|
|
246
|
+
this.workspaceRouteController?.showEmptyState?.();
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
this.commentUi?.attachSession?.(null);
|
|
251
|
+
this.commentUi?.setCurrentFile?.(resolvedCurrentFilePath, {
|
|
252
|
+
fileKind: this.getCommentFileKind(resolvedCurrentFilePath),
|
|
253
|
+
supported: false,
|
|
254
|
+
});
|
|
255
|
+
this.handleCommentThreadsChange?.([]);
|
|
256
|
+
this.handleCommentSelectionChange?.(null);
|
|
257
|
+
this.workspaceRouteController?.showPreviewOnlyState?.(resolvedCurrentFilePath);
|
|
258
|
+
this.layoutController.setView('preview', { persist: false });
|
|
259
|
+
this.syncMainChrome({
|
|
260
|
+
badgeLabel: 'History Preview',
|
|
261
|
+
mode: 'history-preview',
|
|
262
|
+
title: this.getDisplayName(resolvedCurrentFilePath),
|
|
263
|
+
});
|
|
264
|
+
this.syncFileHistoryButton({ filePath: resolvedCurrentFilePath, mode: 'history-preview' });
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
const query = new URLSearchParams();
|
|
268
|
+
query.set('hash', hash);
|
|
269
|
+
query.set('path', filePath);
|
|
270
|
+
const response = await fetch(resolveApiUrl(`/git/file-snapshot?${query.toString()}`));
|
|
271
|
+
const data = await response.json();
|
|
272
|
+
if (!response.ok) {
|
|
273
|
+
throw new Error(data.error || 'Failed to load historical file preview');
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
this.setStaticPreviewDocument?.({
|
|
277
|
+
...data,
|
|
278
|
+
currentFilePath: resolvedCurrentFilePath,
|
|
279
|
+
});
|
|
280
|
+
this.resetPreviewMode();
|
|
281
|
+
this.elements.markdownToolbar?.classList.add('hidden');
|
|
282
|
+
this.elements.outlineToggle?.classList.toggle('hidden', data.fileKind !== 'markdown');
|
|
283
|
+
|
|
284
|
+
if (data.fileKind === 'markdown' || data.fileKind === 'mermaid' || data.fileKind === 'plantuml') {
|
|
285
|
+
if (data.fileKind === 'mermaid') {
|
|
286
|
+
this.elements.previewContent?.classList.add('is-mermaid-file-preview');
|
|
287
|
+
} else if (data.fileKind === 'plantuml') {
|
|
288
|
+
this.elements.previewContent?.classList.add('is-plantuml-file-preview');
|
|
289
|
+
}
|
|
290
|
+
this.previewRenderer.beginDocumentLoad();
|
|
291
|
+
this.previewRenderer.queueRender();
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
this.elements.outlineToggle?.classList.add('hidden');
|
|
296
|
+
this.renderTextFilePreview({
|
|
297
|
+
content: data.content,
|
|
298
|
+
filePath: resolvedCurrentFilePath,
|
|
299
|
+
});
|
|
300
|
+
} catch (error) {
|
|
301
|
+
console.error('[git-preview] Failed to load file snapshot:', error);
|
|
302
|
+
this.clearStaticPreviewDocument?.();
|
|
303
|
+
this.toastController.show('Failed to load historical file preview');
|
|
304
|
+
this.renderTextFilePreview({
|
|
305
|
+
content: 'Failed to load historical file preview.',
|
|
306
|
+
filePath: resolvedCurrentFilePath,
|
|
307
|
+
});
|
|
308
|
+
this.elements.outlineToggle?.classList.add('hidden');
|
|
309
|
+
}
|
|
94
310
|
},
|
|
95
311
|
|
|
96
312
|
async postGitAction(endpoint, payload) {
|
|
313
|
+
const requestId = createWorkspaceRequestId();
|
|
314
|
+
this.pendingWorkspaceRequestIds?.add(requestId);
|
|
97
315
|
const response = await fetch(endpoint, {
|
|
98
316
|
body: JSON.stringify(payload),
|
|
99
317
|
headers: {
|
|
100
318
|
'Content-Type': 'application/json',
|
|
319
|
+
'X-CollabMD-Request-Id': requestId,
|
|
101
320
|
},
|
|
102
321
|
method: 'POST',
|
|
103
322
|
});
|
|
104
323
|
const data = await response.json();
|
|
105
324
|
if (!response.ok) {
|
|
325
|
+
this.pendingWorkspaceRequestIds?.delete(requestId);
|
|
106
326
|
const error = new Error(data.error || 'Git action failed');
|
|
107
327
|
if (typeof data?.code === 'string') {
|
|
108
328
|
error.code = data.code;
|
|
@@ -121,6 +341,30 @@ export const gitFeature = {
|
|
|
121
341
|
await this.gitPanel.refresh({ force: true });
|
|
122
342
|
|
|
123
343
|
const route = this.navigation.getHashRoute();
|
|
344
|
+
if (route.type === 'git-history') {
|
|
345
|
+
await this.showGitHistory();
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (route.type === 'git-file-history') {
|
|
350
|
+
await this.showGitFileHistory({ filePath: route.filePath });
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (route.type === 'git-file-preview') {
|
|
355
|
+
await this.showGitFilePreview({
|
|
356
|
+
hash: route.hash,
|
|
357
|
+
filePath: route.filePath,
|
|
358
|
+
currentFilePath: route.currentFilePath,
|
|
359
|
+
});
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (route.type === 'git-commit') {
|
|
364
|
+
await this.showGitCommit({ hash: route.hash, path: route.path, historyFilePath: route.historyFilePath });
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
|
|
124
368
|
if (route.type !== 'git-diff') {
|
|
125
369
|
return;
|
|
126
370
|
}
|
|
@@ -145,11 +389,6 @@ export const gitFeature = {
|
|
|
145
389
|
const workspaceChange = normalizeWorkspaceChange(result.workspaceChange);
|
|
146
390
|
await this.refreshWorkspaceAfterGitAction({ filePath, preferredScope });
|
|
147
391
|
this.handleWorkspaceChangeForCurrentFile(workspaceChange, { action, local: true, showToast: showLocalFileToast });
|
|
148
|
-
this.lobby.sendWorkspaceEvent({
|
|
149
|
-
action,
|
|
150
|
-
sourceRef: result.sourceRef ?? null,
|
|
151
|
-
workspaceChange,
|
|
152
|
-
});
|
|
153
392
|
return workspaceChange;
|
|
154
393
|
},
|
|
155
394
|
|
|
@@ -165,21 +404,27 @@ export const gitFeature = {
|
|
|
165
404
|
return false;
|
|
166
405
|
}
|
|
167
406
|
|
|
168
|
-
this.navigation.navigateToFile(null);
|
|
169
407
|
if (!showToast) {
|
|
408
|
+
if (renameEntry) {
|
|
409
|
+
this.navigation.navigateToFile(renameEntry.newPath);
|
|
410
|
+
} else {
|
|
411
|
+
this.navigation.navigateToFile(null);
|
|
412
|
+
}
|
|
170
413
|
return true;
|
|
171
414
|
}
|
|
172
415
|
|
|
173
416
|
const displayName = this.getDisplayName(currentFilePath);
|
|
174
417
|
if (renameEntry) {
|
|
418
|
+
this.navigation.navigateToFile(renameEntry.newPath);
|
|
175
419
|
this.toastController.show(
|
|
176
420
|
local
|
|
177
|
-
? `${displayName}
|
|
178
|
-
: `${displayName} moved
|
|
421
|
+
? `${displayName} moved to ${this.getDisplayName(renameEntry.newPath)}`
|
|
422
|
+
: `${displayName} moved on disk`,
|
|
179
423
|
);
|
|
180
424
|
return true;
|
|
181
425
|
}
|
|
182
426
|
|
|
427
|
+
this.navigation.navigateToFile(null);
|
|
183
428
|
this.toastController.show(
|
|
184
429
|
local
|
|
185
430
|
? `${displayName} was removed by ${action}`
|
|
@@ -193,13 +438,45 @@ export const gitFeature = {
|
|
|
193
438
|
return;
|
|
194
439
|
}
|
|
195
440
|
|
|
441
|
+
if (event.requestId && this.pendingWorkspaceRequestIds?.has(event.requestId)) {
|
|
442
|
+
this.pendingWorkspaceRequestIds.delete(event.requestId);
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
|
|
196
446
|
const workspaceChange = normalizeWorkspaceChange(event.workspaceChange);
|
|
197
|
-
|
|
447
|
+
if (event.origin === 'git') {
|
|
448
|
+
await this.refreshGitAfterAction();
|
|
449
|
+
}
|
|
198
450
|
this.handleWorkspaceChangeForCurrentFile(workspaceChange, {
|
|
199
|
-
action: event.action || '
|
|
451
|
+
action: event.action || event.origin || 'workspace',
|
|
200
452
|
local: false,
|
|
201
453
|
showToast: true,
|
|
202
454
|
});
|
|
455
|
+
|
|
456
|
+
if (
|
|
457
|
+
event.origin === 'filesystem'
|
|
458
|
+
&& this.currentFilePath
|
|
459
|
+
&& workspaceChange.changedPaths.includes(this.currentFilePath)
|
|
460
|
+
) {
|
|
461
|
+
const canUseInlineCue = workspaceChange.changedPaths.length === 1;
|
|
462
|
+
const highlightRange = canUseInlineCue && Array.isArray(event.highlightRanges)
|
|
463
|
+
? event.highlightRanges.find((entry) => entry.path === this.currentFilePath)
|
|
464
|
+
: null;
|
|
465
|
+
const didFlash = highlightRange
|
|
466
|
+
? this.session?.flashExternalUpdate?.(highlightRange)
|
|
467
|
+
: false;
|
|
468
|
+
if (!didFlash) {
|
|
469
|
+
this.toastController.show(`${this.getDisplayName(this.currentFilePath)} updated from disk`);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if (
|
|
474
|
+
this.currentFilePath
|
|
475
|
+
&& Array.isArray(event.reloadRequiredPaths)
|
|
476
|
+
&& event.reloadRequiredPaths.includes(this.currentFilePath)
|
|
477
|
+
) {
|
|
478
|
+
this.toastController.show(`${this.getDisplayName(this.currentFilePath)} needs a manual reload`);
|
|
479
|
+
}
|
|
203
480
|
},
|
|
204
481
|
|
|
205
482
|
async stageGitFile(filePath, { scope = 'working-tree' } = {}) {
|
|
@@ -15,10 +15,13 @@ export const uiFeature = {
|
|
|
15
15
|
this.fileExplorer.initialize();
|
|
16
16
|
this.gitPanel.initialize();
|
|
17
17
|
this.gitDiffView.initialize();
|
|
18
|
+
this.fileHistoryView.initialize();
|
|
18
19
|
this.initializePreviewLayoutObserver();
|
|
20
|
+
this.syncIdentityManagementUi();
|
|
19
21
|
this.syncCurrentUserName();
|
|
20
22
|
this.syncWrapToggle();
|
|
21
23
|
this.syncChatNotificationButton();
|
|
24
|
+
this.syncFileHistoryButton({ mode: 'empty' });
|
|
22
25
|
this.renderChat();
|
|
23
26
|
void this.gitPanel.refresh({ force: true });
|
|
24
27
|
this.elements.chatInput?.setAttribute('maxlength', String(this.lobbyChatMessageMaxLength));
|
|
@@ -39,6 +42,11 @@ export const uiFeature = {
|
|
|
39
42
|
});
|
|
40
43
|
},
|
|
41
44
|
|
|
45
|
+
isIdentityManagedByAuth() {
|
|
46
|
+
return this.runtimeConfig?.auth?.strategy === 'oidc'
|
|
47
|
+
&& this.runtimeConfig?.auth?.provider === 'google';
|
|
48
|
+
},
|
|
49
|
+
|
|
42
50
|
bindEvents() {
|
|
43
51
|
this.elements.emptyStateNewFileBtn?.addEventListener('click', () => {
|
|
44
52
|
this.fileExplorer.actionController.handleNewFile();
|
|
@@ -65,6 +73,16 @@ export const uiFeature = {
|
|
|
65
73
|
void this.copyCurrentLink();
|
|
66
74
|
});
|
|
67
75
|
|
|
76
|
+
this.elements.fileHistoryButton?.addEventListener('click', () => {
|
|
77
|
+
const route = this.navigation.getHashRoute();
|
|
78
|
+
if (route.type === 'git-file-preview') {
|
|
79
|
+
this.handleGitFileHistorySelection(route.currentFilePath ?? route.filePath, { closeSidebarOnMobile: true });
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
this.handleGitFileHistorySelection(this.currentFilePath, { closeSidebarOnMobile: true });
|
|
84
|
+
});
|
|
85
|
+
|
|
68
86
|
this.elements.editNameButton?.addEventListener('click', () => {
|
|
69
87
|
this.openDisplayNameDialog();
|
|
70
88
|
});
|
|
@@ -389,6 +407,11 @@ export const uiFeature = {
|
|
|
389
407
|
|
|
390
408
|
handleConnectionChange(state) {
|
|
391
409
|
this.connectionState = state;
|
|
410
|
+
if (state?.firstConnection) {
|
|
411
|
+
this.recordFileOpenMetric?.('ws_connected', {
|
|
412
|
+
status: state.status,
|
|
413
|
+
});
|
|
414
|
+
}
|
|
392
415
|
this.renderPresence();
|
|
393
416
|
|
|
394
417
|
if (state.unreachable && !this.connectionHelpShown) {
|
|
@@ -398,6 +421,10 @@ export const uiFeature = {
|
|
|
398
421
|
},
|
|
399
422
|
|
|
400
423
|
openDisplayNameDialog({ mode = 'edit' } = {}) {
|
|
424
|
+
if (this.isIdentityManagedByAuth()) {
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
|
|
401
428
|
if (!this.isTabActive) {
|
|
402
429
|
return;
|
|
403
430
|
}
|
|
@@ -439,7 +466,12 @@ export const uiFeature = {
|
|
|
439
466
|
},
|
|
440
467
|
|
|
441
468
|
promptForDisplayNameIfNeeded() {
|
|
442
|
-
if (
|
|
469
|
+
if (
|
|
470
|
+
!this.isTabActive
|
|
471
|
+
|| this._hasPromptedForDisplayName
|
|
472
|
+
|| this.getStoredUserName()
|
|
473
|
+
|| this.isIdentityManagedByAuth()
|
|
474
|
+
) {
|
|
443
475
|
return;
|
|
444
476
|
}
|
|
445
477
|
|
|
@@ -450,6 +482,10 @@ export const uiFeature = {
|
|
|
450
482
|
},
|
|
451
483
|
|
|
452
484
|
handleDisplayNameSubmit() {
|
|
485
|
+
if (this.isIdentityManagedByAuth()) {
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
|
|
453
489
|
const input = this.elements.displayNameInput;
|
|
454
490
|
const dialog = this.elements.displayNameDialog;
|
|
455
491
|
if (!input || !dialog) return;
|
|
@@ -501,10 +537,21 @@ export const uiFeature = {
|
|
|
501
537
|
const name = this.getCurrentUserName();
|
|
502
538
|
el.textContent = name || 'Set name';
|
|
503
539
|
el.classList.toggle('has-name', Boolean(name));
|
|
504
|
-
this.
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
540
|
+
if (!this.isIdentityManagedByAuth()) {
|
|
541
|
+
this.elements.editNameButton?.setAttribute(
|
|
542
|
+
'aria-label',
|
|
543
|
+
`${name || 'Set name'}. Change display name`,
|
|
544
|
+
);
|
|
545
|
+
}
|
|
546
|
+
},
|
|
547
|
+
|
|
548
|
+
syncIdentityManagementUi() {
|
|
549
|
+
const isManaged = this.isIdentityManagedByAuth();
|
|
550
|
+
this.elements.editNameButton?.classList.toggle('hidden', isManaged);
|
|
551
|
+
this.elements.editNameButton?.toggleAttribute('disabled', isManaged);
|
|
552
|
+
if (isManaged && this.elements.displayNameDialog?.open) {
|
|
553
|
+
this.elements.displayNameDialog.close();
|
|
554
|
+
}
|
|
508
555
|
},
|
|
509
556
|
|
|
510
557
|
toggleLineWrapping() {
|
|
@@ -534,6 +581,9 @@ export const uiFeature = {
|
|
|
534
581
|
if (!this.lobby.provider) {
|
|
535
582
|
this.lobby.connect();
|
|
536
583
|
}
|
|
584
|
+
if (!this.workspaceSync.provider) {
|
|
585
|
+
this.workspaceSync.connect();
|
|
586
|
+
}
|
|
537
587
|
|
|
538
588
|
if (wasInactive) {
|
|
539
589
|
if (this.fileExplorerReady) {
|
|
@@ -547,13 +597,20 @@ export const uiFeature = {
|
|
|
547
597
|
}
|
|
548
598
|
},
|
|
549
599
|
|
|
550
|
-
handleTabBlocked({ reason } = {}) {
|
|
600
|
+
async handleTabBlocked({ reason } = {}) {
|
|
551
601
|
const wasActive = this.isTabActive;
|
|
602
|
+
const blockedFilePath = this.currentFilePath;
|
|
603
|
+
const shouldPrepareExcalidrawDisconnect = Boolean(
|
|
604
|
+
blockedFilePath
|
|
605
|
+
&& this.isExcalidrawFile?.(blockedFilePath),
|
|
606
|
+
);
|
|
607
|
+
|
|
552
608
|
this.isTabActive = false;
|
|
553
609
|
if (this.elements.displayNameDialog?.open) {
|
|
554
610
|
this.elements.displayNameDialog.close();
|
|
555
611
|
}
|
|
556
612
|
this.lobby.disconnect();
|
|
613
|
+
this.workspaceSync.disconnect();
|
|
557
614
|
this.globalUsers = [];
|
|
558
615
|
this.chatMessages = [];
|
|
559
616
|
this.chatMessageIds.clear();
|
|
@@ -562,9 +619,14 @@ export const uiFeature = {
|
|
|
562
619
|
this.followedUserClientId = null;
|
|
563
620
|
this.followedCursorSignature = '';
|
|
564
621
|
this.connectionState = { status: 'disconnected', unreachable: false };
|
|
622
|
+
this.showTabLockOverlay({ reason });
|
|
623
|
+
|
|
624
|
+
if (shouldPrepareExcalidrawDisconnect) {
|
|
625
|
+
await this.excalidrawEmbed.prepareFileDisconnect(blockedFilePath);
|
|
626
|
+
}
|
|
627
|
+
|
|
565
628
|
this.showEmptyState();
|
|
566
629
|
this.renderChat();
|
|
567
|
-
this.showTabLockOverlay({ reason });
|
|
568
630
|
|
|
569
631
|
if (wasActive && reason === 'taken-over') {
|
|
570
632
|
this.toastController.show('Another tab took over this session');
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
getVaultFileKind,
|
|
2
3
|
isExcalidrawFilePath,
|
|
3
4
|
isImageAttachmentFilePath,
|
|
4
5
|
isMermaidFilePath,
|
|
@@ -28,9 +29,50 @@ export const workspaceFeature = {
|
|
|
28
29
|
},
|
|
29
30
|
|
|
30
31
|
getPreviewSource() {
|
|
32
|
+
const previewDocument = this.getStaticPreviewDocument?.();
|
|
33
|
+
const previewFilePath = previewDocument?.currentFilePath ?? previewDocument?.filePath ?? null;
|
|
34
|
+
if (previewDocument && previewFilePath && previewFilePath === this.currentFilePath) {
|
|
35
|
+
if (this.isMermaidFile(this.currentFilePath)) {
|
|
36
|
+
return this.createDiagramPreviewDocument('mermaid', previewDocument.content);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (this.isPlantUmlFile(this.currentFilePath)) {
|
|
40
|
+
return this.createDiagramPreviewDocument('plantuml', previewDocument.content);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return String(previewDocument.content ?? '');
|
|
44
|
+
}
|
|
45
|
+
|
|
31
46
|
return this.workspacePreviewController.getPreviewSource(this.currentFilePath);
|
|
32
47
|
},
|
|
33
48
|
|
|
49
|
+
getStaticPreviewDocument() {
|
|
50
|
+
return this._staticPreviewDocument ?? null;
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
setStaticPreviewDocument(document) {
|
|
54
|
+
const normalizedFilePath = document?.filePath ?? document?.path ?? null;
|
|
55
|
+
const normalizedCurrentFilePath = document?.currentFilePath ?? normalizedFilePath;
|
|
56
|
+
this._staticPreviewDocument = document
|
|
57
|
+
? {
|
|
58
|
+
content: String(document.content ?? ''),
|
|
59
|
+
currentFilePath: normalizedCurrentFilePath,
|
|
60
|
+
fileKind: document.fileKind ?? getVaultFileKind(normalizedFilePath),
|
|
61
|
+
filePath: normalizedFilePath,
|
|
62
|
+
hash: document.hash ?? null,
|
|
63
|
+
}
|
|
64
|
+
: null;
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
clearStaticPreviewDocument() {
|
|
68
|
+
this._staticPreviewDocument = null;
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
supportsFileHistory(filePath) {
|
|
72
|
+
const kind = getVaultFileKind(filePath);
|
|
73
|
+
return kind !== null && kind !== 'image';
|
|
74
|
+
},
|
|
75
|
+
|
|
34
76
|
getDisplayName(filePath) {
|
|
35
77
|
return stripVaultFileExtension(String(filePath ?? '')
|
|
36
78
|
.split('/')
|
|
@@ -53,6 +95,10 @@ export const workspaceFeature = {
|
|
|
53
95
|
this.workspacePreviewController.renderImageFilePreview(filePath);
|
|
54
96
|
},
|
|
55
97
|
|
|
98
|
+
renderTextFilePreview(payload) {
|
|
99
|
+
this.workspacePreviewController.renderTextFilePreview(payload);
|
|
100
|
+
},
|
|
101
|
+
|
|
56
102
|
createResizeHandler() {
|
|
57
103
|
return this.workspacePreviewController.createResizeHandler(() => this.restoreSidebarState());
|
|
58
104
|
},
|
|
@@ -38,6 +38,8 @@ export function bindAppShellElements(doc = document) {
|
|
|
38
38
|
editorPage: doc.getElementById('editor-page'),
|
|
39
39
|
diffPage: doc.getElementById('diff-page'),
|
|
40
40
|
fileSearch: doc.getElementById('fileSearch'),
|
|
41
|
+
fileHistoryButton: doc.getElementById('fileHistoryBtn'),
|
|
42
|
+
fileHistoryButtonLabel: doc.getElementById('fileHistoryBtnLabel'),
|
|
41
43
|
filesSidebarTab: doc.getElementById('filesSidebarTab'),
|
|
42
44
|
gitSearch: doc.getElementById('gitSearch'),
|
|
43
45
|
gitSearchInput: doc.getElementById('gitSearchInput'),
|
|
@@ -561,7 +561,7 @@ export class MermaidPreviewHydrator extends DiagramPreviewHydrator {
|
|
|
561
561
|
createZoomButton(label, ariaLabel) {
|
|
562
562
|
const button = document.createElement('button');
|
|
563
563
|
button.type = 'button';
|
|
564
|
-
button.className = 'mermaid-zoom-btn
|
|
564
|
+
button.className = 'mermaid-zoom-btn ui-preview-action';
|
|
565
565
|
button.setAttribute('aria-label', ariaLabel);
|
|
566
566
|
button.title = ariaLabel;
|
|
567
567
|
button.textContent = label;
|
|
@@ -554,7 +554,7 @@ export class PlantUmlPreviewHydrator extends DiagramPreviewHydrator {
|
|
|
554
554
|
createToolButton(label, ariaLabel) {
|
|
555
555
|
const button = document.createElement('button');
|
|
556
556
|
button.type = 'button';
|
|
557
|
-
button.className = 'plantuml-tool-btn
|
|
557
|
+
button.className = 'plantuml-tool-btn ui-preview-action';
|
|
558
558
|
button.setAttribute('aria-label', ariaLabel);
|
|
559
559
|
button.title = ariaLabel;
|
|
560
560
|
button.textContent = label;
|