collabmd 0.1.21 → 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 +94 -7
- 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/editor-session-S5AT7BMV.js +22 -0
- package/public/assets/js/{excalidraw-editor-TCIH6GJL.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/index.html +104 -84
- package/src/client/application/app-shell/git-feature.js +247 -9
- package/src/client/application/app-shell/ui-feature.js +65 -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 +95 -0
- package/src/client/domain/runtime-paths.js +5 -0
- package/src/client/excalidraw-editor.js +50 -2
- 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 +47 -7
- package/src/client/infrastructure/editor-view-adapter.js +122 -58
- package/src/client/infrastructure/runtime-config.js +66 -0
- package/src/client/presentation/comment-ui-controller.js +82 -26
- package/src/client/presentation/comments-panel.js +2 -2
- package/src/client/presentation/excalidraw-embed-controller.js +60 -2
- 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 -4945
- 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 +8 -0
- package/src/server/domain/collaboration/collaboration-room.js +90 -47
- 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 +73 -0
- package/src/server/infrastructure/http/create-auth-api-handler.js +30 -2
- package/src/server/infrastructure/http/create-git-api-command-handler.js +2 -0
- package/src/server/infrastructure/http/create-git-api-handler.js +2 -0
- package/src/server/infrastructure/http/create-git-api-query-handler.js +61 -0
- package/src/server/infrastructure/http/create-request-handler.js +15 -0
- package/public/assets/js/chunks/editor-session-EAUBJCHH.js +0 -22
|
@@ -340,8 +340,8 @@ export class EditorViewAdapter {
|
|
|
340
340
|
};
|
|
341
341
|
}
|
|
342
342
|
|
|
343
|
-
|
|
344
|
-
|
|
343
|
+
createUpdateListener() {
|
|
344
|
+
return EditorView.updateListener.of((update) => {
|
|
345
345
|
if (update.docChanged) {
|
|
346
346
|
this.onDocChanged?.();
|
|
347
347
|
}
|
|
@@ -351,62 +351,99 @@ export class EditorViewAdapter {
|
|
|
351
351
|
this.onSelectionChanged?.(update.state);
|
|
352
352
|
}
|
|
353
353
|
});
|
|
354
|
+
}
|
|
354
355
|
|
|
355
|
-
|
|
356
|
+
getBaseExtensions(filePath, { readOnly = false } = {}) {
|
|
357
|
+
const extensions = [
|
|
358
|
+
lineNumbers(),
|
|
359
|
+
highlightActiveLineGutter(),
|
|
360
|
+
highlightSpecialChars(),
|
|
361
|
+
history(),
|
|
362
|
+
foldGutter(),
|
|
363
|
+
drawSelection(),
|
|
364
|
+
EditorState.allowMultipleSelections.of(true),
|
|
365
|
+
indentOnInput(),
|
|
366
|
+
syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
|
|
367
|
+
bracketMatching({ renderMatch: renderBracketMatch }),
|
|
368
|
+
closeBrackets(),
|
|
369
|
+
autocompletion({
|
|
370
|
+
override: [wikiLinkCompletions(this.getFileList)],
|
|
371
|
+
}),
|
|
372
|
+
rectangularSelection(),
|
|
373
|
+
crosshairCursor(),
|
|
374
|
+
highlightActiveLine(),
|
|
375
|
+
highlightSelectionMatches(),
|
|
376
|
+
keymap.of([
|
|
377
|
+
...closeBracketsKeymap,
|
|
378
|
+
...defaultKeymap,
|
|
379
|
+
...historyKeymap,
|
|
380
|
+
...searchKeymap,
|
|
381
|
+
...foldKeymap,
|
|
382
|
+
indentWithTab,
|
|
383
|
+
]),
|
|
384
|
+
EditorView.contentAttributes.of({
|
|
385
|
+
'aria-label': 'Markdown editor',
|
|
386
|
+
}),
|
|
387
|
+
createLanguageExtension(filePath),
|
|
388
|
+
this.themeCompartment.of(createEditorTheme(this.initialTheme)),
|
|
389
|
+
this.syntaxThemeCompartment.of(this.initialTheme === 'dark' ? oneDark : []),
|
|
390
|
+
this.lineWrappingCompartment.of(this.lineWrappingEnabled ? EditorView.lineWrapping : []),
|
|
391
|
+
remoteUpdateFlashField,
|
|
392
|
+
this.createUpdateListener(),
|
|
393
|
+
];
|
|
394
|
+
|
|
395
|
+
if (readOnly) {
|
|
396
|
+
extensions.push(EditorState.readOnly.of(true));
|
|
397
|
+
extensions.push(EditorView.editable.of(false));
|
|
398
|
+
return extensions;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
extensions.splice(13, 0, Prec.highest(EditorView.domEventHandlers({
|
|
402
|
+
paste: (event) => handleImagePasteEvent(event, this.onImagePaste),
|
|
403
|
+
})));
|
|
404
|
+
return extensions;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
clearEditorDom({ preserveLoadingIndicator = true } = {}) {
|
|
408
|
+
if (!this.editorContainer) {
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const loadingIndicator = preserveLoadingIndicator
|
|
413
|
+
? this.editorContainer.querySelector('#editorLoading')
|
|
414
|
+
: null;
|
|
356
415
|
Array.from(this.editorContainer.children).forEach((child) => {
|
|
357
416
|
if (child !== loadingIndicator) {
|
|
358
417
|
child.remove();
|
|
359
418
|
}
|
|
360
419
|
});
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
teardownEditorView({ clearContainer = false } = {}) {
|
|
423
|
+
this.editorView?.contentDOM?.removeEventListener('beforeinput', this.handleLocalInputActivity);
|
|
424
|
+
this.editorView?.contentDOM?.removeEventListener('keydown', this.handleLocalInputActivity);
|
|
425
|
+
this.editorView?.contentDOM?.removeEventListener('paste', this.handleLocalInputActivity);
|
|
426
|
+
this.editorView?.contentDOM?.removeEventListener('compositionstart', this.handleLocalInputActivity);
|
|
427
|
+
this.editorView?.scrollDOM?.removeEventListener('scroll', this.handleScroll);
|
|
428
|
+
this.editorView?.destroy();
|
|
429
|
+
this.editorView = null;
|
|
430
|
+
|
|
431
|
+
if (clearContainer && this.editorContainer) {
|
|
432
|
+
this.editorContainer.innerHTML = '';
|
|
433
|
+
delete this.editorContainer.dataset.editorMode;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
mountEditor(state, { editorMode = 'collaborative', preserveScrollTop = 0 } = {}) {
|
|
438
|
+
this.clearEditorDom({ preserveLoadingIndicator: true });
|
|
439
|
+
this.teardownEditorView();
|
|
361
440
|
|
|
362
441
|
this.editorView = new EditorView({
|
|
363
442
|
parent: this.editorContainer,
|
|
364
|
-
state
|
|
365
|
-
doc: ytext.toString(),
|
|
366
|
-
extensions: [
|
|
367
|
-
lineNumbers(),
|
|
368
|
-
highlightActiveLineGutter(),
|
|
369
|
-
highlightSpecialChars(),
|
|
370
|
-
history(),
|
|
371
|
-
foldGutter(),
|
|
372
|
-
drawSelection(),
|
|
373
|
-
EditorState.allowMultipleSelections.of(true),
|
|
374
|
-
indentOnInput(),
|
|
375
|
-
syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
|
|
376
|
-
bracketMatching({ renderMatch: renderBracketMatch }),
|
|
377
|
-
closeBrackets(),
|
|
378
|
-
autocompletion({
|
|
379
|
-
override: [wikiLinkCompletions(this.getFileList)],
|
|
380
|
-
}),
|
|
381
|
-
rectangularSelection(),
|
|
382
|
-
crosshairCursor(),
|
|
383
|
-
highlightActiveLine(),
|
|
384
|
-
highlightSelectionMatches(),
|
|
385
|
-
keymap.of([
|
|
386
|
-
...closeBracketsKeymap,
|
|
387
|
-
...defaultKeymap,
|
|
388
|
-
...historyKeymap,
|
|
389
|
-
...searchKeymap,
|
|
390
|
-
...foldKeymap,
|
|
391
|
-
indentWithTab,
|
|
392
|
-
]),
|
|
393
|
-
EditorView.contentAttributes.of({
|
|
394
|
-
'aria-label': 'Markdown editor',
|
|
395
|
-
}),
|
|
396
|
-
Prec.highest(EditorView.domEventHandlers({
|
|
397
|
-
paste: (event) => handleImagePasteEvent(event, this.onImagePaste),
|
|
398
|
-
})),
|
|
399
|
-
createLanguageExtension(filePath),
|
|
400
|
-
this.themeCompartment.of(createEditorTheme(this.initialTheme)),
|
|
401
|
-
this.syntaxThemeCompartment.of(this.initialTheme === 'dark' ? oneDark : []),
|
|
402
|
-
this.lineWrappingCompartment.of(this.lineWrappingEnabled ? EditorView.lineWrapping : []),
|
|
403
|
-
remoteUpdateFlashField,
|
|
404
|
-
yCollab(ytext, awareness, { undoManager }),
|
|
405
|
-
updateListener,
|
|
406
|
-
],
|
|
407
|
-
}),
|
|
443
|
+
state,
|
|
408
444
|
});
|
|
409
445
|
|
|
446
|
+
this.editorContainer.dataset.editorMode = editorMode;
|
|
410
447
|
this.editorView.scrollDOM.addEventListener('scroll', this.handleScroll, { passive: true });
|
|
411
448
|
this.editorView.contentDOM.addEventListener('beforeinput', this.handleLocalInputActivity);
|
|
412
449
|
this.editorView.contentDOM.addEventListener('keydown', this.handleLocalInputActivity);
|
|
@@ -415,6 +452,43 @@ export class EditorViewAdapter {
|
|
|
415
452
|
this.updateCursorInfo(this.editorView.state);
|
|
416
453
|
this.onSelectionChanged?.(this.editorView.state);
|
|
417
454
|
this.emitViewportChange();
|
|
455
|
+
|
|
456
|
+
if (preserveScrollTop > 0) {
|
|
457
|
+
requestAnimationFrame(() => {
|
|
458
|
+
if (this.editorView?.scrollDOM) {
|
|
459
|
+
this.editorView.scrollDOM.scrollTop = preserveScrollTop;
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
initialize({ awareness, filePath, undoManager, ytext }) {
|
|
466
|
+
const preserveScrollTop = this.editorView?.scrollDOM?.scrollTop ?? 0;
|
|
467
|
+
const state = EditorState.create({
|
|
468
|
+
doc: ytext.toString(),
|
|
469
|
+
extensions: [
|
|
470
|
+
...this.getBaseExtensions(filePath),
|
|
471
|
+
yCollab(ytext, awareness, { undoManager }),
|
|
472
|
+
],
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
this.mountEditor(state, {
|
|
476
|
+
editorMode: 'collaborative',
|
|
477
|
+
preserveScrollTop,
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
initializeProvisional({ content = '', filePath }) {
|
|
482
|
+
const preserveScrollTop = this.editorView?.scrollDOM?.scrollTop ?? 0;
|
|
483
|
+
const state = EditorState.create({
|
|
484
|
+
doc: String(content ?? ''),
|
|
485
|
+
extensions: this.getBaseExtensions(filePath, { readOnly: true }),
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
this.mountEditor(state, {
|
|
489
|
+
editorMode: 'provisional',
|
|
490
|
+
preserveScrollTop,
|
|
491
|
+
});
|
|
418
492
|
}
|
|
419
493
|
|
|
420
494
|
destroy() {
|
|
@@ -426,17 +500,7 @@ export class EditorViewAdapter {
|
|
|
426
500
|
clearTimeout(this.remoteUpdateFlashTimer);
|
|
427
501
|
this.remoteUpdateFlashTimer = 0;
|
|
428
502
|
}
|
|
429
|
-
this.
|
|
430
|
-
this.editorView?.contentDOM?.removeEventListener('keydown', this.handleLocalInputActivity);
|
|
431
|
-
this.editorView?.contentDOM?.removeEventListener('paste', this.handleLocalInputActivity);
|
|
432
|
-
this.editorView?.contentDOM?.removeEventListener('compositionstart', this.handleLocalInputActivity);
|
|
433
|
-
this.editorView?.scrollDOM?.removeEventListener('scroll', this.handleScroll);
|
|
434
|
-
this.editorView?.destroy();
|
|
435
|
-
this.editorView = null;
|
|
436
|
-
|
|
437
|
-
if (this.editorContainer) {
|
|
438
|
-
this.editorContainer.innerHTML = '';
|
|
439
|
-
}
|
|
503
|
+
this.teardownEditorView({ clearContainer: true });
|
|
440
504
|
}
|
|
441
505
|
|
|
442
506
|
getText() {
|
|
@@ -30,6 +30,36 @@ function normalizeDiffScope(scope) {
|
|
|
30
30
|
export function getHashRoute() {
|
|
31
31
|
const params = getHashParams();
|
|
32
32
|
|
|
33
|
+
if (params.has('git-file-preview')) {
|
|
34
|
+
const historicalFilePath = params.get('git-file-preview') || null;
|
|
35
|
+
return {
|
|
36
|
+
currentFilePath: params.get('current') || historicalFilePath,
|
|
37
|
+
filePath: historicalFilePath,
|
|
38
|
+
hash: params.get('hash') || null,
|
|
39
|
+
type: 'git-file-preview',
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (params.has('git-file-history')) {
|
|
44
|
+
return {
|
|
45
|
+
filePath: params.get('git-file-history') || null,
|
|
46
|
+
type: 'git-file-history',
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (params.has('git-history')) {
|
|
51
|
+
return { type: 'git-history' };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (params.has('git-commit')) {
|
|
55
|
+
return {
|
|
56
|
+
hash: params.get('git-commit') || null,
|
|
57
|
+
historyFilePath: params.get('history') || null,
|
|
58
|
+
path: params.get('path') || null,
|
|
59
|
+
type: 'git-commit',
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
33
63
|
if (params.has('git-diff')) {
|
|
34
64
|
const filePath = params.get('git-diff') || null;
|
|
35
65
|
return {
|
|
@@ -63,3 +93,39 @@ export function navigateToGitDiff({ filePath = null, scope = 'all' } = {}) {
|
|
|
63
93
|
params.set('scope', normalizeDiffScope(scope));
|
|
64
94
|
window.location.hash = params.toString();
|
|
65
95
|
}
|
|
96
|
+
|
|
97
|
+
export function navigateToGitCommit({ hash, path = null, historyFilePath = null } = {}) {
|
|
98
|
+
const normalizedHash = String(hash ?? '').trim();
|
|
99
|
+
const params = new URLSearchParams();
|
|
100
|
+
params.set('git-commit', normalizedHash);
|
|
101
|
+
if (historyFilePath) {
|
|
102
|
+
params.set('history', historyFilePath);
|
|
103
|
+
}
|
|
104
|
+
if (path) {
|
|
105
|
+
params.set('path', path);
|
|
106
|
+
}
|
|
107
|
+
window.location.hash = params.toString();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function navigateToGitHistory() {
|
|
111
|
+
const params = new URLSearchParams();
|
|
112
|
+
params.set('git-history', '1');
|
|
113
|
+
window.location.hash = params.toString();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function navigateToGitFileHistory({ filePath } = {}) {
|
|
117
|
+
const params = new URLSearchParams();
|
|
118
|
+
params.set('git-file-history', filePath ?? '');
|
|
119
|
+
window.location.hash = params.toString();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function navigateToGitFilePreview({ hash, path, currentFilePath = null } = {}) {
|
|
123
|
+
const normalizedHash = String(hash ?? '').trim();
|
|
124
|
+
const params = new URLSearchParams();
|
|
125
|
+
params.set('git-file-preview', path ?? '');
|
|
126
|
+
if (currentFilePath) {
|
|
127
|
+
params.set('current', currentFilePath);
|
|
128
|
+
}
|
|
129
|
+
params.set('hash', normalizedHash);
|
|
130
|
+
window.location.hash = params.toString();
|
|
131
|
+
}
|
|
@@ -9,6 +9,7 @@ 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;
|
|
12
13
|
const COMMENT_PREVIEW_RAIL_SLOT_HEIGHT = 30;
|
|
13
14
|
const COMMENT_PREVIEW_RAIL_MIN_WIDTH = 400;
|
|
14
15
|
const COMMENT_PREVIEW_RAIL_BREAKPOINT = 769;
|
|
@@ -670,6 +671,51 @@ export class CommentUiController {
|
|
|
670
671
|
this.repositionActiveCard();
|
|
671
672
|
}
|
|
672
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
|
+
|
|
673
719
|
scheduleLayoutRefresh() {
|
|
674
720
|
if (this.layoutFrame) {
|
|
675
721
|
return;
|
|
@@ -700,7 +746,7 @@ export class CommentUiController {
|
|
|
700
746
|
this.commentsToggleButton.classList.toggle('active', this.drawerOpen);
|
|
701
747
|
this.commentsToggleButton.setAttribute('aria-expanded', String(this.drawerOpen));
|
|
702
748
|
const label = totalCount > 0 ? `Comments ${totalCount}` : 'Comments';
|
|
703
|
-
const labelElement = this.commentsToggleButton.querySelector('.
|
|
749
|
+
const labelElement = this.commentsToggleButton.querySelector('.ui-action-label');
|
|
704
750
|
if (labelElement) {
|
|
705
751
|
labelElement.textContent = label;
|
|
706
752
|
} else {
|
|
@@ -726,7 +772,7 @@ export class CommentUiController {
|
|
|
726
772
|
groups.forEach((group) => {
|
|
727
773
|
const button = document.createElement('button');
|
|
728
774
|
button.type = 'button';
|
|
729
|
-
button.className = 'comments-drawer-item';
|
|
775
|
+
button.className = 'ui-record-surface comments-drawer-item';
|
|
730
776
|
button.classList.toggle('is-active', this.activeCard?.groupKey === group.key);
|
|
731
777
|
button.addEventListener('pointerdown', (event) => {
|
|
732
778
|
event.preventDefault();
|
|
@@ -740,14 +786,14 @@ export class CommentUiController {
|
|
|
740
786
|
});
|
|
741
787
|
|
|
742
788
|
const header = document.createElement('div');
|
|
743
|
-
header.className = 'comments-drawer-item-header';
|
|
789
|
+
header.className = 'ui-record-header comments-drawer-item-header';
|
|
744
790
|
|
|
745
791
|
const title = document.createElement('span');
|
|
746
792
|
title.className = 'comments-drawer-item-title';
|
|
747
793
|
title.textContent = formatAnchorLabel(group.anchor);
|
|
748
794
|
|
|
749
795
|
const count = document.createElement('span');
|
|
750
|
-
count.className = 'comments-drawer-item-count';
|
|
796
|
+
count.className = 'ui-pill-badge ui-pill-badge--count ui-pill-badge--accent comments-drawer-item-count';
|
|
751
797
|
count.textContent = String(group.threads.length);
|
|
752
798
|
|
|
753
799
|
header.append(title, count);
|
|
@@ -763,7 +809,7 @@ export class CommentUiController {
|
|
|
763
809
|
);
|
|
764
810
|
|
|
765
811
|
const footer = document.createElement('div');
|
|
766
|
-
footer.className = 'comments-drawer-item-footer';
|
|
812
|
+
footer.className = 'ui-record-meta comments-drawer-item-footer';
|
|
767
813
|
const countLabel = document.createElement('span');
|
|
768
814
|
countLabel.textContent = `${group.threads.length} thread${group.threads.length === 1 ? '' : 's'}`;
|
|
769
815
|
|
|
@@ -822,7 +868,7 @@ export class CommentUiController {
|
|
|
822
868
|
|
|
823
869
|
const button = document.createElement('button');
|
|
824
870
|
button.type = 'button';
|
|
825
|
-
button.className = 'comment-editor-badge';
|
|
871
|
+
button.className = 'ui-state-marker ui-state-marker--comment comment-editor-badge';
|
|
826
872
|
button.dataset.count = String(group.threads.length);
|
|
827
873
|
const isActive = this.activeCard?.groupKey === group.key;
|
|
828
874
|
const isHovered = this.hoveredEditorGroupKeys.includes(group.key);
|
|
@@ -886,7 +932,7 @@ export class CommentUiController {
|
|
|
886
932
|
}
|
|
887
933
|
const button = document.createElement('button');
|
|
888
934
|
button.type = 'button';
|
|
889
|
-
button.className = 'comment-selection-chip';
|
|
935
|
+
button.className = 'ui-selection-pill ui-selection-pill--comment comment-selection-chip';
|
|
890
936
|
button.textContent = 'Comment';
|
|
891
937
|
button.style.top = `${chipTop}px`;
|
|
892
938
|
button.style.right = `${COMMENT_SELECTION_CHIP_GAP}px`;
|
|
@@ -920,6 +966,7 @@ export class CommentUiController {
|
|
|
920
966
|
|
|
921
967
|
if (!this.supported || !this.previewElement) {
|
|
922
968
|
this.previewHoverRegions = [];
|
|
969
|
+
this.syncPreviewRailLayout(0);
|
|
923
970
|
return;
|
|
924
971
|
}
|
|
925
972
|
|
|
@@ -928,6 +975,7 @@ export class CommentUiController {
|
|
|
928
975
|
const occupiedTops = [];
|
|
929
976
|
const hoverRegions = [];
|
|
930
977
|
const showPassiveMarkers = this.shouldRenderPassivePreviewMarkers();
|
|
978
|
+
const previewBubbles = [];
|
|
931
979
|
groups.forEach((group) => {
|
|
932
980
|
const target = this.resolvePreviewTarget(group.anchor);
|
|
933
981
|
if (!target?.bubbleRect) {
|
|
@@ -962,7 +1010,7 @@ export class CommentUiController {
|
|
|
962
1010
|
|
|
963
1011
|
const bubble = document.createElement('button');
|
|
964
1012
|
bubble.type = 'button';
|
|
965
|
-
bubble.className = 'comment-preview-badge';
|
|
1013
|
+
bubble.className = 'ui-state-marker ui-state-marker--comment comment-preview-badge';
|
|
966
1014
|
bubble.dataset.commentPreviewGroupKeys = group.key;
|
|
967
1015
|
bubble.classList.toggle('is-active', isActive);
|
|
968
1016
|
bubble.classList.toggle('is-hovered', isHovered);
|
|
@@ -995,9 +1043,17 @@ export class CommentUiController {
|
|
|
995
1043
|
});
|
|
996
1044
|
this.previewLayer?.appendChild(bubble);
|
|
997
1045
|
occupiedTops.push(bubbleTop);
|
|
1046
|
+
previewBubbles.push(bubble);
|
|
998
1047
|
});
|
|
999
1048
|
|
|
1000
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
|
+
}
|
|
1001
1057
|
if (this.lastPreviewPointerPosition) {
|
|
1002
1058
|
this.updateHoveredPreviewGroups(
|
|
1003
1059
|
this.getPreviewGroupKeysAtPoint(this.lastPreviewPointerPosition.x, this.lastPreviewPointerPosition.y),
|
|
@@ -1180,7 +1236,7 @@ export class CommentUiController {
|
|
|
1180
1236
|
});
|
|
1181
1237
|
|
|
1182
1238
|
const header = document.createElement('div');
|
|
1183
|
-
header.className = 'comment-card-header';
|
|
1239
|
+
header.className = 'ui-record-header comment-card-header';
|
|
1184
1240
|
|
|
1185
1241
|
const titleWrap = document.createElement('div');
|
|
1186
1242
|
titleWrap.className = 'comment-card-title-wrap';
|
|
@@ -1197,7 +1253,7 @@ export class CommentUiController {
|
|
|
1197
1253
|
|
|
1198
1254
|
const closeButton = document.createElement('button');
|
|
1199
1255
|
closeButton.type = 'button';
|
|
1200
|
-
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';
|
|
1201
1257
|
closeButton.setAttribute('aria-label', 'Close comments');
|
|
1202
1258
|
closeButton.textContent = 'Close';
|
|
1203
1259
|
closeButton.addEventListener('click', () => this.closeCard());
|
|
@@ -1353,17 +1409,17 @@ export class CommentUiController {
|
|
|
1353
1409
|
textarea.placeholder = 'Add context, feedback, or a question...';
|
|
1354
1410
|
|
|
1355
1411
|
const actions = document.createElement('div');
|
|
1356
|
-
actions.className = 'comment-card-actions';
|
|
1412
|
+
actions.className = 'ui-record-actions comment-card-actions';
|
|
1357
1413
|
|
|
1358
1414
|
const cancel = document.createElement('button');
|
|
1359
1415
|
cancel.type = 'button';
|
|
1360
|
-
cancel.className = 'btn btn-secondary';
|
|
1416
|
+
cancel.className = 'ui-button btn btn-secondary';
|
|
1361
1417
|
cancel.textContent = 'Cancel';
|
|
1362
1418
|
cancel.addEventListener('click', () => this.closeCard());
|
|
1363
1419
|
|
|
1364
1420
|
const submit = document.createElement('button');
|
|
1365
1421
|
submit.type = 'submit';
|
|
1366
|
-
submit.className = 'btn btn-primary';
|
|
1422
|
+
submit.className = 'ui-button btn btn-primary';
|
|
1367
1423
|
submit.textContent = 'Post comment';
|
|
1368
1424
|
|
|
1369
1425
|
actions.append(cancel, submit);
|
|
@@ -1392,7 +1448,7 @@ export class CommentUiController {
|
|
|
1392
1448
|
article.className = 'comment-thread-card';
|
|
1393
1449
|
|
|
1394
1450
|
const header = document.createElement('div');
|
|
1395
|
-
header.className = 'comment-thread-card-header';
|
|
1451
|
+
header.className = 'ui-record-header comment-thread-card-header';
|
|
1396
1452
|
|
|
1397
1453
|
const heading = document.createElement('div');
|
|
1398
1454
|
heading.className = 'comment-thread-card-heading';
|
|
@@ -1406,17 +1462,17 @@ export class CommentUiController {
|
|
|
1406
1462
|
time.textContent = this.formatTimestamp(thread.createdAt);
|
|
1407
1463
|
|
|
1408
1464
|
const actions = document.createElement('div');
|
|
1409
|
-
actions.className = 'comment-thread-card-actions';
|
|
1465
|
+
actions.className = 'ui-record-actions comment-thread-card-actions';
|
|
1410
1466
|
|
|
1411
1467
|
const jump = document.createElement('button');
|
|
1412
1468
|
jump.type = 'button';
|
|
1413
|
-
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';
|
|
1414
1470
|
jump.textContent = 'Jump';
|
|
1415
1471
|
jump.addEventListener('click', () => this.onNavigateToLine?.(thread.anchor?.startLine ?? 1));
|
|
1416
1472
|
|
|
1417
1473
|
const reply = document.createElement('button');
|
|
1418
1474
|
reply.type = 'button';
|
|
1419
|
-
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';
|
|
1420
1476
|
const isReplying = this.activeCard?.replyThreadId === thread.id;
|
|
1421
1477
|
reply.classList.toggle('is-active', isReplying);
|
|
1422
1478
|
reply.textContent = 'Reply';
|
|
@@ -1433,7 +1489,7 @@ export class CommentUiController {
|
|
|
1433
1489
|
|
|
1434
1490
|
const resolve = document.createElement('button');
|
|
1435
1491
|
resolve.type = 'button';
|
|
1436
|
-
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';
|
|
1437
1493
|
resolve.textContent = 'Resolve';
|
|
1438
1494
|
resolve.addEventListener('click', async () => {
|
|
1439
1495
|
await this.onResolveThread?.(thread.id);
|
|
@@ -1461,7 +1517,7 @@ export class CommentUiController {
|
|
|
1461
1517
|
container.className = 'comment-message-card';
|
|
1462
1518
|
|
|
1463
1519
|
const meta = document.createElement('div');
|
|
1464
|
-
meta.className = 'comment-message-card-meta';
|
|
1520
|
+
meta.className = 'ui-record-meta comment-message-card-meta';
|
|
1465
1521
|
|
|
1466
1522
|
const author = document.createElement('span');
|
|
1467
1523
|
author.className = 'comment-message-card-author';
|
|
@@ -1495,7 +1551,7 @@ export class CommentUiController {
|
|
|
1495
1551
|
(message.reactions ?? []).forEach((reaction) => {
|
|
1496
1552
|
const chip = document.createElement('button');
|
|
1497
1553
|
chip.type = 'button';
|
|
1498
|
-
chip.className = 'comment-reaction-chip';
|
|
1554
|
+
chip.className = 'ui-chip-button ui-chip-button--comment comment-reaction-chip';
|
|
1499
1555
|
chip.classList.toggle('is-active', hasLocalReaction(reaction, localUserId));
|
|
1500
1556
|
chip.setAttribute('aria-pressed', String(hasLocalReaction(reaction, localUserId)));
|
|
1501
1557
|
chip.title = reaction.users?.map((user) => user.userName).join(', ') || reaction.emoji;
|
|
@@ -1529,7 +1585,7 @@ export class CommentUiController {
|
|
|
1529
1585
|
|
|
1530
1586
|
const moreButton = document.createElement('button');
|
|
1531
1587
|
moreButton.type = 'button';
|
|
1532
|
-
moreButton.className = 'comment-reaction-more-trigger';
|
|
1588
|
+
moreButton.className = 'ui-chip-button ui-chip-button--comment comment-reaction-more-trigger';
|
|
1533
1589
|
moreButton.dataset.reactionPickerToggle = 'true';
|
|
1534
1590
|
moreButton.setAttribute('aria-expanded', String(
|
|
1535
1591
|
isReactionPickerOpen(this.reactionPicker, thread.id, message.id),
|
|
@@ -1560,7 +1616,7 @@ export class CommentUiController {
|
|
|
1560
1616
|
createQuickReactionButton(thread, message, emoji) {
|
|
1561
1617
|
const button = document.createElement('button');
|
|
1562
1618
|
button.type = 'button';
|
|
1563
|
-
button.className = 'comment-reaction-quick-add';
|
|
1619
|
+
button.className = 'ui-icon-chip ui-icon-chip--comment comment-reaction-quick-add';
|
|
1564
1620
|
button.textContent = emoji;
|
|
1565
1621
|
button.title = `React with ${emoji}`;
|
|
1566
1622
|
button.addEventListener('click', async () => {
|
|
@@ -1585,7 +1641,7 @@ export class CommentUiController {
|
|
|
1585
1641
|
createReactionPickerButton(thread, message, emoji) {
|
|
1586
1642
|
const button = document.createElement('button');
|
|
1587
1643
|
button.type = 'button';
|
|
1588
|
-
button.className = 'comment-reaction-picker-btn';
|
|
1644
|
+
button.className = 'ui-icon-chip ui-icon-chip--comment comment-reaction-picker-btn';
|
|
1589
1645
|
button.textContent = emoji;
|
|
1590
1646
|
button.title = `React with ${emoji}`;
|
|
1591
1647
|
button.addEventListener('click', async () => {
|
|
@@ -1609,11 +1665,11 @@ export class CommentUiController {
|
|
|
1609
1665
|
textarea.placeholder = 'Reply to thread...';
|
|
1610
1666
|
|
|
1611
1667
|
const actions = document.createElement('div');
|
|
1612
|
-
actions.className = 'comment-card-actions';
|
|
1668
|
+
actions.className = 'ui-record-actions comment-card-actions';
|
|
1613
1669
|
|
|
1614
1670
|
const cancel = document.createElement('button');
|
|
1615
1671
|
cancel.type = 'button';
|
|
1616
|
-
cancel.className = 'btn btn-secondary';
|
|
1672
|
+
cancel.className = 'ui-button btn btn-secondary';
|
|
1617
1673
|
cancel.textContent = 'Cancel';
|
|
1618
1674
|
cancel.addEventListener('click', () => {
|
|
1619
1675
|
this.activeCard = {
|
|
@@ -1625,7 +1681,7 @@ export class CommentUiController {
|
|
|
1625
1681
|
|
|
1626
1682
|
const submit = document.createElement('button');
|
|
1627
1683
|
submit.type = 'submit';
|
|
1628
|
-
submit.className = 'btn btn-primary';
|
|
1684
|
+
submit.className = 'ui-button btn btn-primary';
|
|
1629
1685
|
submit.textContent = 'Reply';
|
|
1630
1686
|
|
|
1631
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);
|