@anthusai/papyrus 2.0.0-next.1 → 2.0.0-next.100
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/auth/resource.js +16 -1
- package/amplify/auth/resource.ts +19 -1
- package/amplify/data/function-handler.js +20 -0
- package/amplify/data/function-handler.ts +40 -0
- package/amplify/data/resource.js +158 -61
- package/amplify/data/resource.ts +165 -61
- package/amplify/data/schema.js +2103 -1979
- package/amplify/data/schema.ts +206 -68
- package/amplify/functions/assignment-action/data-access.js +90 -0
- package/amplify/functions/assignment-action/data-access.ts +89 -0
- package/amplify/functions/assignment-action/handler.ts +1 -1
- package/amplify/functions/automation-view/handler.py +17 -0
- package/amplify/functions/automation-view/resource.js +24 -0
- package/amplify/functions/automation-view/resource.ts +34 -0
- package/amplify/functions/automation-view/schema.js +26 -0
- package/amplify/functions/automation-view/schema.ts +28 -0
- package/amplify/functions/automation-view/wire.js +56 -0
- package/amplify/functions/automation-view/wire.ts +76 -0
- package/amplify/functions/content-actions/handler.py +70 -2
- package/amplify/functions/content-actions/requirements.txt +1 -0
- package/amplify/functions/content-actions/resource.js +1 -1
- package/amplify/functions/content-actions/resource.ts +1 -1
- package/amplify/functions/editorial-checks/handler.py +14 -0
- package/amplify/functions/editorial-checks/requirements.txt +1 -0
- package/amplify/functions/editorial-checks/resource.js +43 -0
- package/amplify/functions/editorial-checks/resource.ts +60 -0
- package/amplify/functions/editorial-checks/schema.js +122 -0
- package/amplify/functions/editorial-checks/schema.ts +124 -0
- package/amplify/functions/editorial-checks/wire.js +53 -0
- package/amplify/functions/editorial-checks/wire.ts +61 -0
- package/amplify/functions/model-attachment-upload/handler.ts +1 -0
- package/amplify/functions/monitoring-view/handler.py +11 -0
- package/amplify/functions/monitoring-view/resource.js +24 -0
- package/amplify/functions/monitoring-view/resource.ts +34 -0
- package/amplify/functions/monitoring-view/schema.js +29 -0
- package/amplify/functions/monitoring-view/schema.ts +31 -0
- package/amplify/functions/monitoring-view/wire.js +64 -0
- package/amplify/functions/monitoring-view/wire.ts +81 -0
- package/amplify/functions/public-forms/core.js +247 -0
- package/amplify/functions/public-forms/core.ts +310 -0
- package/amplify/functions/public-forms/handler.ts +41 -0
- package/amplify/functions/public-forms/origins.js +35 -0
- package/amplify/functions/public-forms/origins.ts +43 -0
- package/amplify/functions/public-forms/resource.js +9 -0
- package/amplify/functions/public-forms/resource.ts +10 -0
- package/amplify/functions/public-forms/wire.js +25 -0
- package/amplify/functions/public-forms/wire.ts +32 -0
- package/amplify/functions/shared/editorial-context.ts +63 -4
- package/amplify/functions/shared/python-bundle.js +20 -14
- package/amplify/functions/shared/python-bundle.ts +25 -18
- package/amplify/plugins/newsletter/config.js +245 -0
- package/amplify/plugins/newsletter/config.ts +305 -0
- package/amplify/plugins/newsletter/function/handler.py +6 -0
- package/amplify/plugins/newsletter/function/requirements.txt +5 -0
- package/amplify/plugins/newsletter/ops/handler.py +12 -0
- package/amplify/plugins/newsletter/ops/resource.js +27 -0
- package/amplify/plugins/newsletter/ops/resource.ts +37 -0
- package/amplify/plugins/newsletter/papyrus.plugin.js +263 -0
- package/amplify/plugins/newsletter/papyrus.plugin.ts +311 -0
- package/amplify/plugins/newsletter/schema.js +57 -0
- package/amplify/plugins/newsletter/schema.ts +62 -0
- package/amplify/plugins/newsletter/secure-string-parameter.js +107 -0
- package/amplify/plugins/newsletter/secure-string-parameter.ts +120 -0
- package/amplify/plugins/newsletter/sending.js +140 -0
- package/amplify/plugins/newsletter/sending.ts +186 -0
- package/amplify/plugins/reader-steering/config.js +182 -0
- package/amplify/plugins/reader-steering/config.ts +290 -0
- package/amplify/plugins/reader-steering/function/ai_moderation.py +246 -0
- package/amplify/plugins/reader-steering/function/handler.py +1567 -0
- package/amplify/plugins/reader-steering/function/moderation-prompt.md +27 -0
- package/amplify/plugins/reader-steering/function/moderation.py +316 -0
- package/amplify/plugins/reader-steering/function/proposals.py +458 -0
- package/amplify/plugins/reader-steering/function/resource.js +27 -0
- package/amplify/plugins/reader-steering/function/resource.ts +35 -0
- package/amplify/plugins/reader-steering/papyrus.plugin.js +186 -0
- package/amplify/plugins/reader-steering/papyrus.plugin.ts +212 -0
- package/amplify/plugins/reader-steering/schema.js +229 -0
- package/amplify/plugins/reader-steering/schema.ts +231 -0
- package/amplify/plugins/registry.js +4 -0
- package/amplify/plugins/registry.ts +4 -0
- package/amplify/plugins/signup/schema.js +2 -1
- package/amplify/plugins/signup/schema.ts +2 -1
- package/amplify/plugins/types.ts +5 -0
- package/amplify/plugins/wire.js +1 -0
- package/amplify/plugins/wire.ts +1 -0
- package/amplify/publication/projection-lease.js +30 -0
- package/amplify/publication/projection-lease.ts +39 -0
- package/amplify/publication/redirect-and-index.js +105 -0
- package/amplify/publication/redirect-rules.js +73 -0
- package/amplify/publication/redirect-rules.ts +93 -0
- package/amplify/publication/s3-projection.js +7 -1
- package/amplify/publication/s3-projection.ts +9 -1
- package/amplify/site-backend-config.js +19 -0
- package/amplify/site-backend-config.ts +45 -1
- package/amplify/site-backend-names.js +4 -2
- package/amplify/site-backend-names.ts +4 -2
- package/amplify/site-backend.js +51 -4
- package/amplify/site-backend.ts +60 -4
- package/app/api/auth/prospectus/sign-in/route.ts +4 -0
- package/app/api/newsletter/confirm/route.ts +13 -0
- package/app/api/newsletter/preferences/route.ts +13 -0
- package/app/api/newsletter/state/route.ts +9 -0
- package/app/api/newsletter/subscribe/route.ts +9 -0
- package/app/api/newsletter/unsubscribe/route.ts +13 -0
- package/app/api/newsroom/chat/[...path]/route.ts +17 -0
- package/app/api/newsroom/curation/context/route.ts +9 -0
- package/app/api/newsroom/curation/priming/route.ts +9 -0
- package/app/api/newsroom/curation/publication/route.ts +9 -0
- package/app/api/reader/comment/delete/route.ts +9 -0
- package/app/api/reader/comment/edit/route.ts +9 -0
- package/app/api/reader/comment/route.ts +9 -0
- package/app/api/reader/flag/route.ts +9 -0
- package/app/api/reader/handle/route.ts +9 -0
- package/app/api/reader/me/route.ts +9 -0
- package/app/api/reader/rating/route.ts +9 -0
- package/app/api/reader/report/route.ts +9 -0
- package/app/api/reader/retired/route.ts +9 -0
- package/app/api/reader/submit/route.ts +9 -0
- package/app/api/reader/vote/route.ts +9 -0
- package/app/layout.tsx +5 -1
- package/bin/papyrus-infra.mjs +20 -1
- package/components/automation-panel.tsx +49 -0
- package/components/automation-run-now.tsx +97 -0
- package/components/automation-view.tsx +195 -0
- package/components/editorial-checks-admin-panel.tsx +362 -0
- package/components/editorial-finding-card.tsx +162 -0
- package/components/editorial-findings-panel.tsx +91 -0
- package/components/editorial-preview-popover.tsx +103 -0
- package/components/markus-body-editor.tsx +213 -0
- package/components/markus-ir-preview.tsx +147 -6
- package/components/moderation-panel.tsx +248 -0
- package/components/monitoring-overview-view.tsx +414 -0
- package/components/monitoring-panel.tsx +60 -0
- package/components/news-desk-client-provider.tsx +18 -18
- package/components/news-desk-taxonomy-client.ts +28 -8
- package/components/newsletter-panel.tsx +474 -0
- package/components/newsletter-sections.tsx +163 -0
- package/components/newsroom-access-gate.tsx +16 -2
- package/components/newsroom-article-editor.tsx +145 -18
- package/components/newsroom-articles-view.tsx +6 -6
- package/components/newsroom-assignment-detail.tsx +6 -6
- package/components/newsroom-board-view.tsx +14 -10
- package/components/newsroom-curation-bootstrap.tsx +45 -0
- package/components/newsroom-ops-overview.tsx +26 -96
- package/components/newsroom-ops-shell.tsx +9 -5
- package/components/newsroom-organizer.css +90 -61
- package/components/newsroom-overview-skeleton.tsx +25 -0
- package/components/newsroom-references-view.tsx +192 -49
- package/components/newsroom-theme-fonts/broadsheet-green.css +10 -0
- package/components/newsroom-theme-fonts/broadsheet-green.tsx +6 -0
- package/components/newsroom-theme-fonts/newspaper-gothic.css +9 -0
- package/components/newsroom-theme-fonts/newspaper-gothic.tsx +6 -0
- package/components/newsroom-theme-fonts/night-desk.css +10 -0
- package/components/newsroom-theme-fonts/night-desk.tsx +6 -0
- package/components/newsroom-theme-fonts/refined-newsprint.css +11 -0
- package/components/newsroom-theme-fonts/refined-newsprint.tsx +6 -0
- package/components/newsroom-theme.css +39 -0
- package/components/newsroom-theme.tsx +33 -0
- package/components/notification-center.tsx +281 -0
- package/components/papyrus-console-conversation.css +14 -0
- package/components/papyrus-console-conversation.tsx +55 -0
- package/components/papyrus-console-shell.tsx +62 -420
- package/components/papyrus-private-chat-panel.tsx +134 -0
- package/components/publication-cyclotron-panel.tsx +15 -10
- package/components/reader-auth-state.ts +1 -1
- package/components/reference-add-entry.tsx +75 -0
- package/components/reference-curation-controls.tsx +31 -22
- package/components/reference-cyclotron-priming.tsx +63 -25
- package/components/reference-cyclotron-status.tsx +4 -4
- package/components/reference-cyclotron-suggestion.tsx +15 -11
- package/components/reference-live-agreement.tsx +10 -10
- package/components/reference-priming.tsx +14 -10
- package/components/reference-relevance-review.tsx +4 -4
- package/components/reference-section-suggestion.tsx +2 -2
- package/components/topic-steering-workspace.tsx +241 -144
- package/components/use-editorial-checks.ts +133 -0
- package/infra/amplify-app-shell.js +96 -16
- package/infra/amplify-app-shell.ts +108 -18
- package/infra/cloud-email-config.js +32 -0
- package/infra/cloud-email-config.ts +39 -0
- package/infra/cloud-email.js +57 -0
- package/infra/cloud-email.test.js +69 -0
- package/infra/cloud-email.test.ts +66 -0
- package/infra/cloud-email.ts +60 -0
- package/infra/index.js +1 -0
- package/infra/secure-string-parameter-handler.js +41 -0
- package/infra/secure-string-parameter-handler.ts +41 -0
- package/infra/secure-string-parameter.js +79 -0
- package/infra/secure-string-parameter.ts +91 -0
- package/infra/site-config.js +92 -6
- package/infra/site-config.ts +138 -6
- package/lib/automation-client.ts +54 -0
- package/lib/automation-fixture.ts +93 -0
- package/lib/automation-overview.ts +252 -0
- package/lib/category-repository.ts +14 -12
- package/lib/console-web-context.ts +1 -1
- package/lib/curation-routes.ts +345 -0
- package/lib/cyclotron-suggestions.ts +11 -11
- package/lib/editorial-checks-backend.ts +16 -0
- package/lib/editorial-checks-client.ts +212 -0
- package/lib/editorial-checks-demo.ts +147 -0
- package/lib/editorial-checks-settings.ts +27 -0
- package/lib/editorial-checks.ts +175 -0
- package/lib/editorial-preview.ts +228 -0
- package/lib/monitoring-client.ts +83 -0
- package/lib/monitoring-overview-fixture.ts +51 -0
- package/lib/monitoring-overview.ts +188 -0
- package/lib/news-desk-session.ts +12 -21
- package/lib/newsletter-api.ts +418 -0
- package/lib/newsletter-client.ts +46 -0
- package/lib/newsletter-fixture.ts +184 -0
- package/lib/newsletter-model.ts +202 -0
- package/lib/newsroom-board.ts +27 -2
- package/lib/newsroom-demo-dashboard.ts +1 -1
- package/lib/newsroom-nav.ts +1 -1
- package/lib/newsroom-organizer-nav.ts +2 -1
- package/lib/papyrus-chat-admission.ts +65 -0
- package/lib/papyrus-hosted-chat.ts +217 -0
- package/lib/papyrus-private-chat-client.ts +81 -0
- package/lib/prospectus-session.ts +30 -14
- package/lib/prospectus-sign-in-routes.ts +106 -31
- package/lib/prospectus-site-identity.ts +46 -6
- package/lib/reader-api.ts +145 -0
- package/lib/reader-moderation.ts +220 -0
- package/lib/reader-retirement-route.ts +89 -0
- package/lib/reader-subject.md +29 -0
- package/lib/reader-subject.ts +53 -0
- package/lib/reference-review-details.ts +71 -0
- package/lib/reference-review-request.ts +14 -0
- package/lib/relevance-decisions.ts +42 -10
- package/lib/relevance-status.ts +6 -6
- package/lib/review-client.ts +120 -0
- package/lib/review-fixture.ts +45 -0
- package/lib/review-requests.ts +246 -0
- package/lib/semantic-graph.ts +2 -0
- package/lib/shared-chat-admission.ts +182 -0
- package/lib/site-brand-tenant.ts +11 -6
- package/lib/site-brand.ts +23 -3
- package/lib/site-origin-value.ts +50 -0
- package/lib/site-origin.ts +8 -0
- package/lib/use-reference-review-data.ts +44 -0
- package/middleware.ts +14 -3
- package/package.json +98 -4
- package/routes.manifest.json +227 -2
- package/src/with-papyrus.mjs +1 -1
- package/components/newsroom-fonts/playfair-display-700.ttf +0 -0
- package/components/newsroom-fonts/playfair-display-900.ttf +0 -0
- package/components/newsroom-fonts/playfair-display-OFL.txt +0 -93
- package/components/newsroom-fonts/plus-jakarta-sans-400.ttf +0 -0
- package/components/newsroom-fonts/plus-jakarta-sans-600.ttf +0 -0
- package/components/newsroom-fonts/plus-jakarta-sans-OFL.txt +0 -93
- package/components/newsroom-newsprint-board.css +0 -7
|
@@ -6,11 +6,13 @@ import { SITE_BRAND } from "../lib/site-brand";
|
|
|
6
6
|
import type { NewsDeskShellState } from "../lib/news-desk-session";
|
|
7
7
|
import { cn } from "../lib/utils";
|
|
8
8
|
import { NewsroomOpsShell } from "./newsroom-ops-shell";
|
|
9
|
+
import { NewsroomOverviewSkeleton } from "./newsroom-overview-skeleton";
|
|
9
10
|
import { ReaderAuthControl } from "./reader-auth-control";
|
|
10
11
|
import { buttonVariants } from "./ui/button";
|
|
11
12
|
|
|
12
13
|
export function NewsDeskAccessGate({ shell, showSectionTabs = false }: { shell: NewsDeskShellState | null; showSectionTabs?: boolean }) {
|
|
13
14
|
const accessPhase = shell?.phase ?? "checkingAccess";
|
|
15
|
+
const pending = accessPhase === "checkingAccess" || accessPhase === "loadingDesk";
|
|
14
16
|
|
|
15
17
|
return (
|
|
16
18
|
<NewsroomOpsShell
|
|
@@ -21,9 +23,20 @@ export function NewsDeskAccessGate({ shell, showSectionTabs = false }: { shell:
|
|
|
21
23
|
headerActions={(
|
|
22
24
|
<Link className={cn(buttonVariants({ variant: "ghost", size: "sm" }))} href="/settings">Settings</Link>
|
|
23
25
|
)}
|
|
24
|
-
pageTitle="Newsroom"
|
|
26
|
+
pageTitle={showSectionTabs ? "Notification Center" : "Newsroom"}
|
|
25
27
|
showNavigation={showSectionTabs}
|
|
26
28
|
>
|
|
29
|
+
{pending ? (showSectionTabs ? <NewsroomOverviewSkeleton /> : (
|
|
30
|
+
<div aria-label="Loading newsroom" className="space-y-4" data-newsroom-page-skeleton role="status">
|
|
31
|
+
<div className="h-6 w-48 rounded bg-muted" />
|
|
32
|
+
<div className="h-4 w-3/4 rounded bg-muted" />
|
|
33
|
+
<div className="grid gap-3 sm:grid-cols-2">
|
|
34
|
+
{Array.from({ length: 4 }, (_, index) => <div className="h-32 rounded-xl border bg-card p-4" key={index}>
|
|
35
|
+
<div className="h-4 w-2/3 rounded bg-muted" /><div className="mt-4 h-3 w-1/2 rounded bg-muted" />
|
|
36
|
+
</div>)}
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
)) : (
|
|
27
40
|
<section aria-live="polite" data-news-desk-access={accessPhase} data-news-desk-access-phase={accessPhase}>
|
|
28
41
|
<div className="space-y-3 rounded-xl border border-border bg-card p-6">
|
|
29
42
|
<p className="m-0 text-[0.7rem] font-semibold uppercase tracking-[0.08em] text-muted-foreground">Access</p>
|
|
@@ -38,12 +51,13 @@ export function NewsDeskAccessGate({ shell, showSectionTabs = false }: { shell:
|
|
|
38
51
|
) : null}
|
|
39
52
|
</div>
|
|
40
53
|
</section>
|
|
54
|
+
)}
|
|
41
55
|
</NewsroomOpsShell>
|
|
42
56
|
);
|
|
43
57
|
}
|
|
44
58
|
|
|
45
59
|
function formatAccessTitle(state: NewsDeskShellState | null): string {
|
|
46
|
-
if (!state || state.phase === "checkingAccess") return "Checking
|
|
60
|
+
if (!state || state.phase === "checkingAccess") return "Checking Credentials";
|
|
47
61
|
if (state.phase === "loadingDesk") return "Loading Newsroom Records";
|
|
48
62
|
if (state.phase === "forbidden") return "Editor Role Required";
|
|
49
63
|
if (state.phase === "error") return "Newsroom Unavailable";
|
|
@@ -48,7 +48,15 @@ import {
|
|
|
48
48
|
import { getNewsroomNavHref } from "../lib/newsroom-nav";
|
|
49
49
|
import { SITE_BRAND } from "../lib/site-brand";
|
|
50
50
|
import { cn } from "../lib/utils";
|
|
51
|
+
import { applyReplacement, inlineFindings, visibleFindings } from "../lib/editorial-checks";
|
|
52
|
+
import { graphqlEditorialBackend } from "../lib/editorial-checks-client";
|
|
53
|
+
import { demoEditorialBackend } from "../lib/editorial-checks-demo";
|
|
54
|
+
import type { FindingActions } from "./editorial-finding-card";
|
|
55
|
+
import { EditorialFindingsPanel } from "./editorial-findings-panel";
|
|
56
|
+
import { EditorialPreviewPopover, type PreviewPopoverTarget } from "./editorial-preview-popover";
|
|
57
|
+
import { MarkusBodyEditor, type MarkusBodyEditorHandle } from "./markus-body-editor";
|
|
51
58
|
import { MarkusIrPreview } from "./markus-ir-preview";
|
|
59
|
+
import { useEditorialChecks } from "./use-editorial-checks";
|
|
52
60
|
import { useOptionalNewsDeskClient } from "./news-desk-client-provider";
|
|
53
61
|
import { NewsDeskAccessGate } from "./newsroom-access-gate";
|
|
54
62
|
import { NewsroomOpsShell, NewsroomOpsStatusBanner } from "./newsroom-ops-shell";
|
|
@@ -118,6 +126,9 @@ export function NewsroomArticleEditor({ articleId, demo, embedded = false }: { a
|
|
|
118
126
|
const [notice, setNotice] = useState<Notice | null>(null);
|
|
119
127
|
const [conflictOpen, setConflictOpen] = useState(false);
|
|
120
128
|
const [unpublishOpen, setUnpublishOpen] = useState(false);
|
|
129
|
+
const [publishWarningOpen, setPublishWarningOpen] = useState(false);
|
|
130
|
+
const [previewPopover, setPreviewPopover] = useState<PreviewPopoverTarget | null>(null);
|
|
131
|
+
const previewRef = useRef<HTMLDivElement | null>(null);
|
|
121
132
|
const [mobileTab, setMobileTab] = useState<MobileTab>("preview");
|
|
122
133
|
const [focusedReferenceId, setFocusedReferenceId] = useState<string | null>(null);
|
|
123
134
|
const [lineageId, setLineageId] = useState<string | null>(null);
|
|
@@ -133,7 +144,7 @@ export function NewsroomArticleEditor({ articleId, demo, embedded = false }: { a
|
|
|
133
144
|
const [imageError, setImageError] = useState<string | null>(null);
|
|
134
145
|
|
|
135
146
|
const frontMatterRef = useRef<HTMLTextAreaElement | null>(null);
|
|
136
|
-
const bodyRef = useRef<
|
|
147
|
+
const bodyRef = useRef<MarkusBodyEditorHandle | null>(null);
|
|
137
148
|
const imageInputRef = useRef<HTMLInputElement | null>(null);
|
|
138
149
|
const mediaBackend = useMemo(() => resolveMediaBackend(previewMode), [previewMode]);
|
|
139
150
|
const validationSequence = useRef(0);
|
|
@@ -205,6 +216,21 @@ export function NewsroomArticleEditor({ articleId, demo, embedded = false }: { a
|
|
|
205
216
|
|
|
206
217
|
const frontMatterYaml = editor?.frontMatterYaml ?? null;
|
|
207
218
|
const bodyMarkus = editor?.bodyMarkus ?? null;
|
|
219
|
+
const editorialBackend = useMemo(
|
|
220
|
+
() => (demo ? demoEditorialBackend : !previewMode ? graphqlEditorialBackend : null),
|
|
221
|
+
[demo, previewMode],
|
|
222
|
+
);
|
|
223
|
+
const editorial = useEditorialChecks({
|
|
224
|
+
backend: editorialBackend,
|
|
225
|
+
body: bodyMarkus,
|
|
226
|
+
section: editor?.section ?? "",
|
|
227
|
+
itemId: editor?.id ?? null,
|
|
228
|
+
});
|
|
229
|
+
const editorialOpen = useMemo(
|
|
230
|
+
() => (editorial.result ? visibleFindings(editorial.result.findings, editorial.dismissed) : []),
|
|
231
|
+
[editorial.result, editorial.dismissed],
|
|
232
|
+
);
|
|
233
|
+
const editorialMarks = useMemo(() => inlineFindings(editorialOpen), [editorialOpen]);
|
|
208
234
|
|
|
209
235
|
useEffect(() => {
|
|
210
236
|
if (frontMatterYaml === null || bodyMarkus === null) return;
|
|
@@ -381,7 +407,45 @@ export function NewsroomArticleEditor({ articleId, demo, embedded = false }: { a
|
|
|
381
407
|
}
|
|
382
408
|
}, [aliases, backend, demo, editor]);
|
|
383
409
|
|
|
410
|
+
const requestPublish = () => {
|
|
411
|
+
if (editorial.openWarnings.length > 0) {
|
|
412
|
+
setPublishWarningOpen(true);
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
void publish();
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
const applyEditorialRewrite = (
|
|
419
|
+
option: { span: { start: number; end: number }; replacement: string; expected: string },
|
|
420
|
+
finding: Parameters<typeof editorial.fixed>[0] | null,
|
|
421
|
+
) => {
|
|
422
|
+
if (!editor) return;
|
|
423
|
+
const next = applyReplacement(editor.bodyMarkus, option.span, option.expected, option.replacement);
|
|
424
|
+
if (next === null) {
|
|
425
|
+
setNotice({ tone: "info", text: "That text changed since the check. Check again, then apply the rewrite." });
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
patchEditor({ bodyMarkus: next });
|
|
429
|
+
if (finding) editorial.fixed(finding);
|
|
430
|
+
setPreviewPopover(null);
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
const editorialActions: FindingActions = {
|
|
434
|
+
onSelect: (finding) => {
|
|
435
|
+
editorial.setActiveFindingId(finding.id);
|
|
436
|
+
if (editorial.inlineResult && finding.excerpt) bodyRef.current?.reveal(finding.span.start, finding.span.end);
|
|
437
|
+
},
|
|
438
|
+
onDismiss: (finding, reason) => {
|
|
439
|
+
editorial.dismiss(finding, reason);
|
|
440
|
+
setPreviewPopover(null);
|
|
441
|
+
},
|
|
442
|
+
onAccept: editorial.accept,
|
|
443
|
+
onSuggest: editorial.suggest,
|
|
444
|
+
onApply: applyEditorialRewrite,
|
|
445
|
+
};
|
|
446
|
+
|
|
384
447
|
const publish = async () => {
|
|
448
|
+
setPublishWarningOpen(false);
|
|
385
449
|
if (!editor) return;
|
|
386
450
|
let id = editor.id;
|
|
387
451
|
if (editor.dirty || id === null) {
|
|
@@ -449,8 +513,10 @@ export function NewsroomArticleEditor({ articleId, demo, embedded = false }: { a
|
|
|
449
513
|
}
|
|
450
514
|
if (!editor) {
|
|
451
515
|
return (
|
|
452
|
-
<div
|
|
453
|
-
<
|
|
516
|
+
<div aria-label="Loading article" className="space-y-5" role="status">
|
|
517
|
+
<div className="h-8 w-2/3 rounded bg-muted" />
|
|
518
|
+
<div className="grid gap-4 sm:grid-cols-2"><div className="h-12 rounded bg-muted" /><div className="h-12 rounded bg-muted" /></div>
|
|
519
|
+
<div className="h-64 rounded-xl border bg-card p-4"><div className="h-4 w-5/6 rounded bg-muted" /><div className="mt-3 h-4 w-3/4 rounded bg-muted" /></div>
|
|
454
520
|
</div>
|
|
455
521
|
);
|
|
456
522
|
}
|
|
@@ -551,7 +617,7 @@ export function NewsroomArticleEditor({ articleId, demo, embedded = false }: { a
|
|
|
551
617
|
<Button
|
|
552
618
|
data-newsroom-article-publish
|
|
553
619
|
disabled={publishBlocked}
|
|
554
|
-
onClick={
|
|
620
|
+
onClick={requestPublish}
|
|
555
621
|
size="sm"
|
|
556
622
|
type="button"
|
|
557
623
|
>
|
|
@@ -604,21 +670,21 @@ export function NewsroomArticleEditor({ articleId, demo, embedded = false }: { a
|
|
|
604
670
|
</Select>
|
|
605
671
|
</label>
|
|
606
672
|
<label className="space-y-1 text-sm font-medium">
|
|
607
|
-
<span>
|
|
673
|
+
<span>Section</span>
|
|
608
674
|
<Select
|
|
609
675
|
onValueChange={(value) => {
|
|
610
676
|
if (typeof value === "string") patchEditor({ section: value === NO_DESK_VALUE ? "" : value });
|
|
611
677
|
}}
|
|
612
678
|
value={deskOptions.some(option => option.value === editor.section) ? editor.section : NO_DESK_VALUE}
|
|
613
679
|
>
|
|
614
|
-
<SelectTrigger aria-label="
|
|
615
|
-
<SelectValue>{deskOptions.find(option => option.value === editor.section)?.label ?? "No
|
|
680
|
+
<SelectTrigger aria-label="Section" className="w-full" data-newsroom-article-section>
|
|
681
|
+
<SelectValue>{deskOptions.find(option => option.value === editor.section)?.label ?? "No section"}</SelectValue>
|
|
616
682
|
</SelectTrigger>
|
|
617
683
|
<SelectContent>
|
|
618
|
-
<SelectItem value={NO_DESK_VALUE}>No
|
|
684
|
+
<SelectItem value={NO_DESK_VALUE}>No section</SelectItem>
|
|
619
685
|
{(["core", "rotating"] as const).map((kind) => deskOptions.some(option => option.kind === kind) ? (
|
|
620
686
|
<SelectGroup key={kind}>
|
|
621
|
-
<SelectLabel>{kind === "core" ? "Core
|
|
687
|
+
<SelectLabel>{kind === "core" ? "Core sections" : "Rotating sections"}</SelectLabel>
|
|
622
688
|
{deskOptions.filter(option => option.kind === kind).map((option) => (
|
|
623
689
|
<SelectItem key={option.value} value={option.value}>{option.label}</SelectItem>
|
|
624
690
|
))}
|
|
@@ -714,18 +780,29 @@ export function NewsroomArticleEditor({ articleId, demo, embedded = false }: { a
|
|
|
714
780
|
{imageError}
|
|
715
781
|
</p>
|
|
716
782
|
) : null}
|
|
717
|
-
<
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
783
|
+
<MarkusBodyEditor
|
|
784
|
+
activeFindingId={editorial.activeFindingId}
|
|
785
|
+
findings={editorialMarks}
|
|
786
|
+
onChange={(value) => patchEditor({ bodyMarkus: value })}
|
|
787
|
+
onFindingClick={editorial.setActiveFindingId}
|
|
788
|
+
onSelectionChange={editorial.setSelection}
|
|
722
789
|
ref={bodyRef}
|
|
723
|
-
rows={28}
|
|
724
|
-
spellCheck={false}
|
|
725
790
|
value={editor.bodyMarkus}
|
|
726
791
|
/>
|
|
727
792
|
</div>
|
|
728
793
|
|
|
794
|
+
{editorial.enabled ? (
|
|
795
|
+
<EditorialFindingsPanel
|
|
796
|
+
{...editorialActions}
|
|
797
|
+
activeFindingId={editorial.activeFindingId}
|
|
798
|
+
dismissed={editorial.dismissed}
|
|
799
|
+
onCheck={() => void editorial.check()}
|
|
800
|
+
result={editorial.result}
|
|
801
|
+
selection={editorial.selection}
|
|
802
|
+
status={editorial.status}
|
|
803
|
+
/>
|
|
804
|
+
) : null}
|
|
805
|
+
|
|
729
806
|
{validation.status === "unavailable" ? (
|
|
730
807
|
<NewsroomOpsStatusBanner tone="error">{validation.message}</NewsroomOpsStatusBanner>
|
|
731
808
|
) : null}
|
|
@@ -763,7 +840,7 @@ export function NewsroomArticleEditor({ articleId, demo, embedded = false }: { a
|
|
|
763
840
|
className={cn("min-w-0", mobileTab === "preview" ? "block" : "hidden")}
|
|
764
841
|
data-newsroom-article-preview-column
|
|
765
842
|
>
|
|
766
|
-
<div className="rounded-xl border border-border bg-card p-4" data-newsroom-article-preview>
|
|
843
|
+
<div className="relative rounded-xl border border-border bg-card p-4" data-newsroom-article-preview ref={previewRef}>
|
|
767
844
|
{(readFrontMatterText(editor.frontMatterYaml, FRONT_MATTER_SUBTITLE_KEY) || readFrontMatterText(editor.frontMatterYaml, FRONT_MATTER_BLURB_KEY)) ? (
|
|
768
845
|
<div className="newsroom-article-summary" data-newsroom-article-summary>
|
|
769
846
|
{readFrontMatterText(editor.frontMatterYaml, FRONT_MATTER_SUBTITLE_KEY) ? <p className="newsroom-article-subtitle">{readFrontMatterText(editor.frontMatterYaml, FRONT_MATTER_SUBTITLE_KEY)}</p> : null}
|
|
@@ -774,12 +851,40 @@ export function NewsroomArticleEditor({ articleId, demo, embedded = false }: { a
|
|
|
774
851
|
<p className="mb-2 text-xs text-muted-foreground">Showing the last valid version.</p>
|
|
775
852
|
) : null}
|
|
776
853
|
{bodyIr ? (
|
|
777
|
-
<MarkusIrPreview
|
|
854
|
+
<MarkusIrPreview
|
|
855
|
+
bodyIr={bodyIr}
|
|
856
|
+
editorial={
|
|
857
|
+
editorial.inlineResult
|
|
858
|
+
? {
|
|
859
|
+
result: editorial.inlineResult,
|
|
860
|
+
findings: editorialOpen,
|
|
861
|
+
activeFindingIds: previewPopover?.kind === "findings" ? previewPopover.findingIds : editorial.activeFindingId ? [editorial.activeFindingId] : [],
|
|
862
|
+
onOpenMark: (findingIds, anchor) => {
|
|
863
|
+
editorial.setActiveFindingId(findingIds[0] ?? null);
|
|
864
|
+
setPreviewPopover({ kind: "findings", findingIds, anchor: anchor.getBoundingClientRect() });
|
|
865
|
+
},
|
|
866
|
+
onSelectSource: (source, anchor) => setPreviewPopover({ kind: "selection", source, anchor }),
|
|
867
|
+
}
|
|
868
|
+
: undefined
|
|
869
|
+
}
|
|
870
|
+
imageUrls={imageUrls}
|
|
871
|
+
/>
|
|
778
872
|
) : (
|
|
779
873
|
<p className="text-sm text-muted-foreground">
|
|
780
874
|
{invalid ? "Fix the errors to see a preview." : "The preview appears once the markup is valid."}
|
|
781
875
|
</p>
|
|
782
876
|
)}
|
|
877
|
+
{previewPopover && editorial.enabled ? (
|
|
878
|
+
<EditorialPreviewPopover
|
|
879
|
+
actions={editorialActions}
|
|
880
|
+
body={editor.bodyMarkus}
|
|
881
|
+
container={previewRef.current}
|
|
882
|
+
findings={editorialOpen}
|
|
883
|
+
key={previewPopover.kind === "findings" ? previewPopover.findingIds.join() : `${previewPopover.source.start}-${previewPopover.source.end}`}
|
|
884
|
+
onClose={() => setPreviewPopover(null)}
|
|
885
|
+
target={previewPopover}
|
|
886
|
+
/>
|
|
887
|
+
) : null}
|
|
783
888
|
</div>
|
|
784
889
|
</div>
|
|
785
890
|
</div>
|
|
@@ -801,6 +906,28 @@ export function NewsroomArticleEditor({ articleId, demo, embedded = false }: { a
|
|
|
801
906
|
</DialogContent>
|
|
802
907
|
</Dialog>
|
|
803
908
|
|
|
909
|
+
<Dialog onOpenChange={setPublishWarningOpen} open={publishWarningOpen}>
|
|
910
|
+
<DialogContent>
|
|
911
|
+
<DialogHeader>
|
|
912
|
+
<DialogTitle>Publish with open editorial findings?</DialogTitle>
|
|
913
|
+
<DialogDescription>
|
|
914
|
+
{editorial.openWarnings.length === 1
|
|
915
|
+
? "One editorial finding is still open."
|
|
916
|
+
: `${editorial.openWarnings.length} editorial findings are still open.`}{" "}
|
|
917
|
+
Findings are advice: you can publish anyway.
|
|
918
|
+
</DialogDescription>
|
|
919
|
+
</DialogHeader>
|
|
920
|
+
<ul className="max-h-48 list-disc space-y-1 overflow-auto pl-5 text-sm" data-newsroom-article-publish-warnings>
|
|
921
|
+
{editorial.openWarnings.slice(0, 8).map((finding) => (
|
|
922
|
+
<li key={finding.id}>{finding.message}{finding.excerpt ? `: “${finding.excerpt}”` : ""}</li>
|
|
923
|
+
))}
|
|
924
|
+
</ul>
|
|
925
|
+
<DialogFooter>
|
|
926
|
+
<Button onClick={() => setPublishWarningOpen(false)} type="button" variant="outline">Keep editing</Button>
|
|
927
|
+
<Button data-newsroom-article-publish-anyway onClick={() => void publish()} type="button">Publish anyway</Button>
|
|
928
|
+
</DialogFooter>
|
|
929
|
+
</DialogContent>
|
|
930
|
+
</Dialog>
|
|
804
931
|
<Dialog onOpenChange={setUnpublishOpen} open={unpublishOpen}>
|
|
805
932
|
<DialogContent>
|
|
806
933
|
<DialogHeader>
|
|
@@ -5,7 +5,6 @@ import Link from "next/link";
|
|
|
5
5
|
import { useEffect, useMemo, useState } from "react";
|
|
6
6
|
import { Badge } from "./ui/badge";
|
|
7
7
|
import { buttonVariants } from "./ui/button";
|
|
8
|
-
import { Spinner } from "./ui/spinner";
|
|
9
8
|
import { Tabs, TabsList, TabsTrigger } from "./ui/tabs";
|
|
10
9
|
import { SITE_BRAND } from "../lib/site-brand";
|
|
11
10
|
import { getNewsroomNavHref } from "../lib/newsroom-nav";
|
|
@@ -89,15 +88,16 @@ export function NewsroomArticlesView({ demo }: { demo: boolean }) {
|
|
|
89
88
|
</TabsList>
|
|
90
89
|
</Tabs>
|
|
91
90
|
{loadError ? <NewsroomOpsStatusBanner tone="error">{loadError}</NewsroomOpsStatusBanner> : null}
|
|
92
|
-
{rows === null && !loadError ? (
|
|
93
|
-
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
|
94
|
-
<Spinner /> Loading articles…
|
|
95
|
-
</div>
|
|
96
|
-
) : null}
|
|
97
91
|
{rows !== null && visibleRows.length === 0 ? (
|
|
98
92
|
<p className="text-sm text-muted-foreground">Nothing here yet.</p>
|
|
99
93
|
) : null}
|
|
100
94
|
<ul className="divide-y divide-border rounded-xl border border-border bg-card" data-newsroom-articles-list>
|
|
95
|
+
{rows === null && !loadError ? Array.from({ length: 5 }, (_, index) => (
|
|
96
|
+
<li aria-hidden="true" className="flex items-center justify-between px-4 py-3" key={index}>
|
|
97
|
+
<span className="w-2/3"><span className="block h-4 w-3/4 rounded bg-muted" /><span className="mt-2 block h-3 w-1/2 rounded bg-muted" /></span>
|
|
98
|
+
<span className="h-5 w-16 rounded bg-muted" />
|
|
99
|
+
</li>
|
|
100
|
+
)) : null}
|
|
101
101
|
{visibleRows.map((row) => (
|
|
102
102
|
<li key={row.id}>
|
|
103
103
|
<Link
|
|
@@ -36,7 +36,7 @@ export function NewsroomAssignmentDetail({ assignment, deskOptions = [], onDeskC
|
|
|
36
36
|
const changeDesk = (value: string) => {
|
|
37
37
|
if (!onDeskChange) return;
|
|
38
38
|
setSavingDesk(true); setDeskError(null);
|
|
39
|
-
onDeskChange(value === NO_DESK_VALUE ? null : value).catch((reason: unknown) => setDeskError(reason instanceof Error ? reason.message : "Could not save the
|
|
39
|
+
onDeskChange(value === NO_DESK_VALUE ? null : value).catch((reason: unknown) => setDeskError(reason instanceof Error ? reason.message : "Could not save the section.")).finally(() => setSavingDesk(false));
|
|
40
40
|
};
|
|
41
41
|
const facts: DetailField[] = [
|
|
42
42
|
{ label: "Status", value: assignment.status },
|
|
@@ -58,14 +58,14 @@ export function NewsroomAssignmentDetail({ assignment, deskOptions = [], onDeskC
|
|
|
58
58
|
{facts.map(field => <div key={field.label}><dt>{field.label}</dt><dd>{field.value}</dd></div>)}
|
|
59
59
|
</dl>
|
|
60
60
|
{onDeskChange && <label className="newsroom-assignment-detail-desk">
|
|
61
|
-
<span>
|
|
61
|
+
<span>Section</span>
|
|
62
62
|
<Select onValueChange={(value) => { if (typeof value === "string") changeDesk(value); }} value={deskChoices.some(option => option.value === currentDesk) ? currentDesk : NO_DESK_VALUE} disabled={savingDesk}>
|
|
63
|
-
<SelectTrigger aria-label="
|
|
64
|
-
<SelectValue>{deskChoices.find(option => option.value === currentDesk)?.label ?? "No
|
|
63
|
+
<SelectTrigger aria-label="Section" className="w-full" data-newsroom-assignment-desk>
|
|
64
|
+
<SelectValue>{deskChoices.find(option => option.value === currentDesk)?.label ?? "No section"}</SelectValue>
|
|
65
65
|
</SelectTrigger>
|
|
66
66
|
<SelectContent>
|
|
67
|
-
<SelectItem value={NO_DESK_VALUE}>No
|
|
68
|
-
{(["core", "rotating"] as const).map(kind => deskChoices.some(option => option.kind === kind) ? <SelectGroup key={kind}><SelectLabel>{kind === "core" ? "Core
|
|
67
|
+
<SelectItem value={NO_DESK_VALUE}>No section</SelectItem>
|
|
68
|
+
{(["core", "rotating"] as const).map(kind => deskChoices.some(option => option.kind === kind) ? <SelectGroup key={kind}><SelectLabel>{kind === "core" ? "Core sections" : "Rotating sections"}</SelectLabel>{deskChoices.filter(option => option.kind === kind).map(option => <SelectItem key={option.value} value={option.value}>{option.label}</SelectItem>)}</SelectGroup> : null)}
|
|
69
69
|
</SelectContent>
|
|
70
70
|
</Select>
|
|
71
71
|
{currentDesk && !deskChoices.some(option => option.value === currentDesk) ? <small className="newsroom-desk-legacy-value">Old section value: {currentDesk}</small> : null}
|
|
@@ -3,10 +3,8 @@
|
|
|
3
3
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
4
4
|
import Link from "next/link";
|
|
5
5
|
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
|
6
|
-
import { IssueCollection, IssueDetailPanel, IssueTypeFilter, issueTypeFromFilter, useBoardColumnState } from "@kanbus/ui";
|
|
7
|
-
import "
|
|
8
|
-
import "@kanbus/ui/styles/newsprint.css";
|
|
9
|
-
import "./newsroom-newsprint-board.css";
|
|
6
|
+
import { IssueCollection, IssueDetailPanel, IssueIdFormatProvider, IssueTypeFilter, issueTypeFromFilter, useBoardColumnState } from "@kanbus/ui";
|
|
7
|
+
import "./newsroom-theme";
|
|
10
8
|
import { PlusIcon } from "lucide-react";
|
|
11
9
|
import { newsroomHref } from "../lib/newsroom-base-path";
|
|
12
10
|
import {
|
|
@@ -18,6 +16,7 @@ import {
|
|
|
18
16
|
deskBoardConfig,
|
|
19
17
|
deskContainsAssignment,
|
|
20
18
|
deskContainsContentItem,
|
|
19
|
+
formatNativeIssueId,
|
|
21
20
|
projectAssignmentBoard,
|
|
22
21
|
projectContentItemBoard,
|
|
23
22
|
type NativeBoardIssue,
|
|
@@ -35,7 +34,6 @@ import { isLocalPreviewEnabled } from "../lib/local-preview";
|
|
|
35
34
|
import { NewsroomArticleEditor } from "./newsroom-article-editor";
|
|
36
35
|
import { NewsroomAssignmentDetail } from "./newsroom-assignment-detail";
|
|
37
36
|
import { NewsroomOpsShell, NewsroomOpsStatusBanner } from "./newsroom-ops-shell";
|
|
38
|
-
import { useResolvedPapyrusTheme } from "./use-resolved-papyrus-theme";
|
|
39
37
|
import { readLocalReaderSettings, subscribeReaderSettingsChanges } from "./reader-settings";
|
|
40
38
|
|
|
41
39
|
/** A board with a story or assignment open lives at `?open=<id>`, distinct from the standalone page for that record. */
|
|
@@ -61,7 +59,6 @@ export type NewsroomBoardSubject = "contentItem" | "assignment" | "desk";
|
|
|
61
59
|
export function NewsroomBoardView({ subject, deskId }: { subject: NewsroomBoardSubject; deskId?: string }) {
|
|
62
60
|
const session = useOptionalNewsDeskClient();
|
|
63
61
|
const allowed = canReadNewsroomBoard(session?.shell.phase);
|
|
64
|
-
const theme = useResolvedPapyrusTheme();
|
|
65
62
|
const [lowMotion, setLowMotion] = useState(false);
|
|
66
63
|
const [assignmentBoardIssues, setAssignmentBoardIssues] = useState<NativeBoardIssue[]>([]);
|
|
67
64
|
const [publishedVersions, setPublishedVersions] = useState<ContentItemVersionRecord[]>([]);
|
|
@@ -163,7 +160,7 @@ export function NewsroomBoardView({ subject, deskId }: { subject: NewsroomBoardS
|
|
|
163
160
|
if (!allowed) return <NewsDeskAccessGate shell={session?.shell ?? null} />;
|
|
164
161
|
return <NewsroomOpsShell activeTab={subject === "contentItem" ? "articles" : subject === "desk" ? "desks" : "assignments"} appTitle={SITE_BRAND.appTitle} backLabel={SITE_BRAND.backToHomeLabel} pageTitle={pageTitle}
|
|
165
162
|
headerActions={<>
|
|
166
|
-
<span role="status" className="organizer-board-note">{
|
|
163
|
+
{!loading ? <span role="status" className="organizer-board-note">{issues.length} records loaded.</span> : null}
|
|
167
164
|
<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>
|
|
168
165
|
{showsContentItems && (
|
|
169
166
|
<Link className="organizer-header-button" data-newsroom-article-new href={getNewsroomNavHref(newsroomHref("articles", "new"))}>
|
|
@@ -172,15 +169,21 @@ export function NewsroomBoardView({ subject, deskId }: { subject: NewsroomBoardS
|
|
|
172
169
|
</Link>
|
|
173
170
|
)}
|
|
174
171
|
</>}>
|
|
175
|
-
<
|
|
172
|
+
<IssueIdFormatProvider formatIssueId={formatNativeIssueId}>
|
|
173
|
+
<div className="organizer-native-board">
|
|
176
174
|
{error && <NewsroomOpsStatusBanner tone="error">{error}</NewsroomOpsStatusBanner>}
|
|
177
175
|
{subject === "desk" && <IssueTypeFilter config={config} value={typeFilter} onChange={setTypeFilter} name="Record type" className="organizer-board-type-filter" />}
|
|
178
176
|
<div className="organizer-board-body" data-detail-open={Boolean(issue)} ref={boardBodyRef} onScrollCapture={handleBoardScroll}>
|
|
179
|
-
|
|
177
|
+
{loading && !issues.length ? <div aria-label="Loading board records" className="grid w-full grid-cols-1 gap-3 sm:grid-cols-3" role="status">
|
|
178
|
+
{Array.from({ length: 3 }, (_, column) => <div className="min-h-64 space-y-3 rounded-xl border bg-card p-4" key={column}>
|
|
179
|
+
<div className="h-4 w-1/2 rounded bg-muted" />
|
|
180
|
+
{Array.from({ length: 2 }, (_, row) => <div className="h-24 rounded-lg border bg-background p-3" key={row}><div className="h-4 w-3/4 rounded bg-muted" /><div className="mt-3 h-3 w-1/2 rounded bg-muted" /></div>)}
|
|
181
|
+
</div>)}
|
|
182
|
+
</div> : <IssueCollection issues={issues} config={config} query={{ type: subject === "desk" ? issueTypeFromFilter(typeFilter) : boardType }} view={view}
|
|
180
183
|
motion={lowMotion ? STATIC_MOTION : undefined}
|
|
181
184
|
collapsedColumns={columnState.collapsedColumns} onToggleCollapse={columnState.toggleColumn} initialScroll="start"
|
|
182
185
|
selectedIssueId={selected} detailOpen={Boolean(issue)} onSelectIssue={item => setSelected(item.id)} ariaLabel={pageTitle}
|
|
183
|
-
emptyMessage={
|
|
186
|
+
emptyMessage={error ? "Records could not be loaded." : "No records in this selection."} />}
|
|
184
187
|
{issue && <IssueDetailPanel issue={issue} onClose={() => setSelected(null)} fields={[]}>
|
|
185
188
|
{issue.kind === "contentItem"
|
|
186
189
|
? <>
|
|
@@ -196,5 +199,6 @@ export function NewsroomBoardView({ subject, deskId }: { subject: NewsroomBoardS
|
|
|
196
199
|
</IssueDetailPanel>}
|
|
197
200
|
</div>
|
|
198
201
|
</div>
|
|
202
|
+
</IssueIdFormatProvider>
|
|
199
203
|
</NewsroomOpsShell>;
|
|
200
204
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import {useEffect,useState,type ReactNode} from 'react';
|
|
4
|
+
import {loadProspectusAccountState} from '../lib/prospectus-account-client';
|
|
5
|
+
import type {CurationBootstrap} from '../lib/curation-client';
|
|
6
|
+
import {NewsroomCurationProvider} from './newsroom-curation-provider';
|
|
7
|
+
|
|
8
|
+
type Fetcher=(input:string,init?:RequestInit)=>Promise<Response>;
|
|
9
|
+
const CONTEXT_PATH='/api/newsroom/curation/context';
|
|
10
|
+
|
|
11
|
+
function validBootstrap(value:unknown):value is CurationBootstrap{
|
|
12
|
+
const body=value as Partial<CurationBootstrap>|null;
|
|
13
|
+
const scope=body?.scope;
|
|
14
|
+
return !!scope&&[scope.accountId,scope.publicationId,scope.bindingRevision].every(item=>typeof item==='string'&&item.trim()!=='')
|
|
15
|
+
&&(body!.mode==='embedded'||body!.mode==='remote')&&Array.isArray(body!.capabilities);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Ask the server for the curation bootstrap, or null. Only a signed-in person with a selected
|
|
20
|
+
* account causes a context read; every other outcome (no Prospectus, signed out, no selection,
|
|
21
|
+
* any refusal or failure) is null, so the released CMS is unchanged.
|
|
22
|
+
*/
|
|
23
|
+
export async function loadCurationBootstrap(returnPath:string,fetcher:Fetcher=fetch):Promise<CurationBootstrap|null>{
|
|
24
|
+
try{
|
|
25
|
+
const account=await loadProspectusAccountState(returnPath,fetcher);
|
|
26
|
+
if(account.status!=='signedIn'||!account.selectedAccountId)return null;
|
|
27
|
+
const response=await fetcher(CONTEXT_PATH,{credentials:'same-origin',cache:'no-store'});
|
|
28
|
+
if(!response.ok)return null;
|
|
29
|
+
const body=await response.json().catch(()=>null);
|
|
30
|
+
if(!validBootstrap(body)||body.scope.accountId!==account.selectedAccountId)return null;
|
|
31
|
+
return body;
|
|
32
|
+
}catch{return null;}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Mounts the curation provider for the References tab. Fails closed to no provider content. */
|
|
36
|
+
export function NewsroomCurationBootstrap({enabled=true,children}:{enabled?:boolean;children:ReactNode}){
|
|
37
|
+
const [bootstrap,setBootstrap]=useState<CurationBootstrap|null>(null);
|
|
38
|
+
useEffect(()=>{
|
|
39
|
+
if(!enabled)return;
|
|
40
|
+
let current=true;
|
|
41
|
+
void loadCurationBootstrap(window.location.pathname).then(value=>{if(current)setBootstrap(value);});
|
|
42
|
+
return()=>{current=false;setBootstrap(null);};
|
|
43
|
+
},[enabled]);
|
|
44
|
+
return <NewsroomCurationProvider bootstrap={bootstrap}>{children}</NewsroomCurationProvider>;
|
|
45
|
+
}
|