@anthusai/papyrus 1.2.0-next.2 → 1.2.0-next.4
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.
|
@@ -2194,6 +2194,62 @@ export async function loadNewsroomContentItemVersions(): Promise<ContentItemVers
|
|
|
2194
2194
|
return versionsPerStage.flat();
|
|
2195
2195
|
}
|
|
2196
2196
|
|
|
2197
|
+
const PUBLISHED_ITEMS_QUERY = `
|
|
2198
|
+
query ListPublishedItems($limit: Int, $nextToken: String) {
|
|
2199
|
+
listPublishedItems(limit: $limit, nextToken: $nextToken) {
|
|
2200
|
+
items { sourceItemId itemLineageId versionNumber type status title headline slug section publishedAt }
|
|
2201
|
+
nextToken
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
`;
|
|
2205
|
+
|
|
2206
|
+
type PublishedItemBoardRow = {
|
|
2207
|
+
sourceItemId: string;
|
|
2208
|
+
itemLineageId: string;
|
|
2209
|
+
versionNumber: number;
|
|
2210
|
+
type: string;
|
|
2211
|
+
status: string;
|
|
2212
|
+
title?: string | null;
|
|
2213
|
+
headline?: string | null;
|
|
2214
|
+
slug: string;
|
|
2215
|
+
section?: string | null;
|
|
2216
|
+
publishedAt?: string | null;
|
|
2217
|
+
};
|
|
2218
|
+
|
|
2219
|
+
export async function loadNewsroomPublishedContentItemVersions(): Promise<ContentItemVersionRecord[]> {
|
|
2220
|
+
configureAmplifyClient();
|
|
2221
|
+
const client = generateClient<Schema>() as unknown as {
|
|
2222
|
+
graphql: (options: Record<string, unknown>) => Promise<GraphQLConnectionResponse<PublishedItemBoardRow>>;
|
|
2223
|
+
};
|
|
2224
|
+
const rows: PublishedItemBoardRow[] = [];
|
|
2225
|
+
let nextToken: string | null = null;
|
|
2226
|
+
do {
|
|
2227
|
+
const response: GraphQLConnectionResponse<PublishedItemBoardRow> = await client.graphql({
|
|
2228
|
+
query: PUBLISHED_ITEMS_QUERY,
|
|
2229
|
+
variables: { limit: NEWSROOM_PAGE_LIMIT, nextToken },
|
|
2230
|
+
authMode: USER_POOL_AUTH_MODE,
|
|
2231
|
+
});
|
|
2232
|
+
assertNoGraphQLErrors(response.errors);
|
|
2233
|
+
const connection = response.data?.listPublishedItems;
|
|
2234
|
+
if (!connection) throw new Error("Missing GraphQL connection payload for listPublishedItems.");
|
|
2235
|
+
rows.push(...((connection.items ?? []).filter(Boolean) as PublishedItemBoardRow[]));
|
|
2236
|
+
nextToken = connection.nextToken ?? null;
|
|
2237
|
+
} while (nextToken);
|
|
2238
|
+
return rows.map(row => ({
|
|
2239
|
+
id: row.sourceItemId,
|
|
2240
|
+
lineageId: row.itemLineageId,
|
|
2241
|
+
versionNumber: row.versionNumber,
|
|
2242
|
+
stage: "published",
|
|
2243
|
+
type: row.type,
|
|
2244
|
+
status: row.status,
|
|
2245
|
+
title: row.title,
|
|
2246
|
+
headline: row.headline,
|
|
2247
|
+
slug: row.slug,
|
|
2248
|
+
section: row.section,
|
|
2249
|
+
updatedAt: row.publishedAt,
|
|
2250
|
+
}));
|
|
2251
|
+
}
|
|
2252
|
+
|
|
2197
2253
|
export async function loadModelPayloadsForOwner(
|
|
2198
2254
|
ownerKind: string,
|
|
2199
2255
|
ownerId: string,
|
|
@@ -6,9 +6,10 @@ import { IssueCollection, IssueDetailPanel, IssueTypeFilter, issueTypeFromFilter
|
|
|
6
6
|
import "@kanbus/ui/styles/kanban.css";
|
|
7
7
|
import "./newsroom-newsprint-board.css";
|
|
8
8
|
import { canReadNewsroomBoard, contentItemBoardType, newsroomBoardConfig, projectNewsroomBoard, type NativeBoardIssue } from "../lib/newsroom-board";
|
|
9
|
+
import type { AssignmentRecord } from "../lib/category-repository";
|
|
9
10
|
import { getContentItemName, SITE_BRAND } from "../lib/site-brand";
|
|
10
11
|
import { getNewsroomNavHref } from "../lib/newsroom-nav";
|
|
11
|
-
import { loadNewsroomAssignmentPage, loadNewsroomContentItemVersions,
|
|
12
|
+
import { loadNewsroomAssignmentPage, loadNewsroomContentItemVersions, loadNewsroomPublishedContentItemVersions } from "./news-desk-taxonomy-client";
|
|
12
13
|
import { useOptionalNewsDeskClient } from "./news-desk-client-provider";
|
|
13
14
|
import { NewsDeskAccessGate } from "./newsroom-access-gate";
|
|
14
15
|
import { NewsroomOpsShell, NewsroomOpsStatusBanner } from "./newsroom-ops-shell";
|
|
@@ -17,6 +18,17 @@ import { readLocalReaderSettings, subscribeReaderSettingsChanges } from "./reade
|
|
|
17
18
|
|
|
18
19
|
const STATIC_MOTION = { mode: "static" } as const;
|
|
19
20
|
|
|
21
|
+
async function loadEveryNewsroomAssignment() {
|
|
22
|
+
const assignments: AssignmentRecord[] = [];
|
|
23
|
+
let nextToken: string | null | undefined;
|
|
24
|
+
do {
|
|
25
|
+
const page = await loadNewsroomAssignmentPage({ limit: 100, nextToken });
|
|
26
|
+
assignments.push(...page.items);
|
|
27
|
+
nextToken = page.nextToken;
|
|
28
|
+
} while (nextToken);
|
|
29
|
+
return assignments;
|
|
30
|
+
}
|
|
31
|
+
|
|
20
32
|
/** Read-only shared board over authenticated CMS records; existing workspaces own every mutation. */
|
|
21
33
|
export function NewsroomBoardView() {
|
|
22
34
|
const session = useOptionalNewsDeskClient();
|
|
@@ -30,47 +42,27 @@ export function NewsroomBoardView() {
|
|
|
30
42
|
const [selected, setSelected] = useState<string | null>(null);
|
|
31
43
|
const [loading, setLoading] = useState(false);
|
|
32
44
|
const [error, setError] = useState<string | null>(null);
|
|
33
|
-
const [tokens, setTokens] = useState<{ reference?: string | null; assignment?: string | null }>({});
|
|
34
45
|
const generation = useRef(0);
|
|
35
|
-
const busy = useRef(false);
|
|
36
46
|
useEffect(() => {
|
|
37
47
|
setLowMotion(readLocalReaderSettings().motion === "low");
|
|
38
48
|
return subscribeReaderSettingsChanges(settings => setLowMotion(settings.motion === "low"));
|
|
39
49
|
}, []);
|
|
40
50
|
useEffect(() => {
|
|
41
51
|
const request = ++generation.current;
|
|
42
|
-
|
|
43
|
-
setIssues([]); setSelected(null); setTokens({}); setError(null);
|
|
52
|
+
setIssues([]); setSelected(null); setError(null);
|
|
44
53
|
if (!allowed) { setLoading(false); return; }
|
|
45
|
-
setLoading(true);
|
|
54
|
+
setLoading(true);
|
|
46
55
|
Promise.all([
|
|
47
|
-
loadNewsroomContentItemVersions(),
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
]).then(([contentItemVersions, references, assignments]) => {
|
|
56
|
+
Promise.all([loadNewsroomContentItemVersions(), loadNewsroomPublishedContentItemVersions()]).then(([workingVersions, publishedVersions]) => [...workingVersions, ...publishedVersions]),
|
|
57
|
+
loadEveryNewsroomAssignment(),
|
|
58
|
+
]).then(([contentItemVersions, assignments]) => {
|
|
51
59
|
if (generation.current !== request) return;
|
|
52
|
-
setIssues(projectNewsroomBoard({ contentItemVersions,
|
|
53
|
-
setTokens({ reference: references.nextToken, assignment: assignments.nextToken });
|
|
60
|
+
setIssues(projectNewsroomBoard({ contentItemVersions, assignments }));
|
|
54
61
|
}).catch((reason: unknown) => {
|
|
55
62
|
if (generation.current === request) setError(reason instanceof Error ? reason.message : "Could not load newsroom records.");
|
|
56
|
-
}).finally(() => { if (generation.current === request) { setLoading(false);
|
|
63
|
+
}).finally(() => { if (generation.current === request) { setLoading(false); } });
|
|
57
64
|
return () => { generation.current = request + 1; };
|
|
58
65
|
}, [allowed, revision]);
|
|
59
|
-
async function loadMore(kind: "reference" | "assignment") {
|
|
60
|
-
if (!allowed || busy.current || !tokens[kind]) return;
|
|
61
|
-
const request = generation.current;
|
|
62
|
-
busy.current = true; setLoading(true); setError(null);
|
|
63
|
-
try {
|
|
64
|
-
const added = kind === "reference"
|
|
65
|
-
? await loadNewsroomReferencePage({ limit: 50, nextToken: tokens.reference }).then(page => ({ issues: projectNewsroomBoard({ references: page.items }), token: page.nextToken }))
|
|
66
|
-
: await loadNewsroomAssignmentPage({ limit: 50, nextToken: tokens.assignment }).then(page => ({ issues: projectNewsroomBoard({ assignments: page.items }), token: page.nextToken }));
|
|
67
|
-
if (generation.current !== request) return;
|
|
68
|
-
setIssues(current => [...new Map([...current, ...added.issues].map(issue => [issue.id, issue])).values()]);
|
|
69
|
-
setTokens(current => ({ ...current, [kind]: added.token }));
|
|
70
|
-
} catch (reason) {
|
|
71
|
-
if (generation.current === request) setError(reason instanceof Error ? reason.message : "Could not load more records.");
|
|
72
|
-
} finally { if (generation.current === request) { setLoading(false); busy.current = false; } }
|
|
73
|
-
}
|
|
74
66
|
const config = useMemo(() => newsroomBoardConfig(issues), [issues]);
|
|
75
67
|
const issue = issues.find(item => item.id === selected);
|
|
76
68
|
if (!allowed) return <NewsDeskAccessGate shell={session?.shell ?? null} />;
|
|
@@ -82,7 +74,7 @@ export function NewsroomBoardView() {
|
|
|
82
74
|
</div>
|
|
83
75
|
<p className="organizer-board-note">{`Each ${getContentItemName().singular.toLowerCase()} appears once, in the column for its stage. Open a record to use its existing editor or workflow controls.`}</p>
|
|
84
76
|
{error && <NewsroomOpsStatusBanner tone="error">{error}</NewsroomOpsStatusBanner>}
|
|
85
|
-
<p role="status" className="organizer-board-note">{loading ? "Loading records…" : `${issues.length} records loaded
|
|
77
|
+
<p role="status" className="organizer-board-note">{loading ? "Loading records…" : `${issues.length} records loaded.`}</p>
|
|
86
78
|
<div className="organizer-board-body" data-detail-open={Boolean(issue)}>
|
|
87
79
|
<IssueCollection issues={issues} config={config} query={{ type: issueTypeFromFilter(filter) }} view={view}
|
|
88
80
|
motion={lowMotion ? STATIC_MOTION : undefined}
|
|
@@ -93,7 +85,6 @@ export function NewsroomBoardView() {
|
|
|
93
85
|
<Link href={getNewsroomNavHref(issue.href)}>Open {issue.type}</Link>
|
|
94
86
|
</IssueDetailPanel>}
|
|
95
87
|
</div>
|
|
96
|
-
<div className="organizer-board-pages">{tokens.reference && <button disabled={loading} type="button" onClick={() => void loadMore("reference")}>Load more references</button>}{tokens.assignment && <button disabled={loading} type="button" onClick={() => void loadMore("assignment")}>Load more assignments</button>}</div>
|
|
97
88
|
</div>
|
|
98
89
|
</NewsroomOpsShell>;
|
|
99
90
|
}
|
|
@@ -37,8 +37,8 @@ body:has(.newsroom-organizer)>[data-staging-banner]{flex-shrink:0}
|
|
|
37
37
|
.organizer-main:has(.organizer-native-board){display:flex;overflow:hidden}
|
|
38
38
|
.organizer-native-board{display:flex;flex-direction:column;flex:1;min-height:0;min-width:0;gap:12px}
|
|
39
39
|
.organizer-board-toolbar{display:flex;align-items:center;justify-content:space-between;gap:12px;min-width:0;flex-wrap:wrap}
|
|
40
|
-
.organizer-board-toolbar [role=group]
|
|
41
|
-
.organizer-board-toolbar button
|
|
40
|
+
.organizer-board-toolbar [role=group]{display:flex;gap:8px;flex-wrap:wrap}
|
|
41
|
+
.organizer-board-toolbar button{min-height:44px;padding:8px 12px;border:1px solid var(--org-line)}
|
|
42
42
|
.organizer-board-toolbar button[aria-pressed=true]{background:var(--org-ink);color:var(--org-paper)}
|
|
43
43
|
.organizer-board-note{font-size:11px;line-height:1.5;margin:0;color:var(--org-muted)}
|
|
44
44
|
.organizer-board-body{position:relative;display:grid;grid-template-columns:minmax(0,1fr);flex:1;min-width:0;min-height:0;gap:16px}
|
package/lib/newsroom-board.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { KanbanConfig, KanbanIssue } from "@kanbus/ui";
|
|
2
|
-
import type { AssignmentRecord
|
|
2
|
+
import type { AssignmentRecord } from "./category-repository";
|
|
3
3
|
import {
|
|
4
4
|
contentItemStageKeys,
|
|
5
5
|
contentItemStageLabel,
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
} from "./content-item-lifecycle";
|
|
9
9
|
import { getContentItemName, getContentItemStageDefinitions } from "./site-brand";
|
|
10
10
|
|
|
11
|
-
export type NativeBoardKind = "contentItem" | "
|
|
11
|
+
export type NativeBoardKind = "contentItem" | "assignment";
|
|
12
12
|
export interface NativeBoardIssue extends KanbanIssue {
|
|
13
13
|
kind: NativeBoardKind;
|
|
14
14
|
nativeId: string;
|
|
@@ -26,12 +26,11 @@ function boardTypeForKind(kind: NativeBoardKind): string {
|
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* One card per content item, in the column for its lifecycle stage. Assignments that are work on a content item
|
|
29
|
-
* are counted on that item's card rather than shown as separate cards.
|
|
30
|
-
*
|
|
29
|
+
* are counted on that item's card rather than shown as separate cards. Other assignments keep their own identity and
|
|
30
|
+
* native status. References are not shown on the board.
|
|
31
31
|
*/
|
|
32
32
|
export function projectNewsroomBoard(records: {
|
|
33
33
|
contentItemVersions?: readonly ContentItemVersionRecord[];
|
|
34
|
-
references?: readonly ReferenceRecord[];
|
|
35
34
|
assignments?: readonly AssignmentRecord[];
|
|
36
35
|
}): NativeBoardIssue[] {
|
|
37
36
|
const assignmentCountByContentItem = new Map<string, number>();
|
|
@@ -52,14 +51,6 @@ export function projectNewsroomBoard(records: {
|
|
|
52
51
|
updated_at: version.updatedAt ?? undefined, href: `/newsroom/articles/${encodeURIComponent(version.id)}`,
|
|
53
52
|
};
|
|
54
53
|
}),
|
|
55
|
-
...(records.references ?? []).filter(row => !row.versionState || row.versionState === "current").map(row => ({
|
|
56
|
-
id: `reference:${row.id}`, nativeId: row.id, kind: "reference" as const, type: "reference",
|
|
57
|
-
title: row.title || row.externalItemId || row.id,
|
|
58
|
-
nativeStatus: row.curationStatus || "pending", status: `reference/${row.curationStatus || "pending"}`,
|
|
59
|
-
priority: 2, description: row.curationStatusReason || undefined,
|
|
60
|
-
updated_at: row.updatedAt || row.importedAt || undefined,
|
|
61
|
-
href: `/newsroom/references/${encodeURIComponent(row.lineageId || row.id)}`,
|
|
62
|
-
})),
|
|
63
54
|
...(records.assignments ?? []).filter(row => !row.itemLineageId).map(row => ({
|
|
64
55
|
id: `assignment:${row.id}`, nativeId: row.id, kind: "assignment" as const, type: "assignment",
|
|
65
56
|
title: row.title, nativeStatus: row.status, status: `assignment/${row.status}`,
|
|
@@ -83,7 +74,7 @@ export function contentItemColumnOrder(
|
|
|
83
74
|
/** Content items read left to right through the lifecycle with the final stage last; other record kinds follow it. */
|
|
84
75
|
export function newsroomBoardConfig(issues: readonly NativeBoardIssue[]): KanbanConfig {
|
|
85
76
|
const contentItemType = contentItemBoardType();
|
|
86
|
-
const kinds: NativeBoardKind[] = ["contentItem", "
|
|
77
|
+
const kinds: NativeBoardKind[] = ["contentItem", "assignment"];
|
|
87
78
|
const contentItemStatuses = contentItemColumnOrder(issues.filter(issue => issue.kind === "contentItem").map(issue => issue.nativeStatus))
|
|
88
79
|
.map(stage => ({ key: `${contentItemType}/${stage}`, name: contentItemStageLabel(stage, getContentItemStageDefinitions()), category: "native" }));
|
|
89
80
|
const otherStatuses = [...new Set(issues.filter(issue => issue.kind !== "contentItem").map(issue => issue.status))]
|
package/package.json
CHANGED
package/routes.manifest.json
CHANGED