@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/package.json
CHANGED
|
@@ -77,10 +77,14 @@
|
|
|
77
77
|
"./components/article-video": "./components/article-video.tsx",
|
|
78
78
|
"./components/blog-page-background": "./components/blog-page-background.tsx",
|
|
79
79
|
"./components/dev-sandbox-editor-sign-in": "./components/dev-sandbox-editor-sign-in.tsx",
|
|
80
|
+
"./components/markus-ir-preview": "./components/markus-ir-preview.tsx",
|
|
80
81
|
"./components/news-desk-client-provider": "./components/news-desk-client-provider.tsx",
|
|
81
82
|
"./components/news-desk-page": "./components/news-desk-page.tsx",
|
|
82
83
|
"./components/news-desk-taxonomy-client": "./components/news-desk-taxonomy-client.ts",
|
|
83
84
|
"./components/newspaper": "./components/newspaper.tsx",
|
|
85
|
+
"./components/newsroom-access-gate": "./components/newsroom-access-gate.tsx",
|
|
86
|
+
"./components/newsroom-article-editor": "./components/newsroom-article-editor.tsx",
|
|
87
|
+
"./components/newsroom-articles-view": "./components/newsroom-articles-view.tsx",
|
|
84
88
|
"./components/newsroom-assignments-view": "./components/newsroom-assignments-view.tsx",
|
|
85
89
|
"./components/newsroom-ops-overview": "./components/newsroom-ops-overview.tsx",
|
|
86
90
|
"./components/newsroom-ops-shell": "./components/newsroom-ops-shell.tsx",
|
|
@@ -89,12 +93,15 @@
|
|
|
89
93
|
"./components/papyrus-console-shell": "./components/papyrus-console-shell.tsx",
|
|
90
94
|
"./components/pictogram-figure": "./components/pictogram-figure.tsx",
|
|
91
95
|
"./components/presentation-footer": "./components/presentation-footer.tsx",
|
|
96
|
+
"./components/presentation-header": "./components/presentation-header.tsx",
|
|
97
|
+
"./components/presentation-rhythm-hrule": "./components/presentation-rhythm-hrule.tsx",
|
|
92
98
|
"./components/reader-auth-control": "./components/reader-auth-control.tsx",
|
|
93
99
|
"./components/reader-auth-state": "./components/reader-auth-state.ts",
|
|
94
100
|
"./components/reader-settings": "./components/reader-settings.ts",
|
|
95
101
|
"./components/reader-theme-control": "./components/reader-theme-control.tsx",
|
|
96
102
|
"./components/reference-source-preview": "./components/reference-source-preview.tsx",
|
|
97
103
|
"./components/settings-page": "./components/settings-page.tsx",
|
|
104
|
+
"./components/staging-banner": "./components/staging-banner.tsx",
|
|
98
105
|
"./components/topic-steering-workspace": "./components/topic-steering-workspace.tsx",
|
|
99
106
|
"./components/ui/badge": "./components/ui/badge.tsx",
|
|
100
107
|
"./components/ui/button-group": "./components/ui/button-group.tsx",
|
|
@@ -114,20 +121,30 @@
|
|
|
114
121
|
"./components/ui/tabs": "./components/ui/tabs.tsx",
|
|
115
122
|
"./components/ui/textarea": "./components/ui/textarea.tsx",
|
|
116
123
|
"./components/ui/tooltip": "./components/ui/tooltip.tsx",
|
|
124
|
+
"./components/use-blog-header-obstacles": "./components/use-blog-header-obstacles.ts",
|
|
125
|
+
"./components/use-brand-fonts-ready": "./components/use-brand-fonts-ready.ts",
|
|
117
126
|
"./components/use-resolved-papyrus-theme": "./components/use-resolved-papyrus-theme.ts",
|
|
127
|
+
"./components/use-rhythm-overlay": "./components/use-rhythm-overlay.ts",
|
|
118
128
|
"./lib/amplify-outputs-guard": "./lib/amplify-outputs-guard.ts",
|
|
119
129
|
"./lib/amplify-outputs-path": "./lib/amplify-outputs-path.ts",
|
|
120
130
|
"./lib/amplify-server-runtime": "./lib/amplify-server-runtime.ts",
|
|
121
131
|
"./lib/archive-data": "./lib/archive-data.ts",
|
|
122
132
|
"./lib/archive-types": "./lib/archive-types.ts",
|
|
123
133
|
"./lib/articles": "./lib/articles.ts",
|
|
134
|
+
"./lib/blog-feature-solver": "./lib/blog-feature-solver.ts",
|
|
135
|
+
"./lib/blog-rhythm": "./lib/blog-rhythm.ts",
|
|
136
|
+
"./lib/brand-fonts-watch": "./lib/brand-fonts-watch.ts",
|
|
137
|
+
"./lib/brand-fonts": "./lib/brand-fonts.ts",
|
|
124
138
|
"./lib/cached-content-repository": "./lib/cached-content-repository.ts",
|
|
125
139
|
"./lib/category-dashboard": "./lib/category-dashboard.ts",
|
|
126
140
|
"./lib/category-repository": "./lib/category-repository.ts",
|
|
127
141
|
"./lib/console-chat-client": "./lib/console-chat-client.ts",
|
|
128
142
|
"./lib/console-web-context": "./lib/console-web-context.ts",
|
|
143
|
+
"./lib/content-actions-client": "./lib/content-actions-client.ts",
|
|
144
|
+
"./lib/content-actions-demo": "./lib/content-actions-demo.ts",
|
|
129
145
|
"./lib/content-load-timing": "./lib/content-load-timing.ts",
|
|
130
146
|
"./lib/content-repository": "./lib/content-repository.ts",
|
|
147
|
+
"./lib/content-source-context": "./lib/content-source-context.ts",
|
|
131
148
|
"./lib/content-types": "./lib/content-types.ts",
|
|
132
149
|
"./lib/define-site": "./lib/define-site.ts",
|
|
133
150
|
"./lib/demo-amplify-outputs": "./lib/demo-amplify-outputs.ts",
|
|
@@ -147,16 +164,20 @@
|
|
|
147
164
|
"./lib/markus-to-article": "./lib/markus-to-article.ts",
|
|
148
165
|
"./lib/news-desk-session": "./lib/news-desk-session.ts",
|
|
149
166
|
"./lib/newspaper-layout": "./lib/newspaper-layout.ts",
|
|
167
|
+
"./lib/newsroom-articles": "./lib/newsroom-articles.ts",
|
|
150
168
|
"./lib/newsroom-assignments": "./lib/newsroom-assignments.ts",
|
|
151
169
|
"./lib/newsroom-base-path": "./lib/newsroom-base-path.ts",
|
|
152
170
|
"./lib/newsroom-card-layout": "./lib/newsroom-card-layout.ts",
|
|
153
171
|
"./lib/newsroom-category-drilldown": "./lib/newsroom-category-drilldown.ts",
|
|
172
|
+
"./lib/newsroom-demo-articles": "./lib/newsroom-demo-articles.ts",
|
|
154
173
|
"./lib/newsroom-demo-dashboard": "./lib/newsroom-demo-dashboard.ts",
|
|
155
174
|
"./lib/newsroom-demo-profile": "./lib/newsroom-demo-profile.ts",
|
|
156
175
|
"./lib/newsroom-forum-routes": "./lib/newsroom-forum-routes.ts",
|
|
157
176
|
"./lib/newsroom-index-filters": "./lib/newsroom-index-filters.ts",
|
|
158
177
|
"./lib/newsroom-knowledge-query-request": "./lib/newsroom-knowledge-query-request.ts",
|
|
159
178
|
"./lib/newsroom-list-selection": "./lib/newsroom-list-selection.ts",
|
|
179
|
+
"./lib/newsroom-media-backend": "./lib/newsroom-media-backend.ts",
|
|
180
|
+
"./lib/newsroom-media": "./lib/newsroom-media.ts",
|
|
160
181
|
"./lib/newsroom-nav": "./lib/newsroom-nav.ts",
|
|
161
182
|
"./lib/newsroom-references": "./lib/newsroom-references.ts",
|
|
162
183
|
"./lib/newsroom-sections": "./lib/newsroom-sections.ts",
|
|
@@ -168,6 +189,7 @@
|
|
|
168
189
|
"./lib/pretext-layout": "./lib/pretext-layout.ts",
|
|
169
190
|
"./lib/public-placeholder-config": "./lib/public-placeholder-config.ts",
|
|
170
191
|
"./lib/publication-items": "./lib/publication-items.ts",
|
|
192
|
+
"./lib/reader-base-path": "./lib/reader-base-path.ts",
|
|
171
193
|
"./lib/reader-route-config": "./lib/reader-route-config.ts",
|
|
172
194
|
"./lib/reader-static-params": "./lib/reader-static-params.ts",
|
|
173
195
|
"./lib/reader-storage-url": "./lib/reader-storage-url.ts",
|
|
@@ -177,19 +199,32 @@
|
|
|
177
199
|
"./lib/renderer-config": "./lib/renderer-config.ts",
|
|
178
200
|
"./lib/renderer": "./lib/renderer.ts",
|
|
179
201
|
"./lib/reporting-story-budget": "./lib/reporting-story-budget.ts",
|
|
202
|
+
"./lib/revalidate-secret": "./lib/revalidate-secret.ts",
|
|
203
|
+
"./lib/root-route-redirect": "./lib/root-route-redirect.ts",
|
|
180
204
|
"./lib/semantic-graph": "./lib/semantic-graph.ts",
|
|
181
205
|
"./lib/site-brand-tenant": "./lib/site-brand-tenant.ts",
|
|
182
206
|
"./lib/site-brand": "./lib/site-brand.ts",
|
|
207
|
+
"./lib/site-env": "./lib/site-env.ts",
|
|
183
208
|
"./lib/site-renderer": "./lib/site-renderer.ts",
|
|
184
209
|
"./lib/site-stack": "./lib/site-stack.ts",
|
|
210
|
+
"./lib/staging-gate": "./lib/staging-gate.ts",
|
|
211
|
+
"./lib/staging-gated-path": "./lib/staging-gated-path.ts",
|
|
212
|
+
"./lib/staging-preview-object": "./lib/staging-preview-object.ts",
|
|
185
213
|
"./lib/themed-image": "./lib/themed-image.ts",
|
|
186
214
|
"./lib/utils": "./lib/utils.ts",
|
|
215
|
+
"./lib/video-mode": "./lib/video-mode.ts",
|
|
216
|
+
"./lib/video-script": "./lib/video-script.ts",
|
|
187
217
|
"./renderers/markus/index": "./renderers/markus/index.ts",
|
|
188
218
|
"./renderers/markus/stubs": "./renderers/markus/stubs.tsx",
|
|
189
219
|
"./renderers/pretext/article-page": "./renderers/pretext/article-page.tsx",
|
|
190
220
|
"./renderers/pretext/index": "./renderers/pretext/index.ts",
|
|
191
221
|
"./renderers/pretext/layouts": "./renderers/pretext/layouts.ts",
|
|
222
|
+
"./renderers/pretext/presentation-shared": "./renderers/pretext/presentation-shared.tsx",
|
|
192
223
|
"./renderers/pretext/presentation-shell": "./renderers/pretext/presentation-shell.tsx",
|
|
224
|
+
"./renderers/pretext/rhythm-article-page": "./renderers/pretext/rhythm-article-page.tsx",
|
|
225
|
+
"./renderers/pretext/rhythm-blog-presentation": "./renderers/pretext/rhythm-blog-presentation.tsx",
|
|
226
|
+
"./renderers/pretext/rhythm-shell-style": "./renderers/pretext/rhythm-shell-style.ts",
|
|
227
|
+
"./app/%5F_preview/[[...path]]/route": "./app/%5F_preview/[[...path]]/route.ts",
|
|
193
228
|
"./app/[year]/[month]/[day]/[articleSlug]/page": "./app/[year]/[month]/[day]/[articleSlug]/page.tsx",
|
|
194
229
|
"./app/[year]/[month]/[day]/page": "./app/[year]/[month]/[day]/page.tsx",
|
|
195
230
|
"./app/[year]/[month]/[day]/page/[pageNumber]/page": "./app/[year]/[month]/[day]/page/[pageNumber]/page.tsx",
|
|
@@ -202,6 +237,8 @@
|
|
|
202
237
|
"./app/layout": "./app/layout.tsx",
|
|
203
238
|
"./app/newsroom/[section]/[...selection]/page": "./app/newsroom/[section]/[...selection]/page.tsx",
|
|
204
239
|
"./app/newsroom/[section]/page": "./app/newsroom/[section]/page.tsx",
|
|
240
|
+
"./app/newsroom/articles/[id]/page": "./app/newsroom/articles/[id]/page.tsx",
|
|
241
|
+
"./app/newsroom/articles/page": "./app/newsroom/articles/page.tsx",
|
|
205
242
|
"./app/newsroom/forum/[threadId]/page": "./app/newsroom/forum/[threadId]/page.tsx",
|
|
206
243
|
"./app/newsroom/insights/[threadId]/page": "./app/newsroom/insights/[threadId]/page.tsx",
|
|
207
244
|
"./app/newsroom/insights/page": "./app/newsroom/insights/page.tsx",
|
|
@@ -256,7 +293,7 @@
|
|
|
256
293
|
"optional": true
|
|
257
294
|
}
|
|
258
295
|
},
|
|
259
|
-
"version": "1.0.0-next.
|
|
296
|
+
"version": "1.0.0-next.21",
|
|
260
297
|
"dependencies": {
|
|
261
298
|
"@aws-amplify/adapter-nextjs": "^1.7.3",
|
|
262
299
|
"@aws-sdk/client-cognito-identity-provider": "3.1045.0",
|
|
@@ -278,6 +315,7 @@
|
|
|
278
315
|
"class-variance-authority": "^0.7.1",
|
|
279
316
|
"clsx": "^2.1.1",
|
|
280
317
|
"cmdk": "^1.1.1",
|
|
318
|
+
"esbuild": "^0.28.2",
|
|
281
319
|
"gray-matter": "^4.0.3",
|
|
282
320
|
"gsap": "^3.15.0",
|
|
283
321
|
"katex": "^0.17.0",
|
|
@@ -288,6 +326,7 @@
|
|
|
288
326
|
"streamdown": "^2.5.0",
|
|
289
327
|
"tailwind-merge": "^3.6.0",
|
|
290
328
|
"tailwindcss": "^4.3.0",
|
|
329
|
+
"tsx": "^4.23.15",
|
|
291
330
|
"tw-animate-css": "^1.4.0",
|
|
292
331
|
"use-stick-to-bottom": "^1.1.4",
|
|
293
332
|
"yaml": "^2.9.0",
|
|
@@ -7,21 +7,34 @@ import { getPublicationItemVideoAsset } from "../../lib/publication-items";
|
|
|
7
7
|
import type { ArticlePageEditionFooter, RenderArticleProps, RenderItemProps } from "../../lib/renderer";
|
|
8
8
|
import { SITE_BRAND } from "../../lib/site-brand";
|
|
9
9
|
import { ArticleVideoFigure } from "../../components/article-video";
|
|
10
|
-
import { PictogramFigure as ThreatIntelligencePictogramFigure } from "../../publications/threat_intelligence/pictograms/figure";
|
|
11
10
|
import { PictogramFigure as GenericPictogramFigure } from "../../components/pictogram-figure";
|
|
12
11
|
import { PresentationFooter } from "../../components/presentation-footer";
|
|
12
|
+
import { RhythmArticlePage } from "./rhythm-article-page";
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
// publication gets the generic fallback (components/pictogram-figure.tsx).
|
|
16
|
-
// Same decoupling as renderers/pretext/presentation-shell.tsx -- see
|
|
17
|
-
// docs/pluggable-publishers.md section 2.6 and PPY-d79ff2.
|
|
18
|
-
const PictogramFigure = SITE_BRAND.id === "threat-intelligence" ? ThreatIntelligencePictogramFigure : GenericPictogramFigure;
|
|
14
|
+
const PictogramFigure = SITE_BRAND.components?.PictogramFigure ?? GenericPictogramFigure;
|
|
19
15
|
|
|
20
16
|
export type { ArticlePageEditionFooter };
|
|
21
17
|
|
|
22
18
|
type ArticlePageViewProps = RenderArticleProps;
|
|
23
19
|
|
|
24
|
-
export function ArticlePageView({ article, backHref, backLabel = SITE_BRAND.backToHomeLabel, editionFooter, editionDate }: ArticlePageViewProps) {
|
|
20
|
+
export function ArticlePageView({ article, backHref, backLabel = SITE_BRAND.backToHomeLabel, editionFooter, editionDate, videoScript = null }: ArticlePageViewProps) {
|
|
21
|
+
if (SITE_BRAND.blogLayout === "rhythm") {
|
|
22
|
+
return (
|
|
23
|
+
<RhythmArticlePage
|
|
24
|
+
body={article.body}
|
|
25
|
+
deck={article.deck}
|
|
26
|
+
editionDate={editionDate}
|
|
27
|
+
editionFooter={editionFooter}
|
|
28
|
+
headline={article.headline}
|
|
29
|
+
image={article.image ?? null}
|
|
30
|
+
section={article.section}
|
|
31
|
+
slug={article.slug}
|
|
32
|
+
video={article.video ?? null}
|
|
33
|
+
videoScript={videoScript}
|
|
34
|
+
/>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
25
38
|
const articleDate = editionDate ? formatArticleDate(editionDate) : null;
|
|
26
39
|
|
|
27
40
|
return (
|
|
@@ -60,9 +73,27 @@ export function ArticlePageView({ article, backHref, backLabel = SITE_BRAND.back
|
|
|
60
73
|
|
|
61
74
|
type ItemPageViewProps = RenderItemProps;
|
|
62
75
|
|
|
63
|
-
export function ItemPageView({ item, backHref, backLabel = "Back to edition", editionFooter, editionDate }: ItemPageViewProps) {
|
|
76
|
+
export function ItemPageView({ item, backHref, backLabel = "Back to edition", editionFooter, editionDate, videoScript = null }: ItemPageViewProps) {
|
|
64
77
|
if (item.type === "article") {
|
|
65
|
-
return <ArticlePageView article={item} backHref={backHref} backLabel={backLabel} editionFooter={editionFooter} editionDate={editionDate} />;
|
|
78
|
+
return <ArticlePageView article={item} backHref={backHref} backLabel={backLabel} editionFooter={editionFooter} editionDate={editionDate} videoScript={videoScript} />;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (SITE_BRAND.blogLayout === "rhythm") {
|
|
82
|
+
return (
|
|
83
|
+
<RhythmArticlePage
|
|
84
|
+
body={item.body ?? []}
|
|
85
|
+
deck={item.deck}
|
|
86
|
+
editionDate={editionDate}
|
|
87
|
+
editionFooter={editionFooter}
|
|
88
|
+
headline={item.title}
|
|
89
|
+
image={item.image ?? null}
|
|
90
|
+
itemType={item.type}
|
|
91
|
+
section={item.section ?? item.type}
|
|
92
|
+
slug={item.slug}
|
|
93
|
+
video={getPublicationItemVideoAsset(item) ?? null}
|
|
94
|
+
videoScript={videoScript}
|
|
95
|
+
/>
|
|
96
|
+
);
|
|
66
97
|
}
|
|
67
98
|
|
|
68
99
|
const itemDate = editionDate ? formatArticleDate(editionDate) : null;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { CSSProperties, MouseEvent as ReactMouseEvent, RefObject } from "react";
|
|
4
|
+
import { useEffect, useState } from "react";
|
|
5
|
+
import type { EditionSection } from "../../lib/content-types";
|
|
6
|
+
import { truncateWords } from "../../lib/excerpts";
|
|
7
|
+
import type { PresentationFooterEntry } from "../../lib/presentation-footer";
|
|
8
|
+
import type { PublicationItem } from "../../lib/publication-items";
|
|
9
|
+
import type { TextLine } from "../../lib/pretext-layout";
|
|
10
|
+
|
|
11
|
+
export type PresentationItemMode = "blog" | "magazine" | "magazine-feature";
|
|
12
|
+
|
|
13
|
+
export function MeasuredPresentationLines({ lines }: { lines: TextLine[] }) {
|
|
14
|
+
return (
|
|
15
|
+
<div className="presentation-measured-lines">
|
|
16
|
+
{lines.map((line, index) => (
|
|
17
|
+
<span
|
|
18
|
+
className="presentation-measured-line"
|
|
19
|
+
key={`${index}-${line.text}`}
|
|
20
|
+
style={{
|
|
21
|
+
"--line-font-family": line.fontFamily,
|
|
22
|
+
"--line-font-size": `${line.fontSize}px`,
|
|
23
|
+
"--line-height": `${line.lineHeight}px`,
|
|
24
|
+
"--line-paint-height": `${line.paintHeight}px`,
|
|
25
|
+
left: line.x,
|
|
26
|
+
top: line.y,
|
|
27
|
+
} as CSSProperties}
|
|
28
|
+
>
|
|
29
|
+
{line.text}
|
|
30
|
+
</span>
|
|
31
|
+
))}
|
|
32
|
+
</div>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function useMeasuredWidth(ref: RefObject<HTMLElement | null>): number {
|
|
37
|
+
const [width, setWidth] = useState(0);
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
const node = ref.current;
|
|
40
|
+
if (!node) return;
|
|
41
|
+
const update = () => setWidth(Math.max(1, Math.floor(node.getBoundingClientRect().width)));
|
|
42
|
+
update();
|
|
43
|
+
const observer = new ResizeObserver(update);
|
|
44
|
+
observer.observe(node);
|
|
45
|
+
window.addEventListener("resize", update);
|
|
46
|
+
return () => {
|
|
47
|
+
observer.disconnect();
|
|
48
|
+
window.removeEventListener("resize", update);
|
|
49
|
+
};
|
|
50
|
+
}, [ref]);
|
|
51
|
+
return width;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function usePresentationTargetScroll(targetSection: EditionSection | undefined) {
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
const scrollToCurrentTarget = () => {
|
|
57
|
+
const hashTarget = parseItemAnchorHash(window.location.hash);
|
|
58
|
+
const targetId = hashTarget ?? (targetSection ? getSectionAnchorId(targetSection.key) : null);
|
|
59
|
+
if (!targetId) return;
|
|
60
|
+
document.getElementById(targetId)?.scrollIntoView({ block: "start" });
|
|
61
|
+
};
|
|
62
|
+
requestAnimationFrame(scrollToCurrentTarget);
|
|
63
|
+
window.addEventListener("hashchange", scrollToCurrentTarget);
|
|
64
|
+
return () => window.removeEventListener("hashchange", scrollToCurrentTarget);
|
|
65
|
+
}, [targetSection]);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function getSectionAnchorId(sectionKey: string): string {
|
|
69
|
+
return `section-${sectionKey}`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function getPresentationTitle(item: PublicationItem): string {
|
|
73
|
+
return item.type === "article" ? item.headline : item.title;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function getPresentationBodyText(item: PublicationItem, mode: PresentationItemMode): string {
|
|
77
|
+
const body = item.type === "article" ? item.body.join("\n\n") : (item.body ?? []).join("\n\n");
|
|
78
|
+
if (mode !== "blog") return body;
|
|
79
|
+
const excerpt = String(item.excerpt ?? "").trim();
|
|
80
|
+
if (excerpt) return excerpt;
|
|
81
|
+
return truncateWords(body, 80);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function parseItemAnchorHash(hash: string): string | null {
|
|
85
|
+
if (!hash) return null;
|
|
86
|
+
try {
|
|
87
|
+
const value = decodeURIComponent(hash.slice(1)).trim();
|
|
88
|
+
return /^[a-z0-9][a-z0-9-]*$/i.test(value) ? value : null;
|
|
89
|
+
} catch {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function getBlogFooterSectionHref(entry: PresentationFooterEntry, editionBasePath?: string): string {
|
|
95
|
+
const anchor = `#${getSectionAnchorId(entry.sectionKey)}`;
|
|
96
|
+
return editionBasePath ? `${editionBasePath}${anchor}` : anchor;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function handleBlogFooterSectionClick(
|
|
100
|
+
event: ReactMouseEvent<HTMLAnchorElement>,
|
|
101
|
+
entry: PresentationFooterEntry,
|
|
102
|
+
href: string,
|
|
103
|
+
) {
|
|
104
|
+
event.preventDefault();
|
|
105
|
+
window.history.pushState(null, "", href);
|
|
106
|
+
document.getElementById(getSectionAnchorId(entry.sectionKey))?.scrollIntoView({ block: "start" });
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function getPresentationItemRole(mode: PresentationItemMode, index?: number): "lead" | "secondary" {
|
|
110
|
+
if (mode === "magazine-feature") return "lead";
|
|
111
|
+
if (mode === "blog" && index === 0) return "lead";
|
|
112
|
+
return "secondary";
|
|
113
|
+
}
|
|
@@ -18,21 +18,26 @@ import type { ArticleVideoAsset } from "../../lib/articles";
|
|
|
18
18
|
import type { PresentationTarget, RenderEditionProps } from "../../lib/renderer";
|
|
19
19
|
import { SITE_BRAND, enforcePresentation, getDefaultPretextLayout } from "../../lib/site-brand";
|
|
20
20
|
import { ArticleVideoFigure } from "../../components/article-video";
|
|
21
|
-
import { BlogPageBackground as ThreatIntelligenceBlogPageBackground } from "../../publications/threat_intelligence/blog-defense/page-background";
|
|
22
21
|
import { BlogPageBackground as GenericBlogPageBackground } from "../../components/blog-page-background";
|
|
23
22
|
import { Newspaper } from "../../components/newspaper";
|
|
24
|
-
import { PictogramFigure as ThreatIntelligencePictogramFigure } from "../../publications/threat_intelligence/pictograms/figure";
|
|
25
23
|
import { PictogramFigure as GenericPictogramFigure } from "../../components/pictogram-figure";
|
|
26
24
|
import { PresentationFooter } from "../../components/presentation-footer";
|
|
27
25
|
import { readLocalReaderSettings, resolveReaderSettings, subscribeReaderSettingsChanges } from "../../components/reader-settings";
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
26
|
+
import { RhythmBlogPresentation } from "./rhythm-blog-presentation";
|
|
27
|
+
import {
|
|
28
|
+
getBlogFooterSectionHref,
|
|
29
|
+
getPresentationBodyText,
|
|
30
|
+
getPresentationItemRole,
|
|
31
|
+
getPresentationTitle,
|
|
32
|
+
getSectionAnchorId,
|
|
33
|
+
handleBlogFooterSectionClick,
|
|
34
|
+
MeasuredPresentationLines,
|
|
35
|
+
useMeasuredWidth,
|
|
36
|
+
usePresentationTargetScroll,
|
|
37
|
+
} from "./presentation-shared";
|
|
38
|
+
|
|
39
|
+
const BlogPageBackground = SITE_BRAND.components?.BlogPageBackground ?? GenericBlogPageBackground;
|
|
40
|
+
const PictogramFigure = SITE_BRAND.components?.PictogramFigure ?? GenericPictogramFigure;
|
|
36
41
|
|
|
37
42
|
export type { PresentationTarget };
|
|
38
43
|
|
|
@@ -101,7 +106,9 @@ export function PresentationShell({
|
|
|
101
106
|
|
|
102
107
|
return (
|
|
103
108
|
<PresentationFrame>
|
|
104
|
-
{activePresentation === "blog" ? (
|
|
109
|
+
{activePresentation === "blog" && SITE_BRAND.blogLayout === "rhythm" ? (
|
|
110
|
+
<RhythmBlogPresentation {...presentationProps} />
|
|
111
|
+
) : activePresentation === "blog" ? (
|
|
105
112
|
<BlogPresentation {...presentationProps} />
|
|
106
113
|
) : (
|
|
107
114
|
<MagazinePresentation {...presentationProps} />
|
|
@@ -347,67 +354,6 @@ function PresentationItem({
|
|
|
347
354
|
);
|
|
348
355
|
}
|
|
349
356
|
|
|
350
|
-
function getPresentationItemRole(mode: "blog" | "magazine" | "magazine-feature", index?: number): "lead" | "secondary" {
|
|
351
|
-
if (mode === "magazine-feature") return "lead";
|
|
352
|
-
if (mode === "blog" && index === 0) return "lead";
|
|
353
|
-
return "secondary";
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
function MeasuredPresentationLines({ lines }: { lines: TextLine[] }) {
|
|
357
|
-
return (
|
|
358
|
-
<div className="presentation-measured-lines">
|
|
359
|
-
{lines.map((line, index) => (
|
|
360
|
-
<span
|
|
361
|
-
className="presentation-measured-line"
|
|
362
|
-
key={`${index}-${line.text}`}
|
|
363
|
-
style={{
|
|
364
|
-
"--line-font-family": line.fontFamily,
|
|
365
|
-
"--line-font-size": `${line.fontSize}px`,
|
|
366
|
-
"--line-height": `${line.lineHeight}px`,
|
|
367
|
-
"--line-paint-height": `${line.paintHeight}px`,
|
|
368
|
-
left: line.x,
|
|
369
|
-
top: line.y,
|
|
370
|
-
} as CSSProperties}
|
|
371
|
-
>
|
|
372
|
-
{line.text}
|
|
373
|
-
</span>
|
|
374
|
-
))}
|
|
375
|
-
</div>
|
|
376
|
-
);
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
function useMeasuredWidth(ref: RefObject<HTMLElement | null>): number {
|
|
380
|
-
const [width, setWidth] = useState(0);
|
|
381
|
-
useEffect(() => {
|
|
382
|
-
const node = ref.current;
|
|
383
|
-
if (!node) return;
|
|
384
|
-
const update = () => setWidth(Math.max(1, Math.floor(node.getBoundingClientRect().width)));
|
|
385
|
-
update();
|
|
386
|
-
const observer = new ResizeObserver(update);
|
|
387
|
-
observer.observe(node);
|
|
388
|
-
window.addEventListener("resize", update);
|
|
389
|
-
return () => {
|
|
390
|
-
observer.disconnect();
|
|
391
|
-
window.removeEventListener("resize", update);
|
|
392
|
-
};
|
|
393
|
-
}, [ref]);
|
|
394
|
-
return width;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
function usePresentationTargetScroll(targetSection: EditionSection | undefined) {
|
|
398
|
-
useEffect(() => {
|
|
399
|
-
const scrollToCurrentTarget = () => {
|
|
400
|
-
const hashTarget = parseItemAnchorHash(window.location.hash);
|
|
401
|
-
const targetId = hashTarget ?? (targetSection ? getSectionAnchorId(targetSection.key) : null);
|
|
402
|
-
if (!targetId) return;
|
|
403
|
-
document.getElementById(targetId)?.scrollIntoView({ block: "start" });
|
|
404
|
-
};
|
|
405
|
-
requestAnimationFrame(scrollToCurrentTarget);
|
|
406
|
-
window.addEventListener("hashchange", scrollToCurrentTarget);
|
|
407
|
-
return () => window.removeEventListener("hashchange", scrollToCurrentTarget);
|
|
408
|
-
}, [targetSection]);
|
|
409
|
-
}
|
|
410
|
-
|
|
411
357
|
function getSectionHref(content: EditionContent, section: EditionSection, editionBasePath?: string): string {
|
|
412
358
|
if (SITE_BRAND.sectionLinkStrategy === "anchor") {
|
|
413
359
|
const anchor = `#${getSectionAnchorId(section.key)}`;
|
|
@@ -418,37 +364,11 @@ function getSectionHref(content: EditionContent, section: EditionSection, editio
|
|
|
418
364
|
return datedPath.startsWith("//") ? `#${getSectionAnchorId(section.key)}` : datedPath;
|
|
419
365
|
}
|
|
420
366
|
|
|
421
|
-
function getSectionAnchorId(sectionKey: string): string {
|
|
422
|
-
return `section-${sectionKey}`;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
367
|
function getMeasuredTextHeight(lines: TextLine[]): number {
|
|
426
368
|
const last = lines[lines.length - 1];
|
|
427
369
|
return last ? last.y + last.paintHeight : 0;
|
|
428
370
|
}
|
|
429
371
|
|
|
430
|
-
function getPresentationTitle(item: PublicationItem): string {
|
|
431
|
-
return item.type === "article" ? item.headline : item.title;
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
function getPresentationBodyText(item: PublicationItem, mode: "blog" | "magazine" | "magazine-feature"): string {
|
|
435
|
-
const body = item.type === "article" ? item.body.join("\n\n") : (item.body ?? []).join("\n\n");
|
|
436
|
-
if (mode !== "blog") return body;
|
|
437
|
-
const excerpt = String(item.excerpt ?? "").trim();
|
|
438
|
-
if (excerpt) return excerpt;
|
|
439
|
-
return truncateWords(body, 80);
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
function parseItemAnchorHash(hash: string): string | null {
|
|
443
|
-
if (!hash) return null;
|
|
444
|
-
try {
|
|
445
|
-
const value = decodeURIComponent(hash.slice(1)).trim();
|
|
446
|
-
return /^[a-z0-9][a-z0-9-]*$/i.test(value) ? value : null;
|
|
447
|
-
} catch {
|
|
448
|
-
return null;
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
|
|
452
372
|
function formatMastheadDate(value: string): string {
|
|
453
373
|
const normalized = value?.trim();
|
|
454
374
|
if (!normalized) return value;
|
|
@@ -461,17 +381,3 @@ function formatMastheadDate(value: string): string {
|
|
|
461
381
|
}).format(date);
|
|
462
382
|
}
|
|
463
383
|
|
|
464
|
-
function getBlogFooterSectionHref(entry: PresentationFooterEntry, editionBasePath?: string): string {
|
|
465
|
-
const anchor = `#${getSectionAnchorId(entry.sectionKey)}`;
|
|
466
|
-
return editionBasePath ? `${editionBasePath}${anchor}` : anchor;
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
function handleBlogFooterSectionClick(
|
|
470
|
-
event: ReactMouseEvent<HTMLAnchorElement>,
|
|
471
|
-
entry: PresentationFooterEntry,
|
|
472
|
-
href: string,
|
|
473
|
-
) {
|
|
474
|
-
event.preventDefault();
|
|
475
|
-
window.history.pushState(null, "", href);
|
|
476
|
-
document.getElementById(getSectionAnchorId(entry.sectionKey))?.scrollIntoView({ block: "start" });
|
|
477
|
-
}
|