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,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
createCommentOverviewThread,
|
|
3
|
+
formatAnchorLabel,
|
|
4
|
+
getLatestGroupMessage,
|
|
5
|
+
} from './comment-ui-shared.js';
|
|
2
6
|
|
|
3
7
|
/**
|
|
4
8
|
* @typedef {object} CommentUiRenderContext
|
|
@@ -61,18 +65,38 @@ function renderDrawer() {
|
|
|
61
65
|
}
|
|
62
66
|
|
|
63
67
|
this.commentsDrawer.classList.toggle('hidden', !this.supported || !this.drawerOpen);
|
|
68
|
+
if (!this.supported || !this.drawerOpen) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
64
72
|
this.commentsDrawerList.replaceChildren();
|
|
65
73
|
const groups = this.getThreadGroups();
|
|
66
74
|
this.commentsDrawerEmpty?.classList.toggle('hidden', groups.length > 0);
|
|
67
|
-
if (
|
|
75
|
+
if (groups.length === 0) {
|
|
68
76
|
return;
|
|
69
77
|
}
|
|
70
78
|
|
|
71
79
|
const fragment = document.createDocumentFragment();
|
|
72
80
|
groups.forEach((group) => {
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
81
|
+
const latestMessage = getLatestGroupMessage(group);
|
|
82
|
+
const messageCount = group.threads.reduce(
|
|
83
|
+
(count, thread) => count + (Array.isArray(thread.messages) ? thread.messages.length : 0),
|
|
84
|
+
0,
|
|
85
|
+
);
|
|
86
|
+
const button = createCommentOverviewThread({
|
|
87
|
+
authorName: latestMessage?.userName || group.threads[0]?.createdByName || 'Anonymous',
|
|
88
|
+
buttonClassName: 'comment-overview-thread comments-drawer-item',
|
|
89
|
+
footerClassName: 'comment-overview-thread-footer comments-drawer-item-footer',
|
|
90
|
+
headerClassName: 'comment-overview-thread-header comments-drawer-item-header',
|
|
91
|
+
lineClassName: 'comment-overview-thread-line comments-drawer-item-title',
|
|
92
|
+
lineLabel: formatAnchorLabel(group.anchor),
|
|
93
|
+
messageCount,
|
|
94
|
+
previewBody: latestMessage?.body || '',
|
|
95
|
+
previewClassName: 'comment-markdown comment-overview-thread-preview comments-drawer-item-preview',
|
|
96
|
+
quote: group.anchor?.quote || group.anchor?.excerpt || 'Source anchored comment',
|
|
97
|
+
quoteClassName: 'comment-overview-thread-quote comments-drawer-item-quote',
|
|
98
|
+
timestamp: latestMessage ? this.formatTimestamp(latestMessage.createdAt) : '',
|
|
99
|
+
});
|
|
76
100
|
button.classList.toggle('is-active', this.activeCard?.groupKey === group.key);
|
|
77
101
|
button.addEventListener('pointerdown', (event) => {
|
|
78
102
|
event.preventDefault();
|
|
@@ -81,48 +105,11 @@ function renderDrawer() {
|
|
|
81
105
|
this.onNavigateToLine?.(group.anchor?.startLine ?? 1);
|
|
82
106
|
this.openThreadGroup(group, {
|
|
83
107
|
anchor: group.anchor,
|
|
84
|
-
origin: '
|
|
85
|
-
sourceRect:
|
|
108
|
+
origin: 'editor',
|
|
109
|
+
sourceRect: this.session?.getCommentAnchorClientRect?.(group.anchor)
|
|
110
|
+
?? button.getBoundingClientRect(),
|
|
86
111
|
});
|
|
87
112
|
});
|
|
88
|
-
|
|
89
|
-
const header = document.createElement('div');
|
|
90
|
-
header.className = 'ui-record-header comments-drawer-item-header';
|
|
91
|
-
|
|
92
|
-
const title = document.createElement('span');
|
|
93
|
-
title.className = 'comments-drawer-item-title';
|
|
94
|
-
title.textContent = formatAnchorLabel(group.anchor);
|
|
95
|
-
|
|
96
|
-
const count = document.createElement('span');
|
|
97
|
-
count.className = 'ui-pill-badge ui-pill-badge--count ui-pill-badge--accent comments-drawer-item-count';
|
|
98
|
-
count.textContent = String(group.threads.length);
|
|
99
|
-
|
|
100
|
-
header.append(title, count);
|
|
101
|
-
|
|
102
|
-
const quote = document.createElement('p');
|
|
103
|
-
quote.className = 'comments-drawer-item-quote';
|
|
104
|
-
quote.textContent = group.anchor.quote || group.anchor.excerpt || 'Source anchored comment';
|
|
105
|
-
|
|
106
|
-
const latestMessage = getLatestGroupMessage(group);
|
|
107
|
-
const preview = createRenderedCommentBody(
|
|
108
|
-
latestMessage?.body || '',
|
|
109
|
-
'comment-markdown comments-drawer-item-preview',
|
|
110
|
-
);
|
|
111
|
-
|
|
112
|
-
const footer = document.createElement('div');
|
|
113
|
-
footer.className = 'ui-record-meta comments-drawer-item-footer';
|
|
114
|
-
const countLabel = document.createElement('span');
|
|
115
|
-
countLabel.textContent = `${group.threads.length} thread${group.threads.length === 1 ? '' : 's'}`;
|
|
116
|
-
|
|
117
|
-
const updatedLabel = document.createElement('span');
|
|
118
|
-
updatedLabel.className = 'comments-drawer-item-updated';
|
|
119
|
-
updatedLabel.textContent = latestMessage
|
|
120
|
-
? `${latestMessage.userName} • ${this.formatTimestamp(latestMessage.createdAt)}`
|
|
121
|
-
: '';
|
|
122
|
-
|
|
123
|
-
footer.append(countLabel, updatedLabel);
|
|
124
|
-
|
|
125
|
-
button.append(header, quote, preview, footer);
|
|
126
113
|
fragment.appendChild(button);
|
|
127
114
|
});
|
|
128
115
|
|
|
@@ -13,10 +13,6 @@ export const COMMENT_CARD_WIDTH = 520;
|
|
|
13
13
|
export const COMMENT_SELECTION_REVEAL_DELAY_MS = 150;
|
|
14
14
|
export const COMMENT_SELECTION_CHIP_GAP = 12;
|
|
15
15
|
export const COMMENT_CONTROL_SLOT_HEIGHT = 36;
|
|
16
|
-
export const COMMENT_PREVIEW_BADGE_MIN_WIDTH = 28;
|
|
17
|
-
export const COMMENT_PREVIEW_RAIL_SLOT_HEIGHT = 30;
|
|
18
|
-
export const COMMENT_PREVIEW_RAIL_MIN_WIDTH = 400;
|
|
19
|
-
export const COMMENT_PREVIEW_RAIL_BREAKPOINT = 769;
|
|
20
16
|
export const COMMENT_REACTION_PRESET_EMOJIS = Object.freeze(['👍', '❤️', '🎉', '👀', '🚀']);
|
|
21
17
|
export const COMMENT_REACTION_MORE_EMOJIS = Object.freeze(['😂', '🔥', '✅', '🙏', '💡', '🤔', '👏', '😄', '🎯', '🙌']);
|
|
22
18
|
|
|
@@ -55,19 +51,15 @@ export function areAnchorsEqual(left, right) {
|
|
|
55
51
|
}
|
|
56
52
|
|
|
57
53
|
export function formatAnchorLabel(anchor) {
|
|
58
|
-
|
|
54
|
+
const startLine = Number(anchor?.startLine || 0);
|
|
55
|
+
const endLine = Number(anchor?.endLine || startLine);
|
|
56
|
+
if (!Number.isFinite(startLine) || startLine <= 0) {
|
|
59
57
|
return 'No source anchor';
|
|
60
58
|
}
|
|
61
59
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
: `Lines ${anchor.startLine}-${anchor.endLine}`;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return anchor.startLine === anchor.endLine
|
|
69
|
-
? `Line ${anchor.startLine}`
|
|
70
|
-
: `Lines ${anchor.startLine}-${anchor.endLine}`;
|
|
60
|
+
return startLine === endLine
|
|
61
|
+
? `Line ${startLine}`
|
|
62
|
+
: `Lines ${startLine}-${endLine}`;
|
|
71
63
|
}
|
|
72
64
|
|
|
73
65
|
export function getAnchorGroupKey(anchor = {}) {
|
|
@@ -120,6 +112,65 @@ export function createRenderedCommentBody(body, className = 'comment-markdown')
|
|
|
120
112
|
return container;
|
|
121
113
|
}
|
|
122
114
|
|
|
115
|
+
export function createCommentOverviewThread({
|
|
116
|
+
authorName = 'Anonymous',
|
|
117
|
+
buttonClassName = 'comment-overview-thread',
|
|
118
|
+
footerClassName = 'comment-overview-thread-footer',
|
|
119
|
+
headerClassName = 'comment-overview-thread-header',
|
|
120
|
+
lineClassName = 'comment-overview-thread-line',
|
|
121
|
+
lineLabel = '',
|
|
122
|
+
messageCount = 0,
|
|
123
|
+
previewBody = '',
|
|
124
|
+
previewClassName = 'comment-markdown comment-overview-thread-preview',
|
|
125
|
+
quote = 'Source anchored comment',
|
|
126
|
+
quoteClassName = 'comment-overview-thread-quote',
|
|
127
|
+
timestamp = '',
|
|
128
|
+
} = {}) {
|
|
129
|
+
const button = document.createElement('button');
|
|
130
|
+
button.type = 'button';
|
|
131
|
+
button.className = buttonClassName;
|
|
132
|
+
|
|
133
|
+
const header = document.createElement('div');
|
|
134
|
+
header.className = headerClassName;
|
|
135
|
+
|
|
136
|
+
const line = document.createElement('span');
|
|
137
|
+
line.className = lineClassName;
|
|
138
|
+
line.textContent = lineLabel;
|
|
139
|
+
|
|
140
|
+
const meta = document.createElement('span');
|
|
141
|
+
meta.className = 'comment-overview-thread-meta';
|
|
142
|
+
meta.textContent = timestamp;
|
|
143
|
+
header.append(line, meta);
|
|
144
|
+
|
|
145
|
+
const body = document.createElement('div');
|
|
146
|
+
body.className = 'comment-overview-thread-body';
|
|
147
|
+
const preview = createRenderedCommentBody(previewBody, previewClassName);
|
|
148
|
+
|
|
149
|
+
const quoteSection = document.createElement('div');
|
|
150
|
+
quoteSection.className = 'comment-overview-thread-section comment-overview-thread-reference';
|
|
151
|
+
const quoteLabel = document.createElement('span');
|
|
152
|
+
quoteLabel.className = 'comment-overview-thread-section-label';
|
|
153
|
+
quoteLabel.textContent = 'Reference';
|
|
154
|
+
const quoteNode = document.createElement('span');
|
|
155
|
+
quoteNode.className = quoteClassName;
|
|
156
|
+
quoteNode.textContent = quote;
|
|
157
|
+
quoteSection.append(quoteLabel, quoteNode);
|
|
158
|
+
body.append(preview, quoteSection);
|
|
159
|
+
|
|
160
|
+
const footer = document.createElement('div');
|
|
161
|
+
footer.className = footerClassName;
|
|
162
|
+
const author = document.createElement('span');
|
|
163
|
+
author.className = 'comment-overview-thread-author';
|
|
164
|
+
author.textContent = authorName;
|
|
165
|
+
const messages = document.createElement('span');
|
|
166
|
+
messages.className = 'comment-overview-thread-message-count';
|
|
167
|
+
messages.textContent = `${messageCount} message${messageCount === 1 ? '' : 's'}`;
|
|
168
|
+
footer.append(author, messages);
|
|
169
|
+
|
|
170
|
+
button.append(header, body, footer);
|
|
171
|
+
return button;
|
|
172
|
+
}
|
|
173
|
+
|
|
123
174
|
export function hasLocalReaction(reaction, localUserId) {
|
|
124
175
|
return Boolean(localUserId && reaction?.users?.some((user) => user?.userId === localUserId));
|
|
125
176
|
}
|
|
@@ -89,9 +89,13 @@ export class CommentUiController {
|
|
|
89
89
|
if (this.activeCard?.mode === 'create' && this.activeCard.origin === 'preview') {
|
|
90
90
|
return;
|
|
91
91
|
}
|
|
92
|
-
|
|
92
|
+
const previewSelection = this.supported && this.fileKind === 'markdown'
|
|
93
93
|
? createPreviewSelection(window.getSelection(), this.previewElement, this.session?.getText?.() ?? '')
|
|
94
94
|
: null;
|
|
95
|
+
if (!previewSelection && !this.previewSelection) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
this.previewSelection = previewSelection;
|
|
95
99
|
this.scheduleLayoutRefresh();
|
|
96
100
|
};
|
|
97
101
|
this.handleCommentSelectionButtonPointerDown = (event) => {
|
|
@@ -57,7 +57,9 @@ export class FileActionController {
|
|
|
57
57
|
: { matches: false },
|
|
58
58
|
onFileDelete,
|
|
59
59
|
onFileSelect,
|
|
60
|
+
onShowFileExtensionsChange,
|
|
60
61
|
pendingWorkspaceRequestIds = null,
|
|
62
|
+
showFileExtensions = false,
|
|
61
63
|
state,
|
|
62
64
|
toastController,
|
|
63
65
|
vaultClient,
|
|
@@ -66,13 +68,16 @@ export class FileActionController {
|
|
|
66
68
|
}) {
|
|
67
69
|
this.onFileDelete = onFileDelete;
|
|
68
70
|
this.onFileSelect = onFileSelect;
|
|
71
|
+
this.onShowFileExtensionsChange = onShowFileExtensionsChange;
|
|
69
72
|
this.pendingWorkspaceRequestIds = pendingWorkspaceRequestIds;
|
|
73
|
+
this.showFileExtensions = Boolean(showFileExtensions);
|
|
70
74
|
this.state = state;
|
|
71
75
|
this.toastController = toastController;
|
|
72
76
|
this.vaultClient = vaultClient;
|
|
73
77
|
this.view = view;
|
|
74
78
|
this.refresh = refresh;
|
|
75
79
|
this.createButton = document.getElementById('sidebarCreateBtn');
|
|
80
|
+
this.fileExplorerOptionsButton = document.getElementById('fileExplorerOptionsBtn');
|
|
76
81
|
this.actionDialog = document.getElementById('fileActionDialog');
|
|
77
82
|
this.actionForm = document.getElementById('fileActionForm');
|
|
78
83
|
this.actionTitle = document.getElementById('fileActionTitle');
|
|
@@ -87,6 +92,9 @@ export class FileActionController {
|
|
|
87
92
|
this.createMenu = new CreateMenuPresenter({
|
|
88
93
|
mobileBreakpointQuery,
|
|
89
94
|
});
|
|
95
|
+
this.fileExplorerOptionsMenu = new CreateMenuPresenter({
|
|
96
|
+
mobileBreakpointQuery,
|
|
97
|
+
});
|
|
90
98
|
this.pendingAction = null;
|
|
91
99
|
this.actionBusy = false;
|
|
92
100
|
}
|
|
@@ -95,6 +103,9 @@ export class FileActionController {
|
|
|
95
103
|
this.createButton?.addEventListener('click', () => {
|
|
96
104
|
this.openRootCreateMenu({ anchor: this.createButton });
|
|
97
105
|
});
|
|
106
|
+
this.fileExplorerOptionsButton?.addEventListener('click', () => {
|
|
107
|
+
this.openFileExplorerOptionsMenu({ anchor: this.fileExplorerOptionsButton });
|
|
108
|
+
});
|
|
98
109
|
this.actionCancelButton?.addEventListener('click', () => this.closeActionDialog());
|
|
99
110
|
this.actionForm?.addEventListener('submit', (event) => {
|
|
100
111
|
event.preventDefault();
|
|
@@ -313,6 +324,7 @@ export class FileActionController {
|
|
|
313
324
|
|
|
314
325
|
this.pendingAction = { requiresInput, emptyMessage, onSubmit };
|
|
315
326
|
this.createMenu.close({ restoreFocus: false });
|
|
327
|
+
this.fileExplorerOptionsMenu.close({ restoreFocus: false });
|
|
316
328
|
this.view?.removeContextMenu?.();
|
|
317
329
|
|
|
318
330
|
this.actionTitle.textContent = title;
|
|
@@ -716,6 +728,7 @@ export class FileActionController {
|
|
|
716
728
|
|
|
717
729
|
openCreateMenu({ anchor = this.createButton, parentDir = '' } = {}) {
|
|
718
730
|
this.view?.removeContextMenu?.();
|
|
731
|
+
this.fileExplorerOptionsMenu.close({ restoreFocus: false });
|
|
719
732
|
this.createMenu.toggle({
|
|
720
733
|
anchor,
|
|
721
734
|
items: this.getCreateActions({ parentDir }),
|
|
@@ -723,6 +736,25 @@ export class FileActionController {
|
|
|
723
736
|
});
|
|
724
737
|
}
|
|
725
738
|
|
|
739
|
+
openFileExplorerOptionsMenu({ anchor = this.fileExplorerOptionsButton } = {}) {
|
|
740
|
+
this.view?.removeContextMenu?.();
|
|
741
|
+
this.createMenu.close({ restoreFocus: false });
|
|
742
|
+
this.fileExplorerOptionsMenu.toggle({
|
|
743
|
+
anchor,
|
|
744
|
+
items: [{
|
|
745
|
+
icon: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12s3.5-6 10-6 10 6 10 6-3.5 6-10 6S2 12 2 12Z"></path><circle cx="12" cy="12" r="2.5"></circle></svg>',
|
|
746
|
+
id: 'show-file-extensions',
|
|
747
|
+
label: 'Show file extensions',
|
|
748
|
+
meta: this.showFileExtensions ? 'On' : 'Off',
|
|
749
|
+
onSelect: () => {
|
|
750
|
+
this.showFileExtensions = !this.showFileExtensions;
|
|
751
|
+
this.onShowFileExtensionsChange?.(this.showFileExtensions);
|
|
752
|
+
},
|
|
753
|
+
}],
|
|
754
|
+
title: 'File explorer options',
|
|
755
|
+
});
|
|
756
|
+
}
|
|
757
|
+
|
|
726
758
|
handleNewFile({ parentDir = '' } = {}) {
|
|
727
759
|
const context = this.getCreateContext(parentDir);
|
|
728
760
|
|
|
@@ -9,14 +9,18 @@ export class FileExplorerController {
|
|
|
9
9
|
onFileSelect,
|
|
10
10
|
onFileDelete,
|
|
11
11
|
pendingWorkspaceRequestIds = null,
|
|
12
|
+
onShowFileExtensionsChange,
|
|
13
|
+
showFileExtensions = false,
|
|
12
14
|
toastController,
|
|
13
15
|
vaultClient,
|
|
14
16
|
}) {
|
|
15
17
|
this.onFileSelect = onFileSelect;
|
|
16
18
|
this.onFileDelete = onFileDelete;
|
|
19
|
+
this.onShowFileExtensionsChange = onShowFileExtensionsChange;
|
|
17
20
|
this.toastController = toastController;
|
|
18
21
|
this.vaultClient = vaultClient;
|
|
19
22
|
this.state = new FileTreeState();
|
|
23
|
+
this.showFileExtensions = Boolean(showFileExtensions);
|
|
20
24
|
this.threadCounts = new Map();
|
|
21
25
|
this.view = new FileExplorerView({
|
|
22
26
|
mobileBreakpointQuery,
|
|
@@ -27,7 +31,12 @@ export class FileExplorerController {
|
|
|
27
31
|
},
|
|
28
32
|
onDirectoryToggle: (pathValue) => {
|
|
29
33
|
this.state.toggleDirectory(pathValue);
|
|
30
|
-
this.
|
|
34
|
+
if (!this.view.rerenderDirectoryBranch(pathValue, this.state.tree, {
|
|
35
|
+
activeFilePath: this.state.activeFilePath,
|
|
36
|
+
expandedDirs: this.state.expandedDirs,
|
|
37
|
+
})) {
|
|
38
|
+
this.renderTree();
|
|
39
|
+
}
|
|
31
40
|
},
|
|
32
41
|
onEntryDrop: (payload) => this.actionController.moveEntryByDrop(payload),
|
|
33
42
|
onFileContextMenu: (event, payload) => {
|
|
@@ -54,7 +63,13 @@ export class FileExplorerController {
|
|
|
54
63
|
mobileBreakpointQuery,
|
|
55
64
|
onFileDelete: this.onFileDelete,
|
|
56
65
|
onFileSelect: this.onFileSelect,
|
|
66
|
+
onShowFileExtensionsChange: (enabled) => {
|
|
67
|
+
this.showFileExtensions = Boolean(enabled);
|
|
68
|
+
this.onShowFileExtensionsChange?.(this.showFileExtensions);
|
|
69
|
+
this.renderTree();
|
|
70
|
+
},
|
|
57
71
|
pendingWorkspaceRequestIds,
|
|
72
|
+
showFileExtensions: this.showFileExtensions,
|
|
58
73
|
refresh: () => this.refresh(),
|
|
59
74
|
state: this.state,
|
|
60
75
|
toastController: this.toastController,
|
|
@@ -86,15 +101,25 @@ export class FileExplorerController {
|
|
|
86
101
|
}
|
|
87
102
|
|
|
88
103
|
setActiveFile(filePath) {
|
|
104
|
+
const expandedDirectoryCount = this.state.expandedDirs.size;
|
|
89
105
|
this.state.setActiveFile(filePath);
|
|
90
|
-
this.
|
|
106
|
+
if (this.state.searchQuery) {
|
|
107
|
+
this.view.setActiveFile(filePath);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (
|
|
111
|
+
this.state.expandedDirs.size !== expandedDirectoryCount
|
|
112
|
+
|| !this.view.setActiveFile(filePath)
|
|
113
|
+
) {
|
|
114
|
+
this.renderTree();
|
|
115
|
+
}
|
|
91
116
|
}
|
|
92
117
|
|
|
93
118
|
setThreadCounts(threadCounts = new Map()) {
|
|
94
119
|
this.threadCounts = threadCounts instanceof Map
|
|
95
120
|
? new Map(threadCounts)
|
|
96
121
|
: new Map(Object.entries(threadCounts ?? {}));
|
|
97
|
-
this.
|
|
122
|
+
this.view.setThreadCounts(this.threadCounts);
|
|
98
123
|
}
|
|
99
124
|
|
|
100
125
|
revealFile(filePath, { clearSearch = false } = {}) {
|
|
@@ -111,6 +136,10 @@ export class FileExplorerController {
|
|
|
111
136
|
return this.state.flatFiles;
|
|
112
137
|
}
|
|
113
138
|
|
|
139
|
+
get fileEntries() {
|
|
140
|
+
return this.state.flatFileEntries;
|
|
141
|
+
}
|
|
142
|
+
|
|
114
143
|
get flatDocumentFiles() {
|
|
115
144
|
return this.state.flatFiles.filter((path) => !isImageAttachmentFilePath(path));
|
|
116
145
|
}
|
|
@@ -126,6 +155,7 @@ export class FileExplorerController {
|
|
|
126
155
|
reset,
|
|
127
156
|
searchMatches: this.state.getSearchMatches(),
|
|
128
157
|
searchQuery: this.state.searchQuery,
|
|
158
|
+
showFileExtensions: this.showFileExtensions,
|
|
129
159
|
threadCounts: this.threadCounts,
|
|
130
160
|
tree: this.state.tree,
|
|
131
161
|
});
|
|
@@ -52,6 +52,7 @@ export class FileExplorerView {
|
|
|
52
52
|
this.searchInput = document.getElementById('fileSearchInput');
|
|
53
53
|
this.renderedDirectoryWrappers = new Map();
|
|
54
54
|
this.renderedChildContainers = new Map();
|
|
55
|
+
this.renderedFileItems = new Map();
|
|
55
56
|
this.lastRenderMode = 'tree';
|
|
56
57
|
this.longPressTimer = 0;
|
|
57
58
|
this.longPressContext = null;
|
|
@@ -66,6 +67,7 @@ export class FileExplorerView {
|
|
|
66
67
|
this.autoExpandTargetPath = '';
|
|
67
68
|
this.rootDropZone = null;
|
|
68
69
|
this.threadCounts = new Map();
|
|
70
|
+
this.showFileExtensions = false;
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
initialize() {
|
|
@@ -120,17 +122,51 @@ export class FileExplorerView {
|
|
|
120
122
|
}
|
|
121
123
|
|
|
122
124
|
revealFile(filePath) {
|
|
123
|
-
|
|
124
|
-
.find((element) => element.dataset.path === filePath);
|
|
125
|
-
fileItem?.scrollIntoView({ block: 'nearest' });
|
|
125
|
+
this.renderedFileItems.get(filePath)?.scrollIntoView({ block: 'nearest' });
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
|
|
128
|
+
setActiveFile(filePath) {
|
|
129
|
+
this.treeContainer?.querySelector('.file-tree-file.active')?.classList.remove('active');
|
|
130
|
+
if (!filePath) {
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const fileItem = this.renderedFileItems.get(filePath);
|
|
135
|
+
fileItem?.classList.add('active');
|
|
136
|
+
return Boolean(fileItem);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
setThreadCounts(threadCounts) {
|
|
140
|
+
this.threadCounts = threadCounts instanceof Map ? threadCounts : new Map();
|
|
141
|
+
this.renderedFileItems.forEach((button, filePath) => {
|
|
142
|
+
const threadCount = Number(this.threadCounts.get(filePath) ?? 0) || 0;
|
|
143
|
+
let badge = button.querySelector('.file-tree-comment-count');
|
|
144
|
+
button.classList.toggle('has-comments', threadCount > 0);
|
|
145
|
+
|
|
146
|
+
if (threadCount <= 0) {
|
|
147
|
+
delete button.dataset.threadCount;
|
|
148
|
+
badge?.remove();
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
button.dataset.threadCount = String(threadCount);
|
|
153
|
+
if (!badge) {
|
|
154
|
+
badge = document.createElement('span');
|
|
155
|
+
badge.className = 'file-tree-comment-count';
|
|
156
|
+
button.appendChild(badge);
|
|
157
|
+
}
|
|
158
|
+
badge.textContent = String(threadCount);
|
|
159
|
+
badge.setAttribute('aria-label', `${threadCount} open comment thread${threadCount === 1 ? '' : 's'}`);
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
render({ activeFilePath, changedPaths = null, expandedDirs, reset = false, searchMatches, searchQuery, showFileExtensions = false, threadCounts = new Map(), tree }) {
|
|
129
164
|
if (!this.treeContainer) {
|
|
130
165
|
return;
|
|
131
166
|
}
|
|
132
167
|
|
|
133
168
|
this.threadCounts = threadCounts instanceof Map ? threadCounts : new Map();
|
|
169
|
+
this.showFileExtensions = Boolean(showFileExtensions);
|
|
134
170
|
this.currentSearchQuery = String(searchQuery ?? '');
|
|
135
171
|
if (this.currentSearchQuery) {
|
|
136
172
|
this.clearDragFeedback();
|
|
@@ -248,6 +284,7 @@ export class FileExplorerView {
|
|
|
248
284
|
resetTreeIndexes() {
|
|
249
285
|
this.renderedDirectoryWrappers.clear();
|
|
250
286
|
this.renderedChildContainers.clear();
|
|
287
|
+
this.renderedFileItems.clear();
|
|
251
288
|
}
|
|
252
289
|
|
|
253
290
|
renderNodes(nodes, container, { activeFilePath, depth, expandedDirs }) {
|
|
@@ -408,6 +445,11 @@ export class FileExplorerView {
|
|
|
408
445
|
this.renderedChildContainers.delete(pathValue);
|
|
409
446
|
}
|
|
410
447
|
});
|
|
448
|
+
Array.from(this.renderedFileItems.keys()).forEach((pathValue) => {
|
|
449
|
+
if (pathValue.startsWith(prefix)) {
|
|
450
|
+
this.renderedFileItems.delete(pathValue);
|
|
451
|
+
}
|
|
452
|
+
});
|
|
411
453
|
}
|
|
412
454
|
|
|
413
455
|
createFileItem({ activeFilePath, depth, filePath, fileType = 'file', name }) {
|
|
@@ -453,9 +495,10 @@ export class FileExplorerView {
|
|
|
453
495
|
if (threadCount > 0) {
|
|
454
496
|
button.dataset.threadCount = String(threadCount);
|
|
455
497
|
}
|
|
498
|
+
const displayName = this.showFileExtensions ? String(name ?? '') : stripVaultFileExtension(name);
|
|
456
499
|
button.innerHTML = `
|
|
457
500
|
${this.getFileIconSvg({ isBase, isDrawio, isExcalidraw, isImage, isMermaid, isPlantUml })}
|
|
458
|
-
<span class="file-tree-name">${escapeHtml(
|
|
501
|
+
<span class="file-tree-name">${escapeHtml(displayName)}</span>
|
|
459
502
|
${threadCount > 0 ? `<span class="file-tree-comment-count" aria-label="${threadCount} open comment thread${threadCount === 1 ? '' : 's'}">${threadCount}</span>` : ''}
|
|
460
503
|
`;
|
|
461
504
|
this.configureDragSource(button, { path: filePath, type: 'file' });
|
|
@@ -474,6 +517,7 @@ export class FileExplorerView {
|
|
|
474
517
|
this.onFileContextMenu?.(this.createLongPressEvent(button), { filePath, type: 'file' });
|
|
475
518
|
});
|
|
476
519
|
|
|
520
|
+
this.renderedFileItems.set(filePath, button);
|
|
477
521
|
return button;
|
|
478
522
|
}
|
|
479
523
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { normalizeVaultPathInput } from '../domain/vault-paths.js';
|
|
2
2
|
|
|
3
|
-
function flattenTree(nodes, files = [], searchEntries = []) {
|
|
3
|
+
function flattenTree(nodes, files = [], searchEntries = [], fileEntries = []) {
|
|
4
4
|
for (const node of nodes) {
|
|
5
5
|
if (!node?.path || !node?.type) {
|
|
6
6
|
continue;
|
|
@@ -23,15 +23,19 @@ function flattenTree(nodes, files = [], searchEntries = []) {
|
|
|
23
23
|
|| node.type === 'image'
|
|
24
24
|
) {
|
|
25
25
|
files.push(node.path);
|
|
26
|
+
fileEntries.push({
|
|
27
|
+
mtimeMs: node.mtimeMs,
|
|
28
|
+
path: node.path,
|
|
29
|
+
});
|
|
26
30
|
continue;
|
|
27
31
|
}
|
|
28
32
|
|
|
29
33
|
if (node.type === 'directory' && Array.isArray(node.children)) {
|
|
30
|
-
flattenTree(node.children, files, searchEntries);
|
|
34
|
+
flattenTree(node.children, files, searchEntries, fileEntries);
|
|
31
35
|
}
|
|
32
36
|
}
|
|
33
37
|
|
|
34
|
-
return { files, searchEntries };
|
|
38
|
+
return { fileEntries, files, searchEntries };
|
|
35
39
|
}
|
|
36
40
|
|
|
37
41
|
function findNodeByPath(nodes, pathValue) {
|
|
@@ -82,6 +86,7 @@ export class FileTreeState {
|
|
|
82
86
|
constructor() {
|
|
83
87
|
this.tree = [];
|
|
84
88
|
this.flatFiles = [];
|
|
89
|
+
this.flatFileEntries = [];
|
|
85
90
|
this.flatSearchEntries = [];
|
|
86
91
|
this.activeFilePath = null;
|
|
87
92
|
this.expandedDirs = new Set();
|
|
@@ -90,8 +95,9 @@ export class FileTreeState {
|
|
|
90
95
|
|
|
91
96
|
setTree(tree) {
|
|
92
97
|
this.tree = Array.isArray(tree) ? tree : [];
|
|
93
|
-
const flattened = flattenTree(this.tree, [], []);
|
|
98
|
+
const flattened = flattenTree(this.tree, [], [], []);
|
|
94
99
|
this.flatFiles = flattened.files;
|
|
100
|
+
this.flatFileEntries = flattened.fileEntries;
|
|
95
101
|
this.flatSearchEntries = flattened.searchEntries;
|
|
96
102
|
}
|
|
97
103
|
|