@anthusai/papyrus 1.4.0-next.2 → 1.4.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.
- package/amplify/functions/shared/python-bundle.js +3 -3
- package/amplify/functions/shared/python-bundle.ts +3 -3
- package/app/newsroom/sections/[sectionId]/page.tsx +3 -0
- package/components/news-desk-taxonomy-client.ts +36 -29
- package/components/newsroom-article-editor.tsx +3 -1
- package/components/newsroom-assignment-detail.tsx +45 -0
- package/components/newsroom-board-view.tsx +97 -48
- package/components/newsroom-organizer.css +28 -7
- package/components/newsroom-topics-view.tsx +1 -1
- package/components/topic-steering-workspace.tsx +1 -1
- package/components/ui/sheet.tsx +9 -4
- package/components/use-paged-content-item-versions.ts +151 -0
- package/lib/approved-priming-proof-reports.ts +6 -3
- package/lib/approved-priming-proofs/papyrus.json +90 -0
- package/lib/blog-feature-solver.ts +1 -109
- package/lib/content-actions-demo.ts +3 -0
- package/lib/front-mosaic.ts +2 -1
- package/lib/layout-plan.ts +3 -2
- package/lib/layout-warnings.ts +8 -0
- package/lib/local-preview-seed.ts +20 -0
- package/lib/newsroom-assignments.ts +13 -0
- package/lib/newsroom-board.ts +25 -0
- package/lib/prospectus-account-client.ts +64 -0
- package/lib/site-stack.ts +1 -1
- package/package.json +8 -1
- package/renderers/pretext/rhythm-article-page.tsx +1 -18
- package/routes.manifest.json +1 -1
|
@@ -126,9 +126,9 @@ function bundleFromPip(outputDir, options) {
|
|
|
126
126
|
}
|
|
127
127
|
export function bundleFromCheckout(outputDir, options) {
|
|
128
128
|
fs.copyFileSync(path.join(packageRoot, "amplify/functions", options.functionDir, "handler.py"), path.join(outputDir, "handler.py"));
|
|
129
|
-
for (const
|
|
130
|
-
fs.mkdirSync(path.join(outputDir,
|
|
131
|
-
fs.cpSync(path.join(packageRoot, "src",
|
|
129
|
+
for (const moduleName of options.modules) {
|
|
130
|
+
fs.mkdirSync(path.join(outputDir, moduleName), { recursive: true });
|
|
131
|
+
fs.cpSync(path.join(packageRoot, "src", moduleName), path.join(outputDir, moduleName), { recursive: true });
|
|
132
132
|
}
|
|
133
133
|
options.checkoutExtras?.(outputDir);
|
|
134
134
|
copySiteFiles(outputDir, options.siteFiles);
|
|
@@ -164,9 +164,9 @@ export function bundleFromCheckout(outputDir: string, options: PythonBundleOptio
|
|
|
164
164
|
path.join(packageRoot, "amplify/functions", options.functionDir, "handler.py"),
|
|
165
165
|
path.join(outputDir, "handler.py"),
|
|
166
166
|
);
|
|
167
|
-
for (const
|
|
168
|
-
fs.mkdirSync(path.join(outputDir,
|
|
169
|
-
fs.cpSync(path.join(packageRoot, "src",
|
|
167
|
+
for (const moduleName of options.modules) {
|
|
168
|
+
fs.mkdirSync(path.join(outputDir, moduleName), { recursive: true });
|
|
169
|
+
fs.cpSync(path.join(packageRoot, "src", moduleName), path.join(outputDir, moduleName), { recursive: true });
|
|
170
170
|
}
|
|
171
171
|
options.checkoutExtras?.(outputDir);
|
|
172
172
|
copySiteFiles(outputDir, options.siteFiles);
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { notFound } from "next/navigation";
|
|
2
|
+
import { resolveActiveSiteBrand } from "../../../../lib/site-brand-tenant";
|
|
3
|
+
import { NewsroomBoardView } from "../../../../components/newsroom-board-view";
|
|
2
4
|
import { NewsDeskPage } from "../../../../components/news-desk-page";
|
|
3
5
|
|
|
4
6
|
export const dynamic = "force-dynamic";
|
|
@@ -13,5 +15,6 @@ export default async function NewsroomDeepSectionPage({ params, searchParams }:
|
|
|
13
15
|
const normalizedSectionId = decodeURIComponent(sectionId ?? "").trim();
|
|
14
16
|
if (!normalizedSectionId) notFound();
|
|
15
17
|
|
|
18
|
+
if (resolveActiveSiteBrand().newsroomLayout === "organizer") return <NewsroomBoardView subject="desk" deskId={normalizedSectionId} />;
|
|
16
19
|
return <NewsDeskPage sectionPageId={normalizedSectionId} searchParams={searchParams} />;
|
|
17
20
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { isEditorialWorkAssignment } from "../lib/newsroom-assignments";
|
|
3
4
|
import { isLocalPreviewEnabled } from "../lib/local-preview";
|
|
4
5
|
import { createLocalPreviewAssignments, createLocalPreviewContentItemVersions, createLocalPreviewReferences } from "../lib/local-preview-seed";
|
|
5
6
|
import { generateClient } from "aws-amplify/data";
|
|
@@ -2156,10 +2157,10 @@ export async function loadNewsroomAssignmentPage(options: NewsroomAssignmentPage
|
|
|
2156
2157
|
newsroomFeedKey: "assignments",
|
|
2157
2158
|
limit: options.limit,
|
|
2158
2159
|
nextToken: options.nextToken,
|
|
2159
|
-
matches: equalityMatcher({
|
|
2160
|
+
matches: (assignment) => isEditorialWorkAssignment(assignment) && (equalityMatcher<AssignmentRecord>({
|
|
2160
2161
|
assignmentTypeKey: options.type,
|
|
2161
2162
|
status: options.status,
|
|
2162
|
-
}),
|
|
2163
|
+
})?.(assignment) ?? true),
|
|
2163
2164
|
});
|
|
2164
2165
|
}
|
|
2165
2166
|
|
|
@@ -2172,40 +2173,45 @@ const CONTENT_ITEMS_BY_STAGE_QUERY = `
|
|
|
2172
2173
|
}
|
|
2173
2174
|
`;
|
|
2174
2175
|
|
|
2175
|
-
export
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2176
|
+
export const CONTENT_ITEM_COLUMN_PAGE_LIMIT = 25;
|
|
2177
|
+
|
|
2178
|
+
export type ContentItemStagePage = {
|
|
2179
|
+
versions: ContentItemVersionRecord[];
|
|
2180
|
+
nextToken: string | null;
|
|
2181
|
+
};
|
|
2182
|
+
|
|
2183
|
+
/** One newest-updated-first page of the content items in one stage, so a board column can load as it is scrolled. */
|
|
2184
|
+
export async function loadNewsroomContentItemStagePage(
|
|
2185
|
+
stage: string,
|
|
2186
|
+
nextToken: string | null = null,
|
|
2187
|
+
limit: number = CONTENT_ITEM_COLUMN_PAGE_LIMIT,
|
|
2188
|
+
): Promise<ContentItemStagePage> {
|
|
2183
2189
|
if (isLocalPreviewEnabled()) {
|
|
2184
|
-
|
|
2190
|
+
const versionsInStage = createLocalPreviewContentItemVersions().filter(version => version.stage === stage);
|
|
2191
|
+
const offset = nextToken ? Number(nextToken) : 0;
|
|
2192
|
+
const followingOffset = offset + limit;
|
|
2193
|
+
return {
|
|
2194
|
+
versions: versionsInStage.slice(offset, followingOffset),
|
|
2195
|
+
nextToken: followingOffset < versionsInStage.length ? String(followingOffset) : null,
|
|
2196
|
+
};
|
|
2185
2197
|
}
|
|
2186
2198
|
configureAmplifyClient();
|
|
2187
2199
|
const client = generateClient<Schema>() as unknown as {
|
|
2188
2200
|
graphql: (options: Record<string, unknown>) => Promise<GraphQLConnectionResponse<ContentItemVersionRecord>>;
|
|
2189
2201
|
};
|
|
2190
2202
|
const field = "listItemsByStageAndUpdatedAt";
|
|
2191
|
-
const
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
versions.push(...((connection.items ?? []).filter(Boolean) as ContentItemVersionRecord[]));
|
|
2204
|
-
nextToken = connection.nextToken ?? null;
|
|
2205
|
-
} while (nextToken);
|
|
2206
|
-
return versions;
|
|
2207
|
-
}));
|
|
2208
|
-
return versionsPerStage.flat();
|
|
2203
|
+
const response: GraphQLConnectionResponse<ContentItemVersionRecord> = await client.graphql({
|
|
2204
|
+
query: CONTENT_ITEMS_BY_STAGE_QUERY,
|
|
2205
|
+
variables: { stage, sortDirection: "DESC", limit, nextToken },
|
|
2206
|
+
authMode: USER_POOL_AUTH_MODE,
|
|
2207
|
+
});
|
|
2208
|
+
assertNoGraphQLErrors(response.errors);
|
|
2209
|
+
const connection = response.data?.[field];
|
|
2210
|
+
if (!connection) throw new Error(`Missing GraphQL connection payload for ${field}.`);
|
|
2211
|
+
return {
|
|
2212
|
+
versions: (connection.items ?? []).filter(Boolean) as ContentItemVersionRecord[],
|
|
2213
|
+
nextToken: connection.nextToken ?? null,
|
|
2214
|
+
};
|
|
2209
2215
|
}
|
|
2210
2216
|
|
|
2211
2217
|
const PUBLISHED_ITEMS_QUERY = `
|
|
@@ -2231,6 +2237,7 @@ type PublishedItemBoardRow = {
|
|
|
2231
2237
|
};
|
|
2232
2238
|
|
|
2233
2239
|
export async function loadNewsroomPublishedContentItemVersions(): Promise<ContentItemVersionRecord[]> {
|
|
2240
|
+
if (isLocalPreviewEnabled()) return [];
|
|
2234
2241
|
configureAmplifyClient();
|
|
2235
2242
|
const client = generateClient<Schema>() as unknown as {
|
|
2236
2243
|
graphql: (options: Record<string, unknown>) => Promise<GraphQLConnectionResponse<PublishedItemBoardRow>>;
|
|
@@ -91,7 +91,7 @@ function failureMessage(error: unknown): string {
|
|
|
91
91
|
return error instanceof Error ? error.message : "The request failed.";
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
export function NewsroomArticleEditor({ articleId, demo }: { articleId: string; demo: boolean }) {
|
|
94
|
+
export function NewsroomArticleEditor({ articleId, demo, embedded = false }: { articleId: string; demo: boolean; embedded?: boolean }) {
|
|
95
95
|
const session = useOptionalNewsDeskClient();
|
|
96
96
|
const blocked = !demo && articlesAccessBlocked(session?.shell.phase);
|
|
97
97
|
const backend: ArticlesBackend = useMemo(() => resolveArticlesBackend(demo), [demo]);
|
|
@@ -725,6 +725,8 @@ export function NewsroomArticleEditor({ articleId, demo }: { articleId: string;
|
|
|
725
725
|
);
|
|
726
726
|
})();
|
|
727
727
|
|
|
728
|
+
if (embedded) return <div className="newsroom-embedded-article">{body}</div>;
|
|
729
|
+
|
|
728
730
|
return (
|
|
729
731
|
<NewsroomOpsShell
|
|
730
732
|
activeTab="articles"
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import Link from "next/link";
|
|
4
|
+
import type { AssignmentRecord } from "../lib/category-repository";
|
|
5
|
+
|
|
6
|
+
const assignmentDateFormat = new Intl.DateTimeFormat(undefined, { dateStyle: "medium", timeStyle: "short" });
|
|
7
|
+
|
|
8
|
+
function formattedTimestamp(value: string | null | undefined): string | null {
|
|
9
|
+
if (!value) return null;
|
|
10
|
+
const parsed = new Date(value);
|
|
11
|
+
return Number.isNaN(parsed.getTime()) ? value : assignmentDateFormat.format(parsed);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type DetailField = { label: string; value: string | null | undefined };
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The whole assignment, laid out by container width: one column in a narrow Board panel, labelled
|
|
18
|
+
* columns when the container is wide, such as the dedicated page on a large monitor.
|
|
19
|
+
*/
|
|
20
|
+
export function NewsroomAssignmentDetail({ assignment, pageHref, showTitle = true }: { assignment: AssignmentRecord; pageHref?: string; showTitle?: boolean }) {
|
|
21
|
+
const facts: DetailField[] = [
|
|
22
|
+
{ label: "Status", value: assignment.status },
|
|
23
|
+
{ label: "Type", value: assignment.assignmentTypeKey },
|
|
24
|
+
{ label: "Assignee", value: assignment.assigneeId ?? assignment.assigneeKey },
|
|
25
|
+
{ label: "Section", value: assignment.sectionKey },
|
|
26
|
+
{ label: "Created", value: formattedTimestamp(assignment.createdAt) },
|
|
27
|
+
{ label: "Updated", value: formattedTimestamp(assignment.updatedAt) },
|
|
28
|
+
{ label: "Completed", value: formattedTimestamp(assignment.completedAt) },
|
|
29
|
+
].filter(field => field.value);
|
|
30
|
+
const prose: DetailField[] = [
|
|
31
|
+
{ label: "Summary", value: assignment.summary },
|
|
32
|
+
{ label: "Brief", value: assignment.brief },
|
|
33
|
+
{ label: "Instructions", value: assignment.instructions },
|
|
34
|
+
].filter(field => field.value);
|
|
35
|
+
return <article className="newsroom-assignment-detail">
|
|
36
|
+
{showTitle && <h2 className="newsroom-assignment-detail-title">{assignment.title}</h2>}
|
|
37
|
+
<dl className="newsroom-assignment-detail-facts">
|
|
38
|
+
{facts.map(field => <div key={field.label}><dt>{field.label}</dt><dd>{field.value}</dd></div>)}
|
|
39
|
+
</dl>
|
|
40
|
+
<div className="newsroom-assignment-detail-prose">
|
|
41
|
+
{prose.map(field => <section key={field.label}><h3>{field.label}</h3><p>{field.value}</p></section>)}
|
|
42
|
+
</div>
|
|
43
|
+
{pageHref && <Link className="organizer-header-button" href={pageHref}>Open full page</Link>}
|
|
44
|
+
</article>;
|
|
45
|
+
}
|
|
@@ -2,12 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
4
4
|
import Link from "next/link";
|
|
5
|
-
import { IssueCollection, IssueDetailPanel, useBoardColumnState } from "@kanbus/ui";
|
|
5
|
+
import { IssueCollection, IssueDetailPanel, IssueTypeFilter, issueTypeFromFilter, useBoardColumnState } from "@kanbus/ui";
|
|
6
6
|
import "@kanbus/ui/styles/kanban.css";
|
|
7
7
|
import "./newsroom-newsprint-board.css";
|
|
8
8
|
import { PlusIcon } from "lucide-react";
|
|
9
|
-
import { buttonVariants } from "./ui/button";
|
|
10
|
-
import { cn } from "../lib/utils";
|
|
11
9
|
import { newsroomHref } from "../lib/newsroom-base-path";
|
|
12
10
|
import {
|
|
13
11
|
ASSIGNMENT_BOARD_TYPE,
|
|
@@ -15,22 +13,32 @@ import {
|
|
|
15
13
|
canReadNewsroomBoard,
|
|
16
14
|
contentItemBoardConfig,
|
|
17
15
|
contentItemBoardType,
|
|
16
|
+
deskBoardConfig,
|
|
17
|
+
deskContainsAssignment,
|
|
18
|
+
deskContainsContentItem,
|
|
18
19
|
projectAssignmentBoard,
|
|
19
20
|
projectContentItemBoard,
|
|
20
21
|
type NativeBoardIssue,
|
|
21
22
|
} from "../lib/newsroom-board";
|
|
22
|
-
import type { AssignmentRecord } from "../lib/category-repository";
|
|
23
|
+
import type { AssignmentRecord, NewsroomSectionRecord } from "../lib/category-repository";
|
|
24
|
+
import { displayNewsroomSectionShortTitle } from "../lib/newsroom-sections";
|
|
23
25
|
import { getContentItemName, getContentItemStageDefinitions, SITE_BRAND } from "../lib/site-brand";
|
|
24
26
|
import { getNewsroomNavHref } from "../lib/newsroom-nav";
|
|
25
|
-
import { loadNewsroomAssignmentPage,
|
|
27
|
+
import { loadNewsroomAssignmentPage, loadNewsroomPublishedContentItemVersions } from "./news-desk-taxonomy-client";
|
|
28
|
+
import { usePagedContentItemVersions } from "./use-paged-content-item-versions";
|
|
29
|
+
import type { ContentItemVersionRecord } from "../lib/content-item-lifecycle";
|
|
26
30
|
import { useOptionalNewsDeskClient } from "./news-desk-client-provider";
|
|
27
31
|
import { NewsDeskAccessGate } from "./newsroom-access-gate";
|
|
32
|
+
import { isLocalPreviewEnabled } from "../lib/local-preview";
|
|
33
|
+
import { NewsroomArticleEditor } from "./newsroom-article-editor";
|
|
34
|
+
import { NewsroomAssignmentDetail } from "./newsroom-assignment-detail";
|
|
28
35
|
import { NewsroomOpsShell, NewsroomOpsStatusBanner } from "./newsroom-ops-shell";
|
|
29
36
|
import { useResolvedPapyrusTheme } from "./use-resolved-papyrus-theme";
|
|
30
37
|
import { readLocalReaderSettings, subscribeReaderSettingsChanges } from "./reader-settings";
|
|
31
38
|
|
|
32
39
|
const STATIC_MOTION = { mode: "static" } as const;
|
|
33
40
|
const BOARD_REFRESH_INTERVAL_MILLISECONDS = 30_000;
|
|
41
|
+
const COLUMN_LOAD_MORE_DISTANCE_PIXELS = 240;
|
|
34
42
|
|
|
35
43
|
async function loadEveryNewsroomAssignment() {
|
|
36
44
|
const assignments: AssignmentRecord[] = [];
|
|
@@ -43,87 +51,128 @@ async function loadEveryNewsroomAssignment() {
|
|
|
43
51
|
return assignments;
|
|
44
52
|
}
|
|
45
53
|
|
|
46
|
-
export type NewsroomBoardSubject = "contentItem" | "assignment";
|
|
54
|
+
export type NewsroomBoardSubject = "contentItem" | "assignment" | "desk";
|
|
47
55
|
|
|
48
56
|
/** Read-only board of one record kind over authenticated CMS records; existing workspaces own every mutation. */
|
|
49
|
-
export function NewsroomBoardView({ subject }: { subject: NewsroomBoardSubject }) {
|
|
57
|
+
export function NewsroomBoardView({ subject, deskId }: { subject: NewsroomBoardSubject; deskId?: string }) {
|
|
50
58
|
const session = useOptionalNewsDeskClient();
|
|
51
59
|
const allowed = canReadNewsroomBoard(session?.shell.phase);
|
|
52
60
|
const theme = useResolvedPapyrusTheme();
|
|
53
61
|
const [lowMotion, setLowMotion] = useState(false);
|
|
54
|
-
const [
|
|
62
|
+
const [assignmentBoardIssues, setAssignmentBoardIssues] = useState<NativeBoardIssue[]>([]);
|
|
63
|
+
const [publishedVersions, setPublishedVersions] = useState<ContentItemVersionRecord[]>([]);
|
|
64
|
+
const [assignments, setAssignments] = useState<AssignmentRecord[]>([]);
|
|
55
65
|
const [view, setView] = useState<"board" | "list">("board");
|
|
56
66
|
const [selected, setSelected] = useState<string | null>(null);
|
|
57
|
-
const [
|
|
58
|
-
const [
|
|
67
|
+
const [supportingRecordsLoading, setSupportingRecordsLoading] = useState(false);
|
|
68
|
+
const [supportingRecordsError, setSupportingRecordsError] = useState<string | null>(null);
|
|
59
69
|
const generation = useRef(0);
|
|
60
|
-
const
|
|
61
|
-
const
|
|
70
|
+
const boardBodyRef = useRef<HTMLDivElement | null>(null);
|
|
71
|
+
const [typeFilter, setTypeFilter] = useState<string | null>("all");
|
|
72
|
+
const showsContentItems = subject !== "assignment";
|
|
73
|
+
const contentItemColumnKeyPrefix = `${contentItemBoardType()}/`;
|
|
74
|
+
const boardType = subject === "assignment" ? ASSIGNMENT_BOARD_TYPE : contentItemBoardType();
|
|
75
|
+
const deskAssignmentIssues = useMemo(() => subject === "desk" && deskId
|
|
76
|
+
? projectAssignmentBoard(assignments.filter(assignment => deskContainsAssignment(assignment, deskId))) : [], [assignments, deskId, subject]);
|
|
77
|
+
const columnStateConfig = useMemo(() => subject === "contentItem" ? contentItemBoardConfig([])
|
|
78
|
+
: subject === "desk" ? deskBoardConfig(deskAssignmentIssues)
|
|
79
|
+
: assignmentBoardConfig(assignmentBoardIssues), [assignmentBoardIssues, deskAssignmentIssues, subject]);
|
|
80
|
+
const columnState = useBoardColumnState({ config: columnStateConfig, storageKey: `papyrus.cms.columns.v1.${SITE_BRAND.id}.${subject}` });
|
|
81
|
+
const openedCollapsedUntilOpenedStages = useMemo(() => !showsContentItems ? [] : getContentItemStageDefinitions()
|
|
82
|
+
.filter(definition => definition.collapsedUntilOpened && !columnState.collapsedColumns.has(`${contentItemColumnKeyPrefix}${definition.key}`))
|
|
83
|
+
.map(definition => definition.key), [contentItemColumnKeyPrefix, columnState.collapsedColumns, showsContentItems]);
|
|
84
|
+
const pagedContentItems = usePagedContentItemVersions({ enabled: allowed && showsContentItems, openedCollapsedUntilOpenedStages });
|
|
62
85
|
useEffect(() => {
|
|
63
86
|
setLowMotion(readLocalReaderSettings().motion === "low");
|
|
64
87
|
return subscribeReaderSettingsChanges(settings => setLowMotion(settings.motion === "low"));
|
|
65
88
|
}, []);
|
|
66
89
|
useEffect(() => {
|
|
67
90
|
const request = ++generation.current;
|
|
68
|
-
|
|
69
|
-
if (!allowed) {
|
|
70
|
-
const
|
|
71
|
-
? Promise.all([
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
91
|
+
setAssignmentBoardIssues([]); setPublishedVersions([]); setAssignments([]); setSelected(null); setSupportingRecordsError(null);
|
|
92
|
+
if (!allowed) { setSupportingRecordsLoading(false); return; }
|
|
93
|
+
const loadSupportingRecords = () => subject !== "assignment"
|
|
94
|
+
? Promise.all([loadNewsroomPublishedContentItemVersions(), loadEveryNewsroomAssignment()]).then(([published, everyAssignment]) => {
|
|
95
|
+
if (generation.current === request) { setPublishedVersions(published); setAssignments(everyAssignment); }
|
|
96
|
+
})
|
|
97
|
+
: loadEveryNewsroomAssignment().then(everyAssignment => {
|
|
98
|
+
if (generation.current === request) { setAssignments(everyAssignment); setAssignmentBoardIssues(projectAssignmentBoard(everyAssignment)); }
|
|
99
|
+
});
|
|
76
100
|
const refresh = (initial: boolean) => {
|
|
77
|
-
if (initial)
|
|
78
|
-
|
|
79
|
-
if (generation.current
|
|
80
|
-
setIssues(loadedIssues);
|
|
81
|
-
setError(null);
|
|
101
|
+
if (initial) setSupportingRecordsLoading(true);
|
|
102
|
+
loadSupportingRecords().then(() => {
|
|
103
|
+
if (generation.current === request) setSupportingRecordsError(null);
|
|
82
104
|
}).catch((reason: unknown) => {
|
|
83
|
-
if (generation.current === request && initial)
|
|
84
|
-
}).finally(() => { if (generation.current === request && initial)
|
|
105
|
+
if (generation.current === request && initial) setSupportingRecordsError(reason instanceof Error ? reason.message : "Could not load newsroom records.");
|
|
106
|
+
}).finally(() => { if (generation.current === request && initial) setSupportingRecordsLoading(false); });
|
|
85
107
|
};
|
|
86
|
-
refreshIssuesRef.current = refresh;
|
|
87
108
|
refresh(true);
|
|
88
109
|
const refreshWhenVisible = () => { if (document.visibilityState === "visible") refresh(false); };
|
|
89
110
|
const interval = window.setInterval(refreshWhenVisible, BOARD_REFRESH_INTERVAL_MILLISECONDS);
|
|
90
111
|
document.addEventListener("visibilitychange", refreshWhenVisible);
|
|
91
112
|
return () => { generation.current = request + 1; window.clearInterval(interval); document.removeEventListener("visibilitychange", refreshWhenVisible); };
|
|
92
113
|
}, [allowed, subject]);
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
.filter(
|
|
98
|
-
|
|
114
|
+
const issues = useMemo(() => {
|
|
115
|
+
if (subject === "assignment") return assignmentBoardIssues;
|
|
116
|
+
if (subject === "contentItem") return projectContentItemBoard({ contentItemVersions: [...pagedContentItems.versions, ...publishedVersions], assignments });
|
|
117
|
+
const versionsOnDesk = [...pagedContentItems.versions, ...publishedVersions].filter(version => deskId && deskContainsContentItem(version, deskId));
|
|
118
|
+
return [...projectContentItemBoard({ contentItemVersions: versionsOnDesk, assignments: assignments.filter(assignment => deskId && deskContainsAssignment(assignment, deskId)) }), ...deskAssignmentIssues];
|
|
119
|
+
}, [assignmentBoardIssues, assignments, deskAssignmentIssues, deskId, pagedContentItems.versions, publishedVersions, subject]);
|
|
120
|
+
const loading = supportingRecordsLoading || (showsContentItems && pagedContentItems.loading);
|
|
121
|
+
const error = supportingRecordsError ?? (showsContentItems ? pagedContentItems.error : null);
|
|
122
|
+
const config = useMemo(() => subject === "contentItem" ? contentItemBoardConfig(issues) : subject === "desk" ? deskBoardConfig(issues) : assignmentBoardConfig(issues), [issues, subject]);
|
|
123
|
+
const columnStageFromElement = (element: Element | null) => {
|
|
124
|
+
const columnKey = element?.closest("[data-column-key]")?.getAttribute("data-column-key");
|
|
125
|
+
return columnKey?.startsWith(contentItemColumnKeyPrefix) ? columnKey.slice(contentItemColumnKeyPrefix.length) : null;
|
|
126
|
+
};
|
|
127
|
+
const loadMoreWhenColumnIsNearItsEnd = (columnScroll: HTMLElement) => {
|
|
128
|
+
const distanceToEnd = columnScroll.scrollHeight - columnScroll.scrollTop - columnScroll.clientHeight;
|
|
129
|
+
const stage = columnStageFromElement(columnScroll);
|
|
130
|
+
if (stage && distanceToEnd < COLUMN_LOAD_MORE_DISTANCE_PIXELS) pagedContentItems.loadMoreForStage(stage);
|
|
131
|
+
};
|
|
132
|
+
const handleBoardScroll = (event: React.UIEvent<HTMLDivElement>) => {
|
|
133
|
+
const target = event.target as HTMLElement;
|
|
134
|
+
if (showsContentItems && target.classList?.contains("kb-column-scroll")) loadMoreWhenColumnIsNearItsEnd(target);
|
|
135
|
+
};
|
|
99
136
|
useEffect(() => {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
const
|
|
137
|
+
if (!showsContentItems) return;
|
|
138
|
+
boardBodyRef.current?.querySelectorAll<HTMLElement>(".kb-column-scroll").forEach(loadMoreWhenColumnIsNearItsEnd);
|
|
139
|
+
});
|
|
140
|
+
const deskTitle = session?.shell.dashboard?.newsroomSections?.find(section => section.id === deskId)?.title ?? (deskId ? displayNewsroomSectionShortTitle({ id: deskId } as NewsroomSectionRecord) : "Desk");
|
|
141
|
+
const pageTitle = subject === "contentItem" ? getContentItemName().plural : subject === "desk" ? deskTitle : "Assignments";
|
|
104
142
|
const issue = issues.find(item => item.id === selected);
|
|
105
143
|
if (!allowed) return <NewsDeskAccessGate shell={session?.shell ?? null} />;
|
|
106
|
-
return <NewsroomOpsShell activeTab={subject === "contentItem" ? "articles" : "assignments"} appTitle={SITE_BRAND.appTitle} backLabel={SITE_BRAND.backToHomeLabel} pageTitle={pageTitle}
|
|
144
|
+
return <NewsroomOpsShell activeTab={subject === "contentItem" ? "articles" : subject === "desk" ? "desks" : "assignments"} appTitle={SITE_BRAND.appTitle} backLabel={SITE_BRAND.backToHomeLabel} pageTitle={pageTitle}
|
|
107
145
|
headerActions={<>
|
|
108
|
-
<
|
|
109
|
-
{
|
|
110
|
-
|
|
111
|
-
|
|
146
|
+
<span role="status" className="organizer-board-note">{loading ? "Loading records…" : `${issues.length} records loaded.`}</span>
|
|
147
|
+
<div role="group" aria-label="Record presentation" className="organizer-board-view-toggle"><button type="button" className="organizer-header-button" aria-pressed={view === "board"} onClick={() => setView("board")}>Board</button><button type="button" className="organizer-header-button" aria-pressed={view === "list"} onClick={() => setView("list")}>List</button></div>
|
|
148
|
+
{showsContentItems && (
|
|
149
|
+
<Link className="organizer-header-button" data-newsroom-article-new href={getNewsroomNavHref(newsroomHref("articles", "new"))}>
|
|
150
|
+
<PlusIcon size={14} aria-hidden="true" />
|
|
112
151
|
{`New ${getContentItemName().singular.toLowerCase()}`}
|
|
113
152
|
</Link>
|
|
114
153
|
)}
|
|
115
154
|
</>}>
|
|
116
155
|
<div className="organizer-native-board papyrus-newsprint-board" data-color-mode={theme}>
|
|
117
156
|
{error && <NewsroomOpsStatusBanner tone="error">{error}</NewsroomOpsStatusBanner>}
|
|
118
|
-
<
|
|
119
|
-
<div className="organizer-board-body" data-detail-open={Boolean(issue)}>
|
|
120
|
-
<IssueCollection issues={issues} config={config} query={{ type: boardType }} view={view}
|
|
157
|
+
{subject === "desk" && <IssueTypeFilter config={config} value={typeFilter} onChange={setTypeFilter} name="Record type" className="organizer-board-type-filter" />}
|
|
158
|
+
<div className="organizer-board-body" data-detail-open={Boolean(issue)} ref={boardBodyRef} onScrollCapture={handleBoardScroll}>
|
|
159
|
+
<IssueCollection issues={issues} config={config} query={{ type: subject === "desk" ? issueTypeFromFilter(typeFilter) : boardType }} view={view}
|
|
121
160
|
motion={lowMotion ? STATIC_MOTION : undefined}
|
|
122
161
|
collapsedColumns={columnState.collapsedColumns} onToggleCollapse={columnState.toggleColumn} initialScroll="start"
|
|
123
162
|
selectedIssueId={selected} detailOpen={Boolean(issue)} onSelectIssue={item => setSelected(item.id)} ariaLabel={pageTitle}
|
|
124
163
|
emptyMessage={loading ? "Loading records…" : error ? "Records could not be loaded." : "No records in this selection."} />
|
|
125
|
-
{issue && <IssueDetailPanel issue={issue} onClose={() => setSelected(null)} fields={[
|
|
126
|
-
|
|
164
|
+
{issue && <IssueDetailPanel issue={issue} onClose={() => setSelected(null)} fields={[]}>
|
|
165
|
+
{issue.kind === "contentItem"
|
|
166
|
+
? <>
|
|
167
|
+
<NewsroomArticleEditor key={issue.id} articleId={decodeURIComponent(issue.href.split("/").pop() ?? "")} demo={isLocalPreviewEnabled()} embedded />
|
|
168
|
+
<Link className="organizer-header-button" href={getNewsroomNavHref(issue.href)}>Open full page</Link>
|
|
169
|
+
</>
|
|
170
|
+
: (() => {
|
|
171
|
+
const record = assignments.find(candidate => candidate.id === issue.nativeId);
|
|
172
|
+
return record
|
|
173
|
+
? <NewsroomAssignmentDetail assignment={record} pageHref={getNewsroomNavHref(issue.href)} showTitle={false} />
|
|
174
|
+
: <Link href={getNewsroomNavHref(issue.href)}>Open {issue.type}</Link>;
|
|
175
|
+
})()}
|
|
127
176
|
</IssueDetailPanel>}
|
|
128
177
|
</div>
|
|
129
178
|
</div>
|
|
@@ -20,7 +20,7 @@ body:has(.newsroom-organizer)>[data-staging-banner]{flex-shrink:0}
|
|
|
20
20
|
.organizer-destinations a[aria-current=page]{border-bottom:2px solid var(--org-ink);color:var(--org-ink)}
|
|
21
21
|
.newsroom-organizer :is(a,button):focus-visible,.organizer-menu :is(a,button):focus-visible{outline:2px solid currentColor;outline-offset:-3px}
|
|
22
22
|
.organizer-workspace{display:flex;flex-direction:column;min-height:0;min-width:0;overflow:hidden;padding:0 16px}
|
|
23
|
-
.organizer-topbar{height:
|
|
23
|
+
.organizer-topbar{height:48px;min-height:48px;display:flex;align-items:center;gap:16px;border-bottom:1px solid var(--org-ink)}
|
|
24
24
|
.organizer-masthead{font:900 28px Newsroom Display,Georgia,serif;white-space:nowrap;max-width:30vw;overflow:hidden;text-overflow:ellipsis}
|
|
25
25
|
.organizer-topbar h1{font:400 12px Newsroom UI,system-ui;flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin:0}
|
|
26
26
|
.organizer-toggle{display:flex;align-items:center;justify-content:center;width:36px;min-height:36px;padding:0;border:1px solid var(--org-line);flex-shrink:0}
|
|
@@ -36,20 +36,22 @@ body:has(.newsroom-organizer)>[data-staging-banner]{flex-shrink:0}
|
|
|
36
36
|
.organizer-account-popup [role=menuitem]{min-height:40px;font-size:12px}.organizer-account-popup .reader-auth{display:contents}.organizer-account-popup .reader-auth__error{display:block;white-space:normal;font-size:11px;padding:8px}
|
|
37
37
|
.organizer-bottom-nav{display:none}
|
|
38
38
|
.organizer-main:has(.organizer-native-board){display:flex;overflow:hidden}
|
|
39
|
-
.organizer-native-board{display:flex;flex-direction:column;flex:1;min-height:0;min-width:0;gap:
|
|
39
|
+
.organizer-native-board{display:flex;flex-direction:column;flex:1;min-height:0;min-width:0;gap:8px}
|
|
40
40
|
.organizer-board-view-toggle{display:flex;gap:4px;flex-shrink:0}
|
|
41
|
-
.organizer-
|
|
42
|
-
.organizer-
|
|
43
|
-
.organizer-board-note{font-size:11px;line-height:1.5;margin:0;color:var(--org-muted)}
|
|
41
|
+
.organizer-header-button{display:inline-flex;align-items:center;gap:6px;min-height:36px;padding:4px 12px;border:1px solid var(--org-line);font:inherit;color:inherit;background:transparent;text-decoration:none;white-space:nowrap;cursor:pointer}
|
|
42
|
+
.organizer-header-button[aria-pressed=true]{background:var(--org-ink);color:var(--org-paper)}
|
|
43
|
+
.organizer-board-note{font-size:11px;line-height:1.5;margin:0;color:var(--org-muted);white-space:nowrap}
|
|
44
|
+
@media(min-width:901px){.newsroom-organizer[data-sidebar-collapsed=false] .organizer-masthead{display:none}}
|
|
44
45
|
.organizer-board-body{position:relative;display:grid;grid-template-columns:minmax(0,1fr);flex:1;min-width:0;min-height:0;gap:16px}
|
|
45
|
-
.organizer-board-body[data-detail-open=true]{grid-template-columns:minmax(0,1fr)
|
|
46
|
+
.organizer-board-body[data-detail-open=true]{grid-template-columns:minmax(0,1fr) clamp(320px,34vw,1000px)}
|
|
46
47
|
.organizer-board-body>.kb-issue-collection{height:100%;min-height:0;overflow:auto}
|
|
47
48
|
.organizer-board-body>.kb-issue-detail{overflow:auto;min-height:0}
|
|
48
|
-
.organizer-board-body .kb-issue-detail a{display:block;min-height:44px;text-decoration:underline;padding:12px 0}
|
|
49
|
+
.organizer-board-body .kb-issue-detail a:not(.organizer-header-button){display:block;min-height:44px;text-decoration:underline;padding:12px 0}
|
|
49
50
|
.organizer-menu{height:min(85dvh,760px);max-height:85dvh;display:flex;flex-direction:column;padding:16px;border-radius:24px 24px 0 0;gap:20px}
|
|
50
51
|
.organizer-menu .organizer-destinations{overflow-y:auto}.organizer-menu .organizer-destinations section{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}.organizer-menu .organizer-destinations h3{grid-column:1/-1;font-size:12px;text-transform:none;margin:0 0 4px}
|
|
51
52
|
.organizer-menu .organizer-destinations a{border:1px solid var(--org-line);padding:12px;color:var(--org-ink)}
|
|
52
53
|
@media(max-width:900px){
|
|
54
|
+
.organizer-board-note{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0)}
|
|
53
55
|
.newsroom-organizer,.newsroom-organizer[data-sidebar-collapsed=true]{grid-template-columns:minmax(0,1fr);grid-template-rows:minmax(0,1fr) auto}
|
|
54
56
|
.organizer-sidebar,.organizer-toggle{display:none}.organizer-workspace{padding:0 16px}.organizer-topbar{gap:8px;height:55px;min-height:55px}
|
|
55
57
|
.organizer-masthead{font-size:24px;max-width:36vw}.organizer-topbar h1{font-size:10px}.organizer-account-header{display:flex}.organizer-header-actions{gap:4px;max-width:35%}
|
|
@@ -61,3 +63,22 @@ body:has(.newsroom-organizer)>[data-staging-banner]{flex-shrink:0}
|
|
|
61
63
|
.organizer-board-body>.kb-issue-detail{position:absolute;inset:0;z-index:2;overflow:auto}
|
|
62
64
|
}
|
|
63
65
|
@media(prefers-reduced-motion:reduce){.newsroom-organizer,.newsroom-organizer *,.organizer-menu,.organizer-menu *,.organizer-account-popup,.organizer-account-popup *{scroll-behavior:auto;animation:none!important;transition:none!important}}
|
|
66
|
+
.newsroom-organizer :is(button,input,select,textarea,[role=button],[role=tab],[role=combobox],[role=option],[role=menuitem],a[class]),.organizer-menu :is(button,a),.organizer-account-popup :is(button,a){border-radius:0 !important}
|
|
67
|
+
.newsroom-assignment-detail,.newsroom-embedded-article{container-type:inline-size;min-width:0}
|
|
68
|
+
.newsroom-assignment-detail{display:grid;gap:16px;padding:12px}
|
|
69
|
+
.newsroom-assignment-detail-title{margin:0;font-size:1.25rem;line-height:1.25;overflow-wrap:anywhere}
|
|
70
|
+
.newsroom-assignment-detail-facts{display:grid;gap:8px 16px;grid-template-columns:minmax(0,1fr);margin:0}
|
|
71
|
+
.newsroom-assignment-detail-facts>div{display:grid;grid-template-columns:minmax(80px,max-content) minmax(0,1fr);gap:8px;border-top:1px solid var(--org-line);padding-top:6px}
|
|
72
|
+
.newsroom-assignment-detail-facts dt{color:var(--org-muted);font-size:12px;text-transform:uppercase;letter-spacing:.04em}
|
|
73
|
+
.newsroom-assignment-detail-facts dd{margin:0;overflow-wrap:anywhere}
|
|
74
|
+
.newsroom-assignment-detail-prose{display:grid;gap:12px}
|
|
75
|
+
.newsroom-assignment-detail-prose h3{margin:0 0 4px;font-size:12px;text-transform:uppercase;letter-spacing:.04em;color:var(--org-muted)}
|
|
76
|
+
.newsroom-assignment-detail-prose p{margin:0;white-space:pre-wrap;overflow-wrap:anywhere;line-height:1.5}
|
|
77
|
+
@container (min-width:560px){.newsroom-assignment-detail-facts{grid-template-columns:repeat(2,minmax(0,1fr))}}
|
|
78
|
+
@container (min-width:900px){.newsroom-assignment-detail{grid-template-columns:minmax(0,2fr) minmax(280px,1fr);align-items:start;column-gap:32px}.newsroom-assignment-detail-title{grid-column:1/-1}.newsroom-assignment-detail-facts{grid-template-columns:minmax(0,1fr)}}
|
|
79
|
+
@container (min-width:1500px){.newsroom-assignment-detail-prose{grid-template-columns:repeat(2,minmax(0,1fr))}}
|
|
80
|
+
.newsroom-embedded-article{padding:0 8px 12px}
|
|
81
|
+
.newsroom-embedded-article :is(textarea,input,select){max-width:100%}
|
|
82
|
+
.newsroom-embedded-article [class*="grid-cols-2"],.newsroom-embedded-article [class*="lg:grid"]{grid-template-columns:minmax(0,1fr) !important}
|
|
83
|
+
@container (min-width:1100px){.newsroom-embedded-article [class*="lg:grid"]{grid-template-columns:repeat(2,minmax(0,1fr)) !important}}
|
|
84
|
+
.newsroom-organizer [class*="rounded"]:not([data-slot=avatar]):not([class*="animate-spin"]):not([role=progressbar]),.organizer-menu,.organizer-account-popup,[data-slot=dialog-content],[data-slot=popover-content],[data-slot=dropdown-menu-content],[data-slot=select-content],[data-slot=tooltip-content]{border-radius:0 !important}
|
|
@@ -403,7 +403,7 @@ export function NewsroomTopicsView({
|
|
|
403
403
|
}}
|
|
404
404
|
open={mobileDetailOpen && Boolean(selectedProposal)}
|
|
405
405
|
>
|
|
406
|
-
<SheetContent className="gap-0 p-0 md:hidden" side="bottom">
|
|
406
|
+
<SheetContent backdropClassName="md:hidden" className="gap-0 p-0 md:hidden" side="bottom">
|
|
407
407
|
<SheetHeader className="border-b border-border px-4 py-3">
|
|
408
408
|
<SheetTitle className="text-left text-base">Topic proposal</SheetTitle>
|
|
409
409
|
</SheetHeader>
|
|
@@ -13297,7 +13297,7 @@ function buildUiAnalysisReindexAssignmentPlan({
|
|
|
13297
13297
|
createdBy: actorLabel,
|
|
13298
13298
|
createdAt: now,
|
|
13299
13299
|
updatedAt: now,
|
|
13300
|
-
newsroomFeedKey: "
|
|
13300
|
+
newsroomFeedKey: "operations",
|
|
13301
13301
|
metadata,
|
|
13302
13302
|
};
|
|
13303
13303
|
const event: AssignmentEventRecord = {
|
package/components/ui/sheet.tsx
CHANGED
|
@@ -11,9 +11,9 @@ type SheetSide = "bottom" | "left" | "right";
|
|
|
11
11
|
|
|
12
12
|
const sideClasses: Record<SheetSide, string> = {
|
|
13
13
|
bottom:
|
|
14
|
-
"inset-x-0 bottom-0 top-auto max-h-[85dvh] w-full
|
|
15
|
-
left: "inset-y-0 left-0 h-full w-[min(100vw,20rem)]
|
|
16
|
-
right: "inset-y-0 right-0 h-full w-[min(100vw,20rem)]
|
|
14
|
+
"inset-x-0 bottom-0 top-auto max-h-[85dvh] w-full border-t data-open:slide-in-from-bottom data-closed:slide-out-to-bottom",
|
|
15
|
+
left: "inset-y-0 left-0 h-full w-[min(100vw,20rem)] border-r data-open:slide-in-from-left data-closed:slide-out-to-left",
|
|
16
|
+
right: "inset-y-0 right-0 h-full w-[min(100vw,20rem)] border-l data-open:slide-in-from-right data-closed:slide-out-to-right",
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
function Sheet({ ...props }: DialogPrimitive.Root.Props) {
|
|
@@ -31,17 +31,22 @@ function SheetClose({ ...props }: DialogPrimitive.Close.Props) {
|
|
|
31
31
|
function SheetContent({
|
|
32
32
|
children,
|
|
33
33
|
className,
|
|
34
|
+
backdropClassName,
|
|
34
35
|
showCloseButton = true,
|
|
35
36
|
side = "bottom",
|
|
36
37
|
...props
|
|
37
38
|
}: DialogPrimitive.Popup.Props & {
|
|
39
|
+
backdropClassName?: string;
|
|
38
40
|
showCloseButton?: boolean;
|
|
39
41
|
side?: SheetSide;
|
|
40
42
|
}) {
|
|
41
43
|
return (
|
|
42
44
|
<DialogPrimitive.Portal>
|
|
43
45
|
<DialogPrimitive.Backdrop
|
|
44
|
-
className=
|
|
46
|
+
className={cn(
|
|
47
|
+
"fixed inset-0 z-50 bg-black/40 data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
|
|
48
|
+
backdropClassName,
|
|
49
|
+
)}
|
|
45
50
|
/>
|
|
46
51
|
<DialogPrimitive.Popup
|
|
47
52
|
className={cn(
|