@anthusai/papyrus 2.0.0-next.2 → 2.0.0-next.21
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/data/resource.js +15 -0
- package/amplify/data/resource.ts +17 -0
- package/amplify/data/schema.js +20 -0
- package/amplify/data/schema.ts +22 -0
- package/amplify/functions/assignment-action/data-access.js +88 -0
- package/amplify/functions/assignment-action/data-access.ts +87 -0
- package/amplify/functions/content-actions/handler.py +22 -0
- 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 +92 -0
- package/amplify/functions/editorial-checks/schema.ts +94 -0
- package/amplify/functions/editorial-checks/wire.js +49 -0
- package/amplify/functions/editorial-checks/wire.ts +57 -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 +16 -0
- package/amplify/functions/monitoring-view/schema.ts +18 -0
- package/amplify/functions/monitoring-view/wire.js +37 -0
- package/amplify/functions/monitoring-view/wire.ts +47 -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/python-bundle.js +20 -14
- package/amplify/functions/shared/python-bundle.ts +25 -18
- package/amplify/publication/redirect-and-index.js +83 -0
- package/amplify/publication/redirect-rules.js +61 -0
- package/amplify/publication/redirect-rules.ts +79 -0
- package/amplify/publication/s3-projection.js +5 -1
- package/amplify/publication/s3-projection.ts +7 -1
- package/amplify/site-backend-config.ts +10 -0
- package/amplify/site-backend.js +31 -0
- package/amplify/site-backend.ts +38 -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/bin/papyrus-infra.mjs +20 -1
- package/components/editorial-checks-admin-panel.tsx +347 -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/monitoring-overview-view.tsx +240 -0
- package/components/monitoring-panel.tsx +40 -0
- package/components/newsroom-article-editor.tsx +136 -11
- package/components/newsroom-board-view.tsx +6 -7
- package/components/newsroom-curation-bootstrap.tsx +45 -0
- package/components/newsroom-ops-shell.tsx +7 -3
- package/components/newsroom-organizer.css +74 -61
- package/components/newsroom-references-view.tsx +14 -14
- 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/publication-cyclotron-panel.tsx +10 -10
- package/components/reference-add-entry.tsx +75 -0
- package/components/reference-curation-controls.tsx +30 -21
- package/components/reference-cyclotron-priming.tsx +4 -4
- package/components/reference-cyclotron-suggestion.tsx +1 -1
- package/components/reference-priming.tsx +14 -10
- package/components/reference-section-suggestion.tsx +1 -1
- package/components/topic-steering-workspace.tsx +27 -2
- package/components/use-editorial-checks.ts +133 -0
- package/lib/category-repository.ts +11 -9
- package/lib/curation-routes.ts +342 -0
- package/lib/editorial-checks-backend.ts +16 -0
- package/lib/editorial-checks-client.ts +194 -0
- package/lib/editorial-checks-demo.ts +147 -0
- package/lib/editorial-checks-settings.ts +27 -0
- package/lib/editorial-checks.ts +171 -0
- package/lib/editorial-preview.ts +228 -0
- package/lib/monitoring-client.ts +29 -0
- package/lib/monitoring-overview-fixture.ts +51 -0
- package/lib/monitoring-overview.ts +144 -0
- package/lib/newsroom-board.ts +27 -2
- package/lib/relevance-status.ts +2 -2
- package/lib/site-brand-tenant.ts +11 -6
- package/lib/site-brand.ts +23 -3
- package/package.json +47 -3
- package/routes.manifest.json +34 -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
|
@@ -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) {
|
|
@@ -551,7 +615,7 @@ export function NewsroomArticleEditor({ articleId, demo, embedded = false }: { a
|
|
|
551
615
|
<Button
|
|
552
616
|
data-newsroom-article-publish
|
|
553
617
|
disabled={publishBlocked}
|
|
554
|
-
onClick={
|
|
618
|
+
onClick={requestPublish}
|
|
555
619
|
size="sm"
|
|
556
620
|
type="button"
|
|
557
621
|
>
|
|
@@ -714,18 +778,29 @@ export function NewsroomArticleEditor({ articleId, demo, embedded = false }: { a
|
|
|
714
778
|
{imageError}
|
|
715
779
|
</p>
|
|
716
780
|
) : null}
|
|
717
|
-
<
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
781
|
+
<MarkusBodyEditor
|
|
782
|
+
activeFindingId={editorial.activeFindingId}
|
|
783
|
+
findings={editorialMarks}
|
|
784
|
+
onChange={(value) => patchEditor({ bodyMarkus: value })}
|
|
785
|
+
onFindingClick={editorial.setActiveFindingId}
|
|
786
|
+
onSelectionChange={editorial.setSelection}
|
|
722
787
|
ref={bodyRef}
|
|
723
|
-
rows={28}
|
|
724
|
-
spellCheck={false}
|
|
725
788
|
value={editor.bodyMarkus}
|
|
726
789
|
/>
|
|
727
790
|
</div>
|
|
728
791
|
|
|
792
|
+
{editorial.enabled ? (
|
|
793
|
+
<EditorialFindingsPanel
|
|
794
|
+
{...editorialActions}
|
|
795
|
+
activeFindingId={editorial.activeFindingId}
|
|
796
|
+
dismissed={editorial.dismissed}
|
|
797
|
+
onCheck={() => void editorial.check()}
|
|
798
|
+
result={editorial.result}
|
|
799
|
+
selection={editorial.selection}
|
|
800
|
+
status={editorial.status}
|
|
801
|
+
/>
|
|
802
|
+
) : null}
|
|
803
|
+
|
|
729
804
|
{validation.status === "unavailable" ? (
|
|
730
805
|
<NewsroomOpsStatusBanner tone="error">{validation.message}</NewsroomOpsStatusBanner>
|
|
731
806
|
) : null}
|
|
@@ -763,7 +838,7 @@ export function NewsroomArticleEditor({ articleId, demo, embedded = false }: { a
|
|
|
763
838
|
className={cn("min-w-0", mobileTab === "preview" ? "block" : "hidden")}
|
|
764
839
|
data-newsroom-article-preview-column
|
|
765
840
|
>
|
|
766
|
-
<div className="rounded-xl border border-border bg-card p-4" data-newsroom-article-preview>
|
|
841
|
+
<div className="relative rounded-xl border border-border bg-card p-4" data-newsroom-article-preview ref={previewRef}>
|
|
767
842
|
{(readFrontMatterText(editor.frontMatterYaml, FRONT_MATTER_SUBTITLE_KEY) || readFrontMatterText(editor.frontMatterYaml, FRONT_MATTER_BLURB_KEY)) ? (
|
|
768
843
|
<div className="newsroom-article-summary" data-newsroom-article-summary>
|
|
769
844
|
{readFrontMatterText(editor.frontMatterYaml, FRONT_MATTER_SUBTITLE_KEY) ? <p className="newsroom-article-subtitle">{readFrontMatterText(editor.frontMatterYaml, FRONT_MATTER_SUBTITLE_KEY)}</p> : null}
|
|
@@ -774,12 +849,40 @@ export function NewsroomArticleEditor({ articleId, demo, embedded = false }: { a
|
|
|
774
849
|
<p className="mb-2 text-xs text-muted-foreground">Showing the last valid version.</p>
|
|
775
850
|
) : null}
|
|
776
851
|
{bodyIr ? (
|
|
777
|
-
<MarkusIrPreview
|
|
852
|
+
<MarkusIrPreview
|
|
853
|
+
bodyIr={bodyIr}
|
|
854
|
+
editorial={
|
|
855
|
+
editorial.inlineResult
|
|
856
|
+
? {
|
|
857
|
+
result: editorial.inlineResult,
|
|
858
|
+
findings: editorialOpen,
|
|
859
|
+
activeFindingIds: previewPopover?.kind === "findings" ? previewPopover.findingIds : editorial.activeFindingId ? [editorial.activeFindingId] : [],
|
|
860
|
+
onOpenMark: (findingIds, anchor) => {
|
|
861
|
+
editorial.setActiveFindingId(findingIds[0] ?? null);
|
|
862
|
+
setPreviewPopover({ kind: "findings", findingIds, anchor: anchor.getBoundingClientRect() });
|
|
863
|
+
},
|
|
864
|
+
onSelectSource: (source, anchor) => setPreviewPopover({ kind: "selection", source, anchor }),
|
|
865
|
+
}
|
|
866
|
+
: undefined
|
|
867
|
+
}
|
|
868
|
+
imageUrls={imageUrls}
|
|
869
|
+
/>
|
|
778
870
|
) : (
|
|
779
871
|
<p className="text-sm text-muted-foreground">
|
|
780
872
|
{invalid ? "Fix the errors to see a preview." : "The preview appears once the markup is valid."}
|
|
781
873
|
</p>
|
|
782
874
|
)}
|
|
875
|
+
{previewPopover && editorial.enabled ? (
|
|
876
|
+
<EditorialPreviewPopover
|
|
877
|
+
actions={editorialActions}
|
|
878
|
+
body={editor.bodyMarkus}
|
|
879
|
+
container={previewRef.current}
|
|
880
|
+
findings={editorialOpen}
|
|
881
|
+
key={previewPopover.kind === "findings" ? previewPopover.findingIds.join() : `${previewPopover.source.start}-${previewPopover.source.end}`}
|
|
882
|
+
onClose={() => setPreviewPopover(null)}
|
|
883
|
+
target={previewPopover}
|
|
884
|
+
/>
|
|
885
|
+
) : null}
|
|
783
886
|
</div>
|
|
784
887
|
</div>
|
|
785
888
|
</div>
|
|
@@ -801,6 +904,28 @@ export function NewsroomArticleEditor({ articleId, demo, embedded = false }: { a
|
|
|
801
904
|
</DialogContent>
|
|
802
905
|
</Dialog>
|
|
803
906
|
|
|
907
|
+
<Dialog onOpenChange={setPublishWarningOpen} open={publishWarningOpen}>
|
|
908
|
+
<DialogContent>
|
|
909
|
+
<DialogHeader>
|
|
910
|
+
<DialogTitle>Publish with open editorial findings?</DialogTitle>
|
|
911
|
+
<DialogDescription>
|
|
912
|
+
{editorial.openWarnings.length === 1
|
|
913
|
+
? "One editorial finding is still open."
|
|
914
|
+
: `${editorial.openWarnings.length} editorial findings are still open.`}{" "}
|
|
915
|
+
Findings are advice: you can publish anyway.
|
|
916
|
+
</DialogDescription>
|
|
917
|
+
</DialogHeader>
|
|
918
|
+
<ul className="max-h-48 list-disc space-y-1 overflow-auto pl-5 text-sm" data-newsroom-article-publish-warnings>
|
|
919
|
+
{editorial.openWarnings.slice(0, 8).map((finding) => (
|
|
920
|
+
<li key={finding.id}>{finding.message}{finding.excerpt ? `: “${finding.excerpt}”` : ""}</li>
|
|
921
|
+
))}
|
|
922
|
+
</ul>
|
|
923
|
+
<DialogFooter>
|
|
924
|
+
<Button onClick={() => setPublishWarningOpen(false)} type="button" variant="outline">Keep editing</Button>
|
|
925
|
+
<Button data-newsroom-article-publish-anyway onClick={() => void publish()} type="button">Publish anyway</Button>
|
|
926
|
+
</DialogFooter>
|
|
927
|
+
</DialogContent>
|
|
928
|
+
</Dialog>
|
|
804
929
|
<Dialog onOpenChange={setUnpublishOpen} open={unpublishOpen}>
|
|
805
930
|
<DialogContent>
|
|
806
931
|
<DialogHeader>
|
|
@@ -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[]>([]);
|
|
@@ -172,7 +169,8 @@ 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}>
|
|
@@ -196,5 +194,6 @@ export function NewsroomBoardView({ subject, deskId }: { subject: NewsroomBoardS
|
|
|
196
194
|
</IssueDetailPanel>}
|
|
197
195
|
</div>
|
|
198
196
|
</div>
|
|
197
|
+
</IssueIdFormatProvider>
|
|
199
198
|
</NewsroomOpsShell>;
|
|
200
199
|
}
|
|
@@ -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
|
+
}
|
|
@@ -19,6 +19,7 @@ import { ProspectusAccountChooser, useProspectusAccountSummary } from "./prospec
|
|
|
19
19
|
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription, SheetTrigger } from "./ui/sheet";
|
|
20
20
|
import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuGroup, DropdownMenuLabel, DropdownMenuItem, DropdownMenuSeparator } from "./ui/dropdown-menu";
|
|
21
21
|
import "./newsroom-organizer.css";
|
|
22
|
+
import { NewsroomThemeFonts, useNewsroomThemeAttributes } from "./newsroom-theme";
|
|
22
23
|
|
|
23
24
|
export type NewsroomNavCount = {
|
|
24
25
|
count: number | null;
|
|
@@ -85,11 +86,12 @@ function Account({ placement }: { placement: "sidebar" | "header" }) {
|
|
|
85
86
|
media.addEventListener("change", close);
|
|
86
87
|
return () => media.removeEventListener("change", close);
|
|
87
88
|
}, []);
|
|
89
|
+
const themeAttributes = useNewsroomThemeAttributes();
|
|
88
90
|
return <DropdownMenu open={open} onOpenChange={setOpen}>
|
|
89
91
|
<DropdownMenuTrigger id={`organizer-account-trigger-${placement}`} className={`organizer-account organizer-account-${placement}`} aria-label="Account menu" title="Account menu">
|
|
90
92
|
<UserRound size={18} aria-hidden="true" /><span><strong>{auth.label}</strong><small>{accountSummary ?? (auth.status === "signedIn" ? "Account & preferences" : auth.status === "signedOut" ? "Not signed in" : "Checking session")}</small></span>
|
|
91
93
|
</DropdownMenuTrigger>
|
|
92
|
-
<DropdownMenuContent className="organizer-account-popup" side={placement === "sidebar" ? "top" : "bottom"} align="end">
|
|
94
|
+
<DropdownMenuContent className="organizer-account-popup" {...themeAttributes} side={placement === "sidebar" ? "top" : "bottom"} align="end">
|
|
93
95
|
<DropdownMenuGroup>
|
|
94
96
|
<DropdownMenuLabel>{auth.label}</DropdownMenuLabel><DropdownMenuSeparator />
|
|
95
97
|
<ProspectusAccountChooser />
|
|
@@ -104,6 +106,7 @@ function Account({ placement }: { placement: "sidebar" | "header" }) {
|
|
|
104
106
|
|
|
105
107
|
export function NewsroomOpsShell({ activeTab, appTitle, backHref = "/", backLabel, children, demo = false, headerActions, pageTitle, showNavigation = true }: NewsroomOpsShellProps) {
|
|
106
108
|
const pathname = usePathname();
|
|
109
|
+
const themeAttributes = useNewsroomThemeAttributes();
|
|
107
110
|
const session = useOptionalNewsDeskClient();
|
|
108
111
|
const admin = demo || session?.shell.dashboard?.canManageUsers === true;
|
|
109
112
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
@@ -145,7 +148,8 @@ export function NewsroomOpsShell({ activeTab, appTitle, backHref = "/", backLabe
|
|
|
145
148
|
aria-label={sidebarToggleLabel} aria-expanded={!collapsed} aria-controls="organizer-sidebar" onClick={() => setCollapsed(value => !value)}>
|
|
146
149
|
{collapsed ? <PanelLeftOpen size={16} aria-hidden="true" /> : <PanelLeftClose size={16} aria-hidden="true" />}
|
|
147
150
|
</button>;
|
|
148
|
-
return <div className="newsroom-organizer" data-news-desk data-newsroom-ops-shell="true" data-newsroom-chrome="organizer" data-sidebar-collapsed={collapsed || !showNavigation ? "true" : "false"}>
|
|
151
|
+
return <div className="newsroom-organizer" {...themeAttributes} data-news-desk data-newsroom-ops-shell="true" data-newsroom-chrome="organizer" data-sidebar-collapsed={collapsed || !showNavigation ? "true" : "false"}>
|
|
152
|
+
<NewsroomThemeFonts />
|
|
149
153
|
{showNavigation && <aside className="organizer-sidebar" hidden={collapsed} id="organizer-sidebar">
|
|
150
154
|
<div className="organizer-sidebar-head">
|
|
151
155
|
<Link className="organizer-brand" href={getNewsroomNavHref("/newsroom", demo)}>{SITE_BRAND.components?.NewsroomLogo ? <SITE_BRAND.components.NewsroomLogo className="organizer-brand-logo" /> : appTitle}<small>The newsroom</small></Link>
|
|
@@ -167,7 +171,7 @@ export function NewsroomOpsShell({ activeTab, appTitle, backHref = "/", backLabe
|
|
|
167
171
|
href={getNewsroomNavHref(item.path, demo)} title={item.description} aria-label={item.label} aria-current={activeTab === item.key ? "page" : undefined}>
|
|
168
172
|
<Icon size={18} aria-hidden="true" /><span>{item.label}</span></Link></li>; })}
|
|
169
173
|
<li><Sheet open={menuOpen} onOpenChange={setMenuOpen}><SheetTrigger className="organizer-more" aria-label="More"><Menu size={18} aria-hidden="true" /><span>More</span></SheetTrigger>
|
|
170
|
-
<SheetContent side="bottom" className="organizer-menu"><SheetHeader><SheetTitle>Newsroom</SheetTitle><SheetDescription>Workspaces and administration</SheetDescription></SheetHeader>
|
|
174
|
+
<SheetContent side="bottom" className="organizer-menu" {...themeAttributes}><SheetHeader><SheetTitle>Newsroom</SheetTitle><SheetDescription>Workspaces and administration</SheetDescription></SheetHeader>
|
|
171
175
|
<Navigation activePath={activePath} demo={demo} admin={admin} surface="menu" onNavigate={() => setMenuOpen(false)} />
|
|
172
176
|
</SheetContent>
|
|
173
177
|
</Sheet></li>
|