@anthusai/papyrus 1.0.0-next.2 → 1.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/auth/resource.js +17 -4
- package/amplify/auth/resource.ts +21 -4
- package/amplify/data/resource.js +52 -70
- package/amplify/data/resource.ts +57 -70
- package/amplify/data/schema.js +52 -58
- package/amplify/data/schema.ts +57 -58
- package/amplify/functions/console-chat-responder/py/execute_tactus_runner.py +0 -10
- package/amplify/functions/console-chat-responder/resource.js +0 -7
- package/amplify/functions/console-chat-responder/resource.ts +0 -8
- package/amplify/functions/console-chat-responder/src/main.rs +0 -9
- 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/shared/python-bundle.js +17 -2
- package/amplify/functions/shared/python-bundle.ts +23 -2
- package/amplify/rebuild-trigger-settings.js +22 -0
- package/amplify/rebuild-trigger-settings.ts +42 -0
- package/amplify/site-backend-config.ts +12 -1
- package/amplify/site-backend.js +51 -27
- package/amplify/site-backend.ts +66 -34
- package/amplify/storage/resource.js +3 -0
- package/amplify/storage/resource.ts +3 -0
- package/app/%5F_preview/[[...path]]/route.ts +8 -0
- package/app/[year]/[month]/[day]/[articleSlug]/page.tsx +4 -2
- package/app/[year]/[month]/[day]/edition-route-page.tsx +2 -0
- package/app/api/revalidate/route.ts +8 -7
- package/app/articles/[slug]/page.tsx +6 -4
- package/app/blog-rhythm.css +278 -0
- package/app/globals.css +22 -4
- package/app/layout.tsx +24 -12
- package/app/newsroom/articles/[id]/page.tsx +15 -0
- package/app/newsroom/articles/page.tsx +13 -0
- package/app/page.tsx +12 -4
- package/app/robots.ts +11 -0
- package/bin/papyrus-app.mjs +12 -2
- package/bin/papyrus-infra.mjs +74 -18
- package/components/archive-shell.tsx +2 -22
- package/components/article-video.tsx +247 -3
- package/components/blog-page-background.tsx +23 -6
- package/components/markus-ir-preview.tsx +300 -0
- package/components/news-desk-page.tsx +1 -4
- package/components/newspaper.tsx +6 -2
- 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-ops-overview.tsx +28 -11
- package/components/newsroom-ops-shell.tsx +3 -1
- package/components/pictogram-figure.tsx +9 -2
- package/components/presentation-header.tsx +135 -0
- package/components/presentation-rhythm-hrule.tsx +17 -0
- package/components/settings-page.tsx +2 -1
- package/components/staging-banner.tsx +11 -0
- package/components/topic-steering-workspace.tsx +1 -59
- package/components/use-blog-header-obstacles.ts +81 -0
- package/components/use-brand-fonts-ready.ts +28 -0
- package/components/use-rhythm-overlay.ts +29 -0
- package/infra/amplify-app-shell.js +285 -80
- package/infra/amplify-app-shell.ts +326 -113
- package/infra/build-specs.js +94 -0
- package/infra/build-specs.ts +141 -0
- package/infra/github-oidc-provider.js +24 -0
- package/infra/github-oidc-provider.ts +30 -0
- package/infra/index.js +3 -0
- package/infra/site-config.js +273 -0
- package/infra/site-config.ts +336 -0
- package/lib/blog-feature-solver.ts +459 -0
- package/lib/blog-rhythm.ts +134 -0
- package/lib/brand-fonts-watch.ts +43 -0
- package/lib/brand-fonts.ts +112 -0
- package/lib/cached-content-repository.ts +14 -1
- package/lib/category-repository.ts +1 -93
- package/lib/content-actions-client.ts +98 -0
- package/lib/content-actions-demo.ts +190 -0
- package/lib/content-repository.ts +6 -0
- package/lib/content-source-context.ts +26 -0
- package/lib/content-types.ts +4 -0
- package/lib/edition-routes.ts +31 -29
- package/lib/empty-theme.css +1 -0
- package/lib/graphql-content-repository.ts +239 -43
- package/lib/layout-scenarios.ts +8 -8
- package/lib/newspaper-layout.ts +3 -2
- package/lib/newsroom-articles.ts +325 -0
- package/lib/newsroom-demo-articles.ts +90 -0
- package/lib/newsroom-media-backend.ts +142 -0
- package/lib/newsroom-media.ts +182 -0
- package/lib/newsroom-nav.ts +3 -0
- package/lib/papyrus-web-locations.ts +7 -6
- package/lib/presentation-footer.ts +3 -2
- package/lib/pretext-layout.ts +12 -3
- package/lib/reader-base-path.ts +24 -0
- package/lib/reader-static-params.ts +8 -0
- package/lib/renderer.ts +7 -0
- package/lib/revalidate-secret.ts +59 -0
- package/lib/root-route-redirect.ts +16 -0
- package/lib/site-brand-tenant.ts +3 -22
- package/lib/site-brand.ts +95 -42
- package/lib/site-env.ts +52 -0
- package/lib/staging-gate.ts +37 -0
- package/lib/staging-gated-path.ts +22 -0
- package/lib/staging-preview-object.ts +98 -0
- package/lib/video-mode.ts +42 -0
- package/lib/video-script.ts +77 -0
- package/middleware.ts +56 -42
- package/package.json +40 -1
- package/renderers/pretext/article-page.tsx +40 -9
- package/renderers/pretext/presentation-shared.tsx +113 -0
- package/renderers/pretext/presentation-shell.tsx +18 -112
- package/renderers/pretext/rhythm-article-page.tsx +165 -0
- package/renderers/pretext/rhythm-blog-presentation.tsx +279 -0
- package/renderers/pretext/rhythm-shell-style.ts +35 -0
- package/routes.manifest.json +37 -2
- 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/sync.mjs +30 -6
- package/src/with-papyrus.mjs +15 -2
- package/amplify/functions/graphql-jwt-authorizer/handler.ts +0 -193
- package/amplify/functions/graphql-jwt-authorizer/resource.js +0 -14
- package/amplify/functions/graphql-jwt-authorizer/resource.ts +0 -15
- package/publications/anth_us/brand.ts +0 -68
- package/publications/anth_us/theme.css +0 -66
- package/publications/pilobol_us/brand.ts +0 -37
- package/publications/pilobol_us/theme.css +0 -140
- package/publications/threat_intelligence/blog-defense/graph.ts +0 -636
- package/publications/threat_intelligence/blog-defense/layout.ts +0 -934
- package/publications/threat_intelligence/blog-defense/page-background.tsx +0 -590
- package/publications/threat_intelligence/brand.ts +0 -40
- package/publications/threat_intelligence/pictograms/art.tsx +0 -608
- package/publications/threat_intelligence/pictograms/figure.tsx +0 -82
- package/publications/threat_intelligence/pictograms/registry.ts +0 -30
- package/publications/threat_intelligence/pictograms/system.tsx +0 -305
- package/publications/threat_intelligence/seed/seed-edition-content.json +0 -629
- package/publications/threat_intelligence/theme.css +0 -1440
- /package/{publications/papyrus/theme.css → app/papyrus-theme.css} +0 -0
package/amplify/site-backend.ts
CHANGED
|
@@ -8,17 +8,18 @@ import * as route53 from "aws-cdk-lib/aws-route53";
|
|
|
8
8
|
import * as ses from "aws-cdk-lib/aws-ses";
|
|
9
9
|
import * as sesActions from "aws-cdk-lib/aws-ses-actions";
|
|
10
10
|
import { Effect, PolicyStatement } from "aws-cdk-lib/aws-iam";
|
|
11
|
-
import { CfnEventSourceMapping,
|
|
11
|
+
import { CfnEventSourceMapping, Function as LambdaFunction, FunctionUrlAuthType } from "aws-cdk-lib/aws-lambda";
|
|
12
12
|
import * as s3 from "aws-cdk-lib/aws-s3";
|
|
13
13
|
import { CfnIndex, CfnVectorBucket, CfnVectorBucketPolicy } from "aws-cdk-lib/aws-s3vectors";
|
|
14
14
|
import { dirname, resolve } from "node:path";
|
|
15
15
|
import { fileURLToPath } from "node:url";
|
|
16
|
-
import {
|
|
16
|
+
import { rebuildTriggerSettings } from "./rebuild-trigger-settings";
|
|
17
|
+
import { applyCognitoDomainPrefix, authConfigFromEnv, defineSiteAuth } from "./auth/resource";
|
|
17
18
|
import { data } from "./data/resource";
|
|
18
19
|
import { assignmentAction } from "./functions/assignment-action/resource";
|
|
19
20
|
import { categoryAction } from "./functions/category-action/resource";
|
|
20
21
|
import { ConsoleChatResponderStack } from "./functions/console-chat-responder/resource";
|
|
21
|
-
import {
|
|
22
|
+
import { contentActions } from "./functions/content-actions/resource";
|
|
22
23
|
import { knowledgeQuery } from "./functions/knowledge-query/resource";
|
|
23
24
|
import { manageUserRole } from "./functions/manage-user-role/resource";
|
|
24
25
|
import { modelAttachmentUpload } from "./functions/model-attachment-upload/resource";
|
|
@@ -86,12 +87,13 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
|
|
|
86
87
|
const enableSlackAgent = site.features?.slack ?? readEnvFlag("PAPYRUS_ENABLE_SLACK", false);
|
|
87
88
|
const enableStorageBackups = site.features?.storageBackups ?? readEnvFlag("PAPYRUS_ENABLE_STORAGE_BACKUPS", isAmplifyProductionPipeline);
|
|
88
89
|
|
|
90
|
+
const authConfig = site.auth ?? authConfigFromEnv();
|
|
89
91
|
const backend = defineBackend({
|
|
90
92
|
assignmentAction,
|
|
91
|
-
auth: defineSiteAuth(
|
|
93
|
+
auth: defineSiteAuth(authConfig),
|
|
92
94
|
categoryAction,
|
|
95
|
+
contentActions,
|
|
93
96
|
data,
|
|
94
|
-
graphqlJwtAuthorizer,
|
|
95
97
|
knowledgeQuery,
|
|
96
98
|
manageUserRole,
|
|
97
99
|
modelAttachmentUpload,
|
|
@@ -105,6 +107,15 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
|
|
|
105
107
|
storage,
|
|
106
108
|
});
|
|
107
109
|
|
|
110
|
+
const requestedCognitoDomainPrefix = (authConfig.cognitoDomainPrefix ?? "").trim();
|
|
111
|
+
if (
|
|
112
|
+
authConfig.applyCognitoDomainPrefix === true
|
|
113
|
+
&& requestedCognitoDomainPrefix !== ""
|
|
114
|
+
&& authConfig.disableGoogleOAuth !== true
|
|
115
|
+
) {
|
|
116
|
+
applyCognitoDomainPrefix(backend.auth.stack, requestedCognitoDomainPrefix);
|
|
117
|
+
}
|
|
118
|
+
|
|
108
119
|
const amplifyBackendDir = dirname(fileURLToPath(import.meta.url));
|
|
109
120
|
// Repo root in a Papyrus checkout; package root when installed from npm.
|
|
110
121
|
const projectRoot = resolve(amplifyBackendDir, "..");
|
|
@@ -157,43 +168,19 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
|
|
|
157
168
|
const messageThreadTable = backend.data.resources.tables.MessageThread;
|
|
158
169
|
const dataStack = Stack.of(messageTable);
|
|
159
170
|
const graphqlEndpoint = backend.data.resources.cfnResources.cfnGraphqlApi.attrGraphQlUrl;
|
|
160
|
-
const
|
|
161
|
-
const jwtAuthorizerEnvironment = jwtAuthorizerCfn?.environment;
|
|
162
|
-
const jwtAuthorizerVariables =
|
|
163
|
-
jwtAuthorizerEnvironment
|
|
164
|
-
&& typeof jwtAuthorizerEnvironment === "object"
|
|
165
|
-
&& "variables" in jwtAuthorizerEnvironment
|
|
166
|
-
? (jwtAuthorizerEnvironment as { variables?: Record<string, string> }).variables
|
|
167
|
-
: undefined;
|
|
168
|
-
const jwtSsmEnvConfig =
|
|
169
|
-
process.env.AMPLIFY_SSM_ENV_CONFIG?.trim()
|
|
170
|
-
|| jwtAuthorizerVariables?.AMPLIFY_SSM_ENV_CONFIG?.trim()
|
|
171
|
-
|| "";
|
|
171
|
+
const amplifySsmEnvConfig = process.env.AMPLIFY_SSM_ENV_CONFIG?.trim() || "";
|
|
172
172
|
|
|
173
173
|
if (enableConsoleResponder) {
|
|
174
|
-
|
|
174
|
+
new ConsoleChatResponderStack(dataStack, "ConsoleChatResponder", {
|
|
175
175
|
messageTable,
|
|
176
176
|
messageStreamArn,
|
|
177
177
|
threadTable: messageThreadTable,
|
|
178
178
|
projectRoot,
|
|
179
179
|
graphqlEndpoint,
|
|
180
|
-
amplifySsmEnvConfig: jwtSsmEnvConfig || undefined,
|
|
181
180
|
responseTarget: process.env.PAPYRUS_CONSOLE_RESPONSE_TARGET,
|
|
182
181
|
model: process.env.PAPYRUS_CONSOLE_MODEL,
|
|
183
182
|
prebuiltImageUri: process.env.PAPYRUS_CONSOLE_RESPONDER_IMAGE_URI,
|
|
184
183
|
});
|
|
185
|
-
if (jwtSsmEnvConfig) {
|
|
186
|
-
consoleChatResponder.responderFunction.addEnvironment("AMPLIFY_SSM_ENV_CONFIG", jwtSsmEnvConfig);
|
|
187
|
-
}
|
|
188
|
-
const jwtSecretSsmParam =
|
|
189
|
-
process.env.PAPYRUS_JWT_SECRET_SSM_PARAM?.trim()
|
|
190
|
-
|| (productionAppId !== "" && amplifyAppId === productionAppId
|
|
191
|
-
? `/amplify/${amplifyAppId}/main-branch-cb38ada667/PAPYRUS_JWT_SECRET`
|
|
192
|
-
: "/amplify/papyrus/ryan-sandbox-adcd88a186/PAPYRUS_JWT_SECRET");
|
|
193
|
-
consoleChatResponder.responderFunction.addEnvironment(
|
|
194
|
-
"PAPYRUS_JWT_SECRET_SSM_PARAM",
|
|
195
|
-
jwtSecretSsmParam,
|
|
196
|
-
);
|
|
197
184
|
}
|
|
198
185
|
|
|
199
186
|
if (enableSlackAgent) {
|
|
@@ -214,9 +201,9 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
|
|
|
214
201
|
"PAPYRUS_SLACK_ALLOWED_USER_IDS",
|
|
215
202
|
(process.env.PAPYRUS_SLACK_ALLOWED_USER_IDS ?? "").trim(),
|
|
216
203
|
);
|
|
217
|
-
if (
|
|
218
|
-
backend.slackEvents.addEnvironment("AMPLIFY_SSM_ENV_CONFIG",
|
|
219
|
-
backend.slackDelivery.addEnvironment("AMPLIFY_SSM_ENV_CONFIG",
|
|
204
|
+
if (amplifySsmEnvConfig) {
|
|
205
|
+
backend.slackEvents.addEnvironment("AMPLIFY_SSM_ENV_CONFIG", amplifySsmEnvConfig);
|
|
206
|
+
backend.slackDelivery.addEnvironment("AMPLIFY_SSM_ENV_CONFIG", amplifySsmEnvConfig);
|
|
220
207
|
}
|
|
221
208
|
backend.slackDelivery.addEnvironment("PAPYRUS_GRAPHQL_ENDPOINT", graphqlEndpoint);
|
|
222
209
|
backend.slackDelivery.addEnvironment(
|
|
@@ -386,6 +373,7 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
|
|
|
386
373
|
for (const resource of [
|
|
387
374
|
backend.assignmentAction,
|
|
388
375
|
backend.categoryAction,
|
|
376
|
+
backend.contentActions,
|
|
389
377
|
backend.emailSubmissionProcessor,
|
|
390
378
|
backend.knowledgeQuery,
|
|
391
379
|
backend.modelAttachmentUpload,
|
|
@@ -399,6 +387,50 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
|
|
|
399
387
|
grantNewsroomReadWrite(backend.assignmentAction.resources.lambda as LambdaFunction);
|
|
400
388
|
grantNewsroomReadWrite(backend.categoryAction.resources.lambda as LambdaFunction);
|
|
401
389
|
grantNewsroomReadWrite(backend.newsroomSummary.resources.lambda as LambdaFunction);
|
|
390
|
+
const contentActionsLambda = backend.contentActions.resources.lambda as LambdaFunction;
|
|
391
|
+
grantNewsroomReadWriteDelete(contentActionsLambda);
|
|
392
|
+
contentActionsLambda.addEnvironment(
|
|
393
|
+
"PAPYRUS_GRAPHQL_ENDPOINT",
|
|
394
|
+
backend.data.resources.cfnResources.cfnGraphqlApi.attrGraphQlUrl,
|
|
395
|
+
);
|
|
396
|
+
contentActionsLambda.addToRolePolicy(
|
|
397
|
+
new PolicyStatement({
|
|
398
|
+
actions: ["appsync:GraphQL"],
|
|
399
|
+
resources: ["*"],
|
|
400
|
+
}),
|
|
401
|
+
);
|
|
402
|
+
const rebuildSettings = rebuildTriggerSettings({
|
|
403
|
+
reader: site.reader,
|
|
404
|
+
stagingBuildEnabled: site.stagingBuild?.enabled === true,
|
|
405
|
+
cmsAppId: amplifyAppId,
|
|
406
|
+
region: Stack.of(contentActionsLambda).region,
|
|
407
|
+
account: Stack.of(contentActionsLambda).account,
|
|
408
|
+
});
|
|
409
|
+
for (const [name, value] of Object.entries(rebuildSettings.environment)) {
|
|
410
|
+
contentActionsLambda.addEnvironment(name, value);
|
|
411
|
+
}
|
|
412
|
+
if (rebuildSettings.resources.length > 0) {
|
|
413
|
+
contentActionsLambda.addToRolePolicy(
|
|
414
|
+
new PolicyStatement({ actions: rebuildSettings.actions, resources: rebuildSettings.resources }),
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
if ((site.revalidateBaseUrl ?? "").trim() !== "") {
|
|
418
|
+
backend.contentActions.addEnvironment("PAPYRUS_REVALIDATE_BASE_URL", (site.revalidateBaseUrl ?? "").trim());
|
|
419
|
+
const revalidateSecretParameter = (process.env.PAPYRUS_REVALIDATE_SECRET_PARAMETER ?? "").trim();
|
|
420
|
+
if (!revalidateSecretParameter.startsWith("/")) {
|
|
421
|
+
throw new Error(
|
|
422
|
+
"revalidateBaseUrl is set but PAPYRUS_REVALIDATE_SECRET_PARAMETER is not (an SSM parameter name starting with /). The app-shell template sets it on the production branch for Pretext sites; see docs/site-hosting.md.",
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
backend.contentActions.addEnvironment("PAPYRUS_REVALIDATE_SECRET_PARAMETER", revalidateSecretParameter);
|
|
426
|
+
const revalidateStack = Stack.of(contentActionsLambda);
|
|
427
|
+
contentActionsLambda.addToRolePolicy(
|
|
428
|
+
new PolicyStatement({
|
|
429
|
+
actions: ["ssm:GetParameter"],
|
|
430
|
+
resources: [`arn:${revalidateStack.partition}:ssm:${revalidateStack.region}:${revalidateStack.account}:parameter${revalidateSecretParameter}`],
|
|
431
|
+
}),
|
|
432
|
+
);
|
|
433
|
+
}
|
|
402
434
|
grantNewsroomReadWriteDelete(backend.modelAttachmentUpload.resources.lambda as LambdaFunction);
|
|
403
435
|
grantMediaReadWriteDelete(backend.modelAttachmentUpload.resources.lambda as LambdaFunction);
|
|
404
436
|
if (backend.sesInboundReceive) {
|
|
@@ -11,6 +11,9 @@ export const storage = defineStorage({
|
|
|
11
11
|
"corpora/*": [
|
|
12
12
|
allow.groups(["editor", "admin"]).to(["read"]),
|
|
13
13
|
],
|
|
14
|
+
"preview/*": [
|
|
15
|
+
allow.groups(["editor", "admin"]).to(["read"]),
|
|
16
|
+
],
|
|
14
17
|
"newsroom/*": [
|
|
15
18
|
allow.groups(["editor", "admin"]).to(["read", "write", "delete"]),
|
|
16
19
|
],
|
|
@@ -12,6 +12,9 @@ export const storage = defineStorage({
|
|
|
12
12
|
"corpora/*": [
|
|
13
13
|
allow.groups(["editor", "admin"]).to(["read"]),
|
|
14
14
|
],
|
|
15
|
+
"preview/*": [
|
|
16
|
+
allow.groups(["editor", "admin"]).to(["read"]),
|
|
17
|
+
],
|
|
15
18
|
"newsroom/*": [
|
|
16
19
|
allow.groups(["editor", "admin"]).to(["read", "write", "delete"]),
|
|
17
20
|
],
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { NextRequest } from "next/server";
|
|
2
|
+
import { streamPreviewObject } from "../../../lib/staging-preview-object";
|
|
3
|
+
|
|
4
|
+
export const dynamic = "force-dynamic";
|
|
5
|
+
|
|
6
|
+
export async function GET(request: NextRequest): Promise<Response> {
|
|
7
|
+
return streamPreviewObject(request.nextUrl.pathname);
|
|
8
|
+
}
|
|
@@ -52,10 +52,12 @@ export default async function DateScopedArticlePage({ params }: DateScopedArticl
|
|
|
52
52
|
editionFooter={{
|
|
53
53
|
editionBasePath,
|
|
54
54
|
entries: buildPresentationFooterEntries(content),
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
sections: content.sections.map((section) => ({ key: section.key, label: section.label })),
|
|
56
|
+
subtitle: SITE_BRAND.footerSubtitleOverride ?? (content.description?.trim() || "Inside Papyrus"),
|
|
57
|
+
title: SITE_BRAND.footerTitle,
|
|
57
58
|
}}
|
|
58
59
|
item={item}
|
|
60
|
+
videoScript={content.videoScripts?.[item.slug] ?? null}
|
|
59
61
|
backHref={`${editionBasePath}#${item.slug}`}
|
|
60
62
|
/>
|
|
61
63
|
);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { notFound, redirect } from "next/navigation";
|
|
2
|
+
import { siteServesNextReaderRoutes } from "../../../../lib/reader-static-params";
|
|
2
3
|
import { getSiteRenderer } from "../../../../lib/site-renderer";
|
|
3
4
|
import type { PresentationTarget } from "../../../../renderers/pretext";
|
|
4
5
|
import { loadCachedEditionContent } from "../../../../lib/cached-content-repository";
|
|
@@ -22,6 +23,7 @@ export async function EditionRoutePage({
|
|
|
22
23
|
lockedPresentation,
|
|
23
24
|
target = { kind: "edition" },
|
|
24
25
|
}: EditionRoutePageProps) {
|
|
26
|
+
if (!siteServesNextReaderRoutes()) notFound();
|
|
25
27
|
const route = parseEditionDateRoute({ year, month, day });
|
|
26
28
|
if (!route) notFound();
|
|
27
29
|
if (!route.isCanonical) redirect(route.canonicalPath);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { toPublicReaderPath } from "../../../lib/reader-base-path";
|
|
1
2
|
import { revalidatePath, revalidateTag } from "next/cache";
|
|
2
3
|
import { NextResponse } from "next/server";
|
|
3
4
|
import {
|
|
@@ -8,6 +9,7 @@ import {
|
|
|
8
9
|
editionContentCacheTag,
|
|
9
10
|
editionItemCacheTag,
|
|
10
11
|
} from "../../../lib/cached-content-repository";
|
|
12
|
+
import { isAuthorizedRevalidateRequest } from "../../../lib/revalidate-secret";
|
|
11
13
|
import { getEditionArticlePath, getEditionDatePath } from "../../../lib/edition-routes";
|
|
12
14
|
|
|
13
15
|
export const dynamic = "force-dynamic";
|
|
@@ -21,9 +23,8 @@ type RevalidateRequestBody = {
|
|
|
21
23
|
};
|
|
22
24
|
|
|
23
25
|
export async function POST(request: Request) {
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
if (!configuredSecret || providedSecret !== configuredSecret) {
|
|
26
|
+
const providedSecret = request.headers.get("x-papyrus-revalidate-secret");
|
|
27
|
+
if (!(await isAuthorizedRevalidateRequest(providedSecret))) {
|
|
27
28
|
return NextResponse.json({ ok: false, error: "Unauthorized." }, { status: 401 });
|
|
28
29
|
}
|
|
29
30
|
|
|
@@ -44,8 +45,8 @@ export async function POST(request: Request) {
|
|
|
44
45
|
if (!normalized) continue;
|
|
45
46
|
revalidateTag(articleCacheTag(normalized));
|
|
46
47
|
revalidatedTags.add(articleCacheTag(normalized));
|
|
47
|
-
revalidatePath(`/articles/${encodeURIComponent(normalized)}`);
|
|
48
|
-
revalidatedPaths.add(`/articles/${encodeURIComponent(normalized)}`);
|
|
48
|
+
revalidatePath(toPublicReaderPath(`/articles/${encodeURIComponent(normalized)}`));
|
|
49
|
+
revalidatedPaths.add(toPublicReaderPath(`/articles/${encodeURIComponent(normalized)}`));
|
|
49
50
|
if (editionDate) {
|
|
50
51
|
revalidateTag(editionItemCacheTag(editionDate, normalized));
|
|
51
52
|
revalidatedTags.add(editionItemCacheTag(editionDate, normalized));
|
|
@@ -76,8 +77,8 @@ export async function POST(request: Request) {
|
|
|
76
77
|
revalidatedTags.add(ARCHIVE_CACHE_TAG);
|
|
77
78
|
revalidateTag(ARTICLES_CACHE_TAG);
|
|
78
79
|
revalidatedTags.add(ARTICLES_CACHE_TAG);
|
|
79
|
-
revalidatePath("/archive");
|
|
80
|
-
revalidatedPaths.add("/archive");
|
|
80
|
+
revalidatePath(toPublicReaderPath("/archive"));
|
|
81
|
+
revalidatedPaths.add(toPublicReaderPath("/archive"));
|
|
81
82
|
|
|
82
83
|
for (const path of body.paths ?? []) {
|
|
83
84
|
const normalized = path.trim();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { notFound } from "next/navigation";
|
|
2
2
|
import { getSiteRenderer } from "../../../lib/site-renderer";
|
|
3
|
-
import { getCachedArticle } from "../../../lib/cached-content-repository";
|
|
4
|
-
import { generateArticleStaticParams } from "../../../lib/reader-static-params";
|
|
3
|
+
import { getCachedArticle, getCachedVideoScript } from "../../../lib/cached-content-repository";
|
|
4
|
+
import { generateArticleStaticParams, siteServesNextReaderRoutes } from "../../../lib/reader-static-params";
|
|
5
5
|
import { SITE_BRAND } from "../../../lib/site-brand";
|
|
6
6
|
|
|
7
7
|
// Keep in sync with READER_REVALIDATE_SECONDS in lib/reader-route-config.ts
|
|
@@ -16,6 +16,7 @@ type ArticlePageProps = {
|
|
|
16
16
|
};
|
|
17
17
|
|
|
18
18
|
export async function generateMetadata({ params }: ArticlePageProps) {
|
|
19
|
+
if (!siteServesNextReaderRoutes()) return {};
|
|
19
20
|
const { slug } = await params;
|
|
20
21
|
const article = await getCachedArticle(slug);
|
|
21
22
|
if (!article) return {};
|
|
@@ -26,10 +27,11 @@ export async function generateMetadata({ params }: ArticlePageProps) {
|
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
export default async function ArticlePage({ params }: ArticlePageProps) {
|
|
30
|
+
if (!siteServesNextReaderRoutes()) notFound();
|
|
29
31
|
const { slug } = await params;
|
|
30
|
-
const article = await getCachedArticle(slug);
|
|
32
|
+
const [article, videoScript] = await Promise.all([getCachedArticle(slug), getCachedVideoScript(slug)]);
|
|
31
33
|
if (!article) notFound();
|
|
32
34
|
|
|
33
35
|
const siteRenderer = getSiteRenderer();
|
|
34
|
-
return <siteRenderer.renderArticle article={article} backHref="/" />;
|
|
36
|
+
return <siteRenderer.renderArticle article={article} videoScript={videoScript} backHref="/" />;
|
|
35
37
|
}
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Structural CSS for the rhythm blog layout (blogLayout: "rhythm").
|
|
3
|
+
Every rule is scoped to .blog-rhythm-shell, which only the rhythm layout
|
|
4
|
+
renders, so classic layouts are unaffected. The shell sets --blog-rhythm,
|
|
5
|
+
--blog-row-height, --blog-paint-buffer and --blog-paint-height inline.
|
|
6
|
+
Typography, colors and the header look belong to the publication's theme.css.
|
|
7
|
+
Optional tokens a publication may set:
|
|
8
|
+
--blog-hrule-foreground, --blog-section-rule, --blog-section-band-height,
|
|
9
|
+
--blog-page-padding-x, --blog-secondary-pair-min-width.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
.blog-rhythm-shell {
|
|
13
|
+
--blog-hrule-foreground: var(--border);
|
|
14
|
+
--blog-section-rule: var(--border);
|
|
15
|
+
--blog-section-band-height: calc(var(--blog-row-height) * 2);
|
|
16
|
+
--blog-page-padding-x: calc(var(--blog-rhythm) * 3);
|
|
17
|
+
--paper-rhythm: var(--blog-row-height);
|
|
18
|
+
|
|
19
|
+
isolation: isolate;
|
|
20
|
+
position: relative;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.blog-rhythm-shell.presentation-page--blog {
|
|
24
|
+
padding: calc(var(--blog-row-height) * 4) var(--blog-page-padding-x) 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.blog-rhythm-shell > :not(.blog-page-background) {
|
|
28
|
+
position: relative;
|
|
29
|
+
z-index: 1;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.blog-rhythm-shell[data-rhythm-overlay="true"]::before {
|
|
33
|
+
background-image:
|
|
34
|
+
repeating-linear-gradient(
|
|
35
|
+
to bottom,
|
|
36
|
+
rgb(var(--rhythm-overlay-rule-rgb) / var(--rhythm-overlay-rule-alpha)) 0,
|
|
37
|
+
rgb(var(--rhythm-overlay-rule-rgb) / var(--rhythm-overlay-rule-alpha)) 1px,
|
|
38
|
+
transparent 1px,
|
|
39
|
+
transparent var(--blog-row-height)
|
|
40
|
+
),
|
|
41
|
+
repeating-linear-gradient(
|
|
42
|
+
to bottom,
|
|
43
|
+
rgb(var(--rhythm-overlay-blue-rgb) / var(--rhythm-overlay-blue-alpha)) 0,
|
|
44
|
+
rgb(var(--rhythm-overlay-blue-rgb) / var(--rhythm-overlay-blue-alpha)) var(--blog-row-height),
|
|
45
|
+
rgb(var(--rhythm-overlay-yellow-rgb) / var(--rhythm-overlay-yellow-alpha)) var(--blog-row-height),
|
|
46
|
+
rgb(var(--rhythm-overlay-yellow-rgb) / var(--rhythm-overlay-yellow-alpha)) calc(var(--blog-row-height) * 2)
|
|
47
|
+
);
|
|
48
|
+
content: "";
|
|
49
|
+
inset: 0;
|
|
50
|
+
pointer-events: none;
|
|
51
|
+
position: absolute;
|
|
52
|
+
z-index: 900;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.blog-rhythm-shell .presentation-header {
|
|
56
|
+
max-width: 100%;
|
|
57
|
+
min-width: 0;
|
|
58
|
+
width: 100%;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.blog-rhythm-shell .presentation-section-header,
|
|
62
|
+
.blog-rhythm-shell .presentation-item,
|
|
63
|
+
.blog-rhythm-shell .presentation-item__copy {
|
|
64
|
+
max-width: 100%;
|
|
65
|
+
min-width: 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.blog-rhythm-shell .presentation-section-header {
|
|
69
|
+
align-items: stretch;
|
|
70
|
+
border-bottom: 0;
|
|
71
|
+
border-top: 0;
|
|
72
|
+
display: flex;
|
|
73
|
+
flex-direction: column;
|
|
74
|
+
gap: var(--blog-row-height);
|
|
75
|
+
margin: 0;
|
|
76
|
+
min-height: 0;
|
|
77
|
+
padding: calc(var(--blog-row-height) * 4) 0 calc(var(--blog-row-height) * 2);
|
|
78
|
+
width: 100%;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.blog-rhythm-shell .presentation-section-header__band {
|
|
82
|
+
align-items: center;
|
|
83
|
+
display: grid;
|
|
84
|
+
gap: 0 var(--blog-rhythm);
|
|
85
|
+
grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
|
|
86
|
+
grid-template-rows: var(--blog-section-band-height);
|
|
87
|
+
width: 100%;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.blog-rhythm-shell .presentation-section-header__band::before,
|
|
91
|
+
.blog-rhythm-shell .presentation-section-header__band::after {
|
|
92
|
+
background: var(--blog-section-rule);
|
|
93
|
+
content: "";
|
|
94
|
+
grid-row: 1;
|
|
95
|
+
height: var(--blog-section-band-height);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.blog-rhythm-shell .presentation-section-header__band::before {
|
|
99
|
+
grid-column: 1;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.blog-rhythm-shell .presentation-section-header__band::after {
|
|
103
|
+
grid-column: 3;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.blog-rhythm-shell .presentation-section-header__band p {
|
|
107
|
+
grid-column: 2;
|
|
108
|
+
grid-row: 1;
|
|
109
|
+
height: var(--blog-section-band-height);
|
|
110
|
+
justify-self: center;
|
|
111
|
+
line-height: var(--blog-section-band-height);
|
|
112
|
+
margin: 0;
|
|
113
|
+
padding: 0 var(--blog-rhythm);
|
|
114
|
+
width: max-content;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.blog-rhythm-shell .presentation-item {
|
|
118
|
+
border-bottom: 0;
|
|
119
|
+
gap: var(--blog-row-height) calc(var(--blog-rhythm) * 4);
|
|
120
|
+
padding-bottom: 0;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.blog-rhythm-shell .presentation-item--blog {
|
|
124
|
+
grid-template-columns: minmax(0, 1fr);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.blog-rhythm-shell .presentation-rhythm-hrule {
|
|
128
|
+
display: grid;
|
|
129
|
+
grid-column: 1 / -1;
|
|
130
|
+
grid-template-rows: repeat(5, var(--blog-row-height));
|
|
131
|
+
height: calc(var(--blog-row-height) * 5);
|
|
132
|
+
width: 100%;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.blog-rhythm-shell .presentation-rhythm-hrule__line {
|
|
136
|
+
background: var(--blog-hrule-foreground);
|
|
137
|
+
grid-row: 3;
|
|
138
|
+
height: var(--blog-row-height);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
@media (min-width: 1100px) {
|
|
142
|
+
.blog-rhythm-shell .blog-section:has(> .presentation-item[data-item-index="2"]) {
|
|
143
|
+
column-gap: calc(var(--blog-row-height) * 2);
|
|
144
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.blog-rhythm-shell .blog-section:has(> .presentation-item[data-item-index="2"]) > .presentation-section-header,
|
|
148
|
+
.blog-rhythm-shell .blog-section:has(> .presentation-item[data-item-index="2"]) > .presentation-item[data-item-index="0"] {
|
|
149
|
+
grid-column: 1 / -1;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.blog-rhythm-shell .blog-section:has(> .presentation-item[data-item-index="2"]) > .presentation-rhythm-hrule[data-hide-in-secondary-pair="true"] {
|
|
153
|
+
display: none;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.blog-rhythm-shell .presentation-item[data-feature-layout][data-has-image="true"] {
|
|
158
|
+
display: flex;
|
|
159
|
+
flex-direction: column;
|
|
160
|
+
gap: 0;
|
|
161
|
+
grid-template-columns: minmax(0, 1fr);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.blog-rhythm-shell .presentation-item[data-feature-layout] .presentation-item__text-frame {
|
|
165
|
+
height: var(--feature-text-frame-height);
|
|
166
|
+
max-width: none;
|
|
167
|
+
overflow: hidden;
|
|
168
|
+
position: relative;
|
|
169
|
+
width: 100%;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.blog-rhythm-shell .presentation-item[data-feature-layout] .presentation-item__media {
|
|
173
|
+
height: var(--feature-media-height);
|
|
174
|
+
width: var(--feature-image-width);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.blog-rhythm-shell .presentation-item[data-feature-layout] .pictogram-figure__frame {
|
|
178
|
+
aspect-ratio: auto;
|
|
179
|
+
height: var(--feature-image-height);
|
|
180
|
+
width: var(--feature-image-width);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.blog-rhythm-shell .presentation-item--blog[data-feature-layout][data-has-image="true"] .presentation-item__copy {
|
|
184
|
+
display: contents;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.blog-rhythm-shell .presentation-item--blog[data-feature-layout][data-has-image="true"] .presentation-item__header {
|
|
188
|
+
width: 100%;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.blog-rhythm-shell .presentation-item--blog[data-feature-layout][data-has-image="true"] .presentation-item__body {
|
|
192
|
+
min-width: 0;
|
|
193
|
+
position: relative;
|
|
194
|
+
width: 100%;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.blog-rhythm-shell .presentation-item--blog[data-feature-layout][data-has-image="true"] .presentation-item__body .presentation-item__text-frame {
|
|
198
|
+
margin-top: 0;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.blog-rhythm-shell .presentation-item--blog[data-feature-layout][data-has-image="true"] .presentation-item__media {
|
|
202
|
+
margin: 0;
|
|
203
|
+
position: absolute;
|
|
204
|
+
right: 0;
|
|
205
|
+
top: 0;
|
|
206
|
+
z-index: 1;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.blog-rhythm-shell .presentation-item__image img {
|
|
210
|
+
height: 100%;
|
|
211
|
+
object-fit: contain;
|
|
212
|
+
width: 100%;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.blog-rhythm-shell .presentation-item__video {
|
|
216
|
+
grid-column: 1 / -1;
|
|
217
|
+
margin: var(--blog-row-height) 0 0;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.blog-rhythm-shell .article-page__hero-video {
|
|
221
|
+
margin: 0 0 calc(var(--blog-rhythm) * 5);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.blog-rhythm-shell .article-page__hero-video .article-video,
|
|
225
|
+
.blog-rhythm-shell .presentation-item__video-figure {
|
|
226
|
+
container-type: inline-size;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.blog-rhythm-shell .presentation-item__video-figure .article-video__media,
|
|
230
|
+
.blog-rhythm-shell .article-page__hero-video .article-video__media {
|
|
231
|
+
height: round(up, calc(100cqw * 9 / 16), var(--blog-row-height));
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.blog-rhythm-shell .article-float-grid[data-feature-layout][data-has-image="true"] {
|
|
235
|
+
display: grid;
|
|
236
|
+
gap: var(--feature-layout-gap);
|
|
237
|
+
grid-template-columns: minmax(0, 1fr) var(--feature-image-width);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.blog-rhythm-shell .article-float-grid[data-feature-layout] .article-page__hero-video,
|
|
241
|
+
.blog-rhythm-shell .article-float-grid[data-feature-layout] .article-float-grid__header {
|
|
242
|
+
grid-column: 1 / -1;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.blog-rhythm-shell .article-float-grid[data-feature-layout] .article-float-grid__body {
|
|
246
|
+
align-self: start;
|
|
247
|
+
grid-column: 1;
|
|
248
|
+
max-width: var(--feature-copy-width);
|
|
249
|
+
min-width: 0;
|
|
250
|
+
width: var(--feature-copy-width);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.blog-rhythm-shell .article-float-grid[data-feature-layout] .article-float-grid__media,
|
|
254
|
+
.blog-rhythm-shell .article-float-grid[data-feature-layout] .pictogram-figure__frame {
|
|
255
|
+
height: var(--feature-image-height);
|
|
256
|
+
width: var(--feature-image-width);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.blog-rhythm-shell .article-float-grid[data-feature-layout] .pictogram-figure__frame {
|
|
260
|
+
aspect-ratio: auto;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.blog-rhythm-shell .article-float-grid[data-feature-layout] .article-float-grid__media {
|
|
264
|
+
align-self: start;
|
|
265
|
+
grid-column: 2;
|
|
266
|
+
margin: 0;
|
|
267
|
+
position: relative;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.blog-rhythm-shell .article-float-grid .presentation-item__image {
|
|
271
|
+
margin: 0;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
@media (max-width: 900px) {
|
|
275
|
+
.blog-rhythm-shell .presentation-item--blog {
|
|
276
|
+
grid-template-columns: 1fr;
|
|
277
|
+
}
|
|
278
|
+
}
|
package/app/globals.css
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
@import "
|
|
2
|
-
@import "../publications/papyrus/theme.css";
|
|
3
|
-
@import "../publications/pilobol_us/theme.css";
|
|
4
|
-
@import "../publications/anth_us/theme.css";
|
|
1
|
+
@import "./papyrus-theme.css";
|
|
5
2
|
|
|
6
3
|
@import "tailwindcss";
|
|
7
4
|
@import "tw-animate-css";
|
|
@@ -48,6 +45,7 @@
|
|
|
48
45
|
--color-surface-foreground: var(--surface-foreground);
|
|
49
46
|
--color-foreground-strong: var(--foreground-strong);
|
|
50
47
|
--color-foreground-muted: var(--foreground-muted);
|
|
48
|
+
--color-foreground-subtle: var(--foreground-subtle);
|
|
51
49
|
}
|
|
52
50
|
|
|
53
51
|
:root {
|
|
@@ -74,6 +72,7 @@
|
|
|
74
72
|
--surface-foreground: var(--card-foreground);
|
|
75
73
|
--foreground-strong: var(--slate-12);
|
|
76
74
|
--foreground-muted: var(--slate-10);
|
|
75
|
+
--foreground-subtle: var(--slate-9);
|
|
77
76
|
--border: var(--slate-6);
|
|
78
77
|
--input: var(--slate-6);
|
|
79
78
|
--ring: var(--slate-8);
|
|
@@ -2285,6 +2284,25 @@ img {
|
|
|
2285
2284
|
width: 100%;
|
|
2286
2285
|
}
|
|
2287
2286
|
|
|
2287
|
+
.article-video__preview-frame {
|
|
2288
|
+
aspect-ratio: 16 / 9;
|
|
2289
|
+
background: #000;
|
|
2290
|
+
border: 0;
|
|
2291
|
+
display: block;
|
|
2292
|
+
width: 100%;
|
|
2293
|
+
}
|
|
2294
|
+
|
|
2295
|
+
.article-video__preview-controls {
|
|
2296
|
+
align-items: center;
|
|
2297
|
+
display: flex;
|
|
2298
|
+
gap: 0.75rem;
|
|
2299
|
+
margin-top: 0.5rem;
|
|
2300
|
+
}
|
|
2301
|
+
|
|
2302
|
+
.article-video__preview-controls input[type="range"] {
|
|
2303
|
+
flex: 1;
|
|
2304
|
+
}
|
|
2305
|
+
|
|
2288
2306
|
.article-video__credit {
|
|
2289
2307
|
color: rgb(var(--ink-solid-rgb) / 0.62);
|
|
2290
2308
|
font-size: 0.82rem;
|