@anthusai/papyrus 0.0.0 → 1.0.0-next.1
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 +2037 -0
- package/amplify/data/resource.ts +2148 -0
- package/amplify/data/schema.js +1850 -0
- package/amplify/data/schema.ts +1953 -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/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 +115 -0
- package/amplify/functions/shared/python-bundle.ts +141 -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/site-backend-config.js +1 -0
- package/amplify/site-backend-config.ts +30 -0
- package/amplify/site-backend.js +587 -0
- package/amplify/site-backend.ts +704 -0
- package/amplify/storage/resource.js +18 -0
- package/amplify/storage/resource.ts +19 -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 +10329 -0
- package/app/layout.tsx +146 -0
- package/app/newsroom/[section]/[...selection]/page.tsx +50 -0
- package/app/newsroom/[section]/page.tsx +47 -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/settings/page.tsx +14 -0
- package/app/tailwind.css +65 -0
- package/bin/papyrus-app.mjs +10 -0
- package/bin/papyrus-infra.mjs +39 -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 +47 -0
- package/components/blog-page-background.tsx +20 -0
- package/components/dev-sandbox-editor-sign-in.tsx +60 -0
- package/components/news-desk-client-provider.tsx +211 -0
- package/components/news-desk-page.tsx +119 -0
- package/components/news-desk-taxonomy-client.ts +3684 -0
- package/components/newspaper.tsx +1929 -0
- package/components/newsroom-assignments-view.tsx +664 -0
- package/components/newsroom-ops-overview.tsx +226 -0
- package/components/newsroom-ops-shell.tsx +319 -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/topic-steering-workspace.tsx +18063 -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 +109 -0
- package/infra/amplify-app-shell.ts +155 -0
- package/infra/index.js +12 -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 +89 -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-load-timing.ts +23 -0
- package/lib/content-repository.ts +40 -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/excerpts.ts +8 -0
- package/lib/graphql-content-repository.ts +986 -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-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-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-nav.ts +42 -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 +41 -0
- package/lib/site-brand.ts +179 -0
- package/lib/site-renderer.ts +17 -0
- package/lib/site-stack.ts +195 -0
- package/lib/themed-image.ts +30 -0
- package/lib/utils.ts +6 -0
- package/middleware.ts +78 -0
- package/package.json +296 -1
- package/publications/anth_us/brand.ts +68 -0
- package/publications/anth_us/theme.css +66 -0
- package/publications/papyrus/theme.css +11 -0
- package/publications/pilobol_us/brand.ts +37 -0
- package/publications/pilobol_us/theme.css +140 -0
- package/publications/threat_intelligence/blog-defense/graph.ts +636 -0
- package/publications/threat_intelligence/blog-defense/layout.ts +934 -0
- package/publications/threat_intelligence/blog-defense/page-background.tsx +590 -0
- package/publications/threat_intelligence/brand.ts +40 -0
- package/publications/threat_intelligence/pictograms/art.tsx +608 -0
- package/publications/threat_intelligence/pictograms/figure.tsx +82 -0
- package/publications/threat_intelligence/pictograms/registry.ts +30 -0
- package/publications/threat_intelligence/pictograms/system.tsx +305 -0
- package/publications/threat_intelligence/seed/seed-edition-content.json +629 -0
- package/publications/threat_intelligence/theme.css +1440 -0
- package/renderers/markus/index.ts +25 -0
- package/renderers/markus/stubs.tsx +33 -0
- package/renderers/pretext/article-page.tsx +174 -0
- package/renderers/pretext/index.ts +30 -0
- package/renderers/pretext/layouts.ts +16 -0
- package/renderers/pretext/presentation-shell.tsx +477 -0
- package/routes.manifest.json +182 -0
- package/scripts/fixtures/pilobol-sample.ast.json +234 -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 +47 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import type { EditionPresentationFormat } from "./content-types";
|
|
2
|
+
import papyrusSite from "papyrus-site";
|
|
3
|
+
import { threatIntelligenceBrand } from "../publications/threat_intelligence/brand";
|
|
4
|
+
import { pilobolUsBrand } from "../publications/pilobol_us/brand";
|
|
5
|
+
import { anthUsBrand } from "../publications/anth_us/brand";
|
|
6
|
+
import {
|
|
7
|
+
DEFAULT_THEME_PACK_TOKENS,
|
|
8
|
+
type HostingConfig,
|
|
9
|
+
type OpsChrome,
|
|
10
|
+
type ReaderDeployment,
|
|
11
|
+
type RendererConfig,
|
|
12
|
+
type RootRouteConfig,
|
|
13
|
+
type ThemePackId,
|
|
14
|
+
type ThemePackTokens,
|
|
15
|
+
} from "./site-stack";
|
|
16
|
+
|
|
17
|
+
/** Open string: a publication registers its own brand through `papyrus.config.ts` (`defineSite`). */
|
|
18
|
+
export type SiteBrandId = string;
|
|
19
|
+
|
|
20
|
+
export type SiteBrand = {
|
|
21
|
+
id: SiteBrandId;
|
|
22
|
+
appTitle: string;
|
|
23
|
+
appDescription: string;
|
|
24
|
+
mastheadTitle: string;
|
|
25
|
+
mastheadSubtitle: string;
|
|
26
|
+
mastheadTagline?: string;
|
|
27
|
+
backToHomeLabel: string;
|
|
28
|
+
articleTitleSuffix: string;
|
|
29
|
+
placeholderByline: string;
|
|
30
|
+
defaultPresentation: EditionPresentationFormat;
|
|
31
|
+
forcedPresentation?: EditionPresentationFormat;
|
|
32
|
+
textFont: string;
|
|
33
|
+
footerTitle?: string;
|
|
34
|
+
footerSubtitleOverride?: string;
|
|
35
|
+
mastheadWordSplit: boolean;
|
|
36
|
+
mastheadDateFormat: "raw" | "formatted";
|
|
37
|
+
mastheadSource: "edition" | "brand";
|
|
38
|
+
sectionLinkStrategy: "route" | "anchor";
|
|
39
|
+
defaultVideoCredit?: string;
|
|
40
|
+
themePack: ThemePackId;
|
|
41
|
+
themeTokens: ThemePackTokens;
|
|
42
|
+
renderer: RendererConfig;
|
|
43
|
+
hosting: HostingConfig;
|
|
44
|
+
/** Static reader app when it is not co-hosted with this Papyrus checkout. */
|
|
45
|
+
readerDeployment?: ReaderDeployment;
|
|
46
|
+
/**
|
|
47
|
+
* What the root route (`/`) does. Defaults to `{ kind: "reader" }` (render
|
|
48
|
+
* the publication home page). Set to `{ kind: "redirect", destination }`
|
|
49
|
+
* for a CMS-only deployment whose reader lives elsewhere.
|
|
50
|
+
*/
|
|
51
|
+
rootRoute?: RootRouteConfig;
|
|
52
|
+
/** Public URL prefix for newsroom routes. Default `/newsroom`; use `""` on a CMS-only subdomain. */
|
|
53
|
+
newsroomBasePath?: string;
|
|
54
|
+
opsChrome: OpsChrome;
|
|
55
|
+
corpusKey: string;
|
|
56
|
+
steeringConfigPath: string;
|
|
57
|
+
newsroomSectionsConfigPath: string;
|
|
58
|
+
analysisProfilesPath: string;
|
|
59
|
+
publicationName: string;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const SERIF_TEXT_FONT = 'Georgia, "Times New Roman", serif';
|
|
63
|
+
|
|
64
|
+
const BUILT_IN_SITE_BRANDS: Record<SiteBrandId, SiteBrand> = {
|
|
65
|
+
papyrus: {
|
|
66
|
+
id: "papyrus",
|
|
67
|
+
appTitle: "Papyrus",
|
|
68
|
+
appDescription: "A Pretext-powered responsive newspaper layout lab.",
|
|
69
|
+
mastheadTitle: "PAPYRUS",
|
|
70
|
+
mastheadSubtitle: "Inside Papyrus",
|
|
71
|
+
backToHomeLabel: "Back to Papyrus",
|
|
72
|
+
articleTitleSuffix: "Papyrus",
|
|
73
|
+
placeholderByline: "Papyrus",
|
|
74
|
+
defaultPresentation: "newsprint",
|
|
75
|
+
textFont: SERIF_TEXT_FONT,
|
|
76
|
+
mastheadWordSplit: false,
|
|
77
|
+
mastheadDateFormat: "raw",
|
|
78
|
+
mastheadSource: "edition",
|
|
79
|
+
sectionLinkStrategy: "route",
|
|
80
|
+
themePack: "papyrus",
|
|
81
|
+
themeTokens: DEFAULT_THEME_PACK_TOKENS,
|
|
82
|
+
renderer: { kind: "pretext" },
|
|
83
|
+
hosting: { kind: "amplify-ssr" },
|
|
84
|
+
opsChrome: "app",
|
|
85
|
+
corpusKey: "threat-intelligence",
|
|
86
|
+
steeringConfigPath: "corpora/papyrus-steering.yml",
|
|
87
|
+
newsroomSectionsConfigPath: "corpora/papyrus-newsroom-sections.yml",
|
|
88
|
+
analysisProfilesPath: "corpora/papyrus-analysis-profiles.yml",
|
|
89
|
+
publicationName: "Anthus Threat Intelligence",
|
|
90
|
+
},
|
|
91
|
+
"threat-intelligence": threatIntelligenceBrand,
|
|
92
|
+
"pilobol-us": pilobolUsBrand,
|
|
93
|
+
"anth-us": anthUsBrand,
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Built-in reference brands plus whatever the publication registered in its
|
|
98
|
+
* own `papyrus.config.ts` (resolved through the `papyrus-site` alias).
|
|
99
|
+
*/
|
|
100
|
+
const SITE_BRANDS: Record<SiteBrandId, SiteBrand> = {
|
|
101
|
+
...BUILT_IN_SITE_BRANDS,
|
|
102
|
+
...Object.fromEntries((papyrusSite.brands ?? []).map((brand) => [brand.id, brand])),
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export function normalizeSiteBrandId(value: string | undefined | null): SiteBrandId | null {
|
|
106
|
+
if (!value) return null;
|
|
107
|
+
const normalized = value.trim().toLowerCase().replace(/[._]/g, "-");
|
|
108
|
+
if (!normalized) return null;
|
|
109
|
+
if (normalized === "papyrus") return "papyrus";
|
|
110
|
+
// NOTE: the bare alias `anthus` has resolved to `threat-intelligence` since
|
|
111
|
+
// that brand was added and is kept pointing there so existing
|
|
112
|
+
// PAPYRUS_SITE_BRAND values do not silently change meaning. The Anth.us
|
|
113
|
+
// publication is therefore addressed as `anth-us` / `anth.us` / `anth_us`
|
|
114
|
+
// only. Do not "tidy" this by moving `anthus` across.
|
|
115
|
+
if (normalized === "threat-intelligence" || normalized === "threat-intel" || normalized === "anthus") {
|
|
116
|
+
return "threat-intelligence";
|
|
117
|
+
}
|
|
118
|
+
if (normalized === "anth-us") {
|
|
119
|
+
// `normalized` has already mapped `.` and `_` to `-`, so this covers
|
|
120
|
+
// `anth.us` and `anth_us` too.
|
|
121
|
+
return "anth-us";
|
|
122
|
+
}
|
|
123
|
+
// Bare `pilobol` is deliberately NOT an alias (f268e72): the brand name is
|
|
124
|
+
// Pilobolus; Pilobol.us is the domain and `pilobol-us` the technical id.
|
|
125
|
+
if (normalized === "pilobol-us" || normalized === "pilobolus") {
|
|
126
|
+
return "pilobol-us";
|
|
127
|
+
}
|
|
128
|
+
// Registered brands (publication-owned `papyrus.config.ts`): exact id only.
|
|
129
|
+
if (Object.prototype.hasOwnProperty.call(SITE_BRANDS, normalized)) return normalized;
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function resolveSiteBrandId(
|
|
134
|
+
raw: string | undefined | null = process.env.NEXT_PUBLIC_PAPYRUS_SITE_BRAND ?? process.env.PAPYRUS_SITE_BRAND,
|
|
135
|
+
): SiteBrandId {
|
|
136
|
+
return normalizeSiteBrandId(raw) ?? papyrusSite.defaultBrand ?? "papyrus";
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Cookie set by middleware when `?brand=` is present (demo without rebuild). */
|
|
140
|
+
export function resolveRuntimeSiteBrandId(
|
|
141
|
+
cookieOverride: string | undefined | null = null,
|
|
142
|
+
): SiteBrandId {
|
|
143
|
+
return normalizeSiteBrandId(cookieOverride) ?? resolveSiteBrandId();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function getSiteBrand(id: SiteBrandId = resolveSiteBrandId()): SiteBrand {
|
|
147
|
+
return SITE_BRANDS[id];
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export const SITE_BRAND = getSiteBrand();
|
|
151
|
+
|
|
152
|
+
/** Root-route config for a brand, defaulting to `{ kind: "reader" }`. */
|
|
153
|
+
export function getRootRoute(brand: SiteBrand = SITE_BRAND): RootRouteConfig {
|
|
154
|
+
return brand.rootRoute ?? { kind: "reader" };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export const rootRoute = getRootRoute();
|
|
158
|
+
|
|
159
|
+
export function enforcePresentation(presentation: EditionPresentationFormat): EditionPresentationFormat {
|
|
160
|
+
return SITE_BRAND.forcedPresentation ?? presentation;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function getPresentationChoices(brand: SiteBrand = SITE_BRAND): EditionPresentationFormat[] {
|
|
164
|
+
return brand.forcedPresentation
|
|
165
|
+
? [brand.forcedPresentation]
|
|
166
|
+
: ["newsprint", "blog", "magazine"];
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function getForcedPresentation(): EditionPresentationFormat | undefined {
|
|
170
|
+
return SITE_BRAND.forcedPresentation;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function getDefaultPretextLayout(): EditionPresentationFormat {
|
|
174
|
+
return SITE_BRAND.defaultPresentation;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function getRendererKind(): RendererConfig["kind"] {
|
|
178
|
+
return SITE_BRAND.renderer.kind;
|
|
179
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { MarkusSiteRendererError } from "./renderer-config";
|
|
2
|
+
import { SITE_BRAND } from "./site-brand";
|
|
3
|
+
import type { Renderer } from "./renderer";
|
|
4
|
+
import { pretextRenderer } from "../renderers/pretext";
|
|
5
|
+
|
|
6
|
+
export { MarkusSiteRendererError } from "./renderer-config";
|
|
7
|
+
|
|
8
|
+
export function assertPretextSite(): void {
|
|
9
|
+
if (SITE_BRAND.renderer.kind === "markus") {
|
|
10
|
+
throw new MarkusSiteRendererError("hackerman", SITE_BRAND.id);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function getSiteRenderer(): Renderer {
|
|
15
|
+
assertPretextSite();
|
|
16
|
+
return pretextRenderer;
|
|
17
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Independent site-stack axes. Do not collapse these into one "brand mode".
|
|
3
|
+
* - themePack / themeTokens: ops + shared Shadcn colors
|
|
4
|
+
* - opsChrome: newsroom shell (app vs newsprint)
|
|
5
|
+
* - renderer: publication / reader output (pretext vs markus)
|
|
6
|
+
* - hosting: where the built site is served
|
|
7
|
+
*
|
|
8
|
+
* Shared ops components consume CSS variables only. Pilobolus is the first
|
|
9
|
+
* extra pack, not the platform default (that remains `papyrus`).
|
|
10
|
+
*/
|
|
11
|
+
/** Open string: publications register their own pack id (see lib/define-site.ts). */
|
|
12
|
+
export type ThemePackId = string;
|
|
13
|
+
|
|
14
|
+
export type RendererConfig = {
|
|
15
|
+
kind: "pretext" | "markus";
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type HostingConfig =
|
|
19
|
+
| { kind: "amplify-ssr" }
|
|
20
|
+
| { kind: "amplify-static" };
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* What the root route (`/`) of a Papyrus app does. Independent of hosting and
|
|
24
|
+
* of where the public reader lives — a site configures this explicitly.
|
|
25
|
+
*
|
|
26
|
+
* - `reader`: render the publication home page here (the single-app reader+Cms
|
|
27
|
+
* model used by p.apyr.us and Threat Intelligence). Default when omitted.
|
|
28
|
+
* - `redirect`: redirect `/` to `destination` (a path or absolute URL). Use
|
|
29
|
+
* this for CMS-only deployments whose public reader lives on another app.
|
|
30
|
+
* - `newsroom`: render the newsroom desk at `/` (for a CMS-only subdomain).
|
|
31
|
+
*/
|
|
32
|
+
export type RootRouteConfig =
|
|
33
|
+
| { kind: "reader" }
|
|
34
|
+
| { kind: "redirect"; destination: string; permanent?: boolean }
|
|
35
|
+
| { kind: "newsroom" };
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Optional reader deployment when CMS and public reader ship separately
|
|
39
|
+
* (Pilobolus, Anth.us).
|
|
40
|
+
*
|
|
41
|
+
* `domain` and `amplifyAppId` are optional because a reader can legitimately
|
|
42
|
+
* exist before either is known: a staging port runs on the Amplify-provided
|
|
43
|
+
* hostname with no custom domain attached, and the app id does not exist until
|
|
44
|
+
* someone creates the app. Leave them unset rather than writing down a
|
|
45
|
+
* hostname that is not actually serving this build — a wrong canonical host is
|
|
46
|
+
* worse than an absent one.
|
|
47
|
+
*/
|
|
48
|
+
export type ReaderDeployment = {
|
|
49
|
+
kind: "amplify-static";
|
|
50
|
+
repository: string;
|
|
51
|
+
domain?: string;
|
|
52
|
+
amplifyAppId?: string;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export type OpsChrome = "newsprint" | "app";
|
|
56
|
+
|
|
57
|
+
export type SiteOpsStack = {
|
|
58
|
+
chrome: OpsChrome;
|
|
59
|
+
themePack: ThemePackId;
|
|
60
|
+
themeTokens: ThemePackTokens;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export type SitePublicationStack = {
|
|
64
|
+
renderer: RendererConfig;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/** Ops UI and publication/reader are independently pluggable. */
|
|
68
|
+
export type SiteStack = {
|
|
69
|
+
ops: SiteOpsStack;
|
|
70
|
+
publication: SitePublicationStack;
|
|
71
|
+
hosting: HostingConfig;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export type SiteStackSource = {
|
|
75
|
+
themePack: ThemePackId;
|
|
76
|
+
themeTokens: ThemePackTokens;
|
|
77
|
+
opsChrome: OpsChrome;
|
|
78
|
+
renderer: RendererConfig;
|
|
79
|
+
hosting: HostingConfig;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export function getSiteStack(source: SiteStackSource): SiteStack {
|
|
83
|
+
return {
|
|
84
|
+
ops: {
|
|
85
|
+
chrome: source.opsChrome,
|
|
86
|
+
themePack: source.themePack,
|
|
87
|
+
themeTokens: source.themeTokens,
|
|
88
|
+
},
|
|
89
|
+
publication: { renderer: source.renderer },
|
|
90
|
+
hosting: source.hosting,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export type ThemePackPalette = {
|
|
95
|
+
paper: string;
|
|
96
|
+
moss: string;
|
|
97
|
+
ochre: string;
|
|
98
|
+
ink: string;
|
|
99
|
+
muted?: string;
|
|
100
|
+
card?: string;
|
|
101
|
+
line?: string;
|
|
102
|
+
quote?: string;
|
|
103
|
+
tip?: string;
|
|
104
|
+
caution?: string;
|
|
105
|
+
stage?: string;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export type ThemePackTokens = ThemePackPalette & {
|
|
109
|
+
dark?: ThemePackPalette;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export const DEFAULT_THEME_PACK_TOKENS: ThemePackTokens = {
|
|
113
|
+
paper: "#f4f4f5",
|
|
114
|
+
moss: "#18181b",
|
|
115
|
+
ochre: "#a1a1aa",
|
|
116
|
+
ink: "#3f3f46",
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Pilobolus / fungus-among-us palette copied from AnthusAI/Pilobol.us
|
|
121
|
+
* `web/css/pilobil-theme-v10.css` (`:root` / `@media (prefers-color-scheme: dark)`).
|
|
122
|
+
* Colors only — Shadcn owns typography. Do not load that file at runtime.
|
|
123
|
+
*/
|
|
124
|
+
export const PILOBOL_US_THEME_PACK_TOKENS: ThemePackTokens = {
|
|
125
|
+
ink: "#211d17",
|
|
126
|
+
muted: "#6b6153",
|
|
127
|
+
paper: "#f1ead9",
|
|
128
|
+
card: "#fbf6ea",
|
|
129
|
+
line: "#d7cbb2",
|
|
130
|
+
moss: "#3f5d43",
|
|
131
|
+
ochre: "#a35a2a",
|
|
132
|
+
quote: "#4a3548",
|
|
133
|
+
tip: "#4f6b3a",
|
|
134
|
+
caution: "#8a3324",
|
|
135
|
+
stage: "#ddd4bf",
|
|
136
|
+
dark: {
|
|
137
|
+
ink: "#dfded0",
|
|
138
|
+
muted: "#8b9184",
|
|
139
|
+
paper: "#14170f",
|
|
140
|
+
card: "#1c2117",
|
|
141
|
+
line: "#333b2a",
|
|
142
|
+
moss: "#b7d18a",
|
|
143
|
+
ochre: "#d0895a",
|
|
144
|
+
quote: "#b7a3c4",
|
|
145
|
+
tip: "#9fce7a",
|
|
146
|
+
caution: "#d17e6e",
|
|
147
|
+
stage: "#0d0f0a",
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Anthus palette, read off the live Gatsby site at `AnthusAI/Anth.us`
|
|
153
|
+
* (`src/styles/variables.scss` `:root`). Colors only — Shadcn owns typography.
|
|
154
|
+
* Mapping from that file's names to the pack's slots:
|
|
155
|
+
*
|
|
156
|
+
* | pack slot | Anth.us variable | value |
|
|
157
|
+
* | --------- | ------------------------- | --------- |
|
|
158
|
+
* | `paper` | `--color-layout-bg` | `#f1f9fe` |
|
|
159
|
+
* | `card` | `--color-wrapper-bg` | `#ffffff` |
|
|
160
|
+
* | `ink` | `--color-text` | `#333333` |
|
|
161
|
+
* | `moss` | `--color-primary` | `#dc5497` |
|
|
162
|
+
* | `ochre` | `--color-active` | `#ff57b4` |
|
|
163
|
+
* | `quote` | `--color-gradient-mid` | `#9b165d` |
|
|
164
|
+
* | `tip` | `--color-gradient-end` | `#0389d7` |
|
|
165
|
+
* | `stage` | `--color-hamburger` | `#fedded` |
|
|
166
|
+
*
|
|
167
|
+
* Two slots have no counterpart on the Gatsby site and are **derived**, which
|
|
168
|
+
* is called out here so nobody mistakes them for copied brand values:
|
|
169
|
+
*
|
|
170
|
+
* - `line`: the site's only rule color is `hr { background: hsla(0,0%,0%,0.2) }`
|
|
171
|
+
* over white, flattened to `#cccccc`.
|
|
172
|
+
* - `muted`: the site has no muted-text token at all — it dims with
|
|
173
|
+
* `opacity: 0.5` instead (and references an undefined `--color-text-light`).
|
|
174
|
+
* `#5f6b73` is `--color-text` desaturated toward `paper`'s blue.
|
|
175
|
+
* - `caution`: `#e95800`, which the site uses for one of its `::marker`
|
|
176
|
+
* colors; it has no dedicated danger color.
|
|
177
|
+
*
|
|
178
|
+
* There is deliberately **no `dark` palette**: the Gatsby site has no dark
|
|
179
|
+
* mode. Its only `prefers-color-scheme: dark` block overrides two
|
|
180
|
+
* code-highlight tints. Inventing a dark Anthus palette here would be making
|
|
181
|
+
* up a look rather than porting one, so `theme.css` pins `color-scheme: light`.
|
|
182
|
+
*/
|
|
183
|
+
export const ANTH_US_THEME_PACK_TOKENS: ThemePackTokens = {
|
|
184
|
+
ink: "#333333",
|
|
185
|
+
muted: "#5f6b73",
|
|
186
|
+
paper: "#f1f9fe",
|
|
187
|
+
card: "#ffffff",
|
|
188
|
+
line: "#cccccc",
|
|
189
|
+
moss: "#dc5497",
|
|
190
|
+
ochre: "#ff57b4",
|
|
191
|
+
quote: "#9b165d",
|
|
192
|
+
tip: "#0389d7",
|
|
193
|
+
caution: "#e95800",
|
|
194
|
+
stage: "#fedded",
|
|
195
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ArticleImageThemeVariants, ArticleVideoThemeVariants } from "./articles";
|
|
2
|
+
|
|
3
|
+
export type ResolvedTheme = "light" | "dark";
|
|
4
|
+
|
|
5
|
+
export function resolveThemedImageSrc(
|
|
6
|
+
src: string,
|
|
7
|
+
themeVariants: ArticleImageThemeVariants | undefined,
|
|
8
|
+
theme: ResolvedTheme,
|
|
9
|
+
): string {
|
|
10
|
+
if (theme === "dark" && themeVariants?.dark?.src) return themeVariants.dark.src;
|
|
11
|
+
if (theme === "dark") return resolveStaticDarkSvgFallback(src) ?? src;
|
|
12
|
+
return src;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function resolveThemedVideoSrc(
|
|
16
|
+
src: string,
|
|
17
|
+
themeVariants: ArticleVideoThemeVariants | undefined,
|
|
18
|
+
theme: ResolvedTheme,
|
|
19
|
+
): string {
|
|
20
|
+
if (theme === "light" && themeVariants?.light?.src) return themeVariants.light.src;
|
|
21
|
+
if (theme === "dark" && themeVariants?.dark?.src) return themeVariants.dark.src;
|
|
22
|
+
return src;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function resolveStaticDarkSvgFallback(src: string): string | null {
|
|
26
|
+
if (src.startsWith("/seed-art/threat-intelligence/") && !src.endsWith("-dark.svg") && /\.svg(?:\?.*)?$/i.test(src)) {
|
|
27
|
+
return src.replace(/\.svg(?:\?.*)?$/i, (suffix) => `-dark${suffix}`);
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
package/lib/utils.ts
ADDED
package/middleware.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { NextRequest } from "next/server";
|
|
2
|
+
import { NextResponse } from "next/server";
|
|
3
|
+
import { getSiteBrand, normalizeSiteBrandId, resolveSiteBrandId } from "./lib/site-brand";
|
|
4
|
+
|
|
5
|
+
const BRAND_OVERRIDE_COOKIE = "papyrus-site-brand-override";
|
|
6
|
+
|
|
7
|
+
function usesNewsroomRootPaths(request: NextRequest): boolean {
|
|
8
|
+
const cookieBrand = request.cookies.get(BRAND_OVERRIDE_COOKIE)?.value;
|
|
9
|
+
const brandId = normalizeSiteBrandId(cookieBrand)
|
|
10
|
+
?? normalizeSiteBrandId(request.nextUrl.searchParams.get("brand"))
|
|
11
|
+
?? resolveSiteBrandId();
|
|
12
|
+
// Brand property (not a hard-coded id) so publication-registered brands work.
|
|
13
|
+
return getSiteBrand(brandId).rootRoute?.kind === "newsroom" && getSiteBrand(brandId).newsroomBasePath === "";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function isStaticOrApiPath(pathname: string): boolean {
|
|
17
|
+
return (
|
|
18
|
+
pathname.startsWith("/_next")
|
|
19
|
+
|| pathname.startsWith("/api")
|
|
20
|
+
|| pathname === "/favicon.ico"
|
|
21
|
+
|| pathname.startsWith("/icon")
|
|
22
|
+
|| /\.[a-zA-Z0-9]+$/.test(pathname)
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function middleware(request: NextRequest) {
|
|
27
|
+
const brandParam = request.nextUrl.searchParams.get("brand");
|
|
28
|
+
const brandId = normalizeSiteBrandId(brandParam);
|
|
29
|
+
let response: NextResponse | null = null;
|
|
30
|
+
|
|
31
|
+
if (brandId) {
|
|
32
|
+
response = NextResponse.next();
|
|
33
|
+
response.cookies.set(BRAND_OVERRIDE_COOKIE, brandId, {
|
|
34
|
+
path: "/",
|
|
35
|
+
maxAge: 60 * 60 * 24 * 30,
|
|
36
|
+
sameSite: "lax",
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!usesNewsroomRootPaths(request)) {
|
|
41
|
+
return response ?? NextResponse.next();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const { pathname } = request.nextUrl;
|
|
45
|
+
if (isStaticOrApiPath(pathname)) {
|
|
46
|
+
return response ?? NextResponse.next();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (pathname === "/newsroom" || pathname === "/newsroom/") {
|
|
50
|
+
return NextResponse.redirect(new URL("/", request.url));
|
|
51
|
+
}
|
|
52
|
+
if (pathname.startsWith("/newsroom/")) {
|
|
53
|
+
const target = pathname.slice("/newsroom".length) || "/";
|
|
54
|
+
return NextResponse.redirect(new URL(`${target}${request.nextUrl.search}`, request.url));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (pathname === "/" || pathname === "") {
|
|
58
|
+
return response ?? NextResponse.next();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!pathname.startsWith("/newsroom")) {
|
|
62
|
+
const rewriteUrl = request.nextUrl.clone();
|
|
63
|
+
rewriteUrl.pathname = `/newsroom${pathname}`;
|
|
64
|
+
const rewrite = NextResponse.rewrite(rewriteUrl);
|
|
65
|
+
if (response) {
|
|
66
|
+
for (const cookie of response.cookies.getAll()) {
|
|
67
|
+
rewrite.cookies.set(cookie);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return rewrite;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return response ?? NextResponse.next();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const config = {
|
|
77
|
+
matcher: ["/((?!_next/static|_next/image|favicon.ico|icon).*)"],
|
|
78
|
+
};
|