collabmd 0.1.44 → 0.1.45
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/dist/client/assets/{chunk-AXR3VHVW-0GTk_evH.js → chunk-ERSWUTIN-ME9RVNel.js} +3 -3
- package/dist/client/assets/{chunk-CZQZEIFA-DT3N9fo7.js → chunk-YGJVY4SA-CjSe7Bx7.js} +2 -2
- package/dist/client/assets/{comment-threads-D-iKphIp.js → comment-threads-DDegmaFO.js} +1 -1
- package/dist/client/assets/drawio-editor-ClE8jpoG.js +9 -0
- package/dist/client/assets/drawioEditor-CXaTM9Ds.js +2 -0
- package/dist/client/assets/editor-session-GTwZS6Df.js +16 -0
- package/dist/client/assets/embedded-editor-base-CiNEc8vy.css +1 -0
- package/dist/client/assets/{en-GADW4C6J-DQ6zyCEM.js → en-7NFCYGKX-BrgYZAEe.js} +1 -1
- package/dist/client/assets/{excalidraw-editor-BHu6k8d4.js → excalidraw-editor-BZVM81BO.js} +2 -2
- package/dist/client/assets/excalidraw-editor-CyGwOhLz.css +1 -0
- package/dist/client/assets/excalidraw-scene-C8n9CHsK.js +176 -0
- package/dist/client/assets/excalidrawEditor-DxJanCze.js +2 -0
- package/dist/client/assets/export-page-CUfws2T-.js +39 -0
- package/dist/client/assets/exportDocument-BCuxqXDY.js +2 -0
- package/dist/client/assets/exportDocument-DV-OX74E.css +1 -0
- package/dist/client/assets/image-QXRYHUNP-lfTHE3bb.js +1 -0
- package/dist/client/assets/index-C3YnUgMD.css +1 -0
- package/dist/client/assets/index-D2wXraJ1.js +2 -0
- package/dist/client/assets/main-DtiGz7uI.js +795 -0
- package/dist/client/assets/modulepreload-polyfill-P2Xu9kJm.js +1 -0
- package/dist/client/assets/preview-diagram-utils-txBCoW8d.js +1 -0
- package/dist/client/assets/quick-switcher-controller-shW_6vPY.js +12 -0
- package/dist/client/assets/room-CQLoqtMo.js +1 -0
- package/dist/client/assets/runtime-config-DSGmGE9V.js +1 -0
- package/dist/client/assets/vault-api-client-C6bcGwy-.js +5 -0
- package/dist/client/assets/{yjs-provider-reset-guard-Cox4nxwu.js → yjs-provider-reset-guard-DE0vINDP.js} +1 -1
- package/dist/client/drawio-editor.html +3 -2
- package/dist/client/excalidraw-editor.html +3 -2
- package/dist/client/export-document.html +4 -3
- package/dist/client/index.html +97 -27
- package/package.json +10 -12
- package/src/client/app/drawio-editor-entry.js +0 -3
- package/src/client/app/drawio-editor.html +1 -0
- package/src/client/app/excalidraw-editor-entry.js +0 -3
- package/src/client/app/excalidraw-editor.html +1 -0
- package/src/client/app/export-document-entry.js +0 -3
- package/src/client/app/export-document.html +1 -0
- package/src/client/app/index.html +94 -24
- package/src/client/app/main-entry.js +0 -5
- package/src/client/application/app-shell/chat-feature.js +180 -124
- package/src/client/application/app-shell/comments-feature.js +1 -1
- package/src/client/application/app-shell/git-feature.js +4 -9
- package/src/client/application/app-shell/presence-feature.js +16 -10
- package/src/client/application/app-shell/ui-feature-shell.js +33 -15
- package/src/client/application/app-shell/ui-feature-sidebar.js +74 -1
- package/src/client/application/app-shell/ui-feature-tab-activity.js +2 -2
- package/src/client/application/app-shell/workspace-feature.js +22 -83
- package/src/client/application/app-shell-elements.js +7 -1
- package/src/client/application/diagram-chrome.js +161 -166
- package/src/client/application/diagram-preview-export.js +7 -34
- package/src/client/application/diagram-preview-hydrator.js +1 -19
- package/src/client/application/markdown-frontmatter.js +0 -2
- package/src/client/application/mermaid-preview-hydrator.js +1 -14
- package/src/client/application/plantuml-preview-hydrator.js +0 -10
- package/src/client/application/preview-diagram-utils.js +31 -2
- package/src/client/application/preview-render-compiler.js +2 -1
- package/src/client/application/preview-renderer.js +2 -2
- package/src/client/application/quick-switcher-loader.js +3 -1
- package/src/client/application/workspace-preview-controller.js +0 -3
- package/src/client/bootstrap/collabmd-app-shell.js +125 -39
- package/src/client/domain/excalidraw-collaboration.js +0 -2
- package/src/client/domain/room.js +38 -0
- package/src/client/export/export-pipeline.js +6 -34
- package/src/client/infrastructure/auth-client.js +4 -10
- package/src/client/infrastructure/browser-notification-port.js +33 -20
- package/src/client/infrastructure/browser-preferences-port.js +36 -9
- package/src/client/infrastructure/editor-view-adapter.js +87 -3
- package/src/client/infrastructure/lobby-presence.js +1 -0
- package/src/client/infrastructure/vault-api-client.js +25 -0
- package/src/client/infrastructure/workspace-sync-client.js +13 -1
- package/src/client/presentation/comment-overview-controller.js +18 -53
- package/src/client/presentation/comment-ui/comment-ui-card.js +1 -12
- package/src/client/presentation/comment-ui/comment-ui-layout.js +242 -219
- package/src/client/presentation/comment-ui/comment-ui-render.js +32 -45
- package/src/client/presentation/comment-ui/comment-ui-shared.js +65 -14
- package/src/client/presentation/comment-ui-controller.js +5 -1
- package/src/client/presentation/file-action-controller.js +32 -0
- package/src/client/presentation/file-explorer-controller.js +33 -3
- package/src/client/presentation/file-explorer-view.js +49 -5
- package/src/client/presentation/file-tree-state.js +10 -4
- package/src/client/presentation/quick-switcher-controller.js +243 -72
- package/src/client/presentation/quick-switcher-text-search.js +2 -2
- package/src/client/presentation/toast-controller.js +3 -0
- package/src/client/styles/components/feedback.css +6 -0
- package/src/client/styles/features/collaboration-chat.css +49 -53
- package/src/client/styles/features/collaboration-presence.css +9 -7
- package/src/client/styles/features/comment-card.css +25 -11
- package/src/client/styles/features/comment-overlays.css +0 -4
- package/src/client/styles/features/comment-overview.css +44 -21
- package/src/client/styles/features/comments-drawer.css +2 -100
- package/src/client/styles/features/diagram-preview.css +5 -18
- package/src/client/styles/features/embedded-preview.css +8 -33
- package/src/client/styles/features/quick-switcher.css +8 -0
- package/src/client/styles/foundation/tokens.css +1 -0
- package/src/client/styles/layout/editor-page.css +40 -0
- package/src/client/styles/layout/responsive.css +31 -1
- package/src/client/styles/layout/shell.css +25 -6
- package/src/client/styles/layout/sidebar.css +47 -15
- package/src/client/styles/overrides/codemirror.css +217 -15
- package/src/server/application/workspace-reconciliation.js +1 -1
- package/src/server/create-app-server.js +2 -3
- package/src/server/domain/bases/base-query-service.js +1 -77
- package/src/server/domain/comment-overview.js +10 -3
- package/src/server/domain/docx-exporter.js +24 -33
- package/src/server/domain/ripgrep-search-service.js +3 -1
- package/src/server/domain/workspace-state.js +32 -11
- package/src/server/infrastructure/git/diff-service.js +24 -13
- package/src/server/infrastructure/git/git-service.js +40 -10
- package/src/server/infrastructure/git/history-service.js +42 -29
- package/src/server/infrastructure/git/path-utils.js +1 -1
- package/src/server/infrastructure/git/responses.js +0 -95
- package/src/server/infrastructure/github/github-app-client.js +22 -15
- package/src/server/infrastructure/http/create-request-handler.js +2 -8
- package/src/server/infrastructure/http/create-vault-api-command-handler.js +5 -5
- package/src/server/infrastructure/http/http-errors.js +7 -1
- package/dist/client/assets/bases-preview-controller-2pLHk0uk.js +0 -143
- package/dist/client/assets/button-BCEiw4Rw.js +0 -1
- package/dist/client/assets/diagram-action-icons-aa_KOv9h.js +0 -36
- package/dist/client/assets/drawio-editor-DDvFoarP.js +0 -9
- package/dist/client/assets/drawio-embed-controller-BD1GTRwn.js +0 -1
- package/dist/client/assets/drawioEditor-Bu5grlH5.js +0 -2
- package/dist/client/assets/editor-session-DGlpiAsK.js +0 -16
- package/dist/client/assets/embedded-editor-base-C9roKYpW.css +0 -1
- package/dist/client/assets/excalidraw-editor-Cr_184EF.css +0 -1
- package/dist/client/assets/excalidraw-embed-controller-y2fenN_g.js +0 -1
- package/dist/client/assets/excalidraw-embed-reconciler-DGiagExg.js +0 -1
- package/dist/client/assets/excalidraw-scene-BvsQ6mLP.js +0 -176
- package/dist/client/assets/excalidrawEditor-4vFE--Uw.js +0 -2
- package/dist/client/assets/export-page-Cby8JzPO.js +0 -39
- package/dist/client/assets/exportDocument-C9v-bc7I.css +0 -1
- package/dist/client/assets/exportDocument-CRHCfsdo.js +0 -2
- package/dist/client/assets/file-history-view-controller-BDjEV9xJ.js +0 -112
- package/dist/client/assets/git-diff-view-controller-DgdM1aqb.js +0 -150
- package/dist/client/assets/git-panel-controller-BDTR2HVp.js +0 -207
- package/dist/client/assets/image-XGD7JQR6-XDUWHRpO.js +0 -1
- package/dist/client/assets/index-7xOyXDhP.css +0 -1
- package/dist/client/assets/index-Bz1KuQ6y.js +0 -2
- package/dist/client/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- package/dist/client/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- package/dist/client/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- package/dist/client/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- package/dist/client/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- package/dist/client/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- package/dist/client/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- package/dist/client/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
- package/dist/client/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
- package/dist/client/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
- package/dist/client/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
- package/dist/client/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
- package/dist/client/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
- package/dist/client/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
- package/dist/client/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
- package/dist/client/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
- package/dist/client/assets/main-Ci07m9oP.js +0 -152
- package/dist/client/assets/preview-diagram-utils-n_bbJA9V.js +0 -1
- package/dist/client/assets/quick-switcher-controller-CoiYvllm.js +0 -12
- package/dist/client/assets/room-CDrmIdDt.js +0 -1
- package/dist/client/assets/runtime-config-CDgeuPWN.js +0 -1
- package/dist/client/assets/runtime-config-loader-BHGniHP5.js +0 -1
- package/dist/client/assets/runtime-paths-DvpihCEq.js +0 -1
- package/dist/client/assets/segmented-control-DJrHSz8M.js +0 -1
- package/dist/client/assets/vault-api-client-sLB2bo56.js +0 -1
- package/dist/client/assets/vault-paths-CVkuh0j-.js +0 -5
- package/src/client/bootstrap/lazy-controller-feature.js +0 -433
- package/src/client/infrastructure/backlinks-api-client.js +0 -16
- package/src/client/infrastructure/plantuml-api-client.js +0 -19
- package/src/client/infrastructure/runtime-config-loader.js +0 -56
- package/src/server/infrastructure/git/command-runner.js +0 -49
- package/src/server/infrastructure/git/errors.js +0 -8
- package/src/server/infrastructure/http/create-esm-proxy-handler.js +0 -130
|
@@ -1,3 +1,41 @@
|
|
|
1
|
+
import { getUserAvatarTextColor } from '../../domain/room.js';
|
|
2
|
+
|
|
3
|
+
const CHAT_ALERT_MUTE_STORAGE_KEY = 'collabmd-chat-alerts-muted-until';
|
|
4
|
+
const CHAT_ALERT_MUTE_DURATION_MS = 60 * 60 * 1000;
|
|
5
|
+
|
|
6
|
+
function getChatConnectionState(context) {
|
|
7
|
+
return context.lobby?.getConnectionState?.() ?? { status: 'connected', unreachable: false };
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function isChatAtBottom(list) {
|
|
11
|
+
return list.scrollHeight - list.scrollTop - list.clientHeight <= 24;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function getChatAlertMuteUntil() {
|
|
15
|
+
try {
|
|
16
|
+
const mutedUntil = Number(globalThis.localStorage?.getItem(CHAT_ALERT_MUTE_STORAGE_KEY));
|
|
17
|
+
if (!Number.isFinite(mutedUntil) || mutedUntil <= Date.now()) {
|
|
18
|
+
globalThis.localStorage?.removeItem(CHAT_ALERT_MUTE_STORAGE_KEY);
|
|
19
|
+
return 0;
|
|
20
|
+
}
|
|
21
|
+
return mutedUntil;
|
|
22
|
+
} catch {
|
|
23
|
+
return 0;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function setChatAlertMuteUntil(mutedUntil) {
|
|
28
|
+
try {
|
|
29
|
+
if (mutedUntil > 0) {
|
|
30
|
+
globalThis.localStorage?.setItem(CHAT_ALERT_MUTE_STORAGE_KEY, String(mutedUntil));
|
|
31
|
+
} else {
|
|
32
|
+
globalThis.localStorage?.removeItem(CHAT_ALERT_MUTE_STORAGE_KEY);
|
|
33
|
+
}
|
|
34
|
+
} catch {
|
|
35
|
+
// Local storage can be unavailable in private or restricted browsing modes.
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
1
39
|
export const chatFeature = {
|
|
2
40
|
updateChatMessages(messages, { initial = false } = {}) {
|
|
3
41
|
const previousIds = new Set(this.chatMessageIds);
|
|
@@ -36,7 +74,7 @@ export const chatFeature = {
|
|
|
36
74
|
|
|
37
75
|
toggleChatPanel() {
|
|
38
76
|
if (this.chatIsOpen) {
|
|
39
|
-
this.closeChatPanel();
|
|
77
|
+
this.closeChatPanel({ restoreFocus: true });
|
|
40
78
|
return;
|
|
41
79
|
}
|
|
42
80
|
|
|
@@ -46,24 +84,27 @@ export const chatFeature = {
|
|
|
46
84
|
openChatPanel() {
|
|
47
85
|
this.chatIsOpen = true;
|
|
48
86
|
this.chatUnreadCount = 0;
|
|
49
|
-
this.renderChat();
|
|
87
|
+
this.renderChat({ stickToBottom: true });
|
|
50
88
|
requestAnimationFrame(() => {
|
|
51
89
|
this.elements.chatInput?.focus();
|
|
52
|
-
this.scrollChatToBottom();
|
|
53
90
|
});
|
|
54
91
|
},
|
|
55
92
|
|
|
56
|
-
closeChatPanel() {
|
|
93
|
+
closeChatPanel({ restoreFocus = false } = {}) {
|
|
57
94
|
if (!this.chatIsOpen) {
|
|
58
95
|
return;
|
|
59
96
|
}
|
|
60
97
|
|
|
61
98
|
this.chatIsOpen = false;
|
|
62
|
-
this.renderChat();
|
|
99
|
+
this.renderChat({ messagesChanged: false });
|
|
100
|
+
if (restoreFocus) {
|
|
101
|
+
requestAnimationFrame(() => this.elements.chatToggleButton?.focus());
|
|
102
|
+
}
|
|
63
103
|
},
|
|
64
104
|
|
|
65
105
|
handleChatSubmit() {
|
|
66
|
-
|
|
106
|
+
const connectionState = getChatConnectionState(this);
|
|
107
|
+
if (this.isTabActive === false || connectionState.status !== 'connected') {
|
|
67
108
|
return;
|
|
68
109
|
}
|
|
69
110
|
|
|
@@ -84,32 +125,41 @@ export const chatFeature = {
|
|
|
84
125
|
return;
|
|
85
126
|
}
|
|
86
127
|
|
|
87
|
-
this.renderChat();
|
|
128
|
+
this.renderChat({ stickToBottom: true });
|
|
88
129
|
},
|
|
89
130
|
|
|
90
|
-
renderChat() {
|
|
131
|
+
renderChat({ messagesChanged = true, stickToBottom = false } = {}) {
|
|
91
132
|
this.elements.chatContainer?.classList.toggle('is-open', this.chatIsOpen);
|
|
92
133
|
this.elements.chatPanel?.classList.toggle('hidden', !this.chatIsOpen);
|
|
93
134
|
|
|
94
135
|
this.syncChatToggleButton();
|
|
95
136
|
this.syncChatNotificationButton();
|
|
96
|
-
|
|
97
137
|
const list = this.elements.chatMessages;
|
|
98
138
|
const emptyState = this.elements.chatEmptyState;
|
|
139
|
+
const connectionState = getChatConnectionState(this);
|
|
140
|
+
const isConnected = connectionState.status === 'connected';
|
|
141
|
+
const canSend = isConnected && this.chatInitialSyncComplete && this.isTabActive !== false;
|
|
99
142
|
|
|
100
143
|
if (this.elements.chatStatus) {
|
|
101
|
-
this.elements.chatStatus.textContent = this.chatInitialSyncComplete
|
|
102
|
-
?
|
|
103
|
-
:
|
|
144
|
+
this.elements.chatStatus.textContent = !this.chatInitialSyncComplete
|
|
145
|
+
? 'Syncing...'
|
|
146
|
+
: !isConnected
|
|
147
|
+
? connectionState.unreachable ? 'Server unreachable' : 'Reconnecting...'
|
|
148
|
+
: `${this.globalUsers.length} online`;
|
|
104
149
|
}
|
|
105
150
|
|
|
106
|
-
|
|
107
|
-
|
|
151
|
+
const sendButton = this.elements.chatForm?.querySelector?.('button[type="submit"]');
|
|
152
|
+
if (sendButton) {
|
|
153
|
+
sendButton.disabled = !canSend;
|
|
154
|
+
sendButton.title = canSend ? 'Send message' : 'Chat is unavailable while disconnected';
|
|
108
155
|
}
|
|
109
156
|
|
|
110
|
-
list.
|
|
157
|
+
if (!list || !this.chatIsOpen || !messagesChanged) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
111
160
|
|
|
112
161
|
if (this.chatMessages.length === 0) {
|
|
162
|
+
list.replaceChildren();
|
|
113
163
|
emptyState?.classList.remove('hidden');
|
|
114
164
|
list.classList.add('hidden');
|
|
115
165
|
return;
|
|
@@ -118,13 +168,14 @@ export const chatFeature = {
|
|
|
118
168
|
emptyState?.classList.add('hidden');
|
|
119
169
|
list.classList.remove('hidden');
|
|
120
170
|
|
|
171
|
+
const wasAtBottom = stickToBottom || isChatAtBottom(list);
|
|
121
172
|
const fragment = document.createDocumentFragment();
|
|
122
173
|
this.chatMessages.forEach((message) => {
|
|
123
174
|
fragment.appendChild(this.createChatMessageElement(message));
|
|
124
175
|
});
|
|
125
|
-
list.
|
|
176
|
+
list.replaceChildren(fragment);
|
|
126
177
|
|
|
127
|
-
if (
|
|
178
|
+
if (wasAtBottom) {
|
|
128
179
|
this.scrollChatToBottom();
|
|
129
180
|
}
|
|
130
181
|
},
|
|
@@ -137,7 +188,9 @@ export const chatFeature = {
|
|
|
137
188
|
|
|
138
189
|
const avatar = document.createElement('div');
|
|
139
190
|
avatar.className = 'chat-message-avatar';
|
|
140
|
-
|
|
191
|
+
const avatarColor = message.userColor || 'var(--color-primary-active)';
|
|
192
|
+
avatar.style.backgroundColor = avatarColor;
|
|
193
|
+
avatar.style.color = getUserAvatarTextColor(message.userColor);
|
|
141
194
|
avatar.textContent = (message.userName || '?').charAt(0).toUpperCase();
|
|
142
195
|
avatar.setAttribute('aria-hidden', 'true');
|
|
143
196
|
|
|
@@ -160,7 +213,7 @@ export const chatFeature = {
|
|
|
160
213
|
const fileLabel = this.getChatMessageFileLabel(message.filePath);
|
|
161
214
|
if (fileLabel) {
|
|
162
215
|
const file = document.createElement('span');
|
|
163
|
-
file.className = 'chat-message-file';
|
|
216
|
+
file.className = 'ui-pill chat-message-file';
|
|
164
217
|
file.textContent = fileLabel;
|
|
165
218
|
meta.append(file);
|
|
166
219
|
}
|
|
@@ -212,6 +265,92 @@ export const chatFeature = {
|
|
|
212
265
|
return `${sender}: ${compactText}`;
|
|
213
266
|
},
|
|
214
267
|
|
|
268
|
+
syncChatNotificationButton() {
|
|
269
|
+
const button = this.elements.chatNotificationButton;
|
|
270
|
+
const muteButton = this.elements.chatNotificationMuteButton;
|
|
271
|
+
const status = this.elements.chatNotificationStatus;
|
|
272
|
+
if (!button && !muteButton && !status) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const permission = this.notifications?.getPermission?.() ?? 'unsupported';
|
|
277
|
+
const enabled = permission === 'granted';
|
|
278
|
+
const muted = enabled && getChatAlertMuteUntil() > 0;
|
|
279
|
+
const blocked = permission === 'denied';
|
|
280
|
+
const statusState = enabled
|
|
281
|
+
? {
|
|
282
|
+
text: muted ? 'Alerts muted' : 'Alerts on',
|
|
283
|
+
label: muted ? 'Desktop alerts muted' : 'Desktop alerts enabled',
|
|
284
|
+
}
|
|
285
|
+
: blocked
|
|
286
|
+
? {
|
|
287
|
+
text: 'Alerts blocked',
|
|
288
|
+
label: 'Desktop alerts blocked in browser settings',
|
|
289
|
+
}
|
|
290
|
+
: null;
|
|
291
|
+
|
|
292
|
+
if (button) {
|
|
293
|
+
button.classList.toggle('hidden', permission !== 'default');
|
|
294
|
+
button.disabled = false;
|
|
295
|
+
button.removeAttribute('aria-pressed');
|
|
296
|
+
button.textContent = 'Enable desktop alerts';
|
|
297
|
+
button.title = 'Allow desktop alerts for new chat messages';
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (muteButton) {
|
|
301
|
+
muteButton.classList.toggle('hidden', !enabled);
|
|
302
|
+
muteButton.disabled = false;
|
|
303
|
+
muteButton.setAttribute('aria-pressed', String(muted));
|
|
304
|
+
const muteLabel = muted ? 'Unmute desktop alerts' : 'Mute desktop alerts for one hour';
|
|
305
|
+
muteButton.setAttribute('aria-label', muteLabel);
|
|
306
|
+
muteButton.title = muteLabel;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (status) {
|
|
310
|
+
status.classList.toggle('hidden', !statusState);
|
|
311
|
+
status.textContent = statusState?.text ?? '';
|
|
312
|
+
if (statusState) {
|
|
313
|
+
status.setAttribute('aria-label', statusState.label);
|
|
314
|
+
status.title = statusState.label;
|
|
315
|
+
} else {
|
|
316
|
+
status.removeAttribute('aria-label');
|
|
317
|
+
status.removeAttribute('title');
|
|
318
|
+
}
|
|
319
|
+
status.classList.toggle('ui-status-badge--accent', enabled && !muted);
|
|
320
|
+
status.classList.toggle('ui-status-badge--muted', blocked || muted);
|
|
321
|
+
}
|
|
322
|
+
},
|
|
323
|
+
|
|
324
|
+
async handleChatNotificationToggle() {
|
|
325
|
+
const permission = await this.notifications?.requestPermission?.();
|
|
326
|
+
this.syncChatNotificationButton();
|
|
327
|
+
|
|
328
|
+
if (permission === 'granted') {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (permission === 'denied') {
|
|
333
|
+
(this.chatToastController ?? this.toastController)?.show(
|
|
334
|
+
'Desktop alerts are blocked. Allow them in browser site settings.',
|
|
335
|
+
{ duration: 5000, tone: 'warning' },
|
|
336
|
+
);
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (permission === 'unsupported') {
|
|
341
|
+
(this.chatToastController ?? this.toastController)?.show(
|
|
342
|
+
'This browser does not support desktop alerts.',
|
|
343
|
+
{ duration: 5000, tone: 'warning' },
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
},
|
|
347
|
+
|
|
348
|
+
toggleChatNotificationMute() {
|
|
349
|
+
const mutedUntil = getChatAlertMuteUntil();
|
|
350
|
+
setChatAlertMuteUntil(mutedUntil > 0 ? 0 : Date.now() + CHAT_ALERT_MUTE_DURATION_MS);
|
|
351
|
+
this.renderChat({ messagesChanged: false });
|
|
352
|
+
},
|
|
353
|
+
|
|
215
354
|
syncChatToggleButton() {
|
|
216
355
|
const button = this.elements.chatToggleButton;
|
|
217
356
|
const badge = this.elements.chatToggleBadge;
|
|
@@ -224,6 +363,7 @@ export const chatFeature = {
|
|
|
224
363
|
|
|
225
364
|
button.classList.toggle('is-active', this.chatIsOpen);
|
|
226
365
|
button.classList.toggle('is-unread', shouldEmphasizeUnread);
|
|
366
|
+
button.setAttribute('aria-controls', 'chatPanel');
|
|
227
367
|
button.setAttribute('aria-expanded', String(this.chatIsOpen));
|
|
228
368
|
button.setAttribute(
|
|
229
369
|
'aria-label',
|
|
@@ -241,123 +381,39 @@ export const chatFeature = {
|
|
|
241
381
|
badge.textContent = this.chatUnreadCount > 9 ? '9+' : String(this.chatUnreadCount);
|
|
242
382
|
},
|
|
243
383
|
|
|
244
|
-
syncChatNotificationButton() {
|
|
245
|
-
const button = this.elements.chatNotificationButton;
|
|
246
|
-
if (!button) {
|
|
247
|
-
return;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
const permission = this.notifications.getPermission();
|
|
251
|
-
this.chatNotificationPermission = permission;
|
|
252
|
-
|
|
253
|
-
let label = 'Enable alerts';
|
|
254
|
-
let title = 'Enable browser notifications for new chat messages';
|
|
255
|
-
let pressed = false;
|
|
256
|
-
|
|
257
|
-
if (permission === 'unsupported') {
|
|
258
|
-
label = 'Alerts unavailable';
|
|
259
|
-
title = 'Browser notifications are unavailable here';
|
|
260
|
-
} else if (permission === 'denied') {
|
|
261
|
-
label = 'Alerts off';
|
|
262
|
-
title = 'Browser notifications are blocked for this site';
|
|
263
|
-
} else if (permission === 'granted') {
|
|
264
|
-
pressed = this.chatNotificationsEnabled;
|
|
265
|
-
label = pressed ? 'Alerts on' : 'Alerts off';
|
|
266
|
-
title = pressed
|
|
267
|
-
? 'Disable browser notifications for chat'
|
|
268
|
-
: 'Enable browser notifications for chat';
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
button.textContent = label;
|
|
272
|
-
button.title = title;
|
|
273
|
-
button.setAttribute('aria-pressed', String(pressed));
|
|
274
|
-
button.classList.toggle('is-enabled', pressed);
|
|
275
|
-
button.classList.toggle('is-blocked', permission === 'denied');
|
|
276
|
-
},
|
|
277
|
-
|
|
278
|
-
async handleChatNotificationToggle() {
|
|
279
|
-
const permission = this.notifications.getPermission();
|
|
280
|
-
this.chatNotificationPermission = permission;
|
|
281
|
-
|
|
282
|
-
if (permission === 'unsupported') {
|
|
283
|
-
this.toastController.show('Browser notifications are unavailable here');
|
|
284
|
-
this.syncChatNotificationButton();
|
|
285
|
-
return;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
if (permission === 'denied') {
|
|
289
|
-
this.chatNotificationsEnabled = false;
|
|
290
|
-
this.preferences.setChatNotificationsEnabled(false);
|
|
291
|
-
this.toastController.show('Browser notifications are blocked for this site');
|
|
292
|
-
this.syncChatNotificationButton();
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
if (permission === 'default') {
|
|
297
|
-
const nextPermission = await this.notifications.requestPermission();
|
|
298
|
-
this.chatNotificationPermission = nextPermission;
|
|
299
|
-
|
|
300
|
-
if (nextPermission !== 'granted') {
|
|
301
|
-
this.chatNotificationsEnabled = false;
|
|
302
|
-
this.preferences.setChatNotificationsEnabled(false);
|
|
303
|
-
if (nextPermission === 'denied') {
|
|
304
|
-
this.toastController.show('Browser notifications were blocked');
|
|
305
|
-
}
|
|
306
|
-
this.syncChatNotificationButton();
|
|
307
|
-
return;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
this.chatNotificationsEnabled = true;
|
|
311
|
-
this.preferences.setChatNotificationsEnabled(true);
|
|
312
|
-
this.syncChatNotificationButton();
|
|
313
|
-
return;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
this.chatNotificationsEnabled = !this.chatNotificationsEnabled;
|
|
317
|
-
this.preferences.setChatNotificationsEnabled(this.chatNotificationsEnabled);
|
|
318
|
-
this.syncChatNotificationButton();
|
|
319
|
-
},
|
|
320
|
-
|
|
321
384
|
maybeNotifyChatMessage(message) {
|
|
322
385
|
if (!this.chatInitialSyncComplete) {
|
|
323
386
|
return;
|
|
324
387
|
}
|
|
325
388
|
|
|
326
|
-
if (
|
|
327
|
-
this.maybeShowBrowserChatNotification(message);
|
|
389
|
+
if (this.isTabActive === false || this.chatIsOpen || getChatAlertMuteUntil() > 0) {
|
|
328
390
|
return;
|
|
329
391
|
}
|
|
330
392
|
|
|
331
|
-
|
|
332
|
-
|
|
393
|
+
const pageHidden = Boolean(
|
|
394
|
+
globalThis.document?.hidden || globalThis.document?.visibilityState === 'hidden',
|
|
395
|
+
);
|
|
396
|
+
const pageFocused = typeof globalThis.document?.hasFocus === 'function'
|
|
397
|
+
? globalThis.document.hasFocus()
|
|
398
|
+
: !pageHidden;
|
|
399
|
+
let notification = null;
|
|
400
|
+
if (pageHidden || !pageFocused) {
|
|
401
|
+
notification = this.notifications?.show?.({
|
|
402
|
+
body: String(message?.text ?? '').replace(/\s+/g, ' ').trim(),
|
|
403
|
+
onClick: () => {
|
|
404
|
+
window.focus?.();
|
|
405
|
+
notification?.close?.();
|
|
406
|
+
this.openChatPanel();
|
|
407
|
+
},
|
|
408
|
+
tag: `collabmd-chat-${message?.id ?? 'message'}`,
|
|
409
|
+
title: `New message from ${message?.userName || 'Someone'}`,
|
|
410
|
+
});
|
|
333
411
|
}
|
|
334
412
|
|
|
335
|
-
|
|
336
|
-
},
|
|
337
|
-
|
|
338
|
-
maybeShowBrowserChatNotification(message) {
|
|
339
|
-
if (!this.chatNotificationsEnabled || this.notifications.getPermission() !== 'granted') {
|
|
413
|
+
if (notification) {
|
|
340
414
|
return;
|
|
341
415
|
}
|
|
342
416
|
|
|
343
|
-
|
|
344
|
-
const fileLabel = this.getChatMessageFileLabel(message.filePath);
|
|
345
|
-
const body = fileLabel ? `${fileLabel}: ${message.text}` : message.text;
|
|
346
|
-
const notification = this.notifications.createNotification({
|
|
347
|
-
body,
|
|
348
|
-
onClick: () => {
|
|
349
|
-
if (message.filePath) {
|
|
350
|
-
this.navigation.navigateToFile(message.filePath);
|
|
351
|
-
}
|
|
352
|
-
},
|
|
353
|
-
tag: `collabmd-chat-${message.id}`,
|
|
354
|
-
title,
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
if (notification) {
|
|
358
|
-
setTimeout(() => {
|
|
359
|
-
notification.close?.();
|
|
360
|
-
}, 6000);
|
|
361
|
-
}
|
|
417
|
+
(this.chatToastController ?? this.toastController).show(this.formatChatToastMessage(message), 4000);
|
|
362
418
|
},
|
|
363
419
|
};
|
|
@@ -148,7 +148,7 @@ export const gitFeature = {
|
|
|
148
148
|
|
|
149
149
|
syncMainChrome({ badgeLabel = '', mode, title = null } = {}) {
|
|
150
150
|
const isSpecialMode = mode === 'diff' || mode === 'history' || mode === 'history-preview';
|
|
151
|
-
this.elements.
|
|
151
|
+
this.elements.toolbarViewToggle?.classList.toggle('hidden', isSpecialMode);
|
|
152
152
|
this.elements.mobileViewToggle?.classList.toggle('hidden', isSpecialMode);
|
|
153
153
|
this.elements.userCount?.classList.toggle('hidden', isSpecialMode);
|
|
154
154
|
this.elements.toolbarDiffBadge?.classList.toggle('hidden', !badgeLabel);
|
|
@@ -164,7 +164,6 @@ export const gitFeature = {
|
|
|
164
164
|
},
|
|
165
165
|
|
|
166
166
|
async showGitDiff({ filePath = null, scope = 'all' } = {}) {
|
|
167
|
-
await this.ensureGitControllers?.();
|
|
168
167
|
this.clearStaticPreviewDocument?.();
|
|
169
168
|
this.gitPanel.setSelection(filePath ? { path: filePath, scope, source: 'workspace' } : {});
|
|
170
169
|
this.gitPanel.setMode('changes');
|
|
@@ -179,7 +178,6 @@ export const gitFeature = {
|
|
|
179
178
|
},
|
|
180
179
|
|
|
181
180
|
async showGitCommit({ hash, path = null, historyFilePath = null } = {}) {
|
|
182
|
-
await this.ensureGitControllers?.();
|
|
183
181
|
this.clearStaticPreviewDocument?.();
|
|
184
182
|
this.gitPanel.setMode('history');
|
|
185
183
|
this.gitPanel.setSelection(hash ? { commitHash: hash, path, source: 'commit' } : { source: 'commit' });
|
|
@@ -194,7 +192,6 @@ export const gitFeature = {
|
|
|
194
192
|
},
|
|
195
193
|
|
|
196
194
|
async showGitHistory() {
|
|
197
|
-
await this.ensureGitControllers?.();
|
|
198
195
|
this.clearStaticPreviewDocument?.();
|
|
199
196
|
this.gitPanel.setMode('history');
|
|
200
197
|
this.gitPanel.setSelection({ source: 'commit' });
|
|
@@ -207,7 +204,6 @@ export const gitFeature = {
|
|
|
207
204
|
},
|
|
208
205
|
|
|
209
206
|
async showGitFileHistory({ filePath = null } = {}) {
|
|
210
|
-
await this.ensureGitControllers?.();
|
|
211
207
|
if (!filePath) {
|
|
212
208
|
this.workspaceRouteController?.showEmptyState?.();
|
|
213
209
|
return;
|
|
@@ -240,7 +236,7 @@ export const gitFeature = {
|
|
|
240
236
|
...data,
|
|
241
237
|
currentFilePath: resolvedCurrentFilePath,
|
|
242
238
|
});
|
|
243
|
-
this.resetPreviewMode();
|
|
239
|
+
this.workspacePreviewController.resetPreviewMode();
|
|
244
240
|
this.elements.markdownToolbar?.classList.add('hidden');
|
|
245
241
|
this.elements.outlineToggle?.classList.toggle('hidden', data.fileKind !== 'markdown');
|
|
246
242
|
|
|
@@ -256,7 +252,7 @@ export const gitFeature = {
|
|
|
256
252
|
}
|
|
257
253
|
|
|
258
254
|
this.elements.outlineToggle?.classList.add('hidden');
|
|
259
|
-
this.renderTextFilePreview
|
|
255
|
+
this.workspacePreviewController.renderTextFilePreview({
|
|
260
256
|
content: data.content,
|
|
261
257
|
filePath: resolvedCurrentFilePath,
|
|
262
258
|
});
|
|
@@ -266,7 +262,7 @@ export const gitFeature = {
|
|
|
266
262
|
console.error('[git-preview] Failed to load file snapshot:', error);
|
|
267
263
|
this.clearStaticPreviewDocument?.();
|
|
268
264
|
this.toastController.show('Failed to load historical file preview');
|
|
269
|
-
this.renderTextFilePreview
|
|
265
|
+
this.workspacePreviewController.renderTextFilePreview({
|
|
270
266
|
content: 'Failed to load historical file preview.',
|
|
271
267
|
filePath: resolvedCurrentFilePath,
|
|
272
268
|
});
|
|
@@ -335,7 +331,6 @@ export const gitFeature = {
|
|
|
335
331
|
},
|
|
336
332
|
|
|
337
333
|
async refreshGitAfterAction({ filePath = null, preferredScope = null } = {}) {
|
|
338
|
-
await this.ensureGitControllers?.();
|
|
339
334
|
await this.gitPanel.refresh({ force: true });
|
|
340
335
|
|
|
341
336
|
const route = this.navigation.getHashRoute();
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { getUserAvatarTextColor } from '../../domain/room.js';
|
|
2
|
+
|
|
1
3
|
const MAX_INLINE_AVATARS = 5;
|
|
2
4
|
|
|
3
5
|
function compareUserNames(left, right) {
|
|
@@ -65,7 +67,7 @@ export const presenceFeature = {
|
|
|
65
67
|
this.globalUsers = users;
|
|
66
68
|
this.syncFollowedUser();
|
|
67
69
|
this.renderAvatars();
|
|
68
|
-
this.renderChat();
|
|
70
|
+
this.renderChat({ messagesChanged: false });
|
|
69
71
|
this.renderPresence();
|
|
70
72
|
this.syncCurrentUserName();
|
|
71
73
|
},
|
|
@@ -126,6 +128,7 @@ export const presenceFeature = {
|
|
|
126
128
|
avatar.className = 'user-avatar';
|
|
127
129
|
avatar.classList.toggle('is-local', user.isLocal);
|
|
128
130
|
avatar.style.backgroundColor = user.color;
|
|
131
|
+
avatar.style.color = getUserAvatarTextColor(user.color);
|
|
129
132
|
avatar.classList.toggle('is-following', user.clientId === this.followedUserClientId);
|
|
130
133
|
|
|
131
134
|
const initial = document.createElement('span');
|
|
@@ -181,8 +184,6 @@ export const presenceFeature = {
|
|
|
181
184
|
overflow.appendChild(overflowLabel);
|
|
182
185
|
avatars.appendChild(overflow);
|
|
183
186
|
}
|
|
184
|
-
|
|
185
|
-
this.renderPresencePanel();
|
|
186
187
|
},
|
|
187
188
|
|
|
188
189
|
openPresencePanel() {
|
|
@@ -196,7 +197,6 @@ export const presenceFeature = {
|
|
|
196
197
|
this.closeToolbarOverflowMenu?.();
|
|
197
198
|
this.renderAvatars();
|
|
198
199
|
this.renderPresence();
|
|
199
|
-
this.renderPresencePanel();
|
|
200
200
|
},
|
|
201
201
|
|
|
202
202
|
closePresencePanel() {
|
|
@@ -207,7 +207,6 @@ export const presenceFeature = {
|
|
|
207
207
|
this.presencePanelOpen = false;
|
|
208
208
|
this.renderAvatars();
|
|
209
209
|
this.renderPresence();
|
|
210
|
-
this.renderPresencePanel();
|
|
211
210
|
},
|
|
212
211
|
|
|
213
212
|
togglePresencePanel() {
|
|
@@ -227,6 +226,9 @@ export const presenceFeature = {
|
|
|
227
226
|
|
|
228
227
|
panel.classList.toggle('hidden', !this.presencePanelOpen);
|
|
229
228
|
panel.setAttribute('aria-hidden', String(!this.presencePanelOpen));
|
|
229
|
+
if (!this.presencePanelOpen) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
230
232
|
|
|
231
233
|
const orderedUsers = getPanelUsers(this.globalUsers, this.followedUserClientId);
|
|
232
234
|
const effectiveConnectionState = getConnectedPresenceState(this);
|
|
@@ -268,6 +270,7 @@ export const presenceFeature = {
|
|
|
268
270
|
const avatar = document.createElement('span');
|
|
269
271
|
avatar.className = 'presence-panel-user-avatar';
|
|
270
272
|
avatar.style.backgroundColor = user.color;
|
|
273
|
+
avatar.style.color = getUserAvatarTextColor(user.color);
|
|
271
274
|
avatar.textContent = user.name.charAt(0).toUpperCase();
|
|
272
275
|
avatar.setAttribute('aria-hidden', 'true');
|
|
273
276
|
|
|
@@ -346,10 +349,11 @@ export const presenceFeature = {
|
|
|
346
349
|
|
|
347
350
|
this.followedUserClientId = clientId;
|
|
348
351
|
this.followedCursorSignature = '';
|
|
349
|
-
this.
|
|
350
|
-
|
|
351
|
-
if (closePanel) {
|
|
352
|
+
if (closePanel && this.presencePanelOpen) {
|
|
352
353
|
this.closePresencePanel();
|
|
354
|
+
} else {
|
|
355
|
+
this.renderAvatars();
|
|
356
|
+
this.renderPresencePanel();
|
|
353
357
|
}
|
|
354
358
|
|
|
355
359
|
if (user.currentFile && user.currentFile !== this.currentFilePath) {
|
|
@@ -382,9 +386,11 @@ export const presenceFeature = {
|
|
|
382
386
|
}
|
|
383
387
|
this.followedUserClientId = null;
|
|
384
388
|
this.followedCursorSignature = '';
|
|
385
|
-
this.
|
|
386
|
-
if (closePanel) {
|
|
389
|
+
if (closePanel && this.presencePanelOpen) {
|
|
387
390
|
this.closePresencePanel();
|
|
391
|
+
} else {
|
|
392
|
+
this.renderAvatars();
|
|
393
|
+
this.renderPresencePanel();
|
|
388
394
|
}
|
|
389
395
|
},
|
|
390
396
|
|