@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/lib/edition-routes.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { getReaderBasePath, toPublicReaderPath } from "./reader-base-path";
|
|
2
|
+
|
|
1
3
|
const MONTH_NAMES = [
|
|
2
4
|
"january",
|
|
3
5
|
"february",
|
|
@@ -39,52 +41,52 @@ type ParsedEditionSectionRoute = ParsedEditionDateRoute & {
|
|
|
39
41
|
|
|
40
42
|
const RESERVED_DATE_CHILD_SEGMENTS = new Set(["page", "section"]);
|
|
41
43
|
|
|
42
|
-
export function getEditionDatePath(editionDate: string): string {
|
|
44
|
+
export function getEditionDatePath(editionDate: string, readerBasePath: string = getReaderBasePath()): string {
|
|
43
45
|
const match = editionDate.match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
|
44
|
-
if (!match) return "/";
|
|
46
|
+
if (!match) return toPublicReaderPath("/", readerBasePath);
|
|
45
47
|
|
|
46
48
|
const monthIndex = Number(match[2]) - 1;
|
|
47
49
|
const monthName = MONTH_NAMES[monthIndex];
|
|
48
|
-
if (!monthName) return "/";
|
|
50
|
+
if (!monthName) return toPublicReaderPath("/", readerBasePath);
|
|
49
51
|
|
|
50
|
-
return `/${match[1]}/${monthName}/${match[3]}
|
|
52
|
+
return toPublicReaderPath(`/${match[1]}/${monthName}/${match[3]}`, readerBasePath);
|
|
51
53
|
}
|
|
52
54
|
|
|
53
|
-
export function getEditionPagePath(editionDate: string, pageNumber: number): string {
|
|
54
|
-
const basePath = getEditionDatePath(editionDate);
|
|
55
|
+
export function getEditionPagePath(editionDate: string, pageNumber: number, readerBasePath: string = getReaderBasePath()): string {
|
|
56
|
+
const basePath = getEditionDatePath(editionDate, readerBasePath);
|
|
55
57
|
return pageNumber <= 1 ? basePath : `${basePath}/page/${pageNumber}`;
|
|
56
58
|
}
|
|
57
59
|
|
|
58
|
-
export function getEditionSectionPath(editionDate: string, sectionKey: string): string {
|
|
59
|
-
return `${getEditionDatePath(editionDate)}/section/${encodeURIComponent(sectionKey)}`;
|
|
60
|
+
export function getEditionSectionPath(editionDate: string, sectionKey: string, readerBasePath: string = getReaderBasePath()): string {
|
|
61
|
+
return `${getEditionDatePath(editionDate, readerBasePath)}/section/${encodeURIComponent(sectionKey)}`;
|
|
60
62
|
}
|
|
61
63
|
|
|
62
|
-
export function getEditionArticlePath(editionDate: string, articleSlug: string): string {
|
|
63
|
-
return `${getEditionDatePath(editionDate)}/${encodeURIComponent(articleSlug)}`;
|
|
64
|
+
export function getEditionArticlePath(editionDate: string, articleSlug: string, readerBasePath: string = getReaderBasePath()): string {
|
|
65
|
+
return `${getEditionDatePath(editionDate, readerBasePath)}/${encodeURIComponent(articleSlug)}`;
|
|
64
66
|
}
|
|
65
67
|
|
|
66
68
|
export const getEditionItemPath = getEditionArticlePath;
|
|
67
69
|
|
|
68
|
-
export function parseEditionDateRoute(input: EditionDateRouteInput): ParsedEditionDateRoute | null {
|
|
69
|
-
const parsed = parseEditionDateSegments(input);
|
|
70
|
+
export function parseEditionDateRoute(input: EditionDateRouteInput, readerBasePath: string = getReaderBasePath()): ParsedEditionDateRoute | null {
|
|
71
|
+
const parsed = parseEditionDateSegments(input, readerBasePath);
|
|
70
72
|
if (!parsed) return null;
|
|
71
73
|
|
|
72
74
|
return {
|
|
73
75
|
...parsed,
|
|
74
|
-
isCanonical: getCurrentDatePath(input) === parsed.canonicalPath,
|
|
76
|
+
isCanonical: getCurrentDatePath(input, readerBasePath) === parsed.canonicalPath,
|
|
75
77
|
};
|
|
76
78
|
}
|
|
77
79
|
|
|
78
|
-
export function parseEditionPageRoute(input: EditionDateRouteInput & { pageNumber: string }): ParsedEditionPageRoute | null {
|
|
79
|
-
const parsed = parseEditionDateSegments(input);
|
|
80
|
+
export function parseEditionPageRoute(input: EditionDateRouteInput & { pageNumber: string }, readerBasePath: string = getReaderBasePath()): ParsedEditionPageRoute | null {
|
|
81
|
+
const parsed = parseEditionDateSegments(input, readerBasePath);
|
|
80
82
|
if (!parsed) return null;
|
|
81
83
|
if (!/^\d+$/.test(input.pageNumber)) return null;
|
|
82
84
|
|
|
83
85
|
const pageNumber = Number(input.pageNumber);
|
|
84
86
|
if (!Number.isSafeInteger(pageNumber) || pageNumber < 1) return null;
|
|
85
87
|
|
|
86
|
-
const canonicalPath = getEditionPagePath(parsed.editionDate, pageNumber);
|
|
87
|
-
const currentPath = `${getCurrentDatePath(input)}/page/${input.pageNumber}`;
|
|
88
|
+
const canonicalPath = getEditionPagePath(parsed.editionDate, pageNumber, readerBasePath);
|
|
89
|
+
const currentPath = `${getCurrentDatePath(input, readerBasePath)}/page/${input.pageNumber}`;
|
|
88
90
|
return {
|
|
89
91
|
editionDate: parsed.editionDate,
|
|
90
92
|
canonicalPath,
|
|
@@ -93,13 +95,13 @@ export function parseEditionPageRoute(input: EditionDateRouteInput & { pageNumbe
|
|
|
93
95
|
};
|
|
94
96
|
}
|
|
95
97
|
|
|
96
|
-
export function parseEditionArticleRoute(input: EditionDateRouteInput & { articleSlug: string }): ParsedEditionArticleRoute | null {
|
|
97
|
-
const parsed = parseEditionDateSegments(input);
|
|
98
|
+
export function parseEditionArticleRoute(input: EditionDateRouteInput & { articleSlug: string }, readerBasePath: string = getReaderBasePath()): ParsedEditionArticleRoute | null {
|
|
99
|
+
const parsed = parseEditionDateSegments(input, readerBasePath);
|
|
98
100
|
if (!parsed) return null;
|
|
99
101
|
if (RESERVED_DATE_CHILD_SEGMENTS.has(input.articleSlug.toLowerCase())) return null;
|
|
100
102
|
|
|
101
|
-
const canonicalPath = getEditionArticlePath(parsed.editionDate, input.articleSlug);
|
|
102
|
-
const currentPath = `${getCurrentDatePath(input)}/${input.articleSlug}`;
|
|
103
|
+
const canonicalPath = getEditionArticlePath(parsed.editionDate, input.articleSlug, readerBasePath);
|
|
104
|
+
const currentPath = `${getCurrentDatePath(input, readerBasePath)}/${input.articleSlug}`;
|
|
103
105
|
return {
|
|
104
106
|
editionDate: parsed.editionDate,
|
|
105
107
|
canonicalPath,
|
|
@@ -108,13 +110,13 @@ export function parseEditionArticleRoute(input: EditionDateRouteInput & { articl
|
|
|
108
110
|
};
|
|
109
111
|
}
|
|
110
112
|
|
|
111
|
-
export function parseEditionSectionRoute(input: EditionDateRouteInput & { sectionKey: string }): ParsedEditionSectionRoute | null {
|
|
112
|
-
const parsed = parseEditionDateSegments(input);
|
|
113
|
+
export function parseEditionSectionRoute(input: EditionDateRouteInput & { sectionKey: string }, readerBasePath: string = getReaderBasePath()): ParsedEditionSectionRoute | null {
|
|
114
|
+
const parsed = parseEditionDateSegments(input, readerBasePath);
|
|
113
115
|
if (!parsed) return null;
|
|
114
116
|
if (!input.sectionKey.trim()) return null;
|
|
115
117
|
|
|
116
|
-
const canonicalPath = getEditionSectionPath(parsed.editionDate, input.sectionKey);
|
|
117
|
-
const currentPath = `${getCurrentDatePath(input)}/section/${input.sectionKey}`;
|
|
118
|
+
const canonicalPath = getEditionSectionPath(parsed.editionDate, input.sectionKey, readerBasePath);
|
|
119
|
+
const currentPath = `${getCurrentDatePath(input, readerBasePath)}/section/${input.sectionKey}`;
|
|
118
120
|
return {
|
|
119
121
|
editionDate: parsed.editionDate,
|
|
120
122
|
canonicalPath,
|
|
@@ -123,7 +125,7 @@ export function parseEditionSectionRoute(input: EditionDateRouteInput & { sectio
|
|
|
123
125
|
};
|
|
124
126
|
}
|
|
125
127
|
|
|
126
|
-
function parseEditionDateSegments(input: EditionDateRouteInput): Omit<ParsedEditionDateRoute, "isCanonical"> | null {
|
|
128
|
+
function parseEditionDateSegments(input: EditionDateRouteInput, readerBasePath: string): Omit<ParsedEditionDateRoute, "isCanonical"> | null {
|
|
127
129
|
if (!/^\d{4}$/.test(input.year)) return null;
|
|
128
130
|
|
|
129
131
|
const monthIndex = MONTH_NAMES.indexOf(input.month.toLowerCase() as (typeof MONTH_NAMES)[number]);
|
|
@@ -140,12 +142,12 @@ function parseEditionDateSegments(input: EditionDateRouteInput): Omit<ParsedEdit
|
|
|
140
142
|
|
|
141
143
|
return {
|
|
142
144
|
editionDate,
|
|
143
|
-
canonicalPath: getEditionDatePath(editionDate),
|
|
145
|
+
canonicalPath: getEditionDatePath(editionDate, readerBasePath),
|
|
144
146
|
};
|
|
145
147
|
}
|
|
146
148
|
|
|
147
|
-
function getCurrentDatePath(input: EditionDateRouteInput): string {
|
|
148
|
-
return `/${input.year}/${input.month}/${input.day}
|
|
149
|
+
function getCurrentDatePath(input: EditionDateRouteInput, readerBasePath: string): string {
|
|
150
|
+
return toPublicReaderPath(`/${input.year}/${input.month}/${input.day}`, readerBasePath);
|
|
149
151
|
}
|
|
150
152
|
|
|
151
153
|
function isValidIsoDate(editionDate: string): boolean {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/* Default for `papyrus-site-theme` when a publication ships no publication/theme.css. */
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { generateServerClientUsingCookies } from "@aws-amplify/adapter-nextjs/api";
|
|
1
2
|
import { generateClient } from "aws-amplify/data";
|
|
2
3
|
import type { Schema } from "../amplify/data/resource";
|
|
3
4
|
import type { Article, ArticleImage, ArticleImageAsset, ArticleImageLayout, ArticleImageThemeVariants, ArticleVideoAsset, ArticleVideoThemeVariants } from "./articles";
|
|
@@ -17,11 +18,15 @@ import {
|
|
|
17
18
|
} from "./publication-items";
|
|
18
19
|
import { BodyIrError, matchBodyImages, projectBodyIr, type BodyProjection, type BodyProjectionImage } from "./markus-body";
|
|
19
20
|
import { SITE_BRAND } from "./site-brand";
|
|
21
|
+
import { shouldFetchVideoScripts } from "./video-mode";
|
|
22
|
+
import { collectVideoScriptTargets, parseVideoScriptRef, videomlItemSlug, type VideoScriptRef } from "./video-script";
|
|
23
|
+
import { currentContentSource, currentRequestClient, runWithContentSource } from "./content-source-context";
|
|
24
|
+
import { getContentSource } from "./site-env";
|
|
20
25
|
|
|
21
|
-
const AUTH_MODE = "identityPool";
|
|
22
26
|
const DEFAULT_EDITION_SLUG = "current";
|
|
23
27
|
const PUBLISHED_STATUS = "published";
|
|
24
|
-
const
|
|
28
|
+
const DRAFT_STATUS = "draft";
|
|
29
|
+
const ARTICLE_TYPE = "article";
|
|
25
30
|
|
|
26
31
|
type DataClient = ReturnType<typeof generateClient<Schema>>;
|
|
27
32
|
type EditionPublishedAtIndexQuery = (
|
|
@@ -64,8 +69,6 @@ type GraphQLEditionItem = {
|
|
|
64
69
|
|
|
65
70
|
type GraphQLItem = {
|
|
66
71
|
id: string;
|
|
67
|
-
sourceItemId?: string | null;
|
|
68
|
-
itemLineageId?: string | null;
|
|
69
72
|
versionNumber?: number | null;
|
|
70
73
|
type: string;
|
|
71
74
|
status: string;
|
|
@@ -133,19 +136,38 @@ const IMAGE_ROLES: NonNullable<ArticleImageAsset["roles"]> = [
|
|
|
133
136
|
let readerContextClient: DataClient | null = null;
|
|
134
137
|
let buildTimeClient: DataClient | null = null;
|
|
135
138
|
|
|
139
|
+
function authMode(): "userPool" | "identityPool" {
|
|
140
|
+
return currentContentSource() === "drafts" ? "userPool" : "identityPool";
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function isVisibleItemStatus(status: string): boolean {
|
|
144
|
+
if (status === PUBLISHED_STATUS) return true;
|
|
145
|
+
return currentContentSource() === "drafts" && status === DRAFT_STATUS;
|
|
146
|
+
}
|
|
147
|
+
|
|
136
148
|
async function withReaderGraphQLContext<T>(operation: () => Promise<T>): Promise<T> {
|
|
137
|
-
const { runWithAmplifyServerContext } = getAmplifyServerRuntime();
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
+
const { runWithAmplifyServerContext, config } = getAmplifyServerRuntime();
|
|
150
|
+
const source = getContentSource();
|
|
151
|
+
if (source === "drafts") {
|
|
152
|
+
// Drafts are read as the signed-in visitor: the client reads the session from the request cookies, and it is
|
|
153
|
+
// kept in the request's async context (never a module global) so one visitor's session cannot serve another.
|
|
154
|
+
const { cookies } = await import("next/headers");
|
|
155
|
+
const client = generateServerClientUsingCookies<Schema>({ config, cookies, authMode: "userPool" });
|
|
156
|
+
return runWithContentSource(source, operation, client);
|
|
157
|
+
}
|
|
158
|
+
return runWithContentSource(source, () =>
|
|
159
|
+
runWithAmplifyServerContext({
|
|
160
|
+
nextServerContext: null,
|
|
161
|
+
operation: async () => {
|
|
162
|
+
readerContextClient = generateClient<Schema>({ authMode: authMode() });
|
|
163
|
+
try {
|
|
164
|
+
return await operation();
|
|
165
|
+
} finally {
|
|
166
|
+
readerContextClient = null;
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
}),
|
|
170
|
+
);
|
|
149
171
|
}
|
|
150
172
|
|
|
151
173
|
export const graphqlContentRepository: ContentRepository = {
|
|
@@ -200,7 +222,7 @@ export const graphqlContentRepository: ContentRepository = {
|
|
|
200
222
|
getArticle(slug) {
|
|
201
223
|
return withReaderGraphQLContext(async () => {
|
|
202
224
|
const item = await getItemBySlug(slug);
|
|
203
|
-
if (!item || item.type !==
|
|
225
|
+
if (!item || item.type !== ARTICLE_TYPE || !isVisibleItemStatus(item.status)) return undefined;
|
|
204
226
|
return normalizeArticle(item, await listMediaAssets(item.id));
|
|
205
227
|
});
|
|
206
228
|
},
|
|
@@ -216,11 +238,15 @@ export const graphqlContentRepository: ContentRepository = {
|
|
|
216
238
|
return withReaderGraphQLContext(() => loadEditionItem(editionDate, itemSlug));
|
|
217
239
|
},
|
|
218
240
|
|
|
241
|
+
loadVideoScript(targetSlug) {
|
|
242
|
+
return withReaderGraphQLContext(() => loadVideoScriptForTarget(targetSlug));
|
|
243
|
+
},
|
|
244
|
+
|
|
219
245
|
listArticleSlugs() {
|
|
220
246
|
return withReaderGraphQLContext(async () => {
|
|
221
|
-
const items = await listItemsByTypeStatus(
|
|
247
|
+
const items = await listItemsByTypeStatus(`${ARTICLE_TYPE}#${PUBLISHED_STATUS}`);
|
|
222
248
|
return items
|
|
223
|
-
.filter((item) => item.type ===
|
|
249
|
+
.filter((item) => item.type === ARTICLE_TYPE && isVisibleItemStatus(item.status))
|
|
224
250
|
.map((item) => item.slug)
|
|
225
251
|
.sort();
|
|
226
252
|
});
|
|
@@ -228,11 +254,13 @@ export const graphqlContentRepository: ContentRepository = {
|
|
|
228
254
|
};
|
|
229
255
|
|
|
230
256
|
function getClient(): DataClient {
|
|
257
|
+
const requestClient = currentRequestClient<DataClient>();
|
|
258
|
+
if (requestClient) return requestClient;
|
|
231
259
|
if (readerContextClient) return readerContextClient;
|
|
232
260
|
|
|
233
261
|
if (!buildTimeClient) {
|
|
234
262
|
getAmplifyServerRuntime();
|
|
235
|
-
buildTimeClient = generateClient<Schema>({ authMode:
|
|
263
|
+
buildTimeClient = generateClient<Schema>({ authMode: authMode() });
|
|
236
264
|
}
|
|
237
265
|
return buildTimeClient;
|
|
238
266
|
}
|
|
@@ -331,7 +359,7 @@ async function loadEditionContentFromEdition(edition: GraphQLEdition): Promise<E
|
|
|
331
359
|
await Promise.all(
|
|
332
360
|
editionItems.map(async (editionItem) => {
|
|
333
361
|
const item = await getItemById(editionItem.publishedItemId);
|
|
334
|
-
if (!item || item.status
|
|
362
|
+
if (!item || !isVisibleItemStatus(item.status)) return null;
|
|
335
363
|
return normalizePublicationItem(item, await listMediaAssets(item.id));
|
|
336
364
|
}),
|
|
337
365
|
)
|
|
@@ -344,7 +372,7 @@ async function loadEditionContentFromEdition(edition: GraphQLEdition): Promise<E
|
|
|
344
372
|
await Promise.all(
|
|
345
373
|
missingLayoutItems.map(async (itemId) => {
|
|
346
374
|
const item = await getItemBySlug(itemId);
|
|
347
|
-
if (!item || item.status
|
|
375
|
+
if (!item || !isVisibleItemStatus(item.status)) return null;
|
|
348
376
|
return normalizePublicationItem(item, await listMediaAssets(item.id));
|
|
349
377
|
}),
|
|
350
378
|
)
|
|
@@ -357,6 +385,9 @@ async function loadEditionContentFromEdition(edition: GraphQLEdition): Promise<E
|
|
|
357
385
|
const sanitizedLayoutPlan = pruneLayoutPlanUnavailableItems(layoutPlan, availableItemSlugs, edition.id);
|
|
358
386
|
validateEditionLayoutPlanForItems(sanitizedLayoutPlan, items, `PublishedEdition(${edition.id}).layoutPlan`);
|
|
359
387
|
|
|
388
|
+
const editionVideo = await normalizeEditionVideoAsset(editionMetadata?.editionVideo);
|
|
389
|
+
const videoScripts = await loadEditionVideoScripts(items, editionVideo);
|
|
390
|
+
|
|
360
391
|
return {
|
|
361
392
|
id: edition.id,
|
|
362
393
|
source: "graphql",
|
|
@@ -367,10 +398,38 @@ async function loadEditionContentFromEdition(edition: GraphQLEdition): Promise<E
|
|
|
367
398
|
items,
|
|
368
399
|
sections: createEditionSectionPlan(items, edition.metadata),
|
|
369
400
|
suppressNewsDeskAppendix: editionMetadata?.suppressNewsDeskAppendix === true,
|
|
370
|
-
editionVideo
|
|
401
|
+
editionVideo,
|
|
402
|
+
videoScripts,
|
|
371
403
|
};
|
|
372
404
|
}
|
|
373
405
|
|
|
406
|
+
function videoScriptsEnabled(): boolean {
|
|
407
|
+
return Boolean(SITE_BRAND.videoPlayer) && shouldFetchVideoScripts();
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
async function loadVideoScriptForTarget(targetSlug: string): Promise<VideoScriptRef | null> {
|
|
411
|
+
if (!videoScriptsEnabled()) return null;
|
|
412
|
+
const item = await getItemBySlug(videomlItemSlug(targetSlug));
|
|
413
|
+
if (!item || item.type !== "videoml" || !isVisibleItemStatus(item.status)) return null;
|
|
414
|
+
return parseVideoScriptRef(item);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
async function loadEditionVideoScripts(
|
|
418
|
+
items: PublicationItem[],
|
|
419
|
+
editionVideo: ArticleVideoAsset | null,
|
|
420
|
+
): Promise<Record<string, VideoScriptRef> | undefined> {
|
|
421
|
+
if (!videoScriptsEnabled()) return undefined;
|
|
422
|
+
const targets = collectVideoScriptTargets(items, editionVideo !== null);
|
|
423
|
+
const entries = await Promise.all(
|
|
424
|
+
targets.map(async (targetSlug) => {
|
|
425
|
+
const script = await loadVideoScriptForTarget(targetSlug);
|
|
426
|
+
return script ? ([targetSlug, script] as const) : null;
|
|
427
|
+
}),
|
|
428
|
+
);
|
|
429
|
+
const videoScripts = Object.fromEntries(entries.filter((entry): entry is [string, VideoScriptRef] => entry !== null));
|
|
430
|
+
return Object.keys(videoScripts).length > 0 ? videoScripts : undefined;
|
|
431
|
+
}
|
|
432
|
+
|
|
374
433
|
async function normalizeEditionVideoAsset(value: unknown): Promise<ArticleVideoAsset | null> {
|
|
375
434
|
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
376
435
|
const record = value as Record<string, unknown>;
|
|
@@ -461,7 +520,7 @@ async function loadEditionItem(editionDate: string, itemSlug: string): Promise<P
|
|
|
461
520
|
const editionItems = await listEditionItems(edition.id);
|
|
462
521
|
for (const editionItem of editionItems) {
|
|
463
522
|
const item = await getItemById(editionItem.publishedItemId);
|
|
464
|
-
if (!item || item.slug !== itemSlug || item.status
|
|
523
|
+
if (!item || item.slug !== itemSlug || !isVisibleItemStatus(item.status)) continue;
|
|
465
524
|
return (await normalizePublicationItem(item, await listMediaAssets(item.id))) ?? undefined;
|
|
466
525
|
}
|
|
467
526
|
return undefined;
|
|
@@ -505,36 +564,168 @@ function isMissingReaderBackendError(error: unknown): boolean {
|
|
|
505
564
|
}
|
|
506
565
|
|
|
507
566
|
function getEditionPublishedAtIndexQuery(): EditionPublishedAtIndexQuery | null {
|
|
508
|
-
const editionModel = getPublishedEditionModel()
|
|
509
|
-
listPublishedEditionsByStatusAndPublishedAt?: EditionPublishedAtIndexQuery;
|
|
510
|
-
};
|
|
567
|
+
const editionModel = getPublishedEditionModel();
|
|
511
568
|
return typeof editionModel.listPublishedEditionsByStatusAndPublishedAt === "function"
|
|
512
569
|
? editionModel.listPublishedEditionsByStatusAndPublishedAt
|
|
513
570
|
: null;
|
|
514
571
|
}
|
|
515
572
|
|
|
516
|
-
|
|
573
|
+
type ListOptions = Record<string, unknown>;
|
|
574
|
+
type ListResponse<T> = Promise<GraphQLListResponse<T>>;
|
|
575
|
+
|
|
576
|
+
type EditionSourceModel = {
|
|
577
|
+
publishedEditionBySlug: (input: { slug: string }, options: ListOptions) => ListResponse<GraphQLEdition>;
|
|
578
|
+
listPublishedEditionsByStatusAndEditionDate: (
|
|
579
|
+
input: { status: string; editionDate?: { eq: string } },
|
|
580
|
+
options: ListOptions,
|
|
581
|
+
) => ListResponse<GraphQLEdition>;
|
|
582
|
+
listPublishedEditionsByStatusAndPublishedAt?: EditionPublishedAtIndexQuery;
|
|
583
|
+
};
|
|
584
|
+
|
|
585
|
+
type EditionItemSourceModel = {
|
|
586
|
+
listPublishedEditionItemsByEditionAndSortKey: (
|
|
587
|
+
input: { publishedEditionId: string },
|
|
588
|
+
options: ListOptions,
|
|
589
|
+
) => ListResponse<GraphQLEditionItem>;
|
|
590
|
+
};
|
|
591
|
+
|
|
592
|
+
type ItemSourceModel = {
|
|
593
|
+
get: (input: { id: string }, options: ListOptions) => Promise<GraphQLGetResponse<GraphQLItem>>;
|
|
594
|
+
publishedItemBySlug: (input: { slug: string }, options: ListOptions) => ListResponse<GraphQLItem>;
|
|
595
|
+
listPublishedItemsByTypeStatusAndPublishedAt: (input: { typeStatus: string }, options: ListOptions) => ListResponse<GraphQLItem>;
|
|
596
|
+
};
|
|
597
|
+
|
|
598
|
+
type MediaAssetSourceModel = {
|
|
599
|
+
listPublishedMediaAssetsByItemAndSortKey: (
|
|
600
|
+
input: { publishedItemId: string },
|
|
601
|
+
options: ListOptions,
|
|
602
|
+
) => ListResponse<GraphQLMediaAsset>;
|
|
603
|
+
};
|
|
604
|
+
|
|
605
|
+
const DRAFTS_EDITION_STATUSES = [PUBLISHED_STATUS, DRAFT_STATUS];
|
|
606
|
+
|
|
607
|
+
async function listMergedAcrossStatuses<T>(
|
|
608
|
+
statuses: string[],
|
|
609
|
+
runForStatus: (status: string, pageOptions: ListOptions) => ListResponse<T>,
|
|
610
|
+
sortValue: (row: T) => string,
|
|
611
|
+
options: ListOptions,
|
|
612
|
+
): ListResponse<T> {
|
|
613
|
+
const rows: T[] = [];
|
|
614
|
+
for (const status of statuses) {
|
|
615
|
+
let pageToken: string | null | undefined;
|
|
616
|
+
do {
|
|
617
|
+
const response = await runForStatus(status, { authMode: options.authMode, limit: 100, nextToken: pageToken });
|
|
618
|
+
if (response.errors?.length) return { data: [], errors: response.errors };
|
|
619
|
+
rows.push(...((response.data ?? []).filter(Boolean) as T[]));
|
|
620
|
+
pageToken = response.nextToken;
|
|
621
|
+
} while (pageToken);
|
|
622
|
+
}
|
|
623
|
+
const direction = options.sortDirection === "DESC" ? -1 : 1;
|
|
624
|
+
rows.sort((left, right) => direction * sortValue(left).localeCompare(sortValue(right)));
|
|
625
|
+
const offset = Number(options.nextToken ?? 0) || 0;
|
|
626
|
+
const limit = typeof options.limit === "number" ? options.limit : undefined;
|
|
627
|
+
const end = limit === undefined ? rows.length : offset + limit;
|
|
628
|
+
return {
|
|
629
|
+
data: rows.slice(offset, end),
|
|
630
|
+
nextToken: end < rows.length ? String(end) : undefined,
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
function getPublishedEditionModel(): EditionSourceModel {
|
|
635
|
+
if (currentContentSource() === "drafts") {
|
|
636
|
+
const model = getClient().models.Edition;
|
|
637
|
+
if (!model) throw missingProjectionModelError("Edition");
|
|
638
|
+
return {
|
|
639
|
+
publishedEditionBySlug: (input, options) => model.editionBySlug(input, options) as ListResponse<GraphQLEdition>,
|
|
640
|
+
listPublishedEditionsByStatusAndEditionDate: (input, options) =>
|
|
641
|
+
listMergedAcrossStatuses<GraphQLEdition>(
|
|
642
|
+
DRAFTS_EDITION_STATUSES,
|
|
643
|
+
(status, pageOptions) =>
|
|
644
|
+
model.listEditionsByStatusAndEditionDate({ ...input, status }, pageOptions) as ListResponse<GraphQLEdition>,
|
|
645
|
+
(edition) => edition.editionDate,
|
|
646
|
+
options,
|
|
647
|
+
),
|
|
648
|
+
};
|
|
649
|
+
}
|
|
517
650
|
const model = getClient().models.PublishedEdition;
|
|
518
651
|
if (!model) throw missingProjectionModelError("PublishedEdition");
|
|
519
|
-
return model;
|
|
652
|
+
return model as unknown as EditionSourceModel;
|
|
520
653
|
}
|
|
521
654
|
|
|
522
|
-
function getPublishedEditionItemModel() {
|
|
655
|
+
function getPublishedEditionItemModel(): EditionItemSourceModel {
|
|
656
|
+
if (currentContentSource() === "drafts") {
|
|
657
|
+
const model = getClient().models.EditionItem;
|
|
658
|
+
if (!model) throw missingProjectionModelError("EditionItem");
|
|
659
|
+
return {
|
|
660
|
+
listPublishedEditionItemsByEditionAndSortKey: async ({ publishedEditionId }, options) => {
|
|
661
|
+
const response = (await model.listEditionItemsByEditionAndSortKey(
|
|
662
|
+
{ editionId: publishedEditionId },
|
|
663
|
+
options,
|
|
664
|
+
)) as unknown as GraphQLListResponse<{ id: string; editionId: string; itemId: string; sortKey: string }>;
|
|
665
|
+
return {
|
|
666
|
+
...response,
|
|
667
|
+
data: (response.data ?? []).filter(Boolean).map((row) => ({
|
|
668
|
+
id: row.id,
|
|
669
|
+
publishedEditionId: row.editionId,
|
|
670
|
+
publishedItemId: row.itemId,
|
|
671
|
+
sortKey: row.sortKey,
|
|
672
|
+
})),
|
|
673
|
+
};
|
|
674
|
+
},
|
|
675
|
+
};
|
|
676
|
+
}
|
|
523
677
|
const model = getClient().models.PublishedEditionItem;
|
|
524
678
|
if (!model) throw missingProjectionModelError("PublishedEditionItem");
|
|
525
|
-
return model;
|
|
679
|
+
return model as unknown as EditionItemSourceModel;
|
|
526
680
|
}
|
|
527
681
|
|
|
528
|
-
function getPublishedItemModel() {
|
|
682
|
+
function getPublishedItemModel(): ItemSourceModel {
|
|
683
|
+
if (currentContentSource() === "drafts") {
|
|
684
|
+
const model = getClient().models.Item;
|
|
685
|
+
if (!model) throw missingProjectionModelError("Item");
|
|
686
|
+
return {
|
|
687
|
+
get: (input, options) => model.get(input, options) as Promise<GraphQLGetResponse<GraphQLItem>>,
|
|
688
|
+
publishedItemBySlug: (input, options) => model.itemBySlug(input, options) as ListResponse<GraphQLItem>,
|
|
689
|
+
listPublishedItemsByTypeStatusAndPublishedAt: ({ typeStatus }, options) => {
|
|
690
|
+
const itemType = typeStatus.split("#")[0];
|
|
691
|
+
return listMergedAcrossStatuses<GraphQLItem>(
|
|
692
|
+
[DRAFT_STATUS, PUBLISHED_STATUS],
|
|
693
|
+
(status, pageOptions) =>
|
|
694
|
+
model.listItemsByTypeStatusAndPublishedAt(
|
|
695
|
+
{ typeStatus: `${itemType}#${status}` },
|
|
696
|
+
pageOptions,
|
|
697
|
+
) as ListResponse<GraphQLItem>,
|
|
698
|
+
(item) => item.publishedAt ?? "",
|
|
699
|
+
options,
|
|
700
|
+
);
|
|
701
|
+
},
|
|
702
|
+
};
|
|
703
|
+
}
|
|
529
704
|
const model = getClient().models.PublishedItem;
|
|
530
705
|
if (!model) throw missingProjectionModelError("PublishedItem");
|
|
531
|
-
return model;
|
|
706
|
+
return model as unknown as ItemSourceModel;
|
|
532
707
|
}
|
|
533
708
|
|
|
534
|
-
function getPublishedMediaAssetModel() {
|
|
709
|
+
function getPublishedMediaAssetModel(): MediaAssetSourceModel {
|
|
710
|
+
if (currentContentSource() === "drafts") {
|
|
711
|
+
const model = getClient().models.MediaAsset;
|
|
712
|
+
if (!model) throw missingProjectionModelError("MediaAsset");
|
|
713
|
+
return {
|
|
714
|
+
listPublishedMediaAssetsByItemAndSortKey: async ({ publishedItemId }, options) => {
|
|
715
|
+
const response = (await model.listMediaAssetsByItemAndSortKey(
|
|
716
|
+
{ itemId: publishedItemId },
|
|
717
|
+
options,
|
|
718
|
+
)) as unknown as GraphQLListResponse<Omit<GraphQLMediaAsset, "publishedItemId"> & { itemId: string }>;
|
|
719
|
+
return {
|
|
720
|
+
...response,
|
|
721
|
+
data: (response.data ?? []).filter(Boolean).map((row) => ({ ...row, publishedItemId: row.itemId })),
|
|
722
|
+
};
|
|
723
|
+
},
|
|
724
|
+
};
|
|
725
|
+
}
|
|
535
726
|
const model = getClient().models.PublishedMediaAsset;
|
|
536
727
|
if (!model) throw missingProjectionModelError("PublishedMediaAsset");
|
|
537
|
-
return model;
|
|
728
|
+
return model as unknown as MediaAssetSourceModel;
|
|
538
729
|
}
|
|
539
730
|
|
|
540
731
|
function missingProjectionModelError(modelName: string): Error {
|
|
@@ -561,7 +752,7 @@ async function listItemsByTypeStatus(typeStatus: string): Promise<GraphQLItem[]>
|
|
|
561
752
|
}
|
|
562
753
|
|
|
563
754
|
async function getItemById(id: string): Promise<GraphQLItem | null> {
|
|
564
|
-
const response = await getPublishedItemModel().get({ id }, { authMode:
|
|
755
|
+
const response = await getPublishedItemModel().get({ id }, { authMode: authMode() });
|
|
565
756
|
return readGetResponse<GraphQLItem>(response);
|
|
566
757
|
}
|
|
567
758
|
|
|
@@ -583,7 +774,7 @@ async function listAll<T>(operation: (options: Record<string, unknown>) => Promi
|
|
|
583
774
|
|
|
584
775
|
do {
|
|
585
776
|
const response = await operation({
|
|
586
|
-
authMode:
|
|
777
|
+
authMode: authMode(),
|
|
587
778
|
limit: 100,
|
|
588
779
|
nextToken,
|
|
589
780
|
});
|
|
@@ -597,7 +788,7 @@ async function listAll<T>(operation: (options: Record<string, unknown>) => Promi
|
|
|
597
788
|
|
|
598
789
|
async function listFirst<T>(operation: (options: Record<string, unknown>) => Promise<GraphQLListResponse<T>>): Promise<T[]> {
|
|
599
790
|
const response = await operation({
|
|
600
|
-
authMode:
|
|
791
|
+
authMode: authMode(),
|
|
601
792
|
limit: 1,
|
|
602
793
|
sortDirection: "DESC",
|
|
603
794
|
});
|
|
@@ -611,7 +802,7 @@ async function listPage<T>(
|
|
|
611
802
|
nextToken?: string | null,
|
|
612
803
|
): Promise<{ editions: T[]; nextToken?: string | null }> {
|
|
613
804
|
const response = await operation({
|
|
614
|
-
authMode:
|
|
805
|
+
authMode: authMode(),
|
|
615
806
|
limit,
|
|
616
807
|
nextToken,
|
|
617
808
|
sortDirection: "DESC",
|
|
@@ -663,7 +854,7 @@ async function normalizeArticle(item: GraphQLItem, mediaAssets: GraphQLMediaAsse
|
|
|
663
854
|
|
|
664
855
|
function projectPublishedItemBody(item: GraphQLItem): BodyProjection {
|
|
665
856
|
if (item.bodyIr === null || item.bodyIr === undefined || item.bodyIr === "") {
|
|
666
|
-
throw new BodyIrError(`PublishedItem ${item.slug} has no bodyIr; run: papyrus content convert-bodies`);
|
|
857
|
+
throw new BodyIrError(`PublishedItem ${item.slug} has no bodyIr; run: papyrus ops content convert-bodies`);
|
|
667
858
|
}
|
|
668
859
|
return projectBodyIr(item.bodyIr);
|
|
669
860
|
}
|
|
@@ -785,15 +976,20 @@ function parseAttachmentPointer(value: unknown): ContentAttachmentPointer | null
|
|
|
785
976
|
};
|
|
786
977
|
}
|
|
787
978
|
|
|
788
|
-
function readInlineExcerpt(editorial: unknown): string | null {
|
|
979
|
+
export function readInlineExcerpt(editorial: unknown): string | null {
|
|
789
980
|
const parsed = parseObjectMetadata(editorial);
|
|
790
981
|
if (!parsed) return null;
|
|
791
982
|
const direct = parsed.excerpt;
|
|
792
983
|
if (typeof direct === "string" && direct.trim()) return direct.trim();
|
|
984
|
+
const custom = parsed.customExcerpt;
|
|
985
|
+
if (typeof custom === "string" && custom.trim()) return custom.trim();
|
|
793
986
|
const newsroom = parsed.newsroom;
|
|
794
987
|
if (!newsroom || typeof newsroom !== "object" || Array.isArray(newsroom)) return null;
|
|
795
|
-
const
|
|
796
|
-
|
|
988
|
+
const newsroomRecord = newsroom as Record<string, unknown>;
|
|
989
|
+
const nestedExcerpt = newsroomRecord.excerpt;
|
|
990
|
+
if (typeof nestedExcerpt === "string" && nestedExcerpt.trim()) return nestedExcerpt.trim();
|
|
991
|
+
const nestedCustom = newsroomRecord.customExcerpt;
|
|
992
|
+
return typeof nestedCustom === "string" && nestedCustom.trim() ? nestedCustom.trim() : null;
|
|
797
993
|
}
|
|
798
994
|
|
|
799
995
|
function normalizePublicationItemType(value: string): Exclude<PublicationItemType, "article"> | null {
|
package/lib/layout-scenarios.ts
CHANGED
|
@@ -5,7 +5,6 @@ import { createDefaultEditionLayoutPlan, type EditionLayoutPlan } from "./layout
|
|
|
5
5
|
import { articleToPublicationItem, cloneArticle } from "./publication-items";
|
|
6
6
|
import { PILOBOL_SAMPLE_SCENARIO_ID, createPilobolSampleEditionContent } from "./pilobol-sample";
|
|
7
7
|
import { SITE_BRAND } from "./site-brand";
|
|
8
|
-
import threatIntelligenceSeedContent from "../publications/threat_intelligence/seed/seed-edition-content.json";
|
|
9
8
|
|
|
10
9
|
export type LayoutScenario = EditionContent & {
|
|
11
10
|
source: "scenario";
|
|
@@ -190,7 +189,8 @@ function getDefaultScenarioSeed(): {
|
|
|
190
189
|
editionVideo?: ArticleVideoAsset;
|
|
191
190
|
newsDeskAppendix?: NewsDeskAppendix | null;
|
|
192
191
|
} {
|
|
193
|
-
|
|
192
|
+
const demoEdition = SITE_BRAND.demoEdition;
|
|
193
|
+
if (!demoEdition) {
|
|
194
194
|
return {
|
|
195
195
|
title: "Current Edition",
|
|
196
196
|
editionDate,
|
|
@@ -201,12 +201,12 @@ function getDefaultScenarioSeed(): {
|
|
|
201
201
|
}
|
|
202
202
|
|
|
203
203
|
return {
|
|
204
|
-
title:
|
|
205
|
-
editionDate:
|
|
206
|
-
description:
|
|
207
|
-
articles:
|
|
208
|
-
suppressNewsDeskAppendix:
|
|
209
|
-
editionVideo:
|
|
204
|
+
title: demoEdition.title,
|
|
205
|
+
editionDate: demoEdition.editionDate,
|
|
206
|
+
description: demoEdition.description,
|
|
207
|
+
articles: demoEdition.articles,
|
|
208
|
+
suppressNewsDeskAppendix: demoEdition.suppressNewsDeskAppendix === true,
|
|
209
|
+
editionVideo: demoEdition.editionVideo,
|
|
210
210
|
newsDeskAppendix: null,
|
|
211
211
|
};
|
|
212
212
|
}
|
package/lib/newspaper-layout.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { toPublicReaderPath } from "./reader-base-path";
|
|
1
2
|
import type { ArticleImageAsset, ArticleImageThemeVariants } from "./articles";
|
|
2
3
|
import {
|
|
3
4
|
type ArticleFrameBlockSpec,
|
|
@@ -896,9 +897,9 @@ function solveFrontFooter(blocks: SolvedBlock[], config: LayoutConfig, pageNumbe
|
|
|
896
897
|
: Math.min(entries.length, Math.max(1, config.columnCount <= 1 ? 1 : config.columnCount <= 3 ? 2 : 4));
|
|
897
898
|
const sectionRows = entries.length === 0 ? 0 : Math.ceil(entries.length / sectionColumns);
|
|
898
899
|
const utilityEntries: SolvedFrontFooterUtilityEntry[] = [
|
|
899
|
-
{ id: "archive", label: "Archive", href: "/archive", disabled: false },
|
|
900
|
+
{ id: "archive", label: "Archive", href: toPublicReaderPath("/archive"), disabled: false },
|
|
900
901
|
{ id: "newsDesk", label: "Newsroom", href: "/newsroom", disabled: false },
|
|
901
|
-
{ id: "settings", label: "Settings", href: "/settings", disabled: false },
|
|
902
|
+
{ id: "settings", label: "Settings", href: toPublicReaderPath("/settings"), disabled: false },
|
|
902
903
|
{ id: "login", label: "LOGIN", disabled: true },
|
|
903
904
|
];
|
|
904
905
|
const rowCount = 1 + Math.max(sectionRows, utilityEntries.length);
|