mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-18 19:50:38 +09:00
Refine issue workflow surfaces and live updates
This commit is contained in:
parent
b4a58ba8a6
commit
03dff1a29a
48 changed files with 2800 additions and 1163 deletions
|
|
@ -1,3 +1,5 @@
|
|||
import type { Issue } from "@paperclipai/shared";
|
||||
|
||||
type IssueDetailSource = "issues" | "inbox";
|
||||
|
||||
type IssueDetailBreadcrumb = {
|
||||
|
|
@ -5,10 +7,23 @@ type IssueDetailBreadcrumb = {
|
|||
href: string;
|
||||
};
|
||||
|
||||
export type IssueDetailHeaderSeed = {
|
||||
id: string;
|
||||
identifier: string | null;
|
||||
title: string;
|
||||
status: Issue["status"];
|
||||
priority: Issue["priority"];
|
||||
projectId: string | null;
|
||||
projectName: string | null;
|
||||
originKind?: Issue["originKind"];
|
||||
originId?: string | null;
|
||||
};
|
||||
|
||||
type IssueDetailLocationState = {
|
||||
issueDetailBreadcrumb?: IssueDetailBreadcrumb;
|
||||
issueDetailSource?: IssueDetailSource;
|
||||
issueDetailInboxQuickArchiveArmed?: boolean;
|
||||
issueDetailHeaderSeed?: IssueDetailHeaderSeed;
|
||||
};
|
||||
|
||||
const ISSUE_DETAIL_SOURCE_QUERY_PARAM = "from";
|
||||
|
|
@ -25,6 +40,58 @@ function isIssueDetailSource(value: unknown): value is IssueDetailSource {
|
|||
return value === "issues" || value === "inbox";
|
||||
}
|
||||
|
||||
function isIssueDetailHeaderSeed(value: unknown): value is IssueDetailHeaderSeed {
|
||||
if (typeof value !== "object" || value === null) return false;
|
||||
const candidate = value as Partial<IssueDetailHeaderSeed>;
|
||||
const hasOriginKind =
|
||||
candidate.originKind === undefined || typeof candidate.originKind === "string";
|
||||
const hasOriginId =
|
||||
candidate.originId === undefined || candidate.originId === null || typeof candidate.originId === "string";
|
||||
return (
|
||||
typeof candidate.id === "string"
|
||||
&& (candidate.identifier === null || typeof candidate.identifier === "string")
|
||||
&& typeof candidate.title === "string"
|
||||
&& typeof candidate.status === "string"
|
||||
&& typeof candidate.priority === "string"
|
||||
&& (candidate.projectId === null || typeof candidate.projectId === "string")
|
||||
&& (candidate.projectName === null || typeof candidate.projectName === "string")
|
||||
&& hasOriginKind
|
||||
&& hasOriginId
|
||||
);
|
||||
}
|
||||
|
||||
function createIssueDetailHeaderSeed(issue: Issue): IssueDetailHeaderSeed {
|
||||
return {
|
||||
id: issue.id,
|
||||
identifier: issue.identifier ?? null,
|
||||
title: issue.title,
|
||||
status: issue.status,
|
||||
priority: issue.priority,
|
||||
projectId: issue.projectId ?? null,
|
||||
projectName: issue.project?.name ?? null,
|
||||
originKind: issue.originKind,
|
||||
originId: issue.originId ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
export function withIssueDetailHeaderSeed(state: unknown, issue: Issue): IssueDetailLocationState {
|
||||
const headerSeed = createIssueDetailHeaderSeed(issue);
|
||||
if (typeof state !== "object" || state === null) {
|
||||
return { issueDetailHeaderSeed: headerSeed };
|
||||
}
|
||||
|
||||
return {
|
||||
...(state as IssueDetailLocationState),
|
||||
issueDetailHeaderSeed: headerSeed,
|
||||
};
|
||||
}
|
||||
|
||||
export function readIssueDetailHeaderSeed(state: unknown): IssueDetailHeaderSeed | null {
|
||||
if (typeof state !== "object" || state === null) return null;
|
||||
const candidate = (state as IssueDetailLocationState).issueDetailHeaderSeed;
|
||||
return isIssueDetailHeaderSeed(candidate) ? candidate : null;
|
||||
}
|
||||
|
||||
function readIssueDetailSource(state: unknown): IssueDetailSource | null {
|
||||
if (typeof state !== "object" || state === null) return null;
|
||||
const source = (state as IssueDetailLocationState).issueDetailSource;
|
||||
|
|
@ -96,10 +163,14 @@ function readStoredIssueDetailLocationState(issuePathId: string): IssueDetailLoc
|
|||
: null;
|
||||
const source = inferIssueDetailSource(parsed, breadcrumb);
|
||||
if (!breadcrumb || !source) return null;
|
||||
const headerSeed = isIssueDetailHeaderSeed(parsed.issueDetailHeaderSeed)
|
||||
? parsed.issueDetailHeaderSeed
|
||||
: undefined;
|
||||
return {
|
||||
issueDetailBreadcrumb: breadcrumb,
|
||||
issueDetailSource: source,
|
||||
issueDetailInboxQuickArchiveArmed: parsed.issueDetailInboxQuickArchiveArmed === true,
|
||||
issueDetailHeaderSeed: headerSeed,
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
|
|
@ -115,11 +186,13 @@ function normalizeIssueDetailLocationState(
|
|||
if (isIssueDetailBreadcrumb(candidate)) {
|
||||
const source = inferIssueDetailSource(state as Partial<IssueDetailLocationState>, candidate);
|
||||
if (!source) return null;
|
||||
const headerSeed = readIssueDetailHeaderSeed(state) ?? undefined;
|
||||
return {
|
||||
issueDetailBreadcrumb: candidate,
|
||||
issueDetailSource: source,
|
||||
issueDetailInboxQuickArchiveArmed:
|
||||
(state as IssueDetailLocationState).issueDetailInboxQuickArchiveArmed === true,
|
||||
issueDetailHeaderSeed: headerSeed,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue