@anthusai/papyrus 0.0.0 → 1.0.0-next.10
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/LICENSE +21 -0
- package/amplify/auth/resource.js +61 -0
- package/amplify/auth/resource.ts +65 -0
- package/amplify/data/resource.js +2069 -0
- package/amplify/data/resource.ts +2185 -0
- package/amplify/data/schema.js +1882 -0
- package/amplify/data/schema.ts +1990 -0
- package/amplify/functions/assignment-action/handler.ts +1416 -0
- package/amplify/functions/assignment-action/resource.js +8 -0
- package/amplify/functions/assignment-action/resource.ts +9 -0
- package/amplify/functions/category-action/handler.ts +2259 -0
- package/amplify/functions/category-action/resource.js +8 -0
- package/amplify/functions/category-action/resource.ts +9 -0
- package/amplify/functions/console-chat-responder/Cargo.lock +3456 -0
- package/amplify/functions/console-chat-responder/Cargo.toml +26 -0
- package/amplify/functions/console-chat-responder/Dockerfile +19 -0
- package/amplify/functions/console-chat-responder/py/execute_tactus_runner.py +116 -0
- package/amplify/functions/console-chat-responder/resource.js +153 -0
- package/amplify/functions/console-chat-responder/resource.ts +179 -0
- package/amplify/functions/console-chat-responder/src/main.rs +3242 -0
- package/amplify/functions/content-actions/handler.py +149 -0
- package/amplify/functions/content-actions/requirements.txt +4 -0
- package/amplify/functions/content-actions/resource.js +40 -0
- package/amplify/functions/content-actions/resource.ts +54 -0
- package/amplify/functions/email-submission-processor/handler.py +46 -0
- package/amplify/functions/email-submission-processor/requirements.txt +2 -0
- package/amplify/functions/email-submission-processor/resource.js +56 -0
- package/amplify/functions/email-submission-processor/resource.ts +69 -0
- package/amplify/functions/graphql-jwt-authorizer/handler.ts +193 -0
- package/amplify/functions/graphql-jwt-authorizer/resource.js +14 -0
- package/amplify/functions/graphql-jwt-authorizer/resource.ts +15 -0
- package/amplify/functions/knowledge-query/handler.py +1 -0
- package/amplify/functions/knowledge-query/requirements.txt +3 -0
- package/amplify/functions/knowledge-query/resource.js +50 -0
- package/amplify/functions/knowledge-query/resource.ts +61 -0
- package/amplify/functions/manage-user-role/handler.ts +799 -0
- package/amplify/functions/manage-user-role/resource.js +8 -0
- package/amplify/functions/manage-user-role/resource.ts +9 -0
- package/amplify/functions/model-attachment-upload/handler.ts +387 -0
- package/amplify/functions/model-attachment-upload/resource.js +8 -0
- package/amplify/functions/model-attachment-upload/resource.ts +9 -0
- package/amplify/functions/newsroom-summary/handler.ts +321 -0
- package/amplify/functions/newsroom-summary/resource.js +8 -0
- package/amplify/functions/newsroom-summary/resource.ts +9 -0
- package/amplify/functions/procedure-action/handler.ts +621 -0
- package/amplify/functions/procedure-action/resource.js +8 -0
- package/amplify/functions/procedure-action/resource.ts +9 -0
- package/amplify/functions/reader-settings/handler.ts +322 -0
- package/amplify/functions/reader-settings/resource.js +8 -0
- package/amplify/functions/reader-settings/resource.ts +9 -0
- package/amplify/functions/ses-inbound-receive/handler.ts +493 -0
- package/amplify/functions/ses-inbound-receive/resource.js +8 -0
- package/amplify/functions/ses-inbound-receive/resource.ts +9 -0
- package/amplify/functions/shared/amplify-secrets.ts +65 -0
- package/amplify/functions/shared/console-chat-enqueue.ts +117 -0
- package/amplify/functions/shared/email-mime-intake.ts +196 -0
- package/amplify/functions/shared/email-submission.ts +415 -0
- package/amplify/functions/shared/inbound-email-intake.ts +37 -0
- package/amplify/functions/shared/lambda-data-client.ts +21 -0
- package/amplify/functions/shared/model-payloads.ts +186 -0
- package/amplify/functions/shared/python-bundle.js +130 -0
- package/amplify/functions/shared/python-bundle.ts +162 -0
- package/amplify/functions/shared/slack-events.ts +111 -0
- package/amplify/functions/slack-delivery/handler.py +112 -0
- package/amplify/functions/slack-delivery/requirements.txt +2 -0
- package/amplify/functions/slack-delivery/resource.js +38 -0
- package/amplify/functions/slack-delivery/resource.ts +51 -0
- package/amplify/functions/slack-events/handler.ts +155 -0
- package/amplify/functions/slack-events/resource.js +11 -0
- package/amplify/functions/slack-events/resource.ts +12 -0
- package/amplify/inbound-email/stack.js +46 -0
- package/amplify/inbound-email/stack.ts +61 -0
- package/amplify/package.json +3 -0
- package/amplify/rebuild-trigger-settings.js +22 -0
- package/amplify/rebuild-trigger-settings.ts +42 -0
- package/amplify/site-backend-config.js +1 -0
- package/amplify/site-backend-config.ts +36 -0
- package/amplify/site-backend.js +615 -0
- package/amplify/site-backend.ts +739 -0
- package/amplify/storage/resource.js +21 -0
- package/amplify/storage/resource.ts +22 -0
- package/app/%5F_preview/[[...path]]/route.ts +8 -0
- package/app/[year]/[month]/[day]/[articleSlug]/page.tsx +62 -0
- package/app/[year]/[month]/[day]/edition-route-page.tsx +53 -0
- package/app/[year]/[month]/[day]/page/[pageNumber]/page.tsx +32 -0
- package/app/[year]/[month]/[day]/page.tsx +22 -0
- package/app/[year]/[month]/[day]/section/[sectionKey]/page.tsx +32 -0
- package/app/api/archive/editions/route.ts +18 -0
- package/app/api/media/[...path]/route.ts +35 -0
- package/app/api/revalidate/route.ts +94 -0
- package/app/archive/page.tsx +24 -0
- package/app/articles/[slug]/page.tsx +35 -0
- package/app/globals.css +10345 -0
- package/app/layout.tsx +142 -0
- package/app/newsroom/[section]/[...selection]/page.tsx +50 -0
- package/app/newsroom/[section]/page.tsx +47 -0
- package/app/newsroom/articles/[id]/page.tsx +15 -0
- package/app/newsroom/articles/page.tsx +13 -0
- package/app/newsroom/forum/[threadId]/page.tsx +35 -0
- package/app/newsroom/insights/[threadId]/page.tsx +22 -0
- package/app/newsroom/insights/page.tsx +14 -0
- package/app/newsroom/layout.tsx +12 -0
- package/app/newsroom/page.tsx +31 -0
- package/app/newsroom/sections/[sectionId]/page.tsx +17 -0
- package/app/page.tsx +272 -0
- package/app/papyrus-theme.css +11 -0
- package/app/robots.ts +11 -0
- package/app/settings/page.tsx +14 -0
- package/app/tailwind.css +65 -0
- package/bin/papyrus-app.mjs +20 -0
- package/bin/papyrus-infra.mjs +95 -0
- package/components/ai-elements/conversation.tsx +168 -0
- package/components/ai-elements/message.tsx +360 -0
- package/components/ai-elements/model-selector.tsx +30 -0
- package/components/ai-elements/prompt-input.tsx +1461 -0
- package/components/ai-elements/shimmer.tsx +76 -0
- package/components/ai-elements/suggestion.tsx +54 -0
- package/components/ai-elements/tool.tsx +173 -0
- package/components/amplify-client-provider.tsx +140 -0
- package/components/archive-grid.tsx +89 -0
- package/components/archive-shell.tsx +41 -0
- package/components/article-video.tsx +156 -0
- package/components/blog-page-background.tsx +20 -0
- package/components/dev-sandbox-editor-sign-in.tsx +60 -0
- package/components/markus-ir-preview.tsx +300 -0
- package/components/news-desk-client-provider.tsx +211 -0
- package/components/news-desk-page.tsx +116 -0
- package/components/news-desk-taxonomy-client.ts +3684 -0
- package/components/newspaper.tsx +1929 -0
- package/components/newsroom-access-gate.tsx +66 -0
- package/components/newsroom-article-editor.tsx +741 -0
- package/components/newsroom-articles-view.tsx +129 -0
- package/components/newsroom-assignments-view.tsx +664 -0
- package/components/newsroom-ops-overview.tsx +226 -0
- package/components/newsroom-ops-shell.tsx +321 -0
- package/components/newsroom-references-view.tsx +598 -0
- package/components/newsroom-topics-view.tsx +425 -0
- package/components/papyrus-console-shell.tsx +1912 -0
- package/components/pictogram-figure.tsx +65 -0
- package/components/presentation-footer.tsx +146 -0
- package/components/reader-auth-control.tsx +162 -0
- package/components/reader-auth-state.ts +122 -0
- package/components/reader-settings.ts +332 -0
- package/components/reader-theme-control.tsx +85 -0
- package/components/reference-source-preview.tsx +163 -0
- package/components/settings-page.tsx +239 -0
- package/components/staging-banner.tsx +11 -0
- package/components/topic-steering-workspace.tsx +18005 -0
- package/components/ui/badge.tsx +30 -0
- package/components/ui/button-group.tsx +87 -0
- package/components/ui/button.tsx +58 -0
- package/components/ui/card.tsx +32 -0
- package/components/ui/command.tsx +194 -0
- package/components/ui/dialog.tsx +158 -0
- package/components/ui/dropdown-menu.tsx +266 -0
- package/components/ui/hover-card.tsx +51 -0
- package/components/ui/input-group.tsx +158 -0
- package/components/ui/input.tsx +20 -0
- package/components/ui/scroll-area.tsx +16 -0
- package/components/ui/select.tsx +199 -0
- package/components/ui/separator.tsx +23 -0
- package/components/ui/sheet.tsx +92 -0
- package/components/ui/spinner.tsx +10 -0
- package/components/ui/tabs.tsx +113 -0
- package/components/ui/textarea.tsx +18 -0
- package/components/ui/tooltip.tsx +66 -0
- package/components/use-resolved-papyrus-theme.ts +47 -0
- package/infra/amplify-app-shell.js +234 -0
- package/infra/amplify-app-shell.ts +284 -0
- package/infra/build-specs.js +85 -0
- package/infra/build-specs.ts +131 -0
- package/infra/github-oidc-provider.js +24 -0
- package/infra/github-oidc-provider.ts +30 -0
- package/infra/index.js +15 -0
- package/infra/site-config.js +186 -0
- package/infra/site-config.ts +235 -0
- package/lib/amplify-outputs-guard.ts +24 -0
- package/lib/amplify-outputs-path.ts +16 -0
- package/lib/amplify-server-runtime.ts +63 -0
- package/lib/archive-data.ts +30 -0
- package/lib/archive-types.ts +15 -0
- package/lib/articles.ts +320 -0
- package/lib/cached-content-repository.ts +90 -0
- package/lib/category-dashboard.ts +37 -0
- package/lib/category-repository.ts +1907 -0
- package/lib/console-chat-client.ts +1316 -0
- package/lib/console-web-context.ts +118 -0
- package/lib/content-actions-client.ts +98 -0
- package/lib/content-actions-demo.ts +190 -0
- package/lib/content-load-timing.ts +23 -0
- package/lib/content-repository.ts +43 -0
- package/lib/content-source-context.ts +26 -0
- package/lib/content-types.ts +104 -0
- package/lib/define-site.js +3 -0
- package/lib/define-site.ts +21 -0
- package/lib/demo-amplify-outputs.ts +15 -0
- package/lib/dev-sandbox-editor-auth.ts +18 -0
- package/lib/doctrine.ts +128 -0
- package/lib/edition-routes.ts +155 -0
- package/lib/edition-sections.ts +111 -0
- package/lib/empty-edition-layout-plan.ts +109 -0
- package/lib/empty-theme.css +1 -0
- package/lib/excerpts.ts +8 -0
- package/lib/graphql-content-repository.ts +1140 -0
- package/lib/image-url.ts +10 -0
- package/lib/layout-plan.ts +981 -0
- package/lib/layout-scenarios.ts +564 -0
- package/lib/markus-body.ts +168 -0
- package/lib/markus-ir.ts +235 -0
- package/lib/markus-projection.ts +243 -0
- package/lib/markus-to-article.ts +93 -0
- package/lib/news-desk-session.ts +160 -0
- package/lib/newspaper-layout.ts +3459 -0
- package/lib/newsroom-articles.ts +325 -0
- package/lib/newsroom-assignments.ts +173 -0
- package/lib/newsroom-base-path.ts +48 -0
- package/lib/newsroom-card-layout.ts +102 -0
- package/lib/newsroom-category-drilldown.ts +133 -0
- package/lib/newsroom-demo-articles.ts +90 -0
- package/lib/newsroom-demo-dashboard.ts +267 -0
- package/lib/newsroom-demo-profile.ts +46 -0
- package/lib/newsroom-forum-routes.ts +153 -0
- package/lib/newsroom-index-filters.ts +202 -0
- package/lib/newsroom-knowledge-query-request.ts +51 -0
- package/lib/newsroom-list-selection.ts +42 -0
- package/lib/newsroom-media-backend.ts +142 -0
- package/lib/newsroom-media.ts +182 -0
- package/lib/newsroom-nav.ts +44 -0
- package/lib/newsroom-references.ts +116 -0
- package/lib/newsroom-sections.ts +52 -0
- package/lib/newsroom-topics.ts +118 -0
- package/lib/papyrus-object-kinds.ts +24 -0
- package/lib/papyrus-web-locations.ts +480 -0
- package/lib/pilobol-sample.ts +167 -0
- package/lib/presentation-footer.ts +92 -0
- package/lib/pretext-layout.ts +179 -0
- package/lib/public-placeholder-config.ts +102 -0
- package/lib/publication-items.ts +181 -0
- package/lib/reader-route-config.ts +1 -0
- package/lib/reader-static-params.ts +56 -0
- package/lib/reader-storage-url.ts +63 -0
- package/lib/reference-display.ts +74 -0
- package/lib/reference-policy.ts +97 -0
- package/lib/reference-source-preview.ts +175 -0
- package/lib/renderer-config.ts +50 -0
- package/lib/renderer.ts +87 -0
- package/lib/reporting-story-budget.ts +638 -0
- package/lib/semantic-graph.ts +554 -0
- package/lib/site-brand-tenant.ts +22 -0
- package/lib/site-brand.ts +194 -0
- package/lib/site-env.ts +52 -0
- package/lib/site-renderer.ts +17 -0
- package/lib/site-stack.ts +195 -0
- package/lib/staging-gate.ts +37 -0
- package/lib/staging-preview-object.ts +98 -0
- package/lib/themed-image.ts +30 -0
- package/lib/utils.ts +6 -0
- package/lib/video-mode.ts +42 -0
- package/lib/video-script.ts +63 -0
- package/middleware.ts +103 -0
- package/package.json +318 -1
- package/renderers/markus/index.ts +25 -0
- package/renderers/markus/stubs.tsx +33 -0
- package/renderers/pretext/article-page.tsx +169 -0
- package/renderers/pretext/index.ts +30 -0
- package/renderers/pretext/layouts.ts +16 -0
- package/renderers/pretext/presentation-shell.tsx +470 -0
- package/routes.manifest.json +208 -0
- package/scripts/fixtures/pilobol-sample.ast.json +234 -0
- package/scripts/videoml/build-browser-bundle.mjs +22 -0
- package/scripts/videoml/build-preview-bundle.mjs +50 -0
- package/scripts/videoml/bundle-common.mjs +129 -0
- package/scripts/videoml/preview/preview-entry.tsx +381 -0
- package/scripts/videoml/preview/preview-stage-fit.ts +28 -0
- package/scripts/videoml/preview/preview-timing.ts +42 -0
- package/scripts/videoml/preview.html +77 -0
- package/scripts/videoml/standard-entry.tsx +46 -0
- package/src/backend.js +1 -0
- package/src/backend.ts +3 -0
- package/src/sync.mjs +87 -0
- package/src/with-papyrus.d.mts +2 -0
- package/src/with-papyrus.mjs +60 -0
package/app/layout.tsx
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
import { Playfair_Display, Plus_Jakarta_Sans } from "next/font/google";
|
|
3
|
+
import Script from "next/script";
|
|
4
|
+
import { AmplifyClientProvider } from "../components/amplify-client-provider";
|
|
5
|
+
import { PapyrusConsoleShell } from "../components/papyrus-console-shell";
|
|
6
|
+
import { TooltipProvider } from "../components/ui/tooltip";
|
|
7
|
+
import { getPresentationChoices, getSiteBrand } from "../lib/site-brand";
|
|
8
|
+
import { getSiteStack } from "../lib/site-stack";
|
|
9
|
+
import { isIndexable, showStagingBanner } from "../lib/site-env";
|
|
10
|
+
import { StagingBanner } from "../components/staging-banner";
|
|
11
|
+
import "./tailwind.css";
|
|
12
|
+
import "./globals.css";
|
|
13
|
+
import "papyrus-site-theme";
|
|
14
|
+
|
|
15
|
+
const playfairDisplay = Playfair_Display({
|
|
16
|
+
subsets: ["latin"],
|
|
17
|
+
variable: "--font-serif",
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const plusJakartaSans = Plus_Jakarta_Sans({
|
|
21
|
+
subsets: ["latin"],
|
|
22
|
+
variable: "--font-sans",
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const faviconVersion = "20260517-1";
|
|
26
|
+
const defaultTheme = "system";
|
|
27
|
+
export async function generateMetadata(): Promise<Metadata> {
|
|
28
|
+
const siteBrand = getSiteBrand();
|
|
29
|
+
return {
|
|
30
|
+
title: siteBrand.appTitle,
|
|
31
|
+
description: siteBrand.appDescription,
|
|
32
|
+
...(isIndexable() ? {} : { robots: { index: false, follow: false } }),
|
|
33
|
+
icons: {
|
|
34
|
+
icon: [
|
|
35
|
+
{ url: `/icon-light.png?v=${faviconVersion}`, type: "image/png" },
|
|
36
|
+
{ url: `/icon.png?v=${faviconVersion}`, type: "image/png" },
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export default async function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
|
|
43
|
+
const siteBrand = getSiteBrand();
|
|
44
|
+
const presentationChoices = getPresentationChoices(siteBrand);
|
|
45
|
+
const siteStack = getSiteStack(siteBrand);
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<html
|
|
49
|
+
lang="en"
|
|
50
|
+
className="light light-theme"
|
|
51
|
+
data-papyrus-theme={defaultTheme}
|
|
52
|
+
data-site-brand={siteBrand.id}
|
|
53
|
+
data-theme-pack={siteStack.ops.themePack}
|
|
54
|
+
data-ops-chrome={siteStack.ops.chrome}
|
|
55
|
+
data-renderer={siteStack.publication.renderer.kind}
|
|
56
|
+
data-default-presentation={siteBrand.defaultPresentation}
|
|
57
|
+
data-presentation-choices={presentationChoices.join(",")}
|
|
58
|
+
{...(siteBrand.forcedPresentation ? { "data-forced-presentation": siteBrand.forcedPresentation } : {})}
|
|
59
|
+
suppressHydrationWarning
|
|
60
|
+
>
|
|
61
|
+
<body className={`${playfairDisplay.variable} ${plusJakartaSans.variable}`}>
|
|
62
|
+
<Script id="papyrus-favicon-color-scheme" strategy="beforeInteractive">
|
|
63
|
+
{`(() => {
|
|
64
|
+
const version = "${faviconVersion}";
|
|
65
|
+
const lightHref = "/icon-light.png?v=" + version;
|
|
66
|
+
const darkHref = "/icon-dark.png?v=" + version;
|
|
67
|
+
const settingsStorageKey = "papyrus:reader-settings";
|
|
68
|
+
const defaultTheme = "${defaultTheme}";
|
|
69
|
+
|
|
70
|
+
const getLink = () => {
|
|
71
|
+
const existingTagged = document.querySelector('link[rel="icon"][data-papyrus-theme-icon="true"]');
|
|
72
|
+
if (existingTagged) return existingTagged;
|
|
73
|
+
|
|
74
|
+
const existingPng = document.querySelector('link[rel="icon"][type="image/png"]');
|
|
75
|
+
if (existingPng) {
|
|
76
|
+
existingPng.setAttribute("data-papyrus-theme-icon", "true");
|
|
77
|
+
return existingPng;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const link = document.createElement("link");
|
|
81
|
+
link.rel = "icon";
|
|
82
|
+
link.type = "image/png";
|
|
83
|
+
link.setAttribute("data-papyrus-theme-icon", "true");
|
|
84
|
+
document.head.appendChild(link);
|
|
85
|
+
return link;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const iconLink = getLink();
|
|
89
|
+
const readTheme = () => {
|
|
90
|
+
try {
|
|
91
|
+
const stored = window.localStorage.getItem(settingsStorageKey);
|
|
92
|
+
const parsed = stored ? JSON.parse(stored) : null;
|
|
93
|
+
return parsed && (parsed.theme === "light" || parsed.theme === "dark" || parsed.theme === "system")
|
|
94
|
+
? parsed.theme
|
|
95
|
+
: defaultTheme;
|
|
96
|
+
} catch {
|
|
97
|
+
return defaultTheme;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
const isDarkTheme = (theme) => theme === "dark" || (theme === "system" && media.matches);
|
|
101
|
+
const applyThemeClass = (theme) => {
|
|
102
|
+
const isDark = isDarkTheme(theme);
|
|
103
|
+
document.documentElement.classList.toggle("dark", isDark);
|
|
104
|
+
document.documentElement.classList.toggle("dark-theme", isDark);
|
|
105
|
+
document.documentElement.classList.toggle("light", !isDark);
|
|
106
|
+
document.documentElement.classList.toggle("light-theme", !isDark);
|
|
107
|
+
return isDark;
|
|
108
|
+
};
|
|
109
|
+
const apply = () => {
|
|
110
|
+
const theme = readTheme();
|
|
111
|
+
document.documentElement.dataset.papyrusTheme = theme;
|
|
112
|
+
const isDark = applyThemeClass(theme);
|
|
113
|
+
const nextHref = isDark ? darkHref : lightHref;
|
|
114
|
+
if (iconLink.getAttribute("href") !== nextHref) {
|
|
115
|
+
iconLink.setAttribute("href", nextHref);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const media = window.matchMedia("(prefers-color-scheme: dark)");
|
|
120
|
+
apply();
|
|
121
|
+
|
|
122
|
+
if (typeof media.addEventListener === "function") {
|
|
123
|
+
media.addEventListener("change", apply);
|
|
124
|
+
} else if (typeof media.addListener === "function") {
|
|
125
|
+
media.addListener(apply);
|
|
126
|
+
}
|
|
127
|
+
window.addEventListener("storage", (event) => {
|
|
128
|
+
if (event.key === settingsStorageKey) apply();
|
|
129
|
+
});
|
|
130
|
+
window.addEventListener("papyrus:settings-changed", apply);
|
|
131
|
+
})();`}
|
|
132
|
+
</Script>
|
|
133
|
+
{showStagingBanner() ? <StagingBanner /> : null}
|
|
134
|
+
<AmplifyClientProvider>
|
|
135
|
+
<TooltipProvider>
|
|
136
|
+
<PapyrusConsoleShell>{children}</PapyrusConsoleShell>
|
|
137
|
+
</TooltipProvider>
|
|
138
|
+
</AmplifyClientProvider>
|
|
139
|
+
</body>
|
|
140
|
+
</html>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { notFound, redirect } from "next/navigation";
|
|
2
|
+
import { NewsDeskPage } from "../../../../components/news-desk-page";
|
|
3
|
+
import type { NewsDeskTab } from "../../../../components/topic-steering-workspace";
|
|
4
|
+
|
|
5
|
+
export const dynamic = "force-dynamic";
|
|
6
|
+
|
|
7
|
+
type NewsDeskNestedSectionPageProps = {
|
|
8
|
+
params: Promise<{ section: string; selection: string[] }>;
|
|
9
|
+
searchParams?: Promise<{
|
|
10
|
+
demo?: string | string[];
|
|
11
|
+
panel?: string | string[];
|
|
12
|
+
reference?: string | string[];
|
|
13
|
+
category?: string | string[];
|
|
14
|
+
node?: string | string[];
|
|
15
|
+
assignment?: string | string[];
|
|
16
|
+
message?: string | string[];
|
|
17
|
+
user?: string | string[];
|
|
18
|
+
item?: string | string[];
|
|
19
|
+
q?: string | string[];
|
|
20
|
+
anchorKind?: string | string[];
|
|
21
|
+
anchorId?: string | string[];
|
|
22
|
+
anchorLineageId?: string | string[];
|
|
23
|
+
maxTokens?: string | string[];
|
|
24
|
+
from?: string | string[];
|
|
25
|
+
}>;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const NEWS_DESK_ROUTE_SECTIONS = new Set<NewsDeskTab>([
|
|
29
|
+
"administration",
|
|
30
|
+
"topics",
|
|
31
|
+
"concepts",
|
|
32
|
+
"references",
|
|
33
|
+
"insights",
|
|
34
|
+
"messages",
|
|
35
|
+
"assignments",
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
export default async function NewsDeskNestedSectionPage({ params, searchParams }: NewsDeskNestedSectionPageProps) {
|
|
39
|
+
const { section, selection } = await params;
|
|
40
|
+
if (section === "users") redirect("/newsroom/administration/users");
|
|
41
|
+
if (section === "doctrine") redirect("/newsroom/administration/policies");
|
|
42
|
+
if (section === "administration" && selection[0] === "analysis") redirect("/newsroom/assignments");
|
|
43
|
+
if (section === "desks") {
|
|
44
|
+
const category = selection[0] ? decodeURIComponent(selection[0]) : null;
|
|
45
|
+
if (category) redirect(`/newsroom/topics?category=${encodeURIComponent(category)}`);
|
|
46
|
+
redirect("/newsroom/topics");
|
|
47
|
+
}
|
|
48
|
+
if (!NEWS_DESK_ROUTE_SECTIONS.has(section as NewsDeskTab)) notFound();
|
|
49
|
+
return <NewsDeskPage section={section} selectionPath={selection} searchParams={searchParams} />;
|
|
50
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { notFound, redirect } from "next/navigation";
|
|
2
|
+
import { NewsDeskPage } from "../../../components/news-desk-page";
|
|
3
|
+
import type { NewsDeskTab } from "../../../components/topic-steering-workspace";
|
|
4
|
+
|
|
5
|
+
export const dynamic = "force-dynamic";
|
|
6
|
+
|
|
7
|
+
type NewsDeskSectionPageProps = {
|
|
8
|
+
params: Promise<{ section: string }>;
|
|
9
|
+
searchParams?: Promise<{
|
|
10
|
+
demo?: string | string[];
|
|
11
|
+
panel?: string | string[];
|
|
12
|
+
reference?: string | string[];
|
|
13
|
+
category?: string | string[];
|
|
14
|
+
node?: string | string[];
|
|
15
|
+
assignment?: string | string[];
|
|
16
|
+
message?: string | string[];
|
|
17
|
+
user?: string | string[];
|
|
18
|
+
item?: string | string[];
|
|
19
|
+
q?: string | string[];
|
|
20
|
+
anchorKind?: string | string[];
|
|
21
|
+
anchorId?: string | string[];
|
|
22
|
+
anchorLineageId?: string | string[];
|
|
23
|
+
maxTokens?: string | string[];
|
|
24
|
+
from?: string | string[];
|
|
25
|
+
proposal?: string | string[];
|
|
26
|
+
}>;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const NEWS_DESK_ROUTE_SECTIONS = new Set<NewsDeskTab>([
|
|
30
|
+
"administration",
|
|
31
|
+
"topics",
|
|
32
|
+
"concepts",
|
|
33
|
+
"references",
|
|
34
|
+
"insights",
|
|
35
|
+
"messages",
|
|
36
|
+
"assignments",
|
|
37
|
+
"search",
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
export default async function NewsDeskSectionPage({ params, searchParams }: NewsDeskSectionPageProps) {
|
|
41
|
+
const { section } = await params;
|
|
42
|
+
if (section === "users") redirect("/newsroom/administration/users");
|
|
43
|
+
if (section === "doctrine") redirect("/newsroom/administration/policies");
|
|
44
|
+
if (section === "desks") redirect("/newsroom/topics");
|
|
45
|
+
if (!NEWS_DESK_ROUTE_SECTIONS.has(section as NewsDeskTab)) notFound();
|
|
46
|
+
return <NewsDeskPage section={section} searchParams={searchParams} />;
|
|
47
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { NewsroomArticleEditor } from "../../../../components/newsroom-article-editor";
|
|
2
|
+
|
|
3
|
+
export const dynamic = "force-dynamic";
|
|
4
|
+
|
|
5
|
+
type NewsroomArticleEditorPageProps = {
|
|
6
|
+
params: Promise<{ id: string }>;
|
|
7
|
+
searchParams?: Promise<{ demo?: string | string[] }>;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export default async function NewsroomArticleEditorPage({ params, searchParams }: NewsroomArticleEditorPageProps) {
|
|
11
|
+
const { id } = await params;
|
|
12
|
+
const resolved = await searchParams;
|
|
13
|
+
const demoValue = Array.isArray(resolved?.demo) ? resolved?.demo[0] : resolved?.demo;
|
|
14
|
+
return <NewsroomArticleEditor articleId={decodeURIComponent(id)} demo={demoValue === "1"} />;
|
|
15
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { NewsroomArticlesView } from "../../../components/newsroom-articles-view";
|
|
2
|
+
|
|
3
|
+
export const dynamic = "force-dynamic";
|
|
4
|
+
|
|
5
|
+
type NewsroomArticlesPageProps = {
|
|
6
|
+
searchParams?: Promise<{ demo?: string | string[] }>;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export default async function NewsroomArticlesPage({ searchParams }: NewsroomArticlesPageProps) {
|
|
10
|
+
const resolved = await searchParams;
|
|
11
|
+
const demoValue = Array.isArray(resolved?.demo) ? resolved?.demo[0] : resolved?.demo;
|
|
12
|
+
return <NewsroomArticlesView demo={demoValue === "1"} />;
|
|
13
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { NewsDeskPage } from "../../../../components/news-desk-page";
|
|
2
|
+
|
|
3
|
+
export const dynamic = "force-dynamic";
|
|
4
|
+
|
|
5
|
+
type NewsroomForumThreadPageProps = {
|
|
6
|
+
params: Promise<{ threadId: string }>;
|
|
7
|
+
searchParams?: Promise<{
|
|
8
|
+
demo?: string | string[];
|
|
9
|
+
panel?: string | string[];
|
|
10
|
+
reference?: string | string[];
|
|
11
|
+
category?: string | string[];
|
|
12
|
+
node?: string | string[];
|
|
13
|
+
assignment?: string | string[];
|
|
14
|
+
message?: string | string[];
|
|
15
|
+
user?: string | string[];
|
|
16
|
+
item?: string | string[];
|
|
17
|
+
q?: string | string[];
|
|
18
|
+
anchorKind?: string | string[];
|
|
19
|
+
anchorId?: string | string[];
|
|
20
|
+
anchorLineageId?: string | string[];
|
|
21
|
+
maxTokens?: string | string[];
|
|
22
|
+
from?: string | string[];
|
|
23
|
+
}>;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export default async function NewsroomForumThreadPage({ params, searchParams }: NewsroomForumThreadPageProps) {
|
|
27
|
+
const { threadId } = await params;
|
|
28
|
+
return (
|
|
29
|
+
<NewsDeskPage
|
|
30
|
+
section="overview"
|
|
31
|
+
selectionPath={["forum", threadId]}
|
|
32
|
+
searchParams={searchParams}
|
|
33
|
+
/>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { NewsDeskPage } from "../../../../components/news-desk-page";
|
|
2
|
+
|
|
3
|
+
export const dynamic = "force-dynamic";
|
|
4
|
+
|
|
5
|
+
type NewsroomInsightThreadPageProps = {
|
|
6
|
+
params: Promise<{ threadId: string }>;
|
|
7
|
+
searchParams?: Promise<{
|
|
8
|
+
demo?: string | string[];
|
|
9
|
+
domain?: string | string[];
|
|
10
|
+
}>;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export default async function NewsroomInsightThreadPage({ params, searchParams }: NewsroomInsightThreadPageProps) {
|
|
14
|
+
const { threadId } = await params;
|
|
15
|
+
return (
|
|
16
|
+
<NewsDeskPage
|
|
17
|
+
section="insights"
|
|
18
|
+
selectionPath={[threadId]}
|
|
19
|
+
searchParams={searchParams}
|
|
20
|
+
/>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { NewsDeskPage } from "../../../components/news-desk-page";
|
|
2
|
+
|
|
3
|
+
export const dynamic = "force-dynamic";
|
|
4
|
+
|
|
5
|
+
type NewsroomInsightsPageProps = {
|
|
6
|
+
searchParams?: Promise<{
|
|
7
|
+
demo?: string | string[];
|
|
8
|
+
domain?: string | string[];
|
|
9
|
+
}>;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default async function NewsroomInsightsPage({ searchParams }: NewsroomInsightsPageProps) {
|
|
13
|
+
return <NewsDeskPage section="insights" searchParams={searchParams} />;
|
|
14
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { NewsDeskClientProvider } from "../../components/news-desk-client-provider";
|
|
2
|
+
import { resolveDevSandboxEditorAuth } from "../../lib/dev-sandbox-editor-auth";
|
|
3
|
+
|
|
4
|
+
export default function NewsroomLayout({ children }: Readonly<{ children: React.ReactNode }>) {
|
|
5
|
+
const devSandboxEditorAuth = resolveDevSandboxEditorAuth();
|
|
6
|
+
|
|
7
|
+
return (
|
|
8
|
+
<NewsDeskClientProvider devSandboxEditorAuth={devSandboxEditorAuth}>
|
|
9
|
+
{children}
|
|
10
|
+
</NewsDeskClientProvider>
|
|
11
|
+
);
|
|
12
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { NewsDeskPage } from "../../components/news-desk-page";
|
|
2
|
+
|
|
3
|
+
export const dynamic = "force-dynamic";
|
|
4
|
+
|
|
5
|
+
export type NewsDeskRootSearchParams = {
|
|
6
|
+
demo?: string | string[];
|
|
7
|
+
section?: string | string[];
|
|
8
|
+
tab?: string | string[];
|
|
9
|
+
panel?: string | string[];
|
|
10
|
+
reference?: string | string[];
|
|
11
|
+
category?: string | string[];
|
|
12
|
+
node?: string | string[];
|
|
13
|
+
assignment?: string | string[];
|
|
14
|
+
message?: string | string[];
|
|
15
|
+
user?: string | string[];
|
|
16
|
+
item?: string | string[];
|
|
17
|
+
q?: string | string[];
|
|
18
|
+
anchorKind?: string | string[];
|
|
19
|
+
anchorId?: string | string[];
|
|
20
|
+
anchorLineageId?: string | string[];
|
|
21
|
+
maxTokens?: string | string[];
|
|
22
|
+
from?: string | string[];
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
type NewsDeskRootPageProps = {
|
|
26
|
+
searchParams?: Promise<NewsDeskRootSearchParams>;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export default function NewsDeskRootPage({ searchParams }: NewsDeskRootPageProps) {
|
|
30
|
+
return <NewsDeskPage searchParams={searchParams} />;
|
|
31
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { notFound } from "next/navigation";
|
|
2
|
+
import { NewsDeskPage } from "../../../../components/news-desk-page";
|
|
3
|
+
|
|
4
|
+
export const dynamic = "force-dynamic";
|
|
5
|
+
|
|
6
|
+
type NewsroomDeepSectionPageProps = {
|
|
7
|
+
params: Promise<{ sectionId: string }>;
|
|
8
|
+
searchParams?: Promise<Record<string, string | string[] | undefined>>;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export default async function NewsroomDeepSectionPage({ params, searchParams }: NewsroomDeepSectionPageProps) {
|
|
12
|
+
const { sectionId } = await params;
|
|
13
|
+
const normalizedSectionId = decodeURIComponent(sectionId ?? "").trim();
|
|
14
|
+
if (!normalizedSectionId) notFound();
|
|
15
|
+
|
|
16
|
+
return <NewsDeskPage sectionPageId={normalizedSectionId} searchParams={searchParams} />;
|
|
17
|
+
}
|
package/app/page.tsx
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { getSiteRenderer } from "../lib/site-renderer";
|
|
2
|
+
import { contentRepository, getScenarioIdParam } from "../lib/content-repository";
|
|
3
|
+
import type { EditionContent } from "../lib/content-types";
|
|
4
|
+
import { createEditionSectionPlan } from "../lib/edition-sections";
|
|
5
|
+
import { getEditionDatePath } from "../lib/edition-routes";
|
|
6
|
+
import {
|
|
7
|
+
EMPTY_EDITION_PLACEHOLDER_SLUG,
|
|
8
|
+
buildEmptyEditionLayoutPlan,
|
|
9
|
+
} from "../lib/empty-edition-layout-plan";
|
|
10
|
+
import {
|
|
11
|
+
loadPublicPlaceholderSections,
|
|
12
|
+
loadPublicPlaceholderTopics,
|
|
13
|
+
type PublicPlaceholderSection,
|
|
14
|
+
type PublicPlaceholderTopic,
|
|
15
|
+
} from "../lib/public-placeholder-config";
|
|
16
|
+
import type { PublicationItem } from "../lib/publication-items";
|
|
17
|
+
import { SITE_BRAND, rootRoute } from "../lib/site-brand";
|
|
18
|
+
import { redirect } from "next/navigation";
|
|
19
|
+
import NewsDeskRootPage, { type NewsDeskRootSearchParams } from "./newsroom/page";
|
|
20
|
+
|
|
21
|
+
export const dynamic = "force-dynamic";
|
|
22
|
+
|
|
23
|
+
type HomePageProps = {
|
|
24
|
+
searchParams?: Promise<{
|
|
25
|
+
scenario?: string | string[];
|
|
26
|
+
code?: string | string[];
|
|
27
|
+
state?: string | string[];
|
|
28
|
+
error?: string | string[];
|
|
29
|
+
} & NewsDeskRootSearchParams>;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export default async function Home({ searchParams }: HomePageProps) {
|
|
33
|
+
const resolvedSearchParams = await searchParams;
|
|
34
|
+
const scenarioId = getScenarioIdParam(resolvedSearchParams?.scenario);
|
|
35
|
+
|
|
36
|
+
// Root-route behavior is per-brand (SITE_BRAND.rootRoute). A site that
|
|
37
|
+
// co-hosts reader + CMS uses the default "reader" (render the publication
|
|
38
|
+
// home page here). A CMS-only site points the root at the newsroom or
|
|
39
|
+
// anywhere else. Preserve the scenario and OAuth-callback paths.
|
|
40
|
+
if (rootRoute.kind === "newsroom" && !scenarioId) {
|
|
41
|
+
return <NewsDeskRootPage searchParams={searchParams} />;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (rootRoute.kind === "redirect" && !scenarioId && !hasOAuthRedirectParams(resolvedSearchParams)) {
|
|
45
|
+
redirect(rootRoute.destination);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Resolved only on the reader path: Markus brands (renderer "markus") have
|
|
49
|
+
// no Next.js reader and getSiteRenderer() throws for them, but they can
|
|
50
|
+
// still serve the newsroom at the root via rootRoute above.
|
|
51
|
+
const siteRenderer = getSiteRenderer();
|
|
52
|
+
|
|
53
|
+
if (!scenarioId) {
|
|
54
|
+
if (hasOAuthRedirectParams(resolvedSearchParams)) {
|
|
55
|
+
const mastheadHomeHref = await loadFirstPublishedEditionPath();
|
|
56
|
+
const content = await loadLatestGraphQLEdition();
|
|
57
|
+
if (!content || content.items.length === 0) return <siteRenderer.renderEdition content={createEmptyGraphQLEdition()} />;
|
|
58
|
+
return (
|
|
59
|
+
<siteRenderer.renderEdition
|
|
60
|
+
content={content}
|
|
61
|
+
editionBasePath={getEditionDatePath(content.editionDate)}
|
|
62
|
+
mastheadHomeHref={mastheadHomeHref}
|
|
63
|
+
/>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const latestEdition = await contentRepository.getLatestPublishedEdition();
|
|
68
|
+
if (latestEdition) redirect(getEditionDatePath(latestEdition.editionDate));
|
|
69
|
+
return <siteRenderer.renderEdition content={createEmptyGraphQLEdition()} />;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const content = await loadHomeContent(scenarioId);
|
|
73
|
+
if (content.items.length === 0) return <siteRenderer.renderEdition content={createEmptyGraphQLEdition()} />;
|
|
74
|
+
return <siteRenderer.renderEdition content={content} />;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function loadLatestGraphQLEdition(): Promise<EditionContent | null> {
|
|
78
|
+
const latestEdition = await contentRepository.getLatestPublishedEdition();
|
|
79
|
+
if (!latestEdition) return null;
|
|
80
|
+
return contentRepository.loadEditionContent({ editionDate: latestEdition.editionDate });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function loadFirstPublishedEditionPath(): Promise<string | undefined> {
|
|
84
|
+
const firstEdition = await contentRepository.getFirstPublishedEdition();
|
|
85
|
+
if (!firstEdition) return undefined;
|
|
86
|
+
return getEditionDatePath(firstEdition.editionDate);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function loadHomeContent(scenarioId: string | null): Promise<EditionContent> {
|
|
90
|
+
try {
|
|
91
|
+
return await contentRepository.loadEditionContent({ scenarioId });
|
|
92
|
+
} catch (error) {
|
|
93
|
+
if (scenarioId || !isMissingReaderBackendError(error)) throw error;
|
|
94
|
+
return createEmptyGraphQLEdition();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function createEmptyGraphQLEdition(): EditionContent {
|
|
99
|
+
const placeholderSlug = EMPTY_EDITION_PLACEHOLDER_SLUG;
|
|
100
|
+
const configuredSections = loadPublicPlaceholderSections();
|
|
101
|
+
const publicTopics = loadPublicPlaceholderTopics();
|
|
102
|
+
const sectionItems = createPlaceholderSectionItems(configuredSections);
|
|
103
|
+
const topicItems = createPlaceholderTopicItems(publicTopics);
|
|
104
|
+
const sectionsCta = createPlaceholderCtaItem({
|
|
105
|
+
slug: "empty-edition-sections-newsroom",
|
|
106
|
+
title: "Manage sections in Newsroom",
|
|
107
|
+
deck: "Canonical and rotating sections are managed from the Newsroom.",
|
|
108
|
+
href: "/newsroom/sections",
|
|
109
|
+
group: "sections",
|
|
110
|
+
ctaLabel: "Manage sections in Newsroom",
|
|
111
|
+
});
|
|
112
|
+
const topicsCta = createPlaceholderCtaItem({
|
|
113
|
+
slug: "empty-edition-topics-newsroom",
|
|
114
|
+
title: "Curate topics in Newsroom",
|
|
115
|
+
deck: "The AI/ML technology that powers the knowledge base is curated from the Newsroom.",
|
|
116
|
+
href: "/newsroom/topics",
|
|
117
|
+
group: "topics",
|
|
118
|
+
ctaLabel: "Curate topics in Newsroom",
|
|
119
|
+
});
|
|
120
|
+
const items: EditionContent["items"] = [
|
|
121
|
+
createEmptyEditionLandingItem(placeholderSlug),
|
|
122
|
+
...sectionItems,
|
|
123
|
+
sectionsCta,
|
|
124
|
+
...topicItems,
|
|
125
|
+
topicsCta,
|
|
126
|
+
];
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
id: "empty-graphql-edition",
|
|
130
|
+
source: "graphql",
|
|
131
|
+
placeholderMode: "emptyEdition",
|
|
132
|
+
title: SITE_BRAND.appTitle,
|
|
133
|
+
editionDate: new Date().toISOString().slice(0, 10),
|
|
134
|
+
description: "No published GraphQL edition is available yet.",
|
|
135
|
+
items,
|
|
136
|
+
sections: createEditionSectionPlan(items),
|
|
137
|
+
layoutPlan: buildEmptyEditionLayoutPlan({
|
|
138
|
+
placeholderSlug,
|
|
139
|
+
sectionItemSlugs: sectionItems.map((item) => item.slug),
|
|
140
|
+
sectionsCtaSlug: sectionsCta.slug,
|
|
141
|
+
topicItemSlugs: topicItems.map((item) => item.slug),
|
|
142
|
+
topicsCtaSlug: topicsCta.slug,
|
|
143
|
+
}),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function createEmptyEditionLandingItem(slug: string): PublicationItem {
|
|
148
|
+
return {
|
|
149
|
+
type: "article",
|
|
150
|
+
slug,
|
|
151
|
+
shortSlug: "EMPTY",
|
|
152
|
+
section: "Newsroom",
|
|
153
|
+
headline: "An Autonomous Newsroom for Human-Steered Publishing",
|
|
154
|
+
deck: "An automated newsroom that turns research, judgment, and policy into blog-first editions.",
|
|
155
|
+
byline: SITE_BRAND.placeholderByline,
|
|
156
|
+
dateline: "SANDBOX",
|
|
157
|
+
image: {
|
|
158
|
+
src: "/papyrus-plant-placeholder.png",
|
|
159
|
+
themeVariants: {
|
|
160
|
+
dark: {
|
|
161
|
+
src: "/papyrus-plant-placeholder-dark.png",
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
alt: "Anthus Threat Intelligence emblem",
|
|
165
|
+
credit: "",
|
|
166
|
+
layout: {
|
|
167
|
+
minHeight: 120,
|
|
168
|
+
preferredHeight: 320,
|
|
169
|
+
maxHeight: 440,
|
|
170
|
+
aspectRatio: 0.785,
|
|
171
|
+
crop: "contain",
|
|
172
|
+
wrapsText: true,
|
|
173
|
+
inlineFloat: {
|
|
174
|
+
minColumnCount: 4,
|
|
175
|
+
columnSpan: 1,
|
|
176
|
+
widthRatio: 0.42,
|
|
177
|
+
narrowWidthRatio: 0.34,
|
|
178
|
+
maxWidthRatio: 0.42,
|
|
179
|
+
minWidth: 72,
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
body: [
|
|
184
|
+
`${SITE_BRAND.articleTitleSuffix} helps a publication do what good journalism promises: establish the facts, add context, identify trends, and turn scattered information into something readers can understand.`,
|
|
185
|
+
"Instead of starting with a blank article form, you start by steering the newsroom. You define the mission, set editorial policies, choose sections and topics, and tell research agents what questions or trends deserve attention.",
|
|
186
|
+
"As the system finds source material, you curate what it learns. You rate and vote on references, comment on whether they are relevant or reliable, and help the knowledge base decide what should inform future coverage.",
|
|
187
|
+
"Editor agents use that guidance to plan coverage and create assignments. Reporter agents gather context, copywriting agents draft stories, and layout agents assemble editions for human proofing and approval.",
|
|
188
|
+
"You can steer lightly, give explicit research or writing assignments, or write directly when a piece needs a human voice. If you take your hands off the wheel, the newsroom keeps moving in the direction set by its mission, policies, and curated knowledge.",
|
|
189
|
+
"This is the default placeholder for a fresh installation. Once an edition is published, this front page will become the publication itself.",
|
|
190
|
+
],
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function createPlaceholderSectionItems(sections: PublicPlaceholderSection[]): PublicationItem[] {
|
|
195
|
+
return sections.map((section) => ({
|
|
196
|
+
type: "sectionHeader",
|
|
197
|
+
slug: `empty-edition-section-${section.id}`,
|
|
198
|
+
section: section.type === "canonical" ? "Canonical Section" : "Rotating Section",
|
|
199
|
+
title: section.title,
|
|
200
|
+
deck: section.shortTitle,
|
|
201
|
+
metadata: {
|
|
202
|
+
placeholderKind: "section",
|
|
203
|
+
placeholderGroup: section.type === "canonical" ? "canonical" : "rotating",
|
|
204
|
+
placeholderType: section.type === "canonical" ? "canonical" : "rotating",
|
|
205
|
+
},
|
|
206
|
+
}));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function createPlaceholderTopicItems(topics: PublicPlaceholderTopic[]): PublicationItem[] {
|
|
210
|
+
return topics.map((topic) => ({
|
|
211
|
+
type: "sectionHeader",
|
|
212
|
+
slug: `empty-edition-topic-${topic.key}`,
|
|
213
|
+
section: "Inferred Topic",
|
|
214
|
+
title: topic.title,
|
|
215
|
+
deck: topic.shortTitle,
|
|
216
|
+
body: [topic.description],
|
|
217
|
+
metadata: {
|
|
218
|
+
placeholderKind: "topic",
|
|
219
|
+
placeholderGroup: "topics",
|
|
220
|
+
},
|
|
221
|
+
}));
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function createPlaceholderCtaItem({
|
|
225
|
+
slug,
|
|
226
|
+
title,
|
|
227
|
+
deck,
|
|
228
|
+
href,
|
|
229
|
+
group,
|
|
230
|
+
ctaLabel,
|
|
231
|
+
}: {
|
|
232
|
+
slug: string;
|
|
233
|
+
title: string;
|
|
234
|
+
deck: string;
|
|
235
|
+
href: string;
|
|
236
|
+
group: string;
|
|
237
|
+
ctaLabel: string;
|
|
238
|
+
}): PublicationItem {
|
|
239
|
+
return {
|
|
240
|
+
type: "promo",
|
|
241
|
+
slug,
|
|
242
|
+
section: "Newsroom",
|
|
243
|
+
title,
|
|
244
|
+
deck,
|
|
245
|
+
href,
|
|
246
|
+
metadata: {
|
|
247
|
+
placeholderKind: "cta",
|
|
248
|
+
placeholderGroup: group,
|
|
249
|
+
ctaLabel,
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function isMissingGraphQLEditionError(error: unknown): boolean {
|
|
255
|
+
return error instanceof Error && error.message.includes("No published GraphQL edition found");
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function isMissingReaderBackendError(error: unknown): boolean {
|
|
259
|
+
return (
|
|
260
|
+
isMissingGraphQLEditionError(error)
|
|
261
|
+
|| (error instanceof Error && error.message.includes("missing projection model"))
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function hasOAuthRedirectParams(searchParams: Awaited<HomePageProps["searchParams"]>): boolean {
|
|
266
|
+
return hasParam(searchParams?.error) || (hasParam(searchParams?.code) && hasParam(searchParams?.state));
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function hasParam(value: string | string[] | null | undefined): boolean {
|
|
270
|
+
if (Array.isArray(value)) return value.some(Boolean);
|
|
271
|
+
return Boolean(value);
|
|
272
|
+
}
|