feat(git): add commit-note support and previews in history
This change adds Git notes support to the history UI. Commits now carry a has_note flag which triggers a note indicator. Notes can be previewed on hover and loaded on demand, then the history can be refreshed after edits. - Adds has_note support on commits and parses from logs. - Renders a note indicator in history rows with a hover preview. - Triggers history refresh after note-related actions.
This commit is contained in:
Notes:
Christoph Brandau
2026-08-13 22:56:29 +02:00
one more test
@@ -80,6 +80,7 @@
|
||||
onCherryPickCommit: (commit: GitCommit) => void;
|
||||
onRevertCommit: (commit: GitCommit) => void;
|
||||
onOpenCommitNote: (commit: GitCommit) => void;
|
||||
onLoadCommitNote: (commit: GitCommit) => Promise<string | null>;
|
||||
onSelectCommit: (commit: GitCommit) => void;
|
||||
}
|
||||
|
||||
@@ -108,6 +109,7 @@
|
||||
onCherryPickCommit = () => {},
|
||||
onRevertCommit = () => {},
|
||||
onOpenCommitNote = () => {},
|
||||
onLoadCommitNote = async () => null,
|
||||
onSelectCommit = () => {},
|
||||
}: Props = $props();
|
||||
|
||||
@@ -120,6 +122,16 @@
|
||||
let contextCommit = $state<GitCommit | null>(null);
|
||||
let contextMenuX = $state(0);
|
||||
let contextMenuY = $state(0);
|
||||
let notePreviews = $state<Record<string, string>>({});
|
||||
let notePreviewLoading = $state<Set<string>>(new Set());
|
||||
let notePreviewErrors = $state<Set<string>>(new Set());
|
||||
|
||||
$effect(() => {
|
||||
repositoryKey;
|
||||
notePreviews = {};
|
||||
notePreviewLoading = new Set();
|
||||
notePreviewErrors = new Set();
|
||||
});
|
||||
|
||||
function observeHistoryEnd(node: HTMLElement) {
|
||||
const root = node.closest<HTMLElement>(".history-list");
|
||||
@@ -503,6 +515,34 @@
|
||||
await onOpenCommitNote(commit);
|
||||
}
|
||||
|
||||
async function loadCommitNotePreview(commit: GitCommit) {
|
||||
if (!commit.has_note || notePreviewLoading.has(commit.hash)) return;
|
||||
|
||||
const loading = new Set(notePreviewLoading);
|
||||
loading.add(commit.hash);
|
||||
notePreviewLoading = loading;
|
||||
|
||||
const errors = new Set(notePreviewErrors);
|
||||
errors.delete(commit.hash);
|
||||
notePreviewErrors = errors;
|
||||
|
||||
try {
|
||||
const note = await onLoadCommitNote(commit);
|
||||
notePreviews = {
|
||||
...notePreviews,
|
||||
[commit.hash]: note?.trim() || "This Git note is empty.",
|
||||
};
|
||||
} catch {
|
||||
const nextErrors = new Set(notePreviewErrors);
|
||||
nextErrors.add(commit.hash);
|
||||
notePreviewErrors = nextErrors;
|
||||
} finally {
|
||||
const nextLoading = new Set(notePreviewLoading);
|
||||
nextLoading.delete(commit.hash);
|
||||
notePreviewLoading = nextLoading;
|
||||
}
|
||||
}
|
||||
|
||||
function handleWindowKeydown(event: KeyboardEvent) {
|
||||
if (event.key !== "Escape") return;
|
||||
closeCommitContextMenu();
|
||||
@@ -792,6 +832,7 @@
|
||||
<article
|
||||
class="commit-row graph-row"
|
||||
class:selected={selectedCommitHash === item.hash}
|
||||
class:has-note={item.has_note}
|
||||
data-commit-hash={item.hash}
|
||||
class:graph-ahead-row={rowSyncClass === "ahead"}
|
||||
class:graph-behind-row={rowSyncClass === "behind"}
|
||||
@@ -852,6 +893,9 @@
|
||||
class:has-branch-ref={Boolean(refSummary.primaryBranch)}
|
||||
style={`--ref-lane-color:${row?.dotColor ?? GRAPH_COLORS[0]}`}
|
||||
>
|
||||
{#if item.has_note}
|
||||
<span class="commit-note-rail" aria-hidden="true"></span>
|
||||
{/if}
|
||||
{#if refSummary.primaryBranch || refSummary.primaryTag || refSummary.overflowCount > 0}
|
||||
<div class="commit-ref-area">
|
||||
<div class="commit-ref-strip" aria-label="Commit references">
|
||||
@@ -966,6 +1010,43 @@
|
||||
<div class="commit-meta-line">
|
||||
<span class="commit-hash">{item.short_hash}</span>
|
||||
<span class="commit-author" title={item.author_email}>{item.author_name}</span>
|
||||
{#if item.has_note}
|
||||
<span class="commit-note-indicator">
|
||||
<button
|
||||
class="commit-note-presence"
|
||||
type="button"
|
||||
onpointerenter={() => void loadCommitNotePreview(item)}
|
||||
onfocus={() => void loadCommitNotePreview(item)}
|
||||
onclick={() => openCommitNote(item)}
|
||||
disabled={isBusy}
|
||||
aria-label={`Open Git note for ${item.short_hash}`}
|
||||
aria-describedby={`commit-note-preview-${item.hash}`}
|
||||
>
|
||||
<StickyNote size={11} aria-hidden="true" />
|
||||
<span>Note</span>
|
||||
</button>
|
||||
<span
|
||||
class="commit-note-tooltip"
|
||||
id={`commit-note-preview-${item.hash}`}
|
||||
role="tooltip"
|
||||
>
|
||||
<span class="commit-note-tooltip-head">
|
||||
<StickyNote size={12} aria-hidden="true" />
|
||||
Git Note
|
||||
<small>Click to open</small>
|
||||
</span>
|
||||
<span class="commit-note-tooltip-body">
|
||||
{#if notePreviewLoading.has(item.hash)}
|
||||
Loading note…
|
||||
{:else if notePreviewErrors.has(item.hash)}
|
||||
Note could not be loaded.
|
||||
{:else}
|
||||
{notePreviews[item.hash] ?? "Hover to load the note."}
|
||||
{/if}
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1008,16 +1089,18 @@
|
||||
<div class="commit-actions">
|
||||
<time class="commit-time" datetime={item.date}>{formatCommitDate(item.date)}</time>
|
||||
<div class="commit-action-buttons">
|
||||
<button
|
||||
class="commit-menu-button commit-note-button"
|
||||
type="button"
|
||||
onclick={() => openCommitNote(item)}
|
||||
disabled={isBusy}
|
||||
title={`Open internal note for ${item.short_hash}`}
|
||||
aria-label={`Open internal note for ${item.short_hash}`}
|
||||
>
|
||||
<StickyNote size={14} aria-hidden="true" />
|
||||
</button>
|
||||
{#if !item.has_note}
|
||||
<button
|
||||
class="commit-menu-button commit-note-button"
|
||||
type="button"
|
||||
onclick={() => openCommitNote(item)}
|
||||
disabled={isBusy}
|
||||
title={`Add a Git note to ${item.short_hash}`}
|
||||
aria-label={`Add a Git note to ${item.short_hash}`}
|
||||
>
|
||||
<StickyNote size={14} aria-hidden="true" />
|
||||
</button>
|
||||
{/if}
|
||||
<button
|
||||
class="commit-menu-button"
|
||||
type="button"
|
||||
|
||||
Reference in New Issue
Block a user