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
|
@@ -9,6 +9,10 @@ const COMMENT_CARD_WIDTH = 520;
|
|
|
9
9
|
const COMMENT_SELECTION_REVEAL_DELAY_MS = 150;
|
|
10
10
|
const COMMENT_SELECTION_CHIP_GAP = 12;
|
|
11
11
|
const COMMENT_CONTROL_SLOT_HEIGHT = 36;
|
|
12
|
+
const COMMENT_PREVIEW_BADGE_MIN_WIDTH = 28;
|
|
13
|
+
const COMMENT_PREVIEW_RAIL_SLOT_HEIGHT = 30;
|
|
14
|
+
const COMMENT_PREVIEW_RAIL_MIN_WIDTH = 400;
|
|
15
|
+
const COMMENT_PREVIEW_RAIL_BREAKPOINT = 769;
|
|
12
16
|
const COMMENT_REACTION_PRESET_EMOJIS = Object.freeze(['👍', '❤️', '🎉', '👀', '🚀']);
|
|
13
17
|
const COMMENT_REACTION_MORE_EMOJIS = Object.freeze(['😂', '🔥', '✅', '🙏', '💡', '🤔', '👏', '😄', '🎯', '🙌']);
|
|
14
18
|
|
|
@@ -248,6 +252,25 @@ function createRectFromRects(rects = []) {
|
|
|
248
252
|
};
|
|
249
253
|
}
|
|
250
254
|
|
|
255
|
+
function pointIntersectsRect(x, y, rect) {
|
|
256
|
+
if (!rect) {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return x >= rect.left
|
|
261
|
+
&& x <= rect.right
|
|
262
|
+
&& y >= rect.top
|
|
263
|
+
&& y <= rect.bottom;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function normalizeGroupKeys(keys = []) {
|
|
267
|
+
return [...new Set(keys.filter(Boolean))].sort();
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function serializeGroupKeys(keys = []) {
|
|
271
|
+
return normalizeGroupKeys(keys).join(' ');
|
|
272
|
+
}
|
|
273
|
+
|
|
251
274
|
function createCommentMarkerContent(count) {
|
|
252
275
|
const fragment = document.createDocumentFragment();
|
|
253
276
|
|
|
@@ -315,6 +338,12 @@ export class CommentUiController {
|
|
|
315
338
|
this.pointerSelecting = false;
|
|
316
339
|
this.session = null;
|
|
317
340
|
this.activeCard = null;
|
|
341
|
+
this.hoveredEditorGroupKeys = [];
|
|
342
|
+
this.hoveredEditorGroupKeysSignature = '';
|
|
343
|
+
this.hoveredPreviewGroupKeys = [];
|
|
344
|
+
this.hoveredPreviewGroupKeysSignature = '';
|
|
345
|
+
this.previewHoverRegions = [];
|
|
346
|
+
this.lastPreviewPointerPosition = null;
|
|
318
347
|
this.editorLayer = null;
|
|
319
348
|
this.previewLayer = null;
|
|
320
349
|
this.previewHighlightLayer = null;
|
|
@@ -331,6 +360,20 @@ export class CommentUiController {
|
|
|
331
360
|
this.handleEditorScroll = () => this.scheduleLayoutRefresh();
|
|
332
361
|
this.handlePreviewScroll = () => this.scheduleLayoutRefresh();
|
|
333
362
|
this.handleWindowResize = () => this.scheduleLayoutRefresh();
|
|
363
|
+
this.handlePreviewPointerMove = (event) => {
|
|
364
|
+
this.lastPreviewPointerPosition = { x: event.clientX, y: event.clientY };
|
|
365
|
+
this.updateHoveredPreviewGroups(this.getPreviewGroupKeysAtPoint(event.clientX, event.clientY));
|
|
366
|
+
};
|
|
367
|
+
this.handlePreviewPointerLeave = () => {
|
|
368
|
+
this.lastPreviewPointerPosition = null;
|
|
369
|
+
this.updateHoveredPreviewGroups([]);
|
|
370
|
+
};
|
|
371
|
+
this.handlePreviewFocusIn = (event) => {
|
|
372
|
+
this.updateHoveredPreviewGroups(this.getPreviewGroupKeysForTarget(event.target));
|
|
373
|
+
};
|
|
374
|
+
this.handlePreviewFocusOut = (event) => {
|
|
375
|
+
this.updateHoveredPreviewGroups(this.getPreviewGroupKeysForTarget(event.relatedTarget));
|
|
376
|
+
};
|
|
334
377
|
this.handleCommentSelectionButtonPointerDown = (event) => {
|
|
335
378
|
event.preventDefault();
|
|
336
379
|
};
|
|
@@ -436,6 +479,10 @@ export class CommentUiController {
|
|
|
436
479
|
this.setDrawerOpen(!this.drawerOpen);
|
|
437
480
|
});
|
|
438
481
|
this.previewContainer?.addEventListener('scroll', this.handlePreviewScroll, { passive: true });
|
|
482
|
+
this.previewElement?.addEventListener('pointermove', this.handlePreviewPointerMove, { passive: true });
|
|
483
|
+
this.previewElement?.addEventListener('pointerleave', this.handlePreviewPointerLeave);
|
|
484
|
+
this.previewElement?.addEventListener('focusin', this.handlePreviewFocusIn);
|
|
485
|
+
this.previewElement?.addEventListener('focusout', this.handlePreviewFocusOut);
|
|
439
486
|
this.editorContainer?.addEventListener('pointerdown', this.handleEditorPointerDown);
|
|
440
487
|
this.editorContainer?.addEventListener('focusout', this.handleEditorFocusOut);
|
|
441
488
|
window.addEventListener('resize', this.handleWindowResize);
|
|
@@ -452,6 +499,10 @@ export class CommentUiController {
|
|
|
452
499
|
}
|
|
453
500
|
this.attachSession(null);
|
|
454
501
|
this.previewContainer?.removeEventListener('scroll', this.handlePreviewScroll);
|
|
502
|
+
this.previewElement?.removeEventListener('pointermove', this.handlePreviewPointerMove);
|
|
503
|
+
this.previewElement?.removeEventListener('pointerleave', this.handlePreviewPointerLeave);
|
|
504
|
+
this.previewElement?.removeEventListener('focusin', this.handlePreviewFocusIn);
|
|
505
|
+
this.previewElement?.removeEventListener('focusout', this.handlePreviewFocusOut);
|
|
455
506
|
this.commentSelectionButton?.removeEventListener('pointerdown', this.handleCommentSelectionButtonPointerDown);
|
|
456
507
|
this.editorContainer?.removeEventListener('pointerdown', this.handleEditorPointerDown);
|
|
457
508
|
this.editorContainer?.removeEventListener('focusout', this.handleEditorFocusOut);
|
|
@@ -460,6 +511,7 @@ export class CommentUiController {
|
|
|
460
511
|
document.removeEventListener('pointercancel', this.handleDocumentPointerUp);
|
|
461
512
|
document.removeEventListener('pointerdown', this.handleDocumentPointerDown);
|
|
462
513
|
document.removeEventListener('keydown', this.handleDocumentKeyDown);
|
|
514
|
+
this.previewHoverRegions = [];
|
|
463
515
|
this.cardRoot?.remove();
|
|
464
516
|
this.editorLayer?.remove();
|
|
465
517
|
this.previewLayer?.remove();
|
|
@@ -493,6 +545,10 @@ export class CommentUiController {
|
|
|
493
545
|
this.clearSelectionRevealTimer();
|
|
494
546
|
this.pointerSelecting = false;
|
|
495
547
|
this.activeCard = null;
|
|
548
|
+
this.updateHoveredEditorGroups([]);
|
|
549
|
+
this.updateHoveredPreviewGroups([]);
|
|
550
|
+
this.previewHoverRegions = [];
|
|
551
|
+
this.lastPreviewPointerPosition = null;
|
|
496
552
|
this.reactionPicker = null;
|
|
497
553
|
}
|
|
498
554
|
if (!this.supported) {
|
|
@@ -504,6 +560,10 @@ export class CommentUiController {
|
|
|
504
560
|
this.committedSelectionAnchor = null;
|
|
505
561
|
this.clearSelectionRevealTimer();
|
|
506
562
|
this.pointerSelecting = false;
|
|
563
|
+
this.updateHoveredEditorGroups([]);
|
|
564
|
+
this.updateHoveredPreviewGroups([]);
|
|
565
|
+
this.previewHoverRegions = [];
|
|
566
|
+
this.lastPreviewPointerPosition = null;
|
|
507
567
|
this.reactionPicker = null;
|
|
508
568
|
}
|
|
509
569
|
this.render();
|
|
@@ -611,6 +671,51 @@ export class CommentUiController {
|
|
|
611
671
|
this.repositionActiveCard();
|
|
612
672
|
}
|
|
613
673
|
|
|
674
|
+
syncPreviewRailLayout(maxBubbleWidth = 0) {
|
|
675
|
+
if (!this.previewElement) {
|
|
676
|
+
return false;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
const shouldShowRail = this.supported && maxBubbleWidth > 0;
|
|
680
|
+
const nextReserved = 0;
|
|
681
|
+
const nextOffset = 0;
|
|
682
|
+
let reserved = nextReserved;
|
|
683
|
+
let offset = nextOffset;
|
|
684
|
+
|
|
685
|
+
if (shouldShowRail) {
|
|
686
|
+
const previewStyle = getComputedStyle(this.previewElement);
|
|
687
|
+
const currentReserved = Number.parseFloat(
|
|
688
|
+
previewStyle.getPropertyValue('--preview-comment-rail-reserved'),
|
|
689
|
+
) || 0;
|
|
690
|
+
const currentPaddingRight = Number.parseFloat(previewStyle.paddingRight) || 0;
|
|
691
|
+
const railInset = Number.parseFloat(
|
|
692
|
+
previewStyle.getPropertyValue('--preview-comment-rail-inset'),
|
|
693
|
+
) || 0;
|
|
694
|
+
const basePaddingRight = Math.max(currentPaddingRight - currentReserved, 0);
|
|
695
|
+
const requiredRail = Math.max(maxBubbleWidth + railInset - basePaddingRight, 0);
|
|
696
|
+
const previewContainerRect = this.previewContainer?.getBoundingClientRect();
|
|
697
|
+
const previewRect = this.previewElement.getBoundingClientRect();
|
|
698
|
+
const availableRightGutter = previewContainerRect
|
|
699
|
+
? Math.max(previewContainerRect.right - previewRect.right, 0)
|
|
700
|
+
: 0;
|
|
701
|
+
|
|
702
|
+
offset = Math.min(availableRightGutter, requiredRail);
|
|
703
|
+
reserved = Math.max(requiredRail - offset, 0);
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
const nextReservedValue = `${Math.ceil(reserved)}px`;
|
|
707
|
+
const nextOffsetValue = `${Math.floor(offset)}px`;
|
|
708
|
+
const didChange = this.previewElement.style.getPropertyValue('--preview-comment-rail-reserved') !== nextReservedValue
|
|
709
|
+
|| this.previewElement.style.getPropertyValue('--preview-comment-rail-offset') !== nextOffsetValue;
|
|
710
|
+
|
|
711
|
+
if (didChange) {
|
|
712
|
+
this.previewElement.style.setProperty('--preview-comment-rail-reserved', nextReservedValue);
|
|
713
|
+
this.previewElement.style.setProperty('--preview-comment-rail-offset', nextOffsetValue);
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
return didChange;
|
|
717
|
+
}
|
|
718
|
+
|
|
614
719
|
scheduleLayoutRefresh() {
|
|
615
720
|
if (this.layoutFrame) {
|
|
616
721
|
return;
|
|
@@ -641,7 +746,7 @@ export class CommentUiController {
|
|
|
641
746
|
this.commentsToggleButton.classList.toggle('active', this.drawerOpen);
|
|
642
747
|
this.commentsToggleButton.setAttribute('aria-expanded', String(this.drawerOpen));
|
|
643
748
|
const label = totalCount > 0 ? `Comments ${totalCount}` : 'Comments';
|
|
644
|
-
const labelElement = this.commentsToggleButton.querySelector('.
|
|
749
|
+
const labelElement = this.commentsToggleButton.querySelector('.ui-action-label');
|
|
645
750
|
if (labelElement) {
|
|
646
751
|
labelElement.textContent = label;
|
|
647
752
|
} else {
|
|
@@ -667,7 +772,7 @@ export class CommentUiController {
|
|
|
667
772
|
groups.forEach((group) => {
|
|
668
773
|
const button = document.createElement('button');
|
|
669
774
|
button.type = 'button';
|
|
670
|
-
button.className = 'comments-drawer-item';
|
|
775
|
+
button.className = 'ui-record-surface comments-drawer-item';
|
|
671
776
|
button.classList.toggle('is-active', this.activeCard?.groupKey === group.key);
|
|
672
777
|
button.addEventListener('pointerdown', (event) => {
|
|
673
778
|
event.preventDefault();
|
|
@@ -681,14 +786,14 @@ export class CommentUiController {
|
|
|
681
786
|
});
|
|
682
787
|
|
|
683
788
|
const header = document.createElement('div');
|
|
684
|
-
header.className = 'comments-drawer-item-header';
|
|
789
|
+
header.className = 'ui-record-header comments-drawer-item-header';
|
|
685
790
|
|
|
686
791
|
const title = document.createElement('span');
|
|
687
792
|
title.className = 'comments-drawer-item-title';
|
|
688
793
|
title.textContent = formatAnchorLabel(group.anchor);
|
|
689
794
|
|
|
690
795
|
const count = document.createElement('span');
|
|
691
|
-
count.className = 'comments-drawer-item-count';
|
|
796
|
+
count.className = 'ui-pill-badge ui-pill-badge--count ui-pill-badge--accent comments-drawer-item-count';
|
|
692
797
|
count.textContent = String(group.threads.length);
|
|
693
798
|
|
|
694
799
|
header.append(title, count);
|
|
@@ -704,7 +809,7 @@ export class CommentUiController {
|
|
|
704
809
|
);
|
|
705
810
|
|
|
706
811
|
const footer = document.createElement('div');
|
|
707
|
-
footer.className = 'comments-drawer-item-footer';
|
|
812
|
+
footer.className = 'ui-record-meta comments-drawer-item-footer';
|
|
708
813
|
const countLabel = document.createElement('span');
|
|
709
814
|
countLabel.textContent = `${group.threads.length} thread${group.threads.length === 1 ? '' : 's'}`;
|
|
710
815
|
|
|
@@ -757,11 +862,19 @@ export class CommentUiController {
|
|
|
757
862
|
}
|
|
758
863
|
|
|
759
864
|
const relativeRect = toRelativeRect(rect, containerRect);
|
|
865
|
+
if (relativeRect.bottom < 0 || relativeRect.top > containerRect.height) {
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
|
|
760
869
|
const button = document.createElement('button');
|
|
761
870
|
button.type = 'button';
|
|
762
|
-
button.className = 'comment-editor-badge';
|
|
871
|
+
button.className = 'ui-state-marker ui-state-marker--comment comment-editor-badge';
|
|
763
872
|
button.dataset.count = String(group.threads.length);
|
|
764
|
-
|
|
873
|
+
const isActive = this.activeCard?.groupKey === group.key;
|
|
874
|
+
const isHovered = this.hoveredEditorGroupKeys.includes(group.key);
|
|
875
|
+
button.classList.toggle('is-active', isActive);
|
|
876
|
+
button.classList.toggle('is-hovered', isHovered);
|
|
877
|
+
button.classList.toggle('is-passive', !isActive && !isHovered);
|
|
765
878
|
button.setAttribute('aria-label', `${group.threads.length} comment thread${group.threads.length === 1 ? '' : 's'}`);
|
|
766
879
|
button.appendChild(createCommentMarkerContent(group.threads.length));
|
|
767
880
|
const top = Math.max(relativeRect.top, 8);
|
|
@@ -771,6 +884,18 @@ export class CommentUiController {
|
|
|
771
884
|
button.addEventListener('pointerdown', (event) => {
|
|
772
885
|
event.preventDefault();
|
|
773
886
|
});
|
|
887
|
+
button.addEventListener('pointerenter', () => {
|
|
888
|
+
this.updateHoveredEditorGroups([group.key]);
|
|
889
|
+
});
|
|
890
|
+
button.addEventListener('pointerleave', () => {
|
|
891
|
+
this.updateHoveredEditorGroups([]);
|
|
892
|
+
});
|
|
893
|
+
button.addEventListener('focusin', () => {
|
|
894
|
+
this.updateHoveredEditorGroups([group.key]);
|
|
895
|
+
});
|
|
896
|
+
button.addEventListener('focusout', () => {
|
|
897
|
+
this.updateHoveredEditorGroups([]);
|
|
898
|
+
});
|
|
774
899
|
button.addEventListener('click', () => {
|
|
775
900
|
this.openThreadGroup(group, {
|
|
776
901
|
anchor: group.anchor,
|
|
@@ -807,7 +932,7 @@ export class CommentUiController {
|
|
|
807
932
|
}
|
|
808
933
|
const button = document.createElement('button');
|
|
809
934
|
button.type = 'button';
|
|
810
|
-
button.className = 'comment-selection-chip';
|
|
935
|
+
button.className = 'ui-selection-pill ui-selection-pill--comment comment-selection-chip';
|
|
811
936
|
button.textContent = 'Comment';
|
|
812
937
|
button.style.top = `${chipTop}px`;
|
|
813
938
|
button.style.right = `${COMMENT_SELECTION_CHIP_GAP}px`;
|
|
@@ -840,20 +965,38 @@ export class CommentUiController {
|
|
|
840
965
|
this.previewHighlightLayer?.replaceChildren();
|
|
841
966
|
|
|
842
967
|
if (!this.supported || !this.previewElement) {
|
|
968
|
+
this.previewHoverRegions = [];
|
|
969
|
+
this.syncPreviewRailLayout(0);
|
|
843
970
|
return;
|
|
844
971
|
}
|
|
845
972
|
|
|
846
973
|
const previewRect = this.previewElement.getBoundingClientRect();
|
|
847
974
|
const groups = this.getThreadGroups();
|
|
975
|
+
const occupiedTops = [];
|
|
976
|
+
const hoverRegions = [];
|
|
977
|
+
const showPassiveMarkers = this.shouldRenderPassivePreviewMarkers();
|
|
978
|
+
const previewBubbles = [];
|
|
848
979
|
groups.forEach((group) => {
|
|
849
980
|
const target = this.resolvePreviewTarget(group.anchor);
|
|
850
981
|
if (!target?.bubbleRect) {
|
|
851
982
|
return;
|
|
852
983
|
}
|
|
853
984
|
|
|
985
|
+
hoverRegions.push({
|
|
986
|
+
key: group.key,
|
|
987
|
+
rects: target.hoverRects?.length > 0 ? target.hoverRects : [target.bubbleRect],
|
|
988
|
+
});
|
|
989
|
+
|
|
990
|
+
const isActive = this.activeCard?.groupKey === group.key;
|
|
991
|
+
const isHovered = this.hoveredPreviewGroupKeys.includes(group.key);
|
|
992
|
+
const isEmphasized = isActive || isHovered;
|
|
993
|
+
|
|
854
994
|
target.highlightRects?.forEach((rect) => {
|
|
855
995
|
const highlight = document.createElement('div');
|
|
856
996
|
highlight.className = 'comment-preview-highlight';
|
|
997
|
+
highlight.classList.toggle('is-active', isActive);
|
|
998
|
+
highlight.classList.toggle('is-hovered', isHovered);
|
|
999
|
+
highlight.classList.toggle('is-passive', !isEmphasized);
|
|
857
1000
|
highlight.style.left = `${rect.left - previewRect.left}px`;
|
|
858
1001
|
highlight.style.top = `${rect.top - previewRect.top}px`;
|
|
859
1002
|
highlight.style.width = `${rect.width}px`;
|
|
@@ -861,14 +1004,32 @@ export class CommentUiController {
|
|
|
861
1004
|
this.previewHighlightLayer?.appendChild(highlight);
|
|
862
1005
|
});
|
|
863
1006
|
|
|
1007
|
+
if (!showPassiveMarkers && !isEmphasized) {
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
864
1011
|
const bubble = document.createElement('button');
|
|
865
1012
|
bubble.type = 'button';
|
|
866
|
-
bubble.className = 'comment-preview-badge';
|
|
867
|
-
bubble.
|
|
1013
|
+
bubble.className = 'ui-state-marker ui-state-marker--comment comment-preview-badge';
|
|
1014
|
+
bubble.dataset.commentPreviewGroupKeys = group.key;
|
|
1015
|
+
bubble.classList.toggle('is-active', isActive);
|
|
1016
|
+
bubble.classList.toggle('is-hovered', isHovered);
|
|
1017
|
+
bubble.classList.toggle('is-passive', !isEmphasized);
|
|
868
1018
|
bubble.setAttribute('aria-label', `${group.threads.length} comment thread${group.threads.length === 1 ? '' : 's'}`);
|
|
869
1019
|
bubble.appendChild(createCommentMarkerContent(group.threads.length));
|
|
870
|
-
|
|
871
|
-
|
|
1020
|
+
let bubbleTop = clamp(
|
|
1021
|
+
target.bubbleRect.top - previewRect.top,
|
|
1022
|
+
6,
|
|
1023
|
+
Math.max(this.previewElement.clientHeight - COMMENT_PREVIEW_RAIL_SLOT_HEIGHT, 6),
|
|
1024
|
+
);
|
|
1025
|
+
while (occupiedTops.some((top) => Math.abs(top - bubbleTop) < (COMMENT_PREVIEW_RAIL_SLOT_HEIGHT - 4))) {
|
|
1026
|
+
bubbleTop = clamp(
|
|
1027
|
+
bubbleTop + COMMENT_PREVIEW_RAIL_SLOT_HEIGHT,
|
|
1028
|
+
6,
|
|
1029
|
+
Math.max(this.previewElement.clientHeight - COMMENT_PREVIEW_RAIL_SLOT_HEIGHT, 6),
|
|
1030
|
+
);
|
|
1031
|
+
}
|
|
1032
|
+
bubble.style.top = `${bubbleTop}px`;
|
|
872
1033
|
bubble.title = `${group.threads.length} comment${group.threads.length === 1 ? '' : 's'}`;
|
|
873
1034
|
bubble.addEventListener('pointerdown', (event) => {
|
|
874
1035
|
event.preventDefault();
|
|
@@ -877,11 +1038,27 @@ export class CommentUiController {
|
|
|
877
1038
|
this.openThreadGroup(group, {
|
|
878
1039
|
anchor: group.anchor,
|
|
879
1040
|
origin: 'preview',
|
|
880
|
-
sourceRect:
|
|
1041
|
+
sourceRect: bubble.getBoundingClientRect(),
|
|
881
1042
|
});
|
|
882
1043
|
});
|
|
883
1044
|
this.previewLayer?.appendChild(bubble);
|
|
1045
|
+
occupiedTops.push(bubbleTop);
|
|
1046
|
+
previewBubbles.push(bubble);
|
|
884
1047
|
});
|
|
1048
|
+
|
|
1049
|
+
this.previewHoverRegions = hoverRegions;
|
|
1050
|
+
const maxBubbleWidth = previewBubbles.reduce(
|
|
1051
|
+
(maxWidth, bubble) => Math.max(maxWidth, bubble.offsetWidth || COMMENT_PREVIEW_BADGE_MIN_WIDTH),
|
|
1052
|
+
0,
|
|
1053
|
+
);
|
|
1054
|
+
if (this.syncPreviewRailLayout(maxBubbleWidth)) {
|
|
1055
|
+
this.scheduleLayoutRefresh();
|
|
1056
|
+
}
|
|
1057
|
+
if (this.lastPreviewPointerPosition) {
|
|
1058
|
+
this.updateHoveredPreviewGroups(
|
|
1059
|
+
this.getPreviewGroupKeysAtPoint(this.lastPreviewPointerPosition.x, this.lastPreviewPointerPosition.y),
|
|
1060
|
+
);
|
|
1061
|
+
}
|
|
885
1062
|
}
|
|
886
1063
|
|
|
887
1064
|
resolvePreviewTarget(anchor) {
|
|
@@ -895,6 +1072,7 @@ export class CommentUiController {
|
|
|
895
1072
|
return {
|
|
896
1073
|
bubbleRect: diagramShell.getBoundingClientRect(),
|
|
897
1074
|
highlightRects: [],
|
|
1075
|
+
hoverRects: [diagramShell.getBoundingClientRect()],
|
|
898
1076
|
};
|
|
899
1077
|
}
|
|
900
1078
|
|
|
@@ -911,6 +1089,7 @@ export class CommentUiController {
|
|
|
911
1089
|
return {
|
|
912
1090
|
bubbleRect,
|
|
913
1091
|
highlightRects: rects,
|
|
1092
|
+
hoverRects: rects,
|
|
914
1093
|
};
|
|
915
1094
|
}
|
|
916
1095
|
}
|
|
@@ -923,6 +1102,7 @@ export class CommentUiController {
|
|
|
923
1102
|
return {
|
|
924
1103
|
bubbleRect: fallback.getBoundingClientRect(),
|
|
925
1104
|
highlightRects: [],
|
|
1105
|
+
hoverRects: [fallback.getBoundingClientRect()],
|
|
926
1106
|
};
|
|
927
1107
|
}
|
|
928
1108
|
|
|
@@ -1056,7 +1236,7 @@ export class CommentUiController {
|
|
|
1056
1236
|
});
|
|
1057
1237
|
|
|
1058
1238
|
const header = document.createElement('div');
|
|
1059
|
-
header.className = 'comment-card-header';
|
|
1239
|
+
header.className = 'ui-record-header comment-card-header';
|
|
1060
1240
|
|
|
1061
1241
|
const titleWrap = document.createElement('div');
|
|
1062
1242
|
titleWrap.className = 'comment-card-title-wrap';
|
|
@@ -1073,7 +1253,7 @@ export class CommentUiController {
|
|
|
1073
1253
|
|
|
1074
1254
|
const closeButton = document.createElement('button');
|
|
1075
1255
|
closeButton.type = 'button';
|
|
1076
|
-
closeButton.className = 'comment-card-close';
|
|
1256
|
+
closeButton.className = 'ui-button ui-button--compact btn btn-ghost ui-pill-button ui-action-pill comment-card-close';
|
|
1077
1257
|
closeButton.setAttribute('aria-label', 'Close comments');
|
|
1078
1258
|
closeButton.textContent = 'Close';
|
|
1079
1259
|
closeButton.addEventListener('click', () => this.closeCard());
|
|
@@ -1115,6 +1295,69 @@ export class CommentUiController {
|
|
|
1115
1295
|
this.scheduleLayoutRefresh();
|
|
1116
1296
|
}
|
|
1117
1297
|
|
|
1298
|
+
getPreviewGroupKeysForTarget(target) {
|
|
1299
|
+
if (!(target instanceof Node)) {
|
|
1300
|
+
return [];
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
const keyCarrier = target.closest?.('[data-comment-preview-group-keys]');
|
|
1304
|
+
return serializeGroupKeys(
|
|
1305
|
+
String(keyCarrier?.dataset?.commentPreviewGroupKeys ?? '')
|
|
1306
|
+
.split(/\s+/)
|
|
1307
|
+
.filter(Boolean),
|
|
1308
|
+
).split(' ').filter(Boolean);
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
getPreviewGroupKeysAtPoint(x, y) {
|
|
1312
|
+
if (!Number.isFinite(x) || !Number.isFinite(y)) {
|
|
1313
|
+
return [];
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
const targetAtPoint = document.elementFromPoint(x, y);
|
|
1317
|
+
const targetKeys = this.getPreviewGroupKeysForTarget(targetAtPoint);
|
|
1318
|
+
if (targetKeys.length > 0) {
|
|
1319
|
+
return targetKeys;
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
const matchingKeys = this.previewHoverRegions
|
|
1323
|
+
.filter((region) => region.rects.some((rect) => pointIntersectsRect(x, y, rect)))
|
|
1324
|
+
.map((region) => region.key);
|
|
1325
|
+
return normalizeGroupKeys(matchingKeys);
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
updateHoveredPreviewGroups(nextKeys = []) {
|
|
1329
|
+
const normalizedKeys = normalizeGroupKeys(nextKeys);
|
|
1330
|
+
const signature = normalizedKeys.join(' ');
|
|
1331
|
+
if (signature === this.hoveredPreviewGroupKeysSignature) {
|
|
1332
|
+
return;
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
this.hoveredPreviewGroupKeys = normalizedKeys;
|
|
1336
|
+
this.hoveredPreviewGroupKeysSignature = signature;
|
|
1337
|
+
this.scheduleLayoutRefresh();
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
updateHoveredEditorGroups(nextKeys = []) {
|
|
1341
|
+
const normalizedKeys = normalizeGroupKeys(nextKeys);
|
|
1342
|
+
const signature = normalizedKeys.join(' ');
|
|
1343
|
+
if (signature === this.hoveredEditorGroupKeysSignature) {
|
|
1344
|
+
return;
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
this.hoveredEditorGroupKeys = normalizedKeys;
|
|
1348
|
+
this.hoveredEditorGroupKeysSignature = signature;
|
|
1349
|
+
this.scheduleLayoutRefresh();
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
syncHoveredPreviewGroupsFromTarget(target) {
|
|
1353
|
+
this.updateHoveredPreviewGroups(this.getPreviewGroupKeysForTarget(target));
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
shouldRenderPassivePreviewMarkers() {
|
|
1357
|
+
const previewWidth = this.previewContainer?.clientWidth ?? this.previewElement?.clientWidth ?? 0;
|
|
1358
|
+
return window.innerWidth >= COMMENT_PREVIEW_RAIL_BREAKPOINT && previewWidth >= COMMENT_PREVIEW_RAIL_MIN_WIDTH;
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1118
1361
|
updateReactionPickerPosition(card) {
|
|
1119
1362
|
const bounds = getReactionPickerBounds(card);
|
|
1120
1363
|
if (!bounds) {
|
|
@@ -1166,17 +1409,17 @@ export class CommentUiController {
|
|
|
1166
1409
|
textarea.placeholder = 'Add context, feedback, or a question...';
|
|
1167
1410
|
|
|
1168
1411
|
const actions = document.createElement('div');
|
|
1169
|
-
actions.className = 'comment-card-actions';
|
|
1412
|
+
actions.className = 'ui-record-actions comment-card-actions';
|
|
1170
1413
|
|
|
1171
1414
|
const cancel = document.createElement('button');
|
|
1172
1415
|
cancel.type = 'button';
|
|
1173
|
-
cancel.className = 'btn btn-secondary';
|
|
1416
|
+
cancel.className = 'ui-button btn btn-secondary';
|
|
1174
1417
|
cancel.textContent = 'Cancel';
|
|
1175
1418
|
cancel.addEventListener('click', () => this.closeCard());
|
|
1176
1419
|
|
|
1177
1420
|
const submit = document.createElement('button');
|
|
1178
1421
|
submit.type = 'submit';
|
|
1179
|
-
submit.className = 'btn btn-primary';
|
|
1422
|
+
submit.className = 'ui-button btn btn-primary';
|
|
1180
1423
|
submit.textContent = 'Post comment';
|
|
1181
1424
|
|
|
1182
1425
|
actions.append(cancel, submit);
|
|
@@ -1205,7 +1448,7 @@ export class CommentUiController {
|
|
|
1205
1448
|
article.className = 'comment-thread-card';
|
|
1206
1449
|
|
|
1207
1450
|
const header = document.createElement('div');
|
|
1208
|
-
header.className = 'comment-thread-card-header';
|
|
1451
|
+
header.className = 'ui-record-header comment-thread-card-header';
|
|
1209
1452
|
|
|
1210
1453
|
const heading = document.createElement('div');
|
|
1211
1454
|
heading.className = 'comment-thread-card-heading';
|
|
@@ -1219,17 +1462,17 @@ export class CommentUiController {
|
|
|
1219
1462
|
time.textContent = this.formatTimestamp(thread.createdAt);
|
|
1220
1463
|
|
|
1221
1464
|
const actions = document.createElement('div');
|
|
1222
|
-
actions.className = 'comment-thread-card-actions';
|
|
1465
|
+
actions.className = 'ui-record-actions comment-thread-card-actions';
|
|
1223
1466
|
|
|
1224
1467
|
const jump = document.createElement('button');
|
|
1225
1468
|
jump.type = 'button';
|
|
1226
|
-
jump.className = 'comment-thread-card-action';
|
|
1469
|
+
jump.className = 'ui-button ui-button--compact btn btn-ghost ui-pill-button ui-action-pill comment-thread-card-action';
|
|
1227
1470
|
jump.textContent = 'Jump';
|
|
1228
1471
|
jump.addEventListener('click', () => this.onNavigateToLine?.(thread.anchor?.startLine ?? 1));
|
|
1229
1472
|
|
|
1230
1473
|
const reply = document.createElement('button');
|
|
1231
1474
|
reply.type = 'button';
|
|
1232
|
-
reply.className = 'comment-thread-card-action';
|
|
1475
|
+
reply.className = 'ui-button ui-button--compact btn btn-ghost ui-pill-button ui-action-pill comment-thread-card-action';
|
|
1233
1476
|
const isReplying = this.activeCard?.replyThreadId === thread.id;
|
|
1234
1477
|
reply.classList.toggle('is-active', isReplying);
|
|
1235
1478
|
reply.textContent = 'Reply';
|
|
@@ -1246,7 +1489,7 @@ export class CommentUiController {
|
|
|
1246
1489
|
|
|
1247
1490
|
const resolve = document.createElement('button');
|
|
1248
1491
|
resolve.type = 'button';
|
|
1249
|
-
resolve.className = 'comment-thread-card-action is-danger';
|
|
1492
|
+
resolve.className = 'ui-button ui-button--compact btn btn-ghost ui-pill-button ui-action-pill comment-thread-card-action is-danger';
|
|
1250
1493
|
resolve.textContent = 'Resolve';
|
|
1251
1494
|
resolve.addEventListener('click', async () => {
|
|
1252
1495
|
await this.onResolveThread?.(thread.id);
|
|
@@ -1274,7 +1517,7 @@ export class CommentUiController {
|
|
|
1274
1517
|
container.className = 'comment-message-card';
|
|
1275
1518
|
|
|
1276
1519
|
const meta = document.createElement('div');
|
|
1277
|
-
meta.className = 'comment-message-card-meta';
|
|
1520
|
+
meta.className = 'ui-record-meta comment-message-card-meta';
|
|
1278
1521
|
|
|
1279
1522
|
const author = document.createElement('span');
|
|
1280
1523
|
author.className = 'comment-message-card-author';
|
|
@@ -1308,7 +1551,7 @@ export class CommentUiController {
|
|
|
1308
1551
|
(message.reactions ?? []).forEach((reaction) => {
|
|
1309
1552
|
const chip = document.createElement('button');
|
|
1310
1553
|
chip.type = 'button';
|
|
1311
|
-
chip.className = 'comment-reaction-chip';
|
|
1554
|
+
chip.className = 'ui-chip-button ui-chip-button--comment comment-reaction-chip';
|
|
1312
1555
|
chip.classList.toggle('is-active', hasLocalReaction(reaction, localUserId));
|
|
1313
1556
|
chip.setAttribute('aria-pressed', String(hasLocalReaction(reaction, localUserId)));
|
|
1314
1557
|
chip.title = reaction.users?.map((user) => user.userName).join(', ') || reaction.emoji;
|
|
@@ -1342,7 +1585,7 @@ export class CommentUiController {
|
|
|
1342
1585
|
|
|
1343
1586
|
const moreButton = document.createElement('button');
|
|
1344
1587
|
moreButton.type = 'button';
|
|
1345
|
-
moreButton.className = 'comment-reaction-more-trigger';
|
|
1588
|
+
moreButton.className = 'ui-chip-button ui-chip-button--comment comment-reaction-more-trigger';
|
|
1346
1589
|
moreButton.dataset.reactionPickerToggle = 'true';
|
|
1347
1590
|
moreButton.setAttribute('aria-expanded', String(
|
|
1348
1591
|
isReactionPickerOpen(this.reactionPicker, thread.id, message.id),
|
|
@@ -1373,7 +1616,7 @@ export class CommentUiController {
|
|
|
1373
1616
|
createQuickReactionButton(thread, message, emoji) {
|
|
1374
1617
|
const button = document.createElement('button');
|
|
1375
1618
|
button.type = 'button';
|
|
1376
|
-
button.className = 'comment-reaction-quick-add';
|
|
1619
|
+
button.className = 'ui-icon-chip ui-icon-chip--comment comment-reaction-quick-add';
|
|
1377
1620
|
button.textContent = emoji;
|
|
1378
1621
|
button.title = `React with ${emoji}`;
|
|
1379
1622
|
button.addEventListener('click', async () => {
|
|
@@ -1398,7 +1641,7 @@ export class CommentUiController {
|
|
|
1398
1641
|
createReactionPickerButton(thread, message, emoji) {
|
|
1399
1642
|
const button = document.createElement('button');
|
|
1400
1643
|
button.type = 'button';
|
|
1401
|
-
button.className = 'comment-reaction-picker-btn';
|
|
1644
|
+
button.className = 'ui-icon-chip ui-icon-chip--comment comment-reaction-picker-btn';
|
|
1402
1645
|
button.textContent = emoji;
|
|
1403
1646
|
button.title = `React with ${emoji}`;
|
|
1404
1647
|
button.addEventListener('click', async () => {
|
|
@@ -1422,11 +1665,11 @@ export class CommentUiController {
|
|
|
1422
1665
|
textarea.placeholder = 'Reply to thread...';
|
|
1423
1666
|
|
|
1424
1667
|
const actions = document.createElement('div');
|
|
1425
|
-
actions.className = 'comment-card-actions';
|
|
1668
|
+
actions.className = 'ui-record-actions comment-card-actions';
|
|
1426
1669
|
|
|
1427
1670
|
const cancel = document.createElement('button');
|
|
1428
1671
|
cancel.type = 'button';
|
|
1429
|
-
cancel.className = 'btn btn-secondary';
|
|
1672
|
+
cancel.className = 'ui-button btn btn-secondary';
|
|
1430
1673
|
cancel.textContent = 'Cancel';
|
|
1431
1674
|
cancel.addEventListener('click', () => {
|
|
1432
1675
|
this.activeCard = {
|
|
@@ -1438,7 +1681,7 @@ export class CommentUiController {
|
|
|
1438
1681
|
|
|
1439
1682
|
const submit = document.createElement('button');
|
|
1440
1683
|
submit.type = 'submit';
|
|
1441
|
-
submit.className = 'btn btn-primary';
|
|
1684
|
+
submit.className = 'ui-button btn btn-primary';
|
|
1442
1685
|
submit.textContent = 'Reply';
|
|
1443
1686
|
|
|
1444
1687
|
actions.append(cancel, submit);
|
|
@@ -463,7 +463,7 @@ export class CommentsPanel {
|
|
|
463
463
|
|
|
464
464
|
const cancelButton = document.createElement('button');
|
|
465
465
|
cancelButton.type = 'button';
|
|
466
|
-
cancelButton.className = 'btn btn-secondary';
|
|
466
|
+
cancelButton.className = 'ui-button btn btn-secondary';
|
|
467
467
|
cancelButton.textContent = 'Cancel';
|
|
468
468
|
cancelButton.addEventListener('click', () => {
|
|
469
469
|
this.replyDraftThreadId = null;
|
|
@@ -472,7 +472,7 @@ export class CommentsPanel {
|
|
|
472
472
|
|
|
473
473
|
const submitButton = document.createElement('button');
|
|
474
474
|
submitButton.type = 'submit';
|
|
475
|
-
submitButton.className = 'btn btn-primary';
|
|
475
|
+
submitButton.className = 'ui-button btn btn-primary';
|
|
476
476
|
submitButton.textContent = 'Reply';
|
|
477
477
|
|
|
478
478
|
actions.append(cancelButton, submitButton);
|