mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-15 00:08:15 +00:00
Replaces the Pull Requests tool window with a full review workflow on top of gh's authentication. - List with search, quick filters and paging; a tab per pull request and a New Pull Request tab. - Details: commit picker, changes tree with viewed state, CI and reviewer status, merge, close and reopen. - Timeline editor: comments, review threads, reactions and suggested changes. - Review in the diff viewer and, for the checked-out branch, in the editor (review mode). - New generic vcs extension points: registerDiffViewerProvider, lineInlays, reviewGutter, branch widget extensions; new vcs_merge_base and gh_api commands. - CSP: img-src allows https://avatars.githubusercontent.com for avatars. Fixed while testing against a real repository: remotes behind an SSH host alias, mergeability stuck on UNKNOWN, Apply Locally staying disabled after a checkout, relative links navigating the app's page, and review inlays wider than the pane. Co-authored-by: imacross <30753807+imacross@users.noreply.github.com>
87 lines
3.1 KiB
TypeScript
87 lines
3.1 KiB
TypeScript
/**
|
|
* When each pull request was last seen (IntelliJ's `GHPRPersistentInteractionState`),
|
|
* stored per project; the list's unread dots compare it with `updatedAt` (`isSeen` in
|
|
* `model.ts`). Entries of pull requests updated since and older than
|
|
* `CLEAR_LAST_SEEN_AFTER_DAYS` are dropped.
|
|
*/
|
|
import { create } from "zustand";
|
|
import { useProject } from "@/platform/project";
|
|
import type { GHPullRequestShort, GHUser } from "../api/types";
|
|
import { CLEAR_LAST_SEEN_AFTER_DAYS, isSeen } from "./model";
|
|
|
|
const key = (root: string) => `openidea.github.lastSeen.v1:${root}`;
|
|
const projectRoot = () => useProject.getState().project?.root ?? null;
|
|
|
|
function load(root: string | null): Record<string, number> {
|
|
if (!root) return {};
|
|
try {
|
|
const v = JSON.parse(globalThis.localStorage?.getItem(key(root)) ?? "{}") as unknown;
|
|
if (!v || typeof v !== "object") return {};
|
|
return Object.fromEntries(Object.entries(v).filter(([, t]) => typeof t === "number")) as Record<string, number>;
|
|
} catch {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
function save(root: string | null, lastSeen: Record<string, number>) {
|
|
if (!root) return;
|
|
try {
|
|
globalThis.localStorage?.setItem(key(root), JSON.stringify(lastSeen));
|
|
} catch {
|
|
/* storage unavailable */
|
|
}
|
|
}
|
|
|
|
/** Last-seen times (ms) by pull request node id, for the open project. */
|
|
export const useLastSeen = create<{ root: string | null; lastSeen: Record<string, number> }>(() => ({
|
|
root: projectRoot(),
|
|
lastSeen: load(projectRoot()),
|
|
}));
|
|
|
|
function current(): Record<string, number> {
|
|
const root = projectRoot();
|
|
if (useLastSeen.getState().root !== root) useLastSeen.setState({ root, lastSeen: load(root) });
|
|
return useLastSeen.getState().lastSeen;
|
|
}
|
|
|
|
/** Records that the pull request with node id `prId` was seen now. */
|
|
export function markSeen(prId: string, at = Date.now()) {
|
|
const lastSeen = { ...current(), [prId]: at };
|
|
useLastSeen.setState({ lastSeen });
|
|
save(projectRoot(), lastSeen);
|
|
}
|
|
|
|
/**
|
|
* `isSeen` with the stored time; forgets entries that are stale (unseen updates, older than 14 days).
|
|
* @public
|
|
*/
|
|
export function isPullRequestSeen(
|
|
pr: Pick<GHPullRequestShort, "id" | "author" | "reviewRequests" | "updatedAt">,
|
|
user: Pick<GHUser, "id">,
|
|
now = Date.now(),
|
|
): boolean {
|
|
const all = current();
|
|
const lastSeen = all[pr.id];
|
|
const seen = isSeen(pr, user, lastSeen);
|
|
if (!seen && lastSeen !== undefined && lastSeen < now - CLEAR_LAST_SEEN_AFTER_DAYS * 86_400_000) {
|
|
const { [pr.id]: _, ...rest } = all;
|
|
useLastSeen.setState({ lastSeen: rest });
|
|
save(projectRoot(), rest);
|
|
}
|
|
return seen;
|
|
}
|
|
|
|
/** React hook: whether `pr` is seen; re-renders when the last-seen times change. */
|
|
export function usePullRequestSeen(
|
|
pr: Pick<GHPullRequestShort, "id" | "author" | "reviewRequests" | "updatedAt">,
|
|
user: Pick<GHUser, "id"> | null | undefined,
|
|
): boolean {
|
|
const lastSeen = useLastSeen((s) => s.lastSeen[pr.id]);
|
|
return !user || isSeen(pr, user, lastSeen);
|
|
}
|
|
|
|
useProject.subscribe((s, prev) => {
|
|
const root = s.project?.root ?? null;
|
|
if (root !== (prev.project?.root ?? null)) useLastSeen.setState({ root, lastSeen: load(root) });
|
|
});
|