@anthusai/papyrus 0.0.0 → 1.0.0-next.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/amplify/auth/resource.js +61 -0
- package/amplify/auth/resource.ts +65 -0
- package/amplify/data/resource.js +2069 -0
- package/amplify/data/resource.ts +2185 -0
- package/amplify/data/schema.js +1882 -0
- package/amplify/data/schema.ts +1990 -0
- package/amplify/functions/assignment-action/handler.ts +1416 -0
- package/amplify/functions/assignment-action/resource.js +8 -0
- package/amplify/functions/assignment-action/resource.ts +9 -0
- package/amplify/functions/category-action/handler.ts +2259 -0
- package/amplify/functions/category-action/resource.js +8 -0
- package/amplify/functions/category-action/resource.ts +9 -0
- package/amplify/functions/console-chat-responder/Cargo.lock +3456 -0
- package/amplify/functions/console-chat-responder/Cargo.toml +26 -0
- package/amplify/functions/console-chat-responder/Dockerfile +19 -0
- package/amplify/functions/console-chat-responder/py/execute_tactus_runner.py +116 -0
- package/amplify/functions/console-chat-responder/resource.js +153 -0
- package/amplify/functions/console-chat-responder/resource.ts +179 -0
- package/amplify/functions/console-chat-responder/src/main.rs +3242 -0
- package/amplify/functions/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/email-submission-processor/handler.py +46 -0
- package/amplify/functions/email-submission-processor/requirements.txt +2 -0
- package/amplify/functions/email-submission-processor/resource.js +56 -0
- package/amplify/functions/email-submission-processor/resource.ts +69 -0
- package/amplify/functions/graphql-jwt-authorizer/handler.ts +193 -0
- package/amplify/functions/graphql-jwt-authorizer/resource.js +14 -0
- package/amplify/functions/graphql-jwt-authorizer/resource.ts +15 -0
- package/amplify/functions/knowledge-query/handler.py +1 -0
- package/amplify/functions/knowledge-query/requirements.txt +3 -0
- package/amplify/functions/knowledge-query/resource.js +50 -0
- package/amplify/functions/knowledge-query/resource.ts +61 -0
- package/amplify/functions/manage-user-role/handler.ts +799 -0
- package/amplify/functions/manage-user-role/resource.js +8 -0
- package/amplify/functions/manage-user-role/resource.ts +9 -0
- package/amplify/functions/model-attachment-upload/handler.ts +387 -0
- package/amplify/functions/model-attachment-upload/resource.js +8 -0
- package/amplify/functions/model-attachment-upload/resource.ts +9 -0
- package/amplify/functions/newsroom-summary/handler.ts +321 -0
- package/amplify/functions/newsroom-summary/resource.js +8 -0
- package/amplify/functions/newsroom-summary/resource.ts +9 -0
- package/amplify/functions/procedure-action/handler.ts +621 -0
- package/amplify/functions/procedure-action/resource.js +8 -0
- package/amplify/functions/procedure-action/resource.ts +9 -0
- package/amplify/functions/reader-settings/handler.ts +322 -0
- package/amplify/functions/reader-settings/resource.js +8 -0
- package/amplify/functions/reader-settings/resource.ts +9 -0
- package/amplify/functions/ses-inbound-receive/handler.ts +493 -0
- package/amplify/functions/ses-inbound-receive/resource.js +8 -0
- package/amplify/functions/ses-inbound-receive/resource.ts +9 -0
- package/amplify/functions/shared/amplify-secrets.ts +65 -0
- package/amplify/functions/shared/console-chat-enqueue.ts +117 -0
- package/amplify/functions/shared/email-mime-intake.ts +196 -0
- package/amplify/functions/shared/email-submission.ts +415 -0
- package/amplify/functions/shared/inbound-email-intake.ts +37 -0
- package/amplify/functions/shared/lambda-data-client.ts +21 -0
- package/amplify/functions/shared/model-payloads.ts +186 -0
- package/amplify/functions/shared/python-bundle.js +130 -0
- package/amplify/functions/shared/python-bundle.ts +162 -0
- package/amplify/functions/shared/slack-events.ts +111 -0
- package/amplify/functions/slack-delivery/handler.py +112 -0
- package/amplify/functions/slack-delivery/requirements.txt +2 -0
- package/amplify/functions/slack-delivery/resource.js +38 -0
- package/amplify/functions/slack-delivery/resource.ts +51 -0
- package/amplify/functions/slack-events/handler.ts +155 -0
- package/amplify/functions/slack-events/resource.js +11 -0
- package/amplify/functions/slack-events/resource.ts +12 -0
- package/amplify/inbound-email/stack.js +46 -0
- package/amplify/inbound-email/stack.ts +61 -0
- package/amplify/package.json +3 -0
- package/amplify/rebuild-trigger-settings.js +22 -0
- package/amplify/rebuild-trigger-settings.ts +42 -0
- package/amplify/site-backend-config.js +1 -0
- package/amplify/site-backend-config.ts +36 -0
- package/amplify/site-backend.js +615 -0
- package/amplify/site-backend.ts +739 -0
- package/amplify/storage/resource.js +21 -0
- package/amplify/storage/resource.ts +22 -0
- package/app/%5F_preview/[[...path]]/route.ts +8 -0
- package/app/[year]/[month]/[day]/[articleSlug]/page.tsx +62 -0
- package/app/[year]/[month]/[day]/edition-route-page.tsx +53 -0
- package/app/[year]/[month]/[day]/page/[pageNumber]/page.tsx +32 -0
- package/app/[year]/[month]/[day]/page.tsx +22 -0
- package/app/[year]/[month]/[day]/section/[sectionKey]/page.tsx +32 -0
- package/app/api/archive/editions/route.ts +18 -0
- package/app/api/media/[...path]/route.ts +35 -0
- package/app/api/revalidate/route.ts +94 -0
- package/app/archive/page.tsx +24 -0
- package/app/articles/[slug]/page.tsx +35 -0
- package/app/globals.css +10345 -0
- package/app/layout.tsx +142 -0
- package/app/newsroom/[section]/[...selection]/page.tsx +50 -0
- package/app/newsroom/[section]/page.tsx +47 -0
- package/app/newsroom/articles/[id]/page.tsx +15 -0
- package/app/newsroom/articles/page.tsx +13 -0
- package/app/newsroom/forum/[threadId]/page.tsx +35 -0
- package/app/newsroom/insights/[threadId]/page.tsx +22 -0
- package/app/newsroom/insights/page.tsx +14 -0
- package/app/newsroom/layout.tsx +12 -0
- package/app/newsroom/page.tsx +31 -0
- package/app/newsroom/sections/[sectionId]/page.tsx +17 -0
- package/app/page.tsx +272 -0
- package/app/papyrus-theme.css +11 -0
- package/app/robots.ts +11 -0
- package/app/settings/page.tsx +14 -0
- package/app/tailwind.css +65 -0
- package/bin/papyrus-app.mjs +20 -0
- package/bin/papyrus-infra.mjs +95 -0
- package/components/ai-elements/conversation.tsx +168 -0
- package/components/ai-elements/message.tsx +360 -0
- package/components/ai-elements/model-selector.tsx +30 -0
- package/components/ai-elements/prompt-input.tsx +1461 -0
- package/components/ai-elements/shimmer.tsx +76 -0
- package/components/ai-elements/suggestion.tsx +54 -0
- package/components/ai-elements/tool.tsx +173 -0
- package/components/amplify-client-provider.tsx +140 -0
- package/components/archive-grid.tsx +89 -0
- package/components/archive-shell.tsx +41 -0
- package/components/article-video.tsx +156 -0
- package/components/blog-page-background.tsx +20 -0
- package/components/dev-sandbox-editor-sign-in.tsx +60 -0
- package/components/markus-ir-preview.tsx +300 -0
- package/components/news-desk-client-provider.tsx +211 -0
- package/components/news-desk-page.tsx +116 -0
- package/components/news-desk-taxonomy-client.ts +3684 -0
- package/components/newspaper.tsx +1929 -0
- 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-assignments-view.tsx +664 -0
- package/components/newsroom-ops-overview.tsx +226 -0
- package/components/newsroom-ops-shell.tsx +321 -0
- package/components/newsroom-references-view.tsx +598 -0
- package/components/newsroom-topics-view.tsx +425 -0
- package/components/papyrus-console-shell.tsx +1912 -0
- package/components/pictogram-figure.tsx +65 -0
- package/components/presentation-footer.tsx +146 -0
- package/components/reader-auth-control.tsx +162 -0
- package/components/reader-auth-state.ts +122 -0
- package/components/reader-settings.ts +332 -0
- package/components/reader-theme-control.tsx +85 -0
- package/components/reference-source-preview.tsx +163 -0
- package/components/settings-page.tsx +239 -0
- package/components/staging-banner.tsx +11 -0
- package/components/topic-steering-workspace.tsx +18005 -0
- package/components/ui/badge.tsx +30 -0
- package/components/ui/button-group.tsx +87 -0
- package/components/ui/button.tsx +58 -0
- package/components/ui/card.tsx +32 -0
- package/components/ui/command.tsx +194 -0
- package/components/ui/dialog.tsx +158 -0
- package/components/ui/dropdown-menu.tsx +266 -0
- package/components/ui/hover-card.tsx +51 -0
- package/components/ui/input-group.tsx +158 -0
- package/components/ui/input.tsx +20 -0
- package/components/ui/scroll-area.tsx +16 -0
- package/components/ui/select.tsx +199 -0
- package/components/ui/separator.tsx +23 -0
- package/components/ui/sheet.tsx +92 -0
- package/components/ui/spinner.tsx +10 -0
- package/components/ui/tabs.tsx +113 -0
- package/components/ui/textarea.tsx +18 -0
- package/components/ui/tooltip.tsx +66 -0
- package/components/use-resolved-papyrus-theme.ts +47 -0
- package/infra/amplify-app-shell.js +234 -0
- package/infra/amplify-app-shell.ts +284 -0
- package/infra/build-specs.js +85 -0
- package/infra/build-specs.ts +131 -0
- package/infra/github-oidc-provider.js +24 -0
- package/infra/github-oidc-provider.ts +30 -0
- package/infra/index.js +15 -0
- package/infra/site-config.js +186 -0
- package/infra/site-config.ts +235 -0
- package/lib/amplify-outputs-guard.ts +24 -0
- package/lib/amplify-outputs-path.ts +16 -0
- package/lib/amplify-server-runtime.ts +63 -0
- package/lib/archive-data.ts +30 -0
- package/lib/archive-types.ts +15 -0
- package/lib/articles.ts +320 -0
- package/lib/cached-content-repository.ts +90 -0
- package/lib/category-dashboard.ts +37 -0
- package/lib/category-repository.ts +1907 -0
- package/lib/console-chat-client.ts +1316 -0
- package/lib/console-web-context.ts +118 -0
- package/lib/content-actions-client.ts +98 -0
- package/lib/content-actions-demo.ts +190 -0
- package/lib/content-load-timing.ts +23 -0
- package/lib/content-repository.ts +43 -0
- package/lib/content-source-context.ts +26 -0
- package/lib/content-types.ts +104 -0
- package/lib/define-site.js +3 -0
- package/lib/define-site.ts +21 -0
- package/lib/demo-amplify-outputs.ts +15 -0
- package/lib/dev-sandbox-editor-auth.ts +18 -0
- package/lib/doctrine.ts +128 -0
- package/lib/edition-routes.ts +155 -0
- package/lib/edition-sections.ts +111 -0
- package/lib/empty-edition-layout-plan.ts +109 -0
- package/lib/empty-theme.css +1 -0
- package/lib/excerpts.ts +8 -0
- package/lib/graphql-content-repository.ts +1140 -0
- package/lib/image-url.ts +10 -0
- package/lib/layout-plan.ts +981 -0
- package/lib/layout-scenarios.ts +564 -0
- package/lib/markus-body.ts +168 -0
- package/lib/markus-ir.ts +235 -0
- package/lib/markus-projection.ts +243 -0
- package/lib/markus-to-article.ts +93 -0
- package/lib/news-desk-session.ts +160 -0
- package/lib/newspaper-layout.ts +3459 -0
- package/lib/newsroom-articles.ts +325 -0
- package/lib/newsroom-assignments.ts +173 -0
- package/lib/newsroom-base-path.ts +48 -0
- package/lib/newsroom-card-layout.ts +102 -0
- package/lib/newsroom-category-drilldown.ts +133 -0
- package/lib/newsroom-demo-articles.ts +90 -0
- package/lib/newsroom-demo-dashboard.ts +267 -0
- package/lib/newsroom-demo-profile.ts +46 -0
- package/lib/newsroom-forum-routes.ts +153 -0
- package/lib/newsroom-index-filters.ts +202 -0
- package/lib/newsroom-knowledge-query-request.ts +51 -0
- package/lib/newsroom-list-selection.ts +42 -0
- package/lib/newsroom-media-backend.ts +142 -0
- package/lib/newsroom-media.ts +182 -0
- package/lib/newsroom-nav.ts +44 -0
- package/lib/newsroom-references.ts +116 -0
- package/lib/newsroom-sections.ts +52 -0
- package/lib/newsroom-topics.ts +118 -0
- package/lib/papyrus-object-kinds.ts +24 -0
- package/lib/papyrus-web-locations.ts +480 -0
- package/lib/pilobol-sample.ts +167 -0
- package/lib/presentation-footer.ts +92 -0
- package/lib/pretext-layout.ts +179 -0
- package/lib/public-placeholder-config.ts +102 -0
- package/lib/publication-items.ts +181 -0
- package/lib/reader-route-config.ts +1 -0
- package/lib/reader-static-params.ts +56 -0
- package/lib/reader-storage-url.ts +63 -0
- package/lib/reference-display.ts +74 -0
- package/lib/reference-policy.ts +97 -0
- package/lib/reference-source-preview.ts +175 -0
- package/lib/renderer-config.ts +50 -0
- package/lib/renderer.ts +87 -0
- package/lib/reporting-story-budget.ts +638 -0
- package/lib/semantic-graph.ts +554 -0
- package/lib/site-brand-tenant.ts +22 -0
- package/lib/site-brand.ts +194 -0
- package/lib/site-env.ts +52 -0
- package/lib/site-renderer.ts +17 -0
- package/lib/site-stack.ts +195 -0
- package/lib/staging-gate.ts +37 -0
- package/lib/staging-preview-object.ts +98 -0
- package/lib/themed-image.ts +30 -0
- package/lib/utils.ts +6 -0
- package/lib/video-mode.ts +42 -0
- package/lib/video-script.ts +63 -0
- package/middleware.ts +103 -0
- package/package.json +318 -1
- package/renderers/markus/index.ts +25 -0
- package/renderers/markus/stubs.tsx +33 -0
- package/renderers/pretext/article-page.tsx +169 -0
- package/renderers/pretext/index.ts +30 -0
- package/renderers/pretext/layouts.ts +16 -0
- package/renderers/pretext/presentation-shell.tsx +470 -0
- package/routes.manifest.json +208 -0
- package/scripts/fixtures/pilobol-sample.ast.json +234 -0
- 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/backend.js +1 -0
- package/src/backend.ts +3 -0
- package/src/sync.mjs +87 -0
- package/src/with-papyrus.d.mts +2 -0
- package/src/with-papyrus.mjs +60 -0
package/lib/doctrine.ts
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
export const DOCTRINE_ITEM_TYPE = "doctrine";
|
|
2
|
+
export const DOCTRINE_ITEM_STATUS = "private";
|
|
3
|
+
export const DOCTRINE_ITEM_TYPE_STATUS = "doctrine#private";
|
|
4
|
+
|
|
5
|
+
export type DoctrineKind = "mission" | "policy";
|
|
6
|
+
export type DoctrineScope = "publication" | "category";
|
|
7
|
+
|
|
8
|
+
export type DoctrineDefinition = {
|
|
9
|
+
kind: DoctrineKind;
|
|
10
|
+
label: string;
|
|
11
|
+
slug: string;
|
|
12
|
+
id: string;
|
|
13
|
+
lineageId: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type DoctrineCategory = {
|
|
17
|
+
categorySetId: string;
|
|
18
|
+
categoryKey: string;
|
|
19
|
+
displayName: string;
|
|
20
|
+
shortTitle?: string | null;
|
|
21
|
+
rank?: number | null;
|
|
22
|
+
depth?: number | null;
|
|
23
|
+
lineageId?: string | null;
|
|
24
|
+
id?: string | null;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const DOCTRINE_DEFINITIONS: DoctrineDefinition[] = [
|
|
28
|
+
{
|
|
29
|
+
kind: "mission",
|
|
30
|
+
label: "Editorial Mission",
|
|
31
|
+
slug: "editorial-doctrine-mission",
|
|
32
|
+
id: "item-editorial-doctrine-mission-v1",
|
|
33
|
+
lineageId: "item-editorial-doctrine-mission",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
kind: "policy",
|
|
37
|
+
label: "Editorial Policy",
|
|
38
|
+
slug: "editorial-doctrine-policy",
|
|
39
|
+
id: "item-editorial-doctrine-policy-v1",
|
|
40
|
+
lineageId: "item-editorial-doctrine-policy",
|
|
41
|
+
},
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
export const DOCTRINE_DEFINITION_BY_KIND = new Map(
|
|
45
|
+
DOCTRINE_DEFINITIONS.map((definition) => [definition.kind, definition]),
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
export function buildCategoryDoctrineDefinition(category: DoctrineCategory, kind: DoctrineKind): DoctrineDefinition {
|
|
49
|
+
const safeCategoryKey = safeDoctrineKey(category.categoryKey);
|
|
50
|
+
const categoryLineageId = category.lineageId ?? category.id ?? category.categoryKey;
|
|
51
|
+
const safeCategoryLineageId = safeDoctrineKey(categoryLineageId);
|
|
52
|
+
const label = kind === "mission" ? "Category Mission" : "Category Policies";
|
|
53
|
+
return {
|
|
54
|
+
kind,
|
|
55
|
+
label,
|
|
56
|
+
slug: `desk-doctrine-${safeCategoryKey}-${kind}`,
|
|
57
|
+
id: `item-desk-doctrine-${safeCategoryLineageId}-${kind}-v1`,
|
|
58
|
+
lineageId: `item-desk-doctrine-${safeCategoryLineageId}-${kind}`,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function getCategoryDoctrineDefinitions(category: DoctrineCategory): DoctrineDefinition[] {
|
|
63
|
+
return [
|
|
64
|
+
buildCategoryDoctrineDefinition(category, "mission"),
|
|
65
|
+
buildCategoryDoctrineDefinition(category, "policy"),
|
|
66
|
+
];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function categoryDoctrineEditorialValue(category: DoctrineCategory, kind: DoctrineKind): string {
|
|
70
|
+
return JSON.stringify({
|
|
71
|
+
scope: "category",
|
|
72
|
+
kind,
|
|
73
|
+
categorySetId: category.categorySetId,
|
|
74
|
+
categoryLineageId: category.lineageId ?? category.id ?? category.categoryKey,
|
|
75
|
+
categoryKey: category.categoryKey,
|
|
76
|
+
categoryDepth: category.depth ?? null,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function buildDeskDoctrineDefinition(category: DoctrineCategory, kind: DoctrineKind): DoctrineDefinition {
|
|
81
|
+
return buildCategoryDoctrineDefinition(category, kind);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function getDeskDoctrineDefinitions(category: DoctrineCategory): DoctrineDefinition[] {
|
|
85
|
+
return getCategoryDoctrineDefinitions(category);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function deskDoctrineEditorialValue(category: DoctrineCategory, kind: DoctrineKind): string {
|
|
89
|
+
return categoryDoctrineEditorialValue(category, kind);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function isDeskDoctrineSlug(slug: string | null | undefined): boolean {
|
|
93
|
+
return typeof slug === "string" && slug.startsWith("desk-doctrine-");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function doctrineBodyToText(body: Array<string | null> | null | undefined): string {
|
|
97
|
+
return compactDoctrineParagraphs(body).join("\n\n");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function doctrineTextToBody(text: string): string[] {
|
|
101
|
+
return normalizeDoctrineText(text)
|
|
102
|
+
.split(/\n\s*\n+/)
|
|
103
|
+
.map((paragraph) => paragraph.trim())
|
|
104
|
+
.filter(Boolean);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function doctrineEditorialValue(kind: DoctrineKind): string {
|
|
108
|
+
return JSON.stringify({ kind });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function safeDoctrineKey(value: string): string {
|
|
112
|
+
return value
|
|
113
|
+
.trim()
|
|
114
|
+
.toLowerCase()
|
|
115
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
116
|
+
.replace(/^-+|-+$/g, "")
|
|
117
|
+
|| "topic";
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function normalizeDoctrineText(text: string): string {
|
|
121
|
+
return text.replace(/\r\n?/g, "\n").trim();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function compactDoctrineParagraphs(body: Array<string | null> | null | undefined): string[] {
|
|
125
|
+
return (body ?? [])
|
|
126
|
+
.map((paragraph) => (typeof paragraph === "string" ? paragraph.trim() : ""))
|
|
127
|
+
.filter(Boolean);
|
|
128
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
const MONTH_NAMES = [
|
|
2
|
+
"january",
|
|
3
|
+
"february",
|
|
4
|
+
"march",
|
|
5
|
+
"april",
|
|
6
|
+
"may",
|
|
7
|
+
"june",
|
|
8
|
+
"july",
|
|
9
|
+
"august",
|
|
10
|
+
"september",
|
|
11
|
+
"october",
|
|
12
|
+
"november",
|
|
13
|
+
"december",
|
|
14
|
+
] as const;
|
|
15
|
+
|
|
16
|
+
type EditionDateRouteInput = {
|
|
17
|
+
year: string;
|
|
18
|
+
month: string;
|
|
19
|
+
day: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
type ParsedEditionDateRoute = {
|
|
23
|
+
editionDate: string;
|
|
24
|
+
canonicalPath: string;
|
|
25
|
+
isCanonical: boolean;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
type ParsedEditionPageRoute = ParsedEditionDateRoute & {
|
|
29
|
+
pageNumber: number;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
type ParsedEditionArticleRoute = ParsedEditionDateRoute & {
|
|
33
|
+
articleSlug: string;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
type ParsedEditionSectionRoute = ParsedEditionDateRoute & {
|
|
37
|
+
sectionKey: string;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const RESERVED_DATE_CHILD_SEGMENTS = new Set(["page", "section"]);
|
|
41
|
+
|
|
42
|
+
export function getEditionDatePath(editionDate: string): string {
|
|
43
|
+
const match = editionDate.match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
|
44
|
+
if (!match) return "/";
|
|
45
|
+
|
|
46
|
+
const monthIndex = Number(match[2]) - 1;
|
|
47
|
+
const monthName = MONTH_NAMES[monthIndex];
|
|
48
|
+
if (!monthName) return "/";
|
|
49
|
+
|
|
50
|
+
return `/${match[1]}/${monthName}/${match[3]}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function getEditionPagePath(editionDate: string, pageNumber: number): string {
|
|
54
|
+
const basePath = getEditionDatePath(editionDate);
|
|
55
|
+
return pageNumber <= 1 ? basePath : `${basePath}/page/${pageNumber}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function getEditionSectionPath(editionDate: string, sectionKey: string): string {
|
|
59
|
+
return `${getEditionDatePath(editionDate)}/section/${encodeURIComponent(sectionKey)}`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function getEditionArticlePath(editionDate: string, articleSlug: string): string {
|
|
63
|
+
return `${getEditionDatePath(editionDate)}/${encodeURIComponent(articleSlug)}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const getEditionItemPath = getEditionArticlePath;
|
|
67
|
+
|
|
68
|
+
export function parseEditionDateRoute(input: EditionDateRouteInput): ParsedEditionDateRoute | null {
|
|
69
|
+
const parsed = parseEditionDateSegments(input);
|
|
70
|
+
if (!parsed) return null;
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
...parsed,
|
|
74
|
+
isCanonical: getCurrentDatePath(input) === parsed.canonicalPath,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function parseEditionPageRoute(input: EditionDateRouteInput & { pageNumber: string }): ParsedEditionPageRoute | null {
|
|
79
|
+
const parsed = parseEditionDateSegments(input);
|
|
80
|
+
if (!parsed) return null;
|
|
81
|
+
if (!/^\d+$/.test(input.pageNumber)) return null;
|
|
82
|
+
|
|
83
|
+
const pageNumber = Number(input.pageNumber);
|
|
84
|
+
if (!Number.isSafeInteger(pageNumber) || pageNumber < 1) return null;
|
|
85
|
+
|
|
86
|
+
const canonicalPath = getEditionPagePath(parsed.editionDate, pageNumber);
|
|
87
|
+
const currentPath = `${getCurrentDatePath(input)}/page/${input.pageNumber}`;
|
|
88
|
+
return {
|
|
89
|
+
editionDate: parsed.editionDate,
|
|
90
|
+
canonicalPath,
|
|
91
|
+
isCanonical: currentPath === canonicalPath,
|
|
92
|
+
pageNumber,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function parseEditionArticleRoute(input: EditionDateRouteInput & { articleSlug: string }): ParsedEditionArticleRoute | null {
|
|
97
|
+
const parsed = parseEditionDateSegments(input);
|
|
98
|
+
if (!parsed) return null;
|
|
99
|
+
if (RESERVED_DATE_CHILD_SEGMENTS.has(input.articleSlug.toLowerCase())) return null;
|
|
100
|
+
|
|
101
|
+
const canonicalPath = getEditionArticlePath(parsed.editionDate, input.articleSlug);
|
|
102
|
+
const currentPath = `${getCurrentDatePath(input)}/${input.articleSlug}`;
|
|
103
|
+
return {
|
|
104
|
+
editionDate: parsed.editionDate,
|
|
105
|
+
canonicalPath,
|
|
106
|
+
isCanonical: currentPath === canonicalPath,
|
|
107
|
+
articleSlug: input.articleSlug,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function parseEditionSectionRoute(input: EditionDateRouteInput & { sectionKey: string }): ParsedEditionSectionRoute | null {
|
|
112
|
+
const parsed = parseEditionDateSegments(input);
|
|
113
|
+
if (!parsed) return null;
|
|
114
|
+
if (!input.sectionKey.trim()) return null;
|
|
115
|
+
|
|
116
|
+
const canonicalPath = getEditionSectionPath(parsed.editionDate, input.sectionKey);
|
|
117
|
+
const currentPath = `${getCurrentDatePath(input)}/section/${input.sectionKey}`;
|
|
118
|
+
return {
|
|
119
|
+
editionDate: parsed.editionDate,
|
|
120
|
+
canonicalPath,
|
|
121
|
+
isCanonical: currentPath === canonicalPath,
|
|
122
|
+
sectionKey: input.sectionKey,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function parseEditionDateSegments(input: EditionDateRouteInput): Omit<ParsedEditionDateRoute, "isCanonical"> | null {
|
|
127
|
+
if (!/^\d{4}$/.test(input.year)) return null;
|
|
128
|
+
|
|
129
|
+
const monthIndex = MONTH_NAMES.indexOf(input.month.toLowerCase() as (typeof MONTH_NAMES)[number]);
|
|
130
|
+
if (monthIndex < 0) return null;
|
|
131
|
+
|
|
132
|
+
if (!/^\d{1,2}$/.test(input.day)) return null;
|
|
133
|
+
const dayNumber = Number(input.day);
|
|
134
|
+
if (dayNumber < 1 || dayNumber > 31) return null;
|
|
135
|
+
|
|
136
|
+
const monthNumber = monthIndex + 1;
|
|
137
|
+
const canonicalDay = String(dayNumber).padStart(2, "0");
|
|
138
|
+
const editionDate = `${input.year}-${String(monthNumber).padStart(2, "0")}-${canonicalDay}`;
|
|
139
|
+
if (!isValidIsoDate(editionDate)) return null;
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
editionDate,
|
|
143
|
+
canonicalPath: getEditionDatePath(editionDate),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function getCurrentDatePath(input: EditionDateRouteInput): string {
|
|
148
|
+
return `/${input.year}/${input.month}/${input.day}`;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function isValidIsoDate(editionDate: string): boolean {
|
|
152
|
+
const [year, month, day] = editionDate.split("-").map(Number);
|
|
153
|
+
const date = new Date(Date.UTC(year, month - 1, day));
|
|
154
|
+
return date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 && date.getUTCDate() === day;
|
|
155
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { EditionSection } from "./content-types";
|
|
2
|
+
import type { PublicationItem } from "./publication-items";
|
|
3
|
+
|
|
4
|
+
export function createEditionSectionPlan(items: PublicationItem[], explicitSections?: unknown): EditionSection[] {
|
|
5
|
+
const explicit = normalizeExplicitSections(explicitSections, items);
|
|
6
|
+
if (explicit.length > 0) return explicit;
|
|
7
|
+
|
|
8
|
+
const sections = new Map<string, EditionSection>();
|
|
9
|
+
for (const item of items) {
|
|
10
|
+
const label = normalizeSectionLabel(item.section);
|
|
11
|
+
const key = createSectionKey(label);
|
|
12
|
+
const existing = sections.get(key);
|
|
13
|
+
if (existing) {
|
|
14
|
+
if (!existing.itemIds.includes(item.slug)) existing.itemIds.push(item.slug);
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
sections.set(key, {
|
|
18
|
+
key,
|
|
19
|
+
label,
|
|
20
|
+
itemIds: [item.slug],
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
return [...sections.values()];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function findEditionSection(sections: EditionSection[], sectionKey: string): EditionSection | undefined {
|
|
27
|
+
const normalized = createSectionKey(sectionKey);
|
|
28
|
+
return sections.find((section) => section.key === normalized);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function getEditionSectionItems(section: EditionSection, items: PublicationItem[]): PublicationItem[] {
|
|
32
|
+
const itemsBySlug = new Map(items.map((item) => [item.slug, item]));
|
|
33
|
+
return section.itemIds.map((itemId) => itemsBySlug.get(itemId)).filter((item): item is PublicationItem => Boolean(item));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function createSectionKey(value: string): string {
|
|
37
|
+
return value
|
|
38
|
+
.trim()
|
|
39
|
+
.toLowerCase()
|
|
40
|
+
.replace(/&/g, " and ")
|
|
41
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
42
|
+
.replace(/^-+|-+$/g, "") || "general";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function normalizeExplicitSections(value: unknown, items: PublicationItem[]): EditionSection[] {
|
|
46
|
+
const parsed = parseMaybeJson(value);
|
|
47
|
+
const nestedPlan = readObjectValue(parsed, "sectionPlan");
|
|
48
|
+
const entries = Array.isArray(parsed)
|
|
49
|
+
? parsed
|
|
50
|
+
: readObjectArray(parsed, "sections")
|
|
51
|
+
?? readObjectArray(parsed, "sectionPlan")
|
|
52
|
+
?? readObjectArray(nestedPlan, "sections");
|
|
53
|
+
if (!entries) return [];
|
|
54
|
+
|
|
55
|
+
const itemSlugs = new Set(items.map((item) => item.slug));
|
|
56
|
+
return entries
|
|
57
|
+
.map((entry) => normalizeSectionEntry(entry, itemSlugs))
|
|
58
|
+
.filter((section): section is EditionSection => Boolean(section));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function normalizeSectionEntry(entry: unknown, itemSlugs: Set<string>): EditionSection | null {
|
|
62
|
+
if (!entry || typeof entry !== "object") return null;
|
|
63
|
+
const record = entry as Record<string, unknown>;
|
|
64
|
+
const label = normalizeSectionLabel(readString(record.label) ?? readString(record.title) ?? readString(record.name) ?? readString(record.key));
|
|
65
|
+
const key = createSectionKey(readString(record.key) ?? label);
|
|
66
|
+
const itemIds = readStringArray(record.itemIds ?? record.items)
|
|
67
|
+
.map((itemId) => itemId.trim())
|
|
68
|
+
.filter((itemId, index, all) => itemSlugs.has(itemId) && all.indexOf(itemId) === index);
|
|
69
|
+
|
|
70
|
+
if (!itemIds.length) return null;
|
|
71
|
+
return {
|
|
72
|
+
key,
|
|
73
|
+
label,
|
|
74
|
+
description: readString(record.description),
|
|
75
|
+
itemIds,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function normalizeSectionLabel(value: string | null | undefined): string {
|
|
80
|
+
const label = value?.trim();
|
|
81
|
+
return label || "General";
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function readObjectArray(value: unknown, key: string): unknown[] | null {
|
|
85
|
+
if (!value || typeof value !== "object") return null;
|
|
86
|
+
const candidate = (value as Record<string, unknown>)[key];
|
|
87
|
+
return Array.isArray(candidate) ? candidate : null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function readObjectValue(value: unknown, key: string): unknown {
|
|
91
|
+
if (!value || typeof value !== "object") return null;
|
|
92
|
+
return (value as Record<string, unknown>)[key];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function parseMaybeJson(value: unknown): unknown {
|
|
96
|
+
if (typeof value !== "string") return value;
|
|
97
|
+
try {
|
|
98
|
+
return JSON.parse(value);
|
|
99
|
+
} catch {
|
|
100
|
+
return value;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function readString(value: unknown): string | null {
|
|
105
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function readStringArray(value: unknown): string[] {
|
|
109
|
+
if (!Array.isArray(value)) return [];
|
|
110
|
+
return value.filter((entry): entry is string => typeof entry === "string" && entry.trim().length > 0);
|
|
111
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type { EditionLayoutPlan } from "./layout-plan";
|
|
2
|
+
import { normalizeEditionLayoutPlan } from "./layout-plan";
|
|
3
|
+
|
|
4
|
+
export const EMPTY_EDITION_PLACEHOLDER_SLUG = "empty-edition-placeholder";
|
|
5
|
+
|
|
6
|
+
export type EmptyEditionLayoutPlanInput = {
|
|
7
|
+
placeholderSlug?: string;
|
|
8
|
+
sectionItemSlugs: string[];
|
|
9
|
+
sectionsCtaSlug: string;
|
|
10
|
+
topicItemSlugs: string[];
|
|
11
|
+
topicsCtaSlug: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function buildEmptyEditionLayoutPlan({
|
|
15
|
+
placeholderSlug = EMPTY_EDITION_PLACEHOLDER_SLUG,
|
|
16
|
+
sectionItemSlugs,
|
|
17
|
+
sectionsCtaSlug,
|
|
18
|
+
topicItemSlugs,
|
|
19
|
+
topicsCtaSlug,
|
|
20
|
+
}: EmptyEditionLayoutPlanInput): EditionLayoutPlan {
|
|
21
|
+
return normalizeEditionLayoutPlan({
|
|
22
|
+
pages: [
|
|
23
|
+
{
|
|
24
|
+
id: "page-1",
|
|
25
|
+
pageNumber: 1,
|
|
26
|
+
presetId: "front.mosaic",
|
|
27
|
+
grid: { columns: { min: 1, preferred: 6, max: 6 } },
|
|
28
|
+
regions: [
|
|
29
|
+
{
|
|
30
|
+
id: "empty-front-page",
|
|
31
|
+
type: "fullPage",
|
|
32
|
+
localGrid: { columns: { min: 1, preferred: 6, max: 6 } },
|
|
33
|
+
blocks: [
|
|
34
|
+
{
|
|
35
|
+
id: "empty-edition-placeholder-front",
|
|
36
|
+
type: "articleFrame",
|
|
37
|
+
presetId: "front.teaser",
|
|
38
|
+
itemId: placeholderSlug,
|
|
39
|
+
flowKey: placeholderSlug,
|
|
40
|
+
startCursor: "beginning",
|
|
41
|
+
role: "primary",
|
|
42
|
+
editorialPriority: "primary",
|
|
43
|
+
size: { shrinkToContent: true },
|
|
44
|
+
typography: { headlineScale: "feature" },
|
|
45
|
+
span: { min: 1, preferred: 6, max: 6 },
|
|
46
|
+
localGrid: { columns: { min: 1, preferred: 6, max: 6 } },
|
|
47
|
+
media: [],
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: "page-2",
|
|
55
|
+
pageNumber: 2,
|
|
56
|
+
presetId: "page.full",
|
|
57
|
+
grid: { columns: { min: 1, preferred: 6, max: 6 } },
|
|
58
|
+
regions: [
|
|
59
|
+
{
|
|
60
|
+
id: "empty-sections-page",
|
|
61
|
+
type: "fullPage",
|
|
62
|
+
size: { shrinkToContent: true },
|
|
63
|
+
localGrid: { columns: { min: 1, preferred: 6, max: 6 } },
|
|
64
|
+
blocks: [
|
|
65
|
+
{
|
|
66
|
+
id: "empty-edition-sections-stack",
|
|
67
|
+
type: "itemStack",
|
|
68
|
+
title: "Sections",
|
|
69
|
+
itemIds: [...sectionItemSlugs, sectionsCtaSlug],
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
id: "page-3",
|
|
77
|
+
pageNumber: 3,
|
|
78
|
+
presetId: "page.full",
|
|
79
|
+
grid: { columns: { min: 1, preferred: 6, max: 6 } },
|
|
80
|
+
regions: [
|
|
81
|
+
{
|
|
82
|
+
id: "empty-topics-page",
|
|
83
|
+
type: "fullPage",
|
|
84
|
+
size: { shrinkToContent: true },
|
|
85
|
+
localGrid: { columns: { min: 1, preferred: 6, max: 6 } },
|
|
86
|
+
blocks: [
|
|
87
|
+
{
|
|
88
|
+
id: "empty-edition-topics-stack",
|
|
89
|
+
type: "itemStack",
|
|
90
|
+
title: "Topics",
|
|
91
|
+
itemIds: [...topicItemSlugs, topicsCtaSlug],
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
}, "EmptyGraphQLEdition.layoutPlan");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Page-1-only placeholder used on SiteBrand.rendererConfig.layoutPlan. */
|
|
102
|
+
export function buildDefaultEmptyEditionLayoutPlan(): EditionLayoutPlan {
|
|
103
|
+
return buildEmptyEditionLayoutPlan({
|
|
104
|
+
sectionItemSlugs: [],
|
|
105
|
+
sectionsCtaSlug: "empty-edition-sections-newsroom",
|
|
106
|
+
topicItemSlugs: [],
|
|
107
|
+
topicsCtaSlug: "empty-edition-topics-newsroom",
|
|
108
|
+
});
|
|
109
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/* Default for `papyrus-site-theme` when a publication ships no publication/theme.css. */
|
package/lib/excerpts.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export function truncateWords(value: string | null | undefined, maxWords: number): string {
|
|
2
|
+
const normalized = String(value ?? "").replace(/\s+/g, " ").trim();
|
|
3
|
+
if (!normalized) return "";
|
|
4
|
+
const words = normalized.split(" ");
|
|
5
|
+
if (words.length <= maxWords) return normalized;
|
|
6
|
+
return `${words.slice(0, maxWords).join(" ")}...`;
|
|
7
|
+
}
|
|
8
|
+
|