diff --git a/src/ui/ReviewSidebarView.ts b/src/ui/ReviewSidebarView.ts index d37f8f2..cd7a973 100644 --- a/src/ui/ReviewSidebarView.ts +++ b/src/ui/ReviewSidebarView.ts @@ -119,7 +119,7 @@ export class ReviewSidebarView extends ItemView { private draftText = ""; private pendingFocus: PendingFocus | null = null; private lastDraftKey: string | null = null; - private lastScrolledItemId: string | null = null; + private removeOutsideClickListener: (() => void) | null = null; private composerSubmits = new WeakMap void>(); private composerHintId = 0; @@ -325,22 +325,31 @@ export class ReviewSidebarView extends ItemView { private renderContent(): void { const root = this.contentEl; + // Emptying the scroller collapses it and zeroes scrollTop; put it back + // once the new content exists so a rebuild doesn't move the viewport. + const scrollTop = root.scrollTop; this.resetCommentRenderScope(); root.empty(); root.addClass("critic-sidebar"); + const selectedId = this.renderBody(root); + root.scrollTop = scrollTop; + this.scrollSelectedIntoView(root, selectedId); + } + /** Returns the id of the visibly selected card, if any. */ + private renderBody(root: HTMLElement): string | null { const externalState = this.plugin.getActiveExternalCommentState(); if (externalState) { this.renderHeader(root, externalState.title); this.renderExternalComments(root, externalState); - return; + return null; } const state = this.plugin.getActiveReviewState(); this.renderHeader(root, state?.file.basename ?? "Relay Comments"); if (!state) { this.renderEmptyState(root, "Open a Markdown note to review comments and suggestions."); - return; + return null; } const validMarks = state.marks @@ -371,7 +380,7 @@ export class ReviewSidebarView extends ItemView { "No comments or suggestions in this note yet. Select text and choose “Add comment” to start a discussion.", ); } - return; + return null; } const list = root.createDiv({ cls: "critic-sidebar-list" }); @@ -391,14 +400,16 @@ export class ReviewSidebarView extends ItemView { isSelected(item, state.activeMarkId), state.file.path, ); - if (selected && !visiblySelectedId) { + // The card the user picked wins over the one the editor cursor + // happens to sit in; both render as selected. + if (selected && (!visiblySelectedId || item.id === this.selectedItemId)) { visiblySelectedId = item.id; } } if (draft && !draftRendered) { this.renderDraft(list, draft); } - this.scrollSelectedIntoView(list, visiblySelectedId); + return visiblySelectedId; } private renderExternalComments( @@ -575,15 +586,14 @@ export class ReviewSidebarView extends ItemView { } private scrollSelectedIntoView( - list: HTMLElement, + root: HTMLElement, selectedId: string | null, ): void { - if (selectedId && selectedId !== this.lastScrolledItemId) { - list - .querySelector(`[data-critic-item-id="${CSS.escape(selectedId)}"]`) - ?.scrollIntoView({ block: "nearest" }); - } - this.lastScrolledItemId = selectedId; + if (!selectedId) return; + + root + .querySelector(`[data-critic-item-id="${CSS.escape(selectedId)}"]`) + ?.scrollIntoView({ block: "nearest" }); } private installOutsideClickListener(): void { @@ -697,9 +707,11 @@ export class ReviewSidebarView extends ItemView { const isThreadSelected = selected || this.selectedItemId === item.id || this.replyDraftItemId === item.id; const card = parent.createDiv({ - cls: isThreadSelected - ? "critic-card critic-card-selected" - : "critic-card", + cls: [ + "critic-card", + ...(isThreadSelected ? ["critic-card-selected"] : []), + ...(this.selectedItemId === item.id ? ["critic-card-current"] : []), + ], attr: { tabindex: "0", role: "button", diff --git a/styles.css b/styles.css index 7234544..b33952e 100644 --- a/styles.css +++ b/styles.css @@ -391,6 +391,15 @@ box-shadow: 0 3px 10px rgba(0, 0, 0, 0.14); } +/* The card the user picked (clicked, or reached from its anchor in the note) + takes the accent on its spine and border. A card that is merely under the + editor cursor keeps the softer selected look. */ +.critic-card.critic-card-current, +.critic-card.critic-card-current:hover { + --critic-rail: var(--interactive-accent); + border-color: var(--interactive-accent); +} + /* ---------- Card header ---------- */ .critic-comment-header {