@anthusai/papyrus 1.0.0-next.9 → 1.1.0
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/prospectus-google-client.js +66 -0
- package/amplify/auth/prospectus-google-client.ts +71 -0
- package/amplify/auth/redirect-urls.js +54 -0
- package/amplify/auth/redirect-urls.ts +66 -0
- package/amplify/auth/resource.js +23 -5
- package/amplify/auth/resource.ts +31 -5
- package/amplify/data/resource.js +42 -80
- package/amplify/data/resource.ts +42 -80
- package/amplify/data/schema.js +45 -62
- package/amplify/data/schema.ts +47 -62
- package/amplify/functions/category-action/handler.ts +5 -3
- 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 +143 -1
- package/amplify/functions/content-actions/requirements.txt +2 -1
- package/amplify/functions/content-actions/resource.js +6 -1
- package/amplify/functions/content-actions/resource.ts +9 -2
- package/amplify/functions/email-submission-processor/handler.py +3 -1
- package/amplify/functions/renditions/handler.py +120 -0
- package/amplify/functions/renditions/requirements.txt +4 -0
- package/amplify/functions/ses-inbound-receive/handler.ts +32 -4
- package/amplify/functions/shared/email-submission.ts +109 -1
- package/amplify/functions/shared/python-bundle.js +42 -2
- package/amplify/functions/shared/python-bundle.ts +47 -2
- package/amplify/functions/shared/reference-curation-metadata.ts +29 -0
- package/amplify/inbound-email/ses-intake.js +86 -0
- package/amplify/inbound-email/ses-intake.ts +110 -0
- package/amplify/plugins/registry.js +19 -0
- package/amplify/plugins/registry.ts +23 -0
- package/amplify/plugins/signup/function/handler.py +200 -0
- package/amplify/plugins/signup/function/resource.js +27 -0
- package/amplify/plugins/signup/function/resource.ts +35 -0
- package/amplify/plugins/signup/papyrus.plugin.js +59 -0
- package/amplify/plugins/signup/papyrus.plugin.ts +74 -0
- package/amplify/plugins/signup/schema.js +40 -0
- package/amplify/plugins/signup/schema.ts +41 -0
- package/amplify/plugins/signup/waf.js +48 -0
- package/amplify/plugins/signup/waf.ts +51 -0
- package/amplify/plugins/types.js +1 -0
- package/amplify/plugins/types.ts +39 -0
- package/amplify/plugins/wire.js +24 -0
- package/amplify/plugins/wire.ts +26 -0
- package/amplify/publication/default-publication.js +18 -0
- package/amplify/publication/default-publication.ts +20 -0
- package/amplify/publication/invalidation-alarm.js +26 -0
- package/amplify/publication/invalidation-alarm.ts +30 -0
- package/amplify/publication/redirect-and-index.js +37 -0
- package/amplify/publication/renditions-function.js +74 -0
- package/amplify/publication/renditions-function.ts +112 -0
- package/amplify/publication/renditions-settings.js +51 -0
- package/amplify/publication/renditions-settings.ts +58 -0
- package/amplify/publication/reproject-on-deploy.js +73 -0
- package/amplify/publication/reproject-on-deploy.ts +78 -0
- package/amplify/publication/s3-projection-settings.js +67 -0
- package/amplify/publication/s3-projection-settings.ts +101 -0
- package/amplify/publication/s3-projection.js +151 -0
- package/amplify/publication/s3-projection.ts +201 -0
- package/amplify/resolver-waves.js +26 -0
- package/amplify/resolver-waves.ts +34 -0
- package/amplify/revalidate-invalidation-settings.js +30 -0
- package/amplify/revalidate-invalidation-settings.ts +35 -0
- package/amplify/revalidate-secret-policy.js +13 -0
- package/amplify/revalidate-secret-policy.ts +19 -0
- package/amplify/site-backend-config.js +65 -1
- package/amplify/site-backend-config.ts +153 -2
- package/amplify/site-backend-names.js +103 -0
- package/amplify/site-backend-names.ts +166 -0
- package/amplify/site-backend.js +150 -176
- package/amplify/site-backend.ts +189 -201
- package/amplify/storage-backups/construct.js +31 -0
- package/amplify/storage-backups/construct.ts +47 -0
- package/app/[year]/[month]/[day]/[articleSlug]/page.tsx +8 -1
- package/app/[year]/[month]/[day]/edition-route-page.tsx +2 -0
- package/app/[year]/[month]/[day]/page/[pageNumber]/page.tsx +6 -1
- package/app/[year]/[month]/[day]/page.tsx +1 -1
- package/app/[year]/[month]/[day]/section/[sectionKey]/page.tsx +6 -1
- package/app/api/archive/editions/route.ts +1 -1
- package/app/api/auth/prospectus/callback/route.ts +9 -0
- package/app/api/auth/prospectus/selection/route.ts +9 -0
- package/app/api/auth/prospectus/session/route.ts +9 -0
- package/app/api/auth/prospectus/sign-in/route.ts +9 -0
- package/app/api/auth/prospectus/sign-out/route.ts +9 -0
- package/app/api/cyclotron/priming-proof/route.ts +11 -0
- package/app/api/media/[...path]/route.ts +5 -16
- package/app/api/revalidate/route.ts +15 -7
- package/app/archive/page.tsx +1 -1
- package/app/articles/[slug]/page.tsx +7 -5
- package/app/blog-rhythm.css +278 -0
- package/app/cyclotron/page.tsx +13 -0
- package/app/globals.css +2 -0
- package/app/layout.tsx +16 -0
- package/app/newsroom/[section]/[...selection]/page.tsx +9 -6
- package/app/newsroom/[section]/page.tsx +6 -3
- package/app/newsroom/layout.tsx +4 -9
- package/app/newsroom/settings/page.tsx +6 -0
- package/app/newsroom/work/page.tsx +10 -0
- package/app/page.tsx +22 -8
- package/app/robots.ts +2 -5
- package/app/tailwind.css +1 -6
- package/components/amplify-client-provider.tsx +5 -22
- package/components/archive-shell.tsx +2 -22
- package/components/article-video.tsx +152 -5
- package/components/blog-page-background.tsx +23 -6
- package/components/news-desk-page.tsx +4 -15
- package/components/news-desk-taxonomy-client.ts +90 -3
- package/components/newspaper.tsx +6 -2
- package/components/newsroom-access-gate.tsx +2 -1
- package/components/newsroom-board-view.tsx +99 -0
- package/components/newsroom-client-shell.tsx +12 -0
- package/components/newsroom-curation-provider.tsx +17 -0
- package/components/newsroom-fonts/playfair-display-700.ttf +0 -0
- package/components/newsroom-fonts/playfair-display-900.ttf +0 -0
- package/components/newsroom-fonts/playfair-display-OFL.txt +93 -0
- package/components/newsroom-fonts/plus-jakarta-sans-400.ttf +0 -0
- package/components/newsroom-fonts/plus-jakarta-sans-600.ttf +0 -0
- package/components/newsroom-fonts/plus-jakarta-sans-OFL.txt +93 -0
- package/components/newsroom-newsprint-board.css +48 -0
- package/components/newsroom-ops-overview.tsx +28 -11
- package/components/newsroom-ops-shell.tsx +7 -1
- package/components/newsroom-organizer-shell.tsx +130 -0
- package/components/newsroom-organizer.css +63 -0
- package/components/newsroom-references-view.tsx +92 -8
- package/components/papyrus-console-shell.tsx +4 -2
- package/components/pictogram-figure.tsx +9 -2
- package/components/presentation-header.tsx +135 -0
- package/components/presentation-rhythm-hrule.tsx +17 -0
- package/components/priming-proof-page.tsx +94 -0
- package/components/publication-cyclotron-panel.tsx +57 -0
- package/components/reader-auth-control.tsx +8 -5
- package/components/reference-curation-controls.tsx +77 -0
- package/components/reference-cyclotron-status.tsx +109 -0
- package/components/reference-priming.tsx +18 -0
- package/components/reference-relevance-review.tsx +75 -0
- package/components/settings-page.tsx +2 -1
- package/components/signup-entries-panel.tsx +94 -0
- package/components/topic-steering-workspace.tsx +78 -8
- package/components/use-blog-header-obstacles.ts +81 -0
- package/components/use-brand-fonts-ready.ts +28 -0
- package/components/use-near-viewport.ts +30 -0
- package/components/use-rhythm-overlay.ts +29 -0
- package/infra/amplify-app-shell.js +166 -27
- package/infra/amplify-app-shell.ts +180 -31
- package/infra/build-specs.js +23 -7
- package/infra/build-specs.ts +27 -10
- package/infra/site-config.js +248 -36
- package/infra/site-config.ts +313 -51
- package/lib/amplify-outputs-path.ts +5 -1
- package/lib/amplify-server-runtime.ts +36 -1
- package/lib/approved-priming-proof-reports.ts +6 -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 +12 -0
- package/lib/category-repository.ts +43 -93
- package/lib/content-item-lifecycle.ts +55 -0
- package/lib/content-repository.ts +3 -0
- package/lib/content-types.ts +4 -0
- package/lib/curation-client.ts +61 -0
- package/lib/edition-routes.ts +31 -29
- package/lib/graphql-content-repository.ts +56 -22
- package/lib/media-route-response.ts +45 -0
- package/lib/newspaper-layout.ts +3 -2
- package/lib/newsroom-base-path.ts +12 -3
- package/lib/newsroom-board.ts +99 -0
- package/lib/newsroom-category-drilldown.ts +3 -2
- package/lib/newsroom-forum-routes.ts +6 -4
- package/lib/newsroom-index-filters.ts +14 -10
- package/lib/newsroom-nav.ts +3 -1
- package/lib/newsroom-organizer-nav.ts +37 -0
- package/lib/newsroom-section-routing.ts +20 -0
- package/lib/oauth-redirect-priority.ts +40 -0
- package/lib/papyrus-web-locations.ts +7 -6
- package/lib/plugin-settings.ts +11 -0
- package/lib/presentation-footer.ts +3 -2
- package/lib/pretext-layout.ts +12 -3
- package/lib/priming-proof-report.ts +90 -0
- package/lib/prospectus-publication-access.ts +53 -0
- package/lib/prospectus-session.ts +196 -0
- package/lib/prospectus-sign-in-routes.ts +284 -0
- package/lib/prospectus-site-identity.ts +303 -0
- package/lib/publication-cyclotron.ts +74 -0
- package/lib/rate-limit-retry.ts +55 -0
- package/lib/reader-base-path.ts +24 -0
- package/lib/reader-cache-lifetime.ts +5 -0
- package/lib/reader-static-params.ts +85 -4
- package/lib/reader-storage-url.ts +9 -12
- package/lib/reference-policy.ts +5 -0
- package/lib/relevance-decisions.ts +103 -0
- package/lib/relevance-status.ts +63 -0
- package/lib/renderer.ts +7 -0
- package/lib/revalidate-prewarm.ts +53 -0
- package/lib/revalidate-secret.ts +59 -0
- package/lib/robots-policy.ts +14 -0
- package/lib/root-route-redirect.ts +16 -0
- package/lib/semantic-graph.ts +7 -0
- package/lib/shared-credentials-provider.ts +61 -0
- package/lib/signup-csv.ts +24 -0
- package/lib/site-brand.ts +59 -0
- package/lib/staging-gated-path.ts +23 -0
- package/lib/staging-preview-object.ts +21 -5
- package/lib/test-editor-override.ts +4 -0
- package/lib/video-loading.ts +22 -0
- package/lib/video-script.ts +14 -0
- package/middleware.ts +11 -19
- package/package.json +73 -8
- package/renderers/pretext/article-page.tsx +39 -3
- package/renderers/pretext/presentation-shared.tsx +113 -0
- package/renderers/pretext/presentation-shell.tsx +15 -102
- 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 +123 -12
- package/src/sync.mjs +30 -6
- package/src/with-papyrus.d.mts +9 -0
- package/src/with-papyrus.mjs +48 -3
- 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/components/ai-elements/message.tsx +0 -360
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import Link from "next/link";
|
|
4
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
5
|
+
import { solveFeaturedItem } from "../../lib/blog-feature-solver";
|
|
6
|
+
import { createDefaultBlogTextStyle, getMeasuredTextHeight, reserveRhythmRows } from "../../lib/blog-rhythm";
|
|
7
|
+
import type { ArticleVideoAsset } from "../../lib/articles";
|
|
8
|
+
import type { EditionContent, EditionSection } from "../../lib/content-types";
|
|
9
|
+
import { getEditionSectionItems } from "../../lib/edition-sections";
|
|
10
|
+
import { layoutAllTextLines, prepareWithSegments } from "../../lib/pretext-layout";
|
|
11
|
+
import { buildPresentationFooterEntries } from "../../lib/presentation-footer";
|
|
12
|
+
import {
|
|
13
|
+
getPublicationItemImageAssets,
|
|
14
|
+
getPublicationItemVideoAsset,
|
|
15
|
+
type PublicationItem,
|
|
16
|
+
} from "../../lib/publication-items";
|
|
17
|
+
import { SITE_BRAND } from "../../lib/site-brand";
|
|
18
|
+
import { EDITION_OVERVIEW_VIDEO_KEY, type VideoScriptRef } from "../../lib/video-script";
|
|
19
|
+
import { ArticleVideoFigure } from "../../components/article-video";
|
|
20
|
+
import { BlogPageBackground as GenericBlogPageBackground } from "../../components/blog-page-background";
|
|
21
|
+
import { PictogramFigure as GenericPictogramFigure } from "../../components/pictogram-figure";
|
|
22
|
+
import { PresentationFooter } from "../../components/presentation-footer";
|
|
23
|
+
import { getSectionAnchorId, PresentationHeader } from "../../components/presentation-header";
|
|
24
|
+
import { PresentationRhythmHRule } from "../../components/presentation-rhythm-hrule";
|
|
25
|
+
import { useBlogHeaderObstacles } from "../../components/use-blog-header-obstacles";
|
|
26
|
+
import { useBrandFontsReadyVersion } from "../../components/use-brand-fonts-ready";
|
|
27
|
+
import { useRhythmOverlay } from "../../components/use-rhythm-overlay";
|
|
28
|
+
import {
|
|
29
|
+
getBlogFooterSectionHref,
|
|
30
|
+
getPresentationBodyText,
|
|
31
|
+
getPresentationItemRole,
|
|
32
|
+
getPresentationTitle,
|
|
33
|
+
handleBlogFooterSectionClick,
|
|
34
|
+
MeasuredPresentationLines,
|
|
35
|
+
useMeasuredWidth,
|
|
36
|
+
usePresentationTargetScroll,
|
|
37
|
+
} from "./presentation-shared";
|
|
38
|
+
import { BLOG_RHYTHM, getFeaturedLayoutStyle, getRhythmShellStyle } from "./rhythm-shell-style";
|
|
39
|
+
|
|
40
|
+
const BlogPageBackground = SITE_BRAND.components?.BlogPageBackground ?? GenericBlogPageBackground;
|
|
41
|
+
const PictogramFigure = SITE_BRAND.components?.PictogramFigure ?? GenericPictogramFigure;
|
|
42
|
+
const BLOG_TEXT_STYLE = createDefaultBlogTextStyle(SITE_BRAND.textFont);
|
|
43
|
+
const SECONDARY_PAIR_ITEM_INDEX = 2;
|
|
44
|
+
|
|
45
|
+
type PapyrusTestWindow = Window & typeof globalThis & {
|
|
46
|
+
__PAPYRUS_SCENARIO__?: string;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export function RhythmBlogPresentation({
|
|
50
|
+
content,
|
|
51
|
+
editionBasePath,
|
|
52
|
+
targetSection,
|
|
53
|
+
}: {
|
|
54
|
+
content: EditionContent;
|
|
55
|
+
editionBasePath?: string;
|
|
56
|
+
targetSection?: EditionSection;
|
|
57
|
+
}) {
|
|
58
|
+
const footerEntries = useMemo(() => buildPresentationFooterEntries(content), [content]);
|
|
59
|
+
const footerSubtitle = SITE_BRAND.id === "papyrus" ? (content.description?.trim() || "Inside Papyrus") : SITE_BRAND.mastheadSubtitle;
|
|
60
|
+
const pageRef = useRef<HTMLElement | null>(null);
|
|
61
|
+
const showRhythmOverlay = useRhythmOverlay();
|
|
62
|
+
const headerObstacles = useBlogHeaderObstacles(pageRef, BLOG_RHYTHM.paintBuffer);
|
|
63
|
+
usePresentationTargetScroll(targetSection);
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
(window as PapyrusTestWindow).__PAPYRUS_SCENARIO__ = content.scenarioId;
|
|
66
|
+
return () => {
|
|
67
|
+
delete (window as PapyrusTestWindow).__PAPYRUS_SCENARIO__;
|
|
68
|
+
};
|
|
69
|
+
}, [content.scenarioId]);
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<main
|
|
73
|
+
className="presentation-page presentation-page--blog blog-rhythm-shell"
|
|
74
|
+
data-presentation-engine="blog"
|
|
75
|
+
data-rhythm-overlay={showRhythmOverlay ? "true" : "false"}
|
|
76
|
+
ref={pageRef}
|
|
77
|
+
style={getRhythmShellStyle(BLOG_RHYTHM)}
|
|
78
|
+
>
|
|
79
|
+
<BlogPageBackground headerObstacles={headerObstacles} pageRef={pageRef} rhythm={BLOG_RHYTHM} />
|
|
80
|
+
<PresentationHeader
|
|
81
|
+
description={content.description}
|
|
82
|
+
editionBasePath={editionBasePath}
|
|
83
|
+
editionDate={content.editionDate}
|
|
84
|
+
sections={content.sections}
|
|
85
|
+
title={content.title}
|
|
86
|
+
/>
|
|
87
|
+
<div className="blog-sections">
|
|
88
|
+
{content.sections.map((section, sectionIndex) => (
|
|
89
|
+
<section className="blog-section" data-edition-section={section.key} id={getSectionAnchorId(section.key)} key={section.key}>
|
|
90
|
+
{sectionIndex === 0 && content.editionVideo ? (
|
|
91
|
+
<EditionOverviewVideo
|
|
92
|
+
editionVideo={content.editionVideo}
|
|
93
|
+
videoScript={content.videoScripts?.[EDITION_OVERVIEW_VIDEO_KEY] ?? null}
|
|
94
|
+
/>
|
|
95
|
+
) : null}
|
|
96
|
+
<header className="presentation-section-header">
|
|
97
|
+
<div className="presentation-section-header__band">
|
|
98
|
+
<p>{section.label}</p>
|
|
99
|
+
</div>
|
|
100
|
+
{section.description ? <span>{section.description}</span> : null}
|
|
101
|
+
</header>
|
|
102
|
+
{getEditionSectionItems(section, content.items).flatMap((item, index) => [
|
|
103
|
+
...(index > 0
|
|
104
|
+
? [
|
|
105
|
+
<PresentationRhythmHRule
|
|
106
|
+
hideInSecondaryPair={index === SECONDARY_PAIR_ITEM_INDEX}
|
|
107
|
+
key={`hrule-before-${item.slug}`}
|
|
108
|
+
/>,
|
|
109
|
+
]
|
|
110
|
+
: []),
|
|
111
|
+
<RhythmPresentationItem
|
|
112
|
+
editionBasePath={editionBasePath}
|
|
113
|
+
index={index}
|
|
114
|
+
item={item}
|
|
115
|
+
key={item.slug}
|
|
116
|
+
videoScript={content.videoScripts?.[item.slug] ?? null}
|
|
117
|
+
/>,
|
|
118
|
+
])}
|
|
119
|
+
</section>
|
|
120
|
+
))}
|
|
121
|
+
</div>
|
|
122
|
+
<PresentationFooter
|
|
123
|
+
editionBasePath={editionBasePath}
|
|
124
|
+
entries={footerEntries}
|
|
125
|
+
onSectionClick={handleBlogFooterSectionClick}
|
|
126
|
+
resolveSectionHref={(entry) => getBlogFooterSectionHref(entry, editionBasePath)}
|
|
127
|
+
subtitle={SITE_BRAND.footerSubtitleOverride ?? footerSubtitle}
|
|
128
|
+
title={SITE_BRAND.footerTitle}
|
|
129
|
+
/>
|
|
130
|
+
</main>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function EditionOverviewVideo({
|
|
135
|
+
editionVideo,
|
|
136
|
+
videoScript,
|
|
137
|
+
}: {
|
|
138
|
+
editionVideo: ArticleVideoAsset;
|
|
139
|
+
videoScript: VideoScriptRef | null;
|
|
140
|
+
}) {
|
|
141
|
+
return (
|
|
142
|
+
<section className="presentation-edition-video" aria-label={editionVideo.alt}>
|
|
143
|
+
<ArticleVideoFigure
|
|
144
|
+
figureClassName="presentation-edition-video__figure article-video"
|
|
145
|
+
slug={EDITION_OVERVIEW_VIDEO_KEY}
|
|
146
|
+
video={editionVideo}
|
|
147
|
+
videoScript={videoScript}
|
|
148
|
+
/>
|
|
149
|
+
</section>
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function RhythmPresentationItem({
|
|
154
|
+
editionBasePath,
|
|
155
|
+
index,
|
|
156
|
+
item,
|
|
157
|
+
videoScript,
|
|
158
|
+
}: {
|
|
159
|
+
editionBasePath?: string;
|
|
160
|
+
index: number;
|
|
161
|
+
item: PublicationItem;
|
|
162
|
+
videoScript: VideoScriptRef | null;
|
|
163
|
+
}) {
|
|
164
|
+
const articleRef = useRef<HTMLElement | null>(null);
|
|
165
|
+
const frameRef = useRef<HTMLDivElement | null>(null);
|
|
166
|
+
const containerWidth = useMeasuredWidth(articleRef);
|
|
167
|
+
const frameWidth = useMeasuredWidth(frameRef);
|
|
168
|
+
const viewportWidth = useViewportWidth();
|
|
169
|
+
const fontsReadyVersion = useBrandFontsReadyVersion();
|
|
170
|
+
const textStyle = useMemo(() => ({ ...BLOG_TEXT_STYLE, fontsReadyVersion }), [fontsReadyVersion]);
|
|
171
|
+
const image = getPublicationItemImageAssets(item)[0];
|
|
172
|
+
const video = getPublicationItemVideoAsset(item);
|
|
173
|
+
const text = getPresentationBodyText(item, "blog");
|
|
174
|
+
const hasImage = Boolean(image);
|
|
175
|
+
const featuredLayout = useMemo(() => {
|
|
176
|
+
if (!hasImage || !containerWidth || !image) return null;
|
|
177
|
+
return solveFeaturedItem({
|
|
178
|
+
text,
|
|
179
|
+
containerWidth,
|
|
180
|
+
viewportWidth,
|
|
181
|
+
rhythm: BLOG_RHYTHM,
|
|
182
|
+
textStyle,
|
|
183
|
+
imageAsset: image,
|
|
184
|
+
itemIndex: index,
|
|
185
|
+
});
|
|
186
|
+
}, [containerWidth, hasImage, image, index, text, textStyle, viewportWidth]);
|
|
187
|
+
const lines = useMemo(() => {
|
|
188
|
+
if (featuredLayout) return featuredLayout.textLines;
|
|
189
|
+
const maxWidth = frameWidth || containerWidth;
|
|
190
|
+
if (!maxWidth || !text.trim()) return [];
|
|
191
|
+
return layoutAllTextLines({
|
|
192
|
+
prepared: prepareWithSegments(text, `${textStyle.fontSize}px ${textStyle.fontFamily}`, { whiteSpace: "pre-wrap" }),
|
|
193
|
+
maxWidth,
|
|
194
|
+
...textStyle,
|
|
195
|
+
});
|
|
196
|
+
}, [containerWidth, featuredLayout, frameWidth, text, textStyle]);
|
|
197
|
+
const textHeight = featuredLayout?.textFrameHeight ?? reserveRhythmRows(getMeasuredTextHeight(lines), BLOG_RHYTHM);
|
|
198
|
+
const layoutMode = featuredLayout?.mode ?? "stacked";
|
|
199
|
+
const directHref = editionBasePath ? `${editionBasePath}/${encodeURIComponent(item.slug)}` : `/articles/${encodeURIComponent(item.slug)}`;
|
|
200
|
+
|
|
201
|
+
return (
|
|
202
|
+
<article
|
|
203
|
+
className="presentation-item presentation-item--blog"
|
|
204
|
+
data-feature-layout={hasImage ? layoutMode : undefined}
|
|
205
|
+
data-has-image={hasImage ? "true" : "false"}
|
|
206
|
+
data-has-video={video ? "true" : "false"}
|
|
207
|
+
data-item-id={item.slug}
|
|
208
|
+
data-item-index={index}
|
|
209
|
+
data-item-role={getPresentationItemRole("blog", index)}
|
|
210
|
+
data-item-type={item.type}
|
|
211
|
+
id={item.slug}
|
|
212
|
+
ref={articleRef}
|
|
213
|
+
style={featuredLayout ? getFeaturedLayoutStyle(featuredLayout) : undefined}
|
|
214
|
+
>
|
|
215
|
+
<div className="presentation-item__copy">
|
|
216
|
+
<header className="presentation-item__header">
|
|
217
|
+
<p>{item.section ?? "General"}</p>
|
|
218
|
+
<h2>
|
|
219
|
+
<Link href={directHref}>{getPresentationTitle(item)}</Link>
|
|
220
|
+
</h2>
|
|
221
|
+
{item.deck ? <span>{item.deck}</span> : null}
|
|
222
|
+
</header>
|
|
223
|
+
<div className="presentation-item__body">
|
|
224
|
+
{image ? (
|
|
225
|
+
<div className="presentation-item__media">
|
|
226
|
+
<PictogramFigure
|
|
227
|
+
alt={image.alt}
|
|
228
|
+
caption={image.caption}
|
|
229
|
+
credit={image.credit}
|
|
230
|
+
figureClassName="presentation-item__image"
|
|
231
|
+
frameHeight={featuredLayout?.imageHeight}
|
|
232
|
+
frameWidth={featuredLayout?.imageWidth}
|
|
233
|
+
height={760}
|
|
234
|
+
layout={image.layout}
|
|
235
|
+
sizes="(max-width: 900px) 100vw, 760px"
|
|
236
|
+
slug={item.slug}
|
|
237
|
+
src={image.src}
|
|
238
|
+
themeVariants={image.themeVariants}
|
|
239
|
+
width={1200}
|
|
240
|
+
/>
|
|
241
|
+
</div>
|
|
242
|
+
) : null}
|
|
243
|
+
<div
|
|
244
|
+
className="presentation-item__text-frame"
|
|
245
|
+
data-layout-mode={layoutMode}
|
|
246
|
+
ref={frameRef}
|
|
247
|
+
style={{ height: textHeight }}
|
|
248
|
+
>
|
|
249
|
+
<MeasuredPresentationLines lines={lines} />
|
|
250
|
+
</div>
|
|
251
|
+
<Link className="presentation-item__cta" href={directHref}>
|
|
252
|
+
Read Article
|
|
253
|
+
</Link>
|
|
254
|
+
</div>
|
|
255
|
+
</div>
|
|
256
|
+
{video ? (
|
|
257
|
+
<div className="presentation-item__video">
|
|
258
|
+
<ArticleVideoFigure
|
|
259
|
+
figureClassName="presentation-item__video-figure article-video"
|
|
260
|
+
slug={item.slug}
|
|
261
|
+
video={video}
|
|
262
|
+
videoScript={videoScript}
|
|
263
|
+
/>
|
|
264
|
+
</div>
|
|
265
|
+
) : null}
|
|
266
|
+
</article>
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function useViewportWidth(): number {
|
|
271
|
+
const [width, setWidth] = useState(0);
|
|
272
|
+
useEffect(() => {
|
|
273
|
+
const update = () => setWidth(Math.max(1, Math.floor(window.innerWidth)));
|
|
274
|
+
update();
|
|
275
|
+
window.addEventListener("resize", update);
|
|
276
|
+
return () => window.removeEventListener("resize", update);
|
|
277
|
+
}, []);
|
|
278
|
+
return width;
|
|
279
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { CSSProperties } from "react";
|
|
2
|
+
import {
|
|
3
|
+
BLOG_COPY_ROW_MULTIPLE,
|
|
4
|
+
createDefaultBlogVerticalRhythm,
|
|
5
|
+
type VerticalRhythm,
|
|
6
|
+
} from "../../lib/blog-rhythm";
|
|
7
|
+
|
|
8
|
+
export const BLOG_RHYTHM: VerticalRhythm = createDefaultBlogVerticalRhythm();
|
|
9
|
+
|
|
10
|
+
export function getRhythmShellStyle(rhythm: VerticalRhythm): CSSProperties {
|
|
11
|
+
return {
|
|
12
|
+
"--blog-rhythm": `${rhythm.rowHeight / BLOG_COPY_ROW_MULTIPLE}px`,
|
|
13
|
+
"--blog-row-height": `${rhythm.rowHeight}px`,
|
|
14
|
+
"--blog-paint-buffer": `${rhythm.paintBuffer}px`,
|
|
15
|
+
"--blog-paint-height": `${rhythm.paintHeight}px`,
|
|
16
|
+
} as CSSProperties;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function getFeaturedLayoutStyle(layout: {
|
|
20
|
+
copyWidth: number;
|
|
21
|
+
imageWidth: number;
|
|
22
|
+
imageHeight: number;
|
|
23
|
+
gap: number;
|
|
24
|
+
mediaHeight?: number;
|
|
25
|
+
textFrameHeight?: number;
|
|
26
|
+
}): CSSProperties {
|
|
27
|
+
return {
|
|
28
|
+
"--feature-copy-width": `${layout.copyWidth}px`,
|
|
29
|
+
"--feature-image-width": `${layout.imageWidth}px`,
|
|
30
|
+
"--feature-image-height": `${layout.imageHeight}px`,
|
|
31
|
+
"--feature-layout-gap": `${layout.gap}px`,
|
|
32
|
+
...(layout.mediaHeight === undefined ? {} : { "--feature-media-height": `${layout.mediaHeight}px` }),
|
|
33
|
+
...(layout.textFrameHeight === undefined ? {} : { "--feature-text-frame-height": `${layout.textFrameHeight}px` }),
|
|
34
|
+
} as CSSProperties;
|
|
35
|
+
}
|
package/routes.manifest.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.
|
|
2
|
+
"version": "1.1.0",
|
|
3
3
|
"routes": [
|
|
4
4
|
{
|
|
5
5
|
"file": "app/%5F_preview/[[...path]]/route.ts",
|
|
@@ -13,38 +13,47 @@
|
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
15
|
"file": "app/[year]/[month]/[day]/[articleSlug]/page.tsx",
|
|
16
|
+
"reader": true,
|
|
16
17
|
"hasDefault": true,
|
|
17
18
|
"exports": [
|
|
18
|
-
"generateMetadata"
|
|
19
|
+
"generateMetadata",
|
|
20
|
+
"generateStaticParams"
|
|
19
21
|
],
|
|
20
22
|
"config": {
|
|
21
|
-
"revalidate": "
|
|
23
|
+
"revalidate": "60"
|
|
22
24
|
}
|
|
23
25
|
},
|
|
24
26
|
{
|
|
25
27
|
"file": "app/[year]/[month]/[day]/page.tsx",
|
|
28
|
+
"reader": true,
|
|
26
29
|
"hasDefault": true,
|
|
27
30
|
"exports": [
|
|
28
31
|
"generateStaticParams"
|
|
29
32
|
],
|
|
30
33
|
"config": {
|
|
31
|
-
"revalidate": "
|
|
34
|
+
"revalidate": "60"
|
|
32
35
|
}
|
|
33
36
|
},
|
|
34
37
|
{
|
|
35
38
|
"file": "app/[year]/[month]/[day]/page/[pageNumber]/page.tsx",
|
|
39
|
+
"reader": true,
|
|
36
40
|
"hasDefault": true,
|
|
37
|
-
"exports": [
|
|
41
|
+
"exports": [
|
|
42
|
+
"generateStaticParams"
|
|
43
|
+
],
|
|
38
44
|
"config": {
|
|
39
|
-
"revalidate": "
|
|
45
|
+
"revalidate": "60"
|
|
40
46
|
}
|
|
41
47
|
},
|
|
42
48
|
{
|
|
43
49
|
"file": "app/[year]/[month]/[day]/section/[sectionKey]/page.tsx",
|
|
50
|
+
"reader": true,
|
|
44
51
|
"hasDefault": true,
|
|
45
|
-
"exports": [
|
|
52
|
+
"exports": [
|
|
53
|
+
"generateStaticParams"
|
|
54
|
+
],
|
|
46
55
|
"config": {
|
|
47
|
-
"revalidate": "
|
|
56
|
+
"revalidate": "60"
|
|
48
57
|
}
|
|
49
58
|
},
|
|
50
59
|
{
|
|
@@ -54,9 +63,72 @@
|
|
|
54
63
|
"GET"
|
|
55
64
|
],
|
|
56
65
|
"config": {
|
|
57
|
-
"revalidate": "
|
|
66
|
+
"revalidate": "60"
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"file": "app/api/auth/prospectus/callback/route.ts",
|
|
71
|
+
"hasDefault": false,
|
|
72
|
+
"exports": [
|
|
73
|
+
"GET"
|
|
74
|
+
],
|
|
75
|
+
"config": {
|
|
76
|
+
"dynamic": "\"force-dynamic\"",
|
|
77
|
+
"runtime": "\"nodejs\""
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"file": "app/api/auth/prospectus/selection/route.ts",
|
|
82
|
+
"hasDefault": false,
|
|
83
|
+
"exports": [
|
|
84
|
+
"POST"
|
|
85
|
+
],
|
|
86
|
+
"config": {
|
|
87
|
+
"dynamic": "\"force-dynamic\"",
|
|
88
|
+
"runtime": "\"nodejs\""
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"file": "app/api/auth/prospectus/session/route.ts",
|
|
93
|
+
"hasDefault": false,
|
|
94
|
+
"exports": [
|
|
95
|
+
"GET"
|
|
96
|
+
],
|
|
97
|
+
"config": {
|
|
98
|
+
"dynamic": "\"force-dynamic\"",
|
|
99
|
+
"runtime": "\"nodejs\""
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"file": "app/api/auth/prospectus/sign-in/route.ts",
|
|
104
|
+
"hasDefault": false,
|
|
105
|
+
"exports": [
|
|
106
|
+
"GET"
|
|
107
|
+
],
|
|
108
|
+
"config": {
|
|
109
|
+
"dynamic": "\"force-dynamic\"",
|
|
110
|
+
"runtime": "\"nodejs\""
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"file": "app/api/auth/prospectus/sign-out/route.ts",
|
|
115
|
+
"hasDefault": false,
|
|
116
|
+
"exports": [
|
|
117
|
+
"POST"
|
|
118
|
+
],
|
|
119
|
+
"config": {
|
|
120
|
+
"dynamic": "\"force-dynamic\"",
|
|
121
|
+
"runtime": "\"nodejs\""
|
|
58
122
|
}
|
|
59
123
|
},
|
|
124
|
+
{
|
|
125
|
+
"file": "app/api/cyclotron/priming-proof/route.ts",
|
|
126
|
+
"hasDefault": false,
|
|
127
|
+
"exports": [
|
|
128
|
+
"GET"
|
|
129
|
+
],
|
|
130
|
+
"config": {}
|
|
131
|
+
},
|
|
60
132
|
{
|
|
61
133
|
"file": "app/api/media/[...path]/route.ts",
|
|
62
134
|
"hasDefault": false,
|
|
@@ -79,23 +151,33 @@
|
|
|
79
151
|
},
|
|
80
152
|
{
|
|
81
153
|
"file": "app/archive/page.tsx",
|
|
154
|
+
"reader": true,
|
|
82
155
|
"hasDefault": true,
|
|
83
156
|
"exports": [],
|
|
84
157
|
"config": {
|
|
85
|
-
"revalidate": "
|
|
158
|
+
"revalidate": "60"
|
|
86
159
|
}
|
|
87
160
|
},
|
|
88
161
|
{
|
|
89
162
|
"file": "app/articles/[slug]/page.tsx",
|
|
163
|
+
"reader": true,
|
|
90
164
|
"hasDefault": true,
|
|
91
165
|
"exports": [
|
|
92
166
|
"generateMetadata",
|
|
93
167
|
"generateStaticParams"
|
|
94
168
|
],
|
|
95
169
|
"config": {
|
|
96
|
-
"revalidate": "
|
|
170
|
+
"revalidate": "60"
|
|
97
171
|
}
|
|
98
172
|
},
|
|
173
|
+
{
|
|
174
|
+
"file": "app/cyclotron/page.tsx",
|
|
175
|
+
"hasDefault": true,
|
|
176
|
+
"exports": [
|
|
177
|
+
"metadata"
|
|
178
|
+
],
|
|
179
|
+
"config": {}
|
|
180
|
+
},
|
|
99
181
|
{
|
|
100
182
|
"file": "app/layout.tsx",
|
|
101
183
|
"hasDefault": true,
|
|
@@ -182,8 +264,35 @@
|
|
|
182
264
|
"dynamic": "\"force-dynamic\""
|
|
183
265
|
}
|
|
184
266
|
},
|
|
267
|
+
{
|
|
268
|
+
"file": "app/newsroom/settings/page.tsx",
|
|
269
|
+
"hasDefault": true,
|
|
270
|
+
"exports": [
|
|
271
|
+
"metadata"
|
|
272
|
+
],
|
|
273
|
+
"config": {
|
|
274
|
+
"dynamic": "\"force-dynamic\""
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
"file": "app/newsroom/work/page.tsx",
|
|
279
|
+
"hasDefault": true,
|
|
280
|
+
"exports": [],
|
|
281
|
+
"config": {
|
|
282
|
+
"dynamic": "\"force-dynamic\""
|
|
283
|
+
}
|
|
284
|
+
},
|
|
185
285
|
{
|
|
186
286
|
"file": "app/page.tsx",
|
|
287
|
+
"reader": true,
|
|
288
|
+
"hasDefault": true,
|
|
289
|
+
"exports": [],
|
|
290
|
+
"config": {
|
|
291
|
+
"dynamic": "\"force-dynamic\""
|
|
292
|
+
}
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
"file": "app/robots.ts",
|
|
187
296
|
"hasDefault": true,
|
|
188
297
|
"exports": [],
|
|
189
298
|
"config": {
|
|
@@ -192,6 +301,7 @@
|
|
|
192
301
|
},
|
|
193
302
|
{
|
|
194
303
|
"file": "app/settings/page.tsx",
|
|
304
|
+
"reader": true,
|
|
195
305
|
"hasDefault": true,
|
|
196
306
|
"exports": [
|
|
197
307
|
"metadata"
|
|
@@ -203,6 +313,7 @@
|
|
|
203
313
|
],
|
|
204
314
|
"middleware": {
|
|
205
315
|
"file": "middleware.ts",
|
|
206
|
-
"matcher": "[\"/((?!_next/static|_next/image|favicon.ico|icon).*)\"]"
|
|
316
|
+
"matcher": "[\"/((?!_next/static|_next/image|favicon.ico|icon).*)\"]",
|
|
317
|
+
"runtime": "nodejs"
|
|
207
318
|
}
|
|
208
319
|
}
|
package/src/sync.mjs
CHANGED
|
@@ -37,18 +37,42 @@ function shimFor(route) {
|
|
|
37
37
|
return lines.join("\n") + "\n";
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
function middlewareShim(manifest) {
|
|
40
|
+
export function middlewareShim(manifest) {
|
|
41
41
|
const matcher = manifest.middleware.matcher ?? '["/((?!_next/static|_next/image|favicon.ico|icon).*)"]';
|
|
42
42
|
return [
|
|
43
43
|
MARKER,
|
|
44
44
|
`export { middleware } from "${PKG}/middleware";`,
|
|
45
|
-
`export const config = { matcher: ${matcher} };`,
|
|
45
|
+
`export const config = { ${manifest.middleware.runtime ? `runtime: ${JSON.stringify(manifest.middleware.runtime)}, ` : ""}matcher: ${matcher} };`,
|
|
46
46
|
"",
|
|
47
47
|
].join("\n");
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
const RESERVED_FIRST_SEGMENTS = new Set(["newsroom", "api", "_next", "_preview", "%5F_preview"]);
|
|
51
|
+
|
|
52
|
+
export function normalizeReaderBasePath(value) {
|
|
53
|
+
const trimmed = (value ?? "").trim().replace(/\/+$/, "");
|
|
54
|
+
if (!trimmed) return "";
|
|
55
|
+
return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function readReaderBasePath(root) {
|
|
59
|
+
let text;
|
|
60
|
+
try {
|
|
61
|
+
text = fs.readFileSync(path.join(root, "papyrus.config.ts"), "utf8");
|
|
62
|
+
} catch {
|
|
63
|
+
return "";
|
|
64
|
+
}
|
|
65
|
+
const match = text.match(/\breaderBasePath\s*:\s*(["'`])([^"'`]*)\1/);
|
|
66
|
+
return normalizeReaderBasePath(match ? match[2] : "");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function sync({ root = process.cwd(), log = console.log, manifest: manifestOverride, readerBasePath: readerBasePathOverride } = {}) {
|
|
70
|
+
const manifest = manifestOverride ?? JSON.parse(fs.readFileSync(path.join(pkgRoot, "routes.manifest.json"), "utf8"));
|
|
71
|
+
const readerBasePath = normalizeReaderBasePath(readerBasePathOverride ?? readReaderBasePath(root));
|
|
72
|
+
if (readerBasePath && RESERVED_FIRST_SEGMENTS.has(readerBasePath.split("/")[1])) {
|
|
73
|
+
throw new Error(`readerBasePath "${readerBasePath}" collides with a Papyrus-owned route (newsroom, api, _preview)`);
|
|
74
|
+
}
|
|
75
|
+
const readerRouteFile = (file) => (readerBasePath ? file.replace(/^app\//, `app${readerBasePath}/`) : file);
|
|
52
76
|
const generated = [];
|
|
53
77
|
const skipped = [];
|
|
54
78
|
const write = (relPath, content) => {
|
|
@@ -61,7 +85,7 @@ export function sync({ root = process.cwd(), log = console.log } = {}) {
|
|
|
61
85
|
fs.writeFileSync(dest, content);
|
|
62
86
|
generated.push(relPath);
|
|
63
87
|
};
|
|
64
|
-
for (const route of manifest.routes) write(route.file, shimFor(route));
|
|
88
|
+
for (const route of manifest.routes) write(route.reader ? readerRouteFile(route.file) : route.file, shimFor(route));
|
|
65
89
|
write("middleware.ts", middlewareShim(manifest));
|
|
66
90
|
|
|
67
91
|
// Remove shims that are no longer in the manifest (e.g. after an upgrade).
|
|
@@ -81,7 +105,7 @@ export function sync({ root = process.cwd(), log = console.log } = {}) {
|
|
|
81
105
|
const re = new RegExp(`${GITIGNORE_BEGIN.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[\\s\\S]*?${GITIGNORE_END}`);
|
|
82
106
|
fs.writeFileSync(giFile, re.test(gi) ? gi.replace(re, block) : `${gi}${gi.endsWith("\n") || !gi ? "" : "\n"}${block}\n`);
|
|
83
107
|
|
|
84
|
-
log(`papyrus-app sync: ${generated.length} shim(s) written, ${skipped.length} site-owned file(s) kept (${PKG}@${manifest.version})`);
|
|
108
|
+
log(`papyrus-app sync: ${generated.length} shim(s) written, ${skipped.length} site-owned file(s) kept (${PKG}@${manifest.version})${readerBasePath ? `, reader mounted at ${readerBasePath}` : ""}`);
|
|
85
109
|
for (const rel of skipped) log(` site-owned: ${rel}`);
|
|
86
110
|
return { generated, skipped };
|
|
87
111
|
}
|
package/src/with-papyrus.d.mts
CHANGED
|
@@ -1,2 +1,11 @@
|
|
|
1
1
|
import type { NextConfig } from "next";
|
|
2
2
|
export function withPapyrus(nextConfig?: NextConfig, options?: { root?: string }): NextConfig;
|
|
3
|
+
export const READER_EXPIRE_TIME_SECONDS: number;
|
|
4
|
+
export const DEFAULT_STATIC_GENERATION_WORKERS: number;
|
|
5
|
+
export const DEFAULT_STATIC_GENERATION_CONCURRENCY: number;
|
|
6
|
+
export const DEFAULT_STATIC_GENERATION_RETRY_COUNT: number;
|
|
7
|
+
export function staticGenerationSettings(): {
|
|
8
|
+
cpus: number;
|
|
9
|
+
staticGenerationMaxConcurrency: number;
|
|
10
|
+
staticGenerationRetryCount: number;
|
|
11
|
+
};
|