@anthusai/papyrus 1.0.0-next.9 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/amplify/auth/redirect-urls.js +54 -0
- package/amplify/auth/redirect-urls.ts +66 -0
- package/amplify/auth/resource.js +20 -5
- package/amplify/auth/resource.ts +27 -5
- package/amplify/data/resource.js +42 -80
- package/amplify/data/resource.ts +42 -80
- package/amplify/data/schema.js +45 -60
- package/amplify/data/schema.ts +47 -60
- package/amplify/functions/category-action/handler.ts +5 -3
- package/amplify/functions/console-chat-responder/py/execute_tactus_runner.py +0 -10
- package/amplify/functions/console-chat-responder/resource.js +0 -7
- package/amplify/functions/console-chat-responder/resource.ts +0 -8
- package/amplify/functions/console-chat-responder/src/main.rs +0 -9
- package/amplify/functions/content-actions/handler.py +143 -1
- package/amplify/functions/content-actions/requirements.txt +2 -1
- package/amplify/functions/content-actions/resource.js +6 -1
- package/amplify/functions/content-actions/resource.ts +9 -2
- package/amplify/functions/renditions/handler.py +120 -0
- package/amplify/functions/renditions/requirements.txt +4 -0
- package/amplify/functions/shared/python-bundle.js +42 -2
- package/amplify/functions/shared/python-bundle.ts +47 -2
- package/amplify/functions/shared/reference-curation-metadata.ts +29 -0
- package/amplify/inbound-email/ses-intake.js +86 -0
- package/amplify/inbound-email/ses-intake.ts +110 -0
- package/amplify/plugins/registry.js +19 -0
- package/amplify/plugins/registry.ts +23 -0
- package/amplify/plugins/signup/function/handler.py +200 -0
- package/amplify/plugins/signup/function/resource.js +27 -0
- package/amplify/plugins/signup/function/resource.ts +35 -0
- package/amplify/plugins/signup/papyrus.plugin.js +59 -0
- package/amplify/plugins/signup/papyrus.plugin.ts +74 -0
- package/amplify/plugins/signup/schema.js +40 -0
- package/amplify/plugins/signup/schema.ts +41 -0
- package/amplify/plugins/signup/waf.js +48 -0
- package/amplify/plugins/signup/waf.ts +51 -0
- package/amplify/plugins/types.js +1 -0
- package/amplify/plugins/types.ts +39 -0
- package/amplify/plugins/wire.js +24 -0
- package/amplify/plugins/wire.ts +26 -0
- package/amplify/publication/default-publication.js +18 -0
- package/amplify/publication/default-publication.ts +20 -0
- package/amplify/publication/invalidation-alarm.js +26 -0
- package/amplify/publication/invalidation-alarm.ts +30 -0
- package/amplify/publication/redirect-and-index.js +37 -0
- package/amplify/publication/renditions-function.js +74 -0
- package/amplify/publication/renditions-function.ts +112 -0
- package/amplify/publication/renditions-settings.js +51 -0
- package/amplify/publication/renditions-settings.ts +58 -0
- package/amplify/publication/reproject-on-deploy.js +73 -0
- package/amplify/publication/reproject-on-deploy.ts +78 -0
- package/amplify/publication/s3-projection-settings.js +67 -0
- package/amplify/publication/s3-projection-settings.ts +101 -0
- package/amplify/publication/s3-projection.js +151 -0
- package/amplify/publication/s3-projection.ts +201 -0
- package/amplify/resolver-waves.js +26 -0
- package/amplify/resolver-waves.ts +34 -0
- package/amplify/revalidate-invalidation-settings.js +30 -0
- package/amplify/revalidate-invalidation-settings.ts +35 -0
- package/amplify/revalidate-secret-policy.js +13 -0
- package/amplify/revalidate-secret-policy.ts +19 -0
- package/amplify/site-backend-config.js +65 -1
- package/amplify/site-backend-config.ts +153 -2
- package/amplify/site-backend-names.js +103 -0
- package/amplify/site-backend-names.ts +166 -0
- package/amplify/site-backend.js +143 -176
- package/amplify/site-backend.ts +181 -201
- package/amplify/storage-backups/construct.js +31 -0
- package/amplify/storage-backups/construct.ts +47 -0
- package/app/[year]/[month]/[day]/[articleSlug]/page.tsx +8 -1
- package/app/[year]/[month]/[day]/edition-route-page.tsx +2 -0
- package/app/[year]/[month]/[day]/page/[pageNumber]/page.tsx +6 -1
- package/app/[year]/[month]/[day]/page.tsx +1 -1
- package/app/[year]/[month]/[day]/section/[sectionKey]/page.tsx +6 -1
- package/app/api/archive/editions/route.ts +1 -1
- package/app/api/cyclotron/priming-proof/route.ts +11 -0
- package/app/api/media/[...path]/route.ts +5 -16
- package/app/api/revalidate/route.ts +15 -7
- package/app/archive/page.tsx +1 -1
- package/app/articles/[slug]/page.tsx +7 -5
- package/app/blog-rhythm.css +278 -0
- package/app/cyclotron/page.tsx +13 -0
- package/app/globals.css +2 -0
- package/app/layout.tsx +16 -0
- package/app/newsroom/[section]/[...selection]/page.tsx +9 -6
- package/app/newsroom/[section]/page.tsx +6 -3
- package/app/newsroom/layout.tsx +4 -9
- package/app/newsroom/settings/page.tsx +6 -0
- package/app/newsroom/work/page.tsx +10 -0
- package/app/page.tsx +22 -8
- package/app/robots.ts +2 -5
- package/app/tailwind.css +1 -0
- package/components/amplify-client-provider.tsx +5 -22
- package/components/archive-shell.tsx +2 -22
- package/components/article-video.tsx +152 -5
- package/components/blog-page-background.tsx +23 -6
- package/components/news-desk-page.tsx +4 -15
- package/components/news-desk-taxonomy-client.ts +90 -3
- package/components/newspaper.tsx +6 -2
- package/components/newsroom-access-gate.tsx +2 -1
- package/components/newsroom-board-view.tsx +99 -0
- package/components/newsroom-client-shell.tsx +12 -0
- package/components/newsroom-curation-provider.tsx +17 -0
- package/components/newsroom-fonts/playfair-display-700.ttf +0 -0
- package/components/newsroom-fonts/playfair-display-900.ttf +0 -0
- package/components/newsroom-fonts/playfair-display-OFL.txt +93 -0
- package/components/newsroom-fonts/plus-jakarta-sans-400.ttf +0 -0
- package/components/newsroom-fonts/plus-jakarta-sans-600.ttf +0 -0
- package/components/newsroom-fonts/plus-jakarta-sans-OFL.txt +93 -0
- package/components/newsroom-newsprint-board.css +48 -0
- package/components/newsroom-ops-overview.tsx +28 -11
- package/components/newsroom-ops-shell.tsx +7 -1
- package/components/newsroom-organizer-shell.tsx +130 -0
- package/components/newsroom-organizer.css +63 -0
- package/components/newsroom-references-view.tsx +92 -8
- package/components/papyrus-console-shell.tsx +4 -2
- package/components/pictogram-figure.tsx +9 -2
- package/components/presentation-header.tsx +135 -0
- package/components/presentation-rhythm-hrule.tsx +17 -0
- package/components/priming-proof-page.tsx +92 -0
- package/components/publication-cyclotron-panel.tsx +57 -0
- package/components/reader-auth-control.tsx +8 -5
- package/components/reference-curation-controls.tsx +77 -0
- package/components/reference-cyclotron-status.tsx +109 -0
- package/components/reference-priming.tsx +18 -0
- package/components/reference-relevance-review.tsx +75 -0
- package/components/settings-page.tsx +2 -1
- package/components/signup-entries-panel.tsx +94 -0
- package/components/topic-steering-workspace.tsx +78 -6
- package/components/use-blog-header-obstacles.ts +81 -0
- package/components/use-brand-fonts-ready.ts +28 -0
- package/components/use-near-viewport.ts +30 -0
- package/components/use-rhythm-overlay.ts +29 -0
- package/infra/amplify-app-shell.js +148 -27
- package/infra/amplify-app-shell.ts +160 -31
- package/infra/build-specs.js +23 -7
- package/infra/build-specs.ts +27 -10
- package/infra/site-config.js +248 -36
- package/infra/site-config.ts +313 -51
- package/lib/amplify-outputs-path.ts +5 -1
- package/lib/amplify-server-runtime.ts +36 -1
- package/lib/approved-priming-proof-reports.ts +6 -0
- package/lib/blog-feature-solver.ts +459 -0
- package/lib/blog-rhythm.ts +134 -0
- package/lib/brand-fonts-watch.ts +43 -0
- package/lib/brand-fonts.ts +112 -0
- package/lib/cached-content-repository.ts +12 -0
- package/lib/category-repository.ts +43 -93
- package/lib/content-item-lifecycle.ts +55 -0
- package/lib/content-repository.ts +3 -0
- package/lib/content-types.ts +4 -0
- package/lib/curation-client.ts +61 -0
- package/lib/edition-routes.ts +31 -29
- package/lib/graphql-content-repository.ts +56 -22
- package/lib/media-route-response.ts +45 -0
- package/lib/newspaper-layout.ts +3 -2
- package/lib/newsroom-base-path.ts +12 -3
- package/lib/newsroom-board.ts +99 -0
- package/lib/newsroom-category-drilldown.ts +3 -2
- package/lib/newsroom-forum-routes.ts +6 -4
- package/lib/newsroom-index-filters.ts +14 -10
- package/lib/newsroom-nav.ts +3 -1
- package/lib/newsroom-organizer-nav.ts +37 -0
- package/lib/newsroom-section-routing.ts +20 -0
- package/lib/oauth-redirect-priority.ts +40 -0
- package/lib/papyrus-web-locations.ts +7 -6
- package/lib/plugin-settings.ts +11 -0
- package/lib/presentation-footer.ts +3 -2
- package/lib/pretext-layout.ts +12 -3
- package/lib/priming-proof-report.ts +89 -0
- package/lib/prospectus-site-identity.ts +303 -0
- package/lib/publication-cyclotron.ts +74 -0
- package/lib/rate-limit-retry.ts +55 -0
- package/lib/reader-base-path.ts +24 -0
- package/lib/reader-cache-lifetime.ts +5 -0
- package/lib/reader-static-params.ts +85 -4
- package/lib/reader-storage-url.ts +9 -12
- package/lib/reference-policy.ts +5 -0
- package/lib/relevance-decisions.ts +103 -0
- package/lib/relevance-status.ts +63 -0
- package/lib/renderer.ts +7 -0
- package/lib/revalidate-prewarm.ts +53 -0
- package/lib/revalidate-secret.ts +59 -0
- package/lib/robots-policy.ts +14 -0
- package/lib/root-route-redirect.ts +16 -0
- package/lib/semantic-graph.ts +7 -0
- package/lib/shared-credentials-provider.ts +61 -0
- package/lib/signup-csv.ts +24 -0
- package/lib/site-brand.ts +59 -0
- package/lib/staging-gated-path.ts +23 -0
- package/lib/staging-preview-object.ts +21 -5
- package/lib/test-editor-override.ts +4 -0
- package/lib/video-loading.ts +22 -0
- package/lib/video-script.ts +14 -0
- package/middleware.ts +11 -19
- package/package.json +64 -1
- package/renderers/pretext/article-page.tsx +39 -3
- package/renderers/pretext/presentation-shared.tsx +113 -0
- package/renderers/pretext/presentation-shell.tsx +15 -102
- package/renderers/pretext/rhythm-article-page.tsx +165 -0
- package/renderers/pretext/rhythm-blog-presentation.tsx +279 -0
- package/renderers/pretext/rhythm-shell-style.ts +35 -0
- package/routes.manifest.json +68 -12
- package/src/sync.mjs +30 -6
- package/src/with-papyrus.d.mts +9 -0
- package/src/with-papyrus.mjs +48 -3
- package/amplify/functions/graphql-jwt-authorizer/handler.ts +0 -193
- package/amplify/functions/graphql-jwt-authorizer/resource.js +0 -14
- package/amplify/functions/graphql-jwt-authorizer/resource.ts +0 -15
|
@@ -10,6 +10,9 @@ import Link from "next/link";
|
|
|
10
10
|
import { usePathname, useRouter } from "next/navigation";
|
|
11
11
|
import type { ReactNode, RefObject } from "react";
|
|
12
12
|
import { useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, useTransition } from "react";
|
|
13
|
+
import amplifyOutputs from "papyrus-amplify-outputs";
|
|
14
|
+
import { SignupEntriesPanel } from "./signup-entries-panel";
|
|
15
|
+
import { pluginSettings, type PluginOutputs } from "../lib/plugin-settings";
|
|
13
16
|
import { generateClient } from "aws-amplify/data";
|
|
14
17
|
import type { Schema } from "../amplify/data/resource";
|
|
15
18
|
import {
|
|
@@ -53,6 +56,8 @@ import {
|
|
|
53
56
|
type ForumThreadWithMessages,
|
|
54
57
|
type KnowledgeQueryResponse,
|
|
55
58
|
type NewsroomRecordPage,
|
|
59
|
+
loadCyclotronStatus,
|
|
60
|
+
requestCyclotronReviewRate,
|
|
56
61
|
} from "./news-desk-taxonomy-client";
|
|
57
62
|
import { listConsoleThreads } from "../lib/console-chat-client";
|
|
58
63
|
import {
|
|
@@ -76,6 +81,7 @@ import { buildNewsroomKnowledgeQueryInput, type NewsroomKnowledgeQueryAnchor as
|
|
|
76
81
|
import { NewsroomConsoleProgressToggle, PapyrusConsoleChatIcon, usePapyrusConsole } from "./papyrus-console-shell";
|
|
77
82
|
import { useResolvedPapyrusTheme } from "./use-resolved-papyrus-theme";
|
|
78
83
|
import { SITE_BRAND } from "../lib/site-brand";
|
|
84
|
+
import { browserPathToInternalNewsroomPath, toPublicNewsroomPath } from "../lib/newsroom-base-path";
|
|
79
85
|
import { getNewsroomNavHref } from "../lib/newsroom-nav";
|
|
80
86
|
import { cn } from "../lib/utils";
|
|
81
87
|
import { useOptionalNewsDeskClient } from "./news-desk-client-provider";
|
|
@@ -150,9 +156,13 @@ import {
|
|
|
150
156
|
type SemanticObjectSummary,
|
|
151
157
|
} from "../lib/semantic-graph";
|
|
152
158
|
import {
|
|
159
|
+
normalizeReferenceRejectionReasonCode,
|
|
153
160
|
REFERENCE_REJECTION_REASON_CODES,
|
|
154
161
|
referenceCurationStatusForAction,
|
|
155
162
|
} from "../lib/reference-policy";
|
|
163
|
+
import { currentRelevanceDecisions } from "../lib/relevance-decisions";
|
|
164
|
+
import { buildReviewRateRequest, DEMO_CYCLOTRON_STATUS } from "../lib/relevance-status";
|
|
165
|
+
import type { CyclotronStatus } from "cyclotron/cyclotron-status";
|
|
156
166
|
import {
|
|
157
167
|
referenceDisplaySummary,
|
|
158
168
|
referenceMetadataField,
|
|
@@ -228,7 +238,7 @@ type AssignmentAction = "claim" | "release" | "complete" | "cancel" | "reopen" |
|
|
|
228
238
|
type ReportingPacketReviewDecision = "select" | "merge" | "brief" | "hold" | "kill";
|
|
229
239
|
type AssignmentDeskViewMode = "queue" | "budget";
|
|
230
240
|
type UserRoleAction = "grant" | "revoke";
|
|
231
|
-
type AdministrationPanel = "users" | "policies" | "sections" | "procedures";
|
|
241
|
+
type AdministrationPanel = "users" | "policies" | "sections" | "procedures" | "signups";
|
|
232
242
|
export type NewsDeskTab = "overview" | "desks" | "topics" | "concepts" | "references" | "insights" | "messages" | "assignments" | "administration" | "search";
|
|
233
243
|
type LexicalRuleScope = "publication" | "corpus" | "classifier" | "category";
|
|
234
244
|
type AnalysisReindexMode = "online-update" | "classifier-retrain" | "scoped-topic-rebuild" | "entity-graph-rebuild" | "generated-analysis-rebuild";
|
|
@@ -775,6 +785,24 @@ function NewsDeskDashboard({
|
|
|
775
785
|
const [referenceAttachments, setReferenceAttachments] = useState(dashboard.referenceAttachments);
|
|
776
786
|
const [messages, setMessages] = useState(dashboard.messages);
|
|
777
787
|
const [semanticRelations, setSemanticRelations] = useState(dashboard.semanticRelations);
|
|
788
|
+
const relevanceDecisions = useMemo(() => currentRelevanceDecisions(semanticRelations), [semanticRelations]);
|
|
789
|
+
const relevanceCyclotronId = useMemo(
|
|
790
|
+
() => [...relevanceDecisions.values()].find((decision) => decision.cyclotronId)?.cyclotronId ?? null,
|
|
791
|
+
[relevanceDecisions],
|
|
792
|
+
);
|
|
793
|
+
const [cyclotronStatus, setCyclotronStatus] = useState<CyclotronStatus | null>(null);
|
|
794
|
+
useEffect(() => {
|
|
795
|
+
if (!relevanceCyclotronId) return;
|
|
796
|
+
if (dashboard.isDemo) {
|
|
797
|
+
setCyclotronStatus(DEMO_CYCLOTRON_STATUS);
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
let cancelled = false;
|
|
801
|
+
loadCyclotronStatus(relevanceCyclotronId)
|
|
802
|
+
.then((status) => { if (!cancelled) setCyclotronStatus(status); })
|
|
803
|
+
.catch(() => { if (!cancelled) setCyclotronStatus(null); });
|
|
804
|
+
return () => { cancelled = true; };
|
|
805
|
+
}, [dashboard.isDemo, relevanceCyclotronId]);
|
|
778
806
|
const [semanticNodes, setSemanticNodes] = useState(dashboard.semanticNodes);
|
|
779
807
|
const [assignments, setAssignments] = useState(dashboard.assignments);
|
|
780
808
|
const [assignmentEvents, setAssignmentEvents] = useState(dashboard.assignmentEvents);
|
|
@@ -1506,7 +1534,13 @@ function NewsDeskDashboard({
|
|
|
1506
1534
|
});
|
|
1507
1535
|
}
|
|
1508
1536
|
|
|
1509
|
-
function runReferenceCurationAction(
|
|
1537
|
+
function runReferenceCurationAction(
|
|
1538
|
+
reference: ReferenceRecord,
|
|
1539
|
+
action: ReferenceCurationAction,
|
|
1540
|
+
note?: string,
|
|
1541
|
+
reasonCode?: ReferenceRejectionReasonCode | null,
|
|
1542
|
+
relevance?: { decisionRelationId: string; shareable: boolean },
|
|
1543
|
+
) {
|
|
1510
1544
|
const nextStatus = referenceCurationStatusForAction(action);
|
|
1511
1545
|
setActionState({ id: reference.id, message: `${action} pending`, tone: "pending" });
|
|
1512
1546
|
if (dashboard.isDemo) {
|
|
@@ -1534,6 +1568,7 @@ function NewsDeskDashboard({
|
|
|
1534
1568
|
action,
|
|
1535
1569
|
curationStatus: nextStatus,
|
|
1536
1570
|
reasonCode: action === "reject" ? reasonCode ?? null : null,
|
|
1571
|
+
...(relevance ? { decisionRelationId: relevance.decisionRelationId, shareable: relevance.shareable } : {}),
|
|
1537
1572
|
},
|
|
1538
1573
|
}, ...current]);
|
|
1539
1574
|
setSemanticRelations((current) => [{
|
|
@@ -1574,6 +1609,8 @@ function NewsDeskDashboard({
|
|
|
1574
1609
|
actorLabel: authState.label,
|
|
1575
1610
|
note: note?.trim() || undefined,
|
|
1576
1611
|
reasonCode: action === "reject" ? reasonCode ?? undefined : undefined,
|
|
1612
|
+
decisionRelationId: relevance?.decisionRelationId,
|
|
1613
|
+
shareable: relevance ? relevance.shareable : undefined,
|
|
1577
1614
|
},
|
|
1578
1615
|
{ authMode: USER_POOL_AUTH_MODE },
|
|
1579
1616
|
);
|
|
@@ -3152,6 +3189,20 @@ function NewsDeskDashboard({
|
|
|
3152
3189
|
demo={Boolean(dashboard.isDemo)}
|
|
3153
3190
|
disabled={controlsDisabled}
|
|
3154
3191
|
initialReferenceLineageId={initialSelection.reference ?? pathnameReferenceLineageId}
|
|
3192
|
+
cyclotronStatus={cyclotronStatus}
|
|
3193
|
+
decisions={relevanceDecisions}
|
|
3194
|
+
onRequestReviewRate={relevanceCyclotronId ? async (choice) => {
|
|
3195
|
+
const request = buildReviewRateRequest(choice, authState.label);
|
|
3196
|
+
if (dashboard.isDemo) return;
|
|
3197
|
+
await requestCyclotronReviewRate(relevanceCyclotronId, request);
|
|
3198
|
+
} : undefined}
|
|
3199
|
+
onRelevanceReview={(reference, curation) => runReferenceCurationAction(
|
|
3200
|
+
reference,
|
|
3201
|
+
curation.action,
|
|
3202
|
+
curation.note ?? undefined,
|
|
3203
|
+
normalizeReferenceRejectionReasonCode(curation.reasonCode),
|
|
3204
|
+
{ decisionRelationId: curation.decisionRelationId, shareable: curation.shareable },
|
|
3205
|
+
)}
|
|
3155
3206
|
onReview={(reference, action) => runReferenceCurationAction(reference, action)}
|
|
3156
3207
|
referenceAttachments={referenceAttachments}
|
|
3157
3208
|
references={references}
|
|
@@ -4404,8 +4455,20 @@ function AdministrationDeskView({
|
|
|
4404
4455
|
<strong>Procedures</strong>
|
|
4405
4456
|
<span>Procedure registry and versioned Tactus code</span>
|
|
4406
4457
|
</Link>
|
|
4458
|
+
{signupPluginEnabled() ? (
|
|
4459
|
+
<Link
|
|
4460
|
+
href={administrationPanelHref("signups", isDemo)}
|
|
4461
|
+
data-news-desk-admin-nav="signups"
|
|
4462
|
+
data-active={administrationPanel === "signups" || undefined}
|
|
4463
|
+
onClick={() => onPanelChange("signups")}
|
|
4464
|
+
>
|
|
4465
|
+
<strong>Signups</strong>
|
|
4466
|
+
<span>Addresses from the site's signup forms</span>
|
|
4467
|
+
</Link>
|
|
4468
|
+
) : null}
|
|
4407
4469
|
</nav>
|
|
4408
4470
|
<div className="news-desk-settings-panel">
|
|
4471
|
+
{administrationPanel === "signups" ? <SignupEntriesPanel /> : null}
|
|
4409
4472
|
{administrationPanel === "users" ? (
|
|
4410
4473
|
<AdministrationUsersPanel
|
|
4411
4474
|
canManageUsers={canManageUsers}
|
|
@@ -13598,8 +13661,13 @@ function semanticVersionKey(kind: string, id: string): string {
|
|
|
13598
13661
|
return `${kind}#${id}`;
|
|
13599
13662
|
}
|
|
13600
13663
|
|
|
13664
|
+
function signupPluginEnabled(): boolean {
|
|
13665
|
+
return pluginSettings(amplifyOutputs as PluginOutputs, "signup") !== null;
|
|
13666
|
+
}
|
|
13667
|
+
|
|
13601
13668
|
function normalizeAdministrationPanel(value: string | null | undefined): AdministrationPanel {
|
|
13602
13669
|
if (value === "policies" || value === "sections" || value === "procedures") return value;
|
|
13670
|
+
if (value === "signups" && signupPluginEnabled()) return value;
|
|
13603
13671
|
return "users";
|
|
13604
13672
|
}
|
|
13605
13673
|
|
|
@@ -14974,6 +15042,9 @@ function buildUiCopywritingAssignment({ actorLabel, assignment, decision, messag
|
|
|
14974
15042
|
message: MessageRecord;
|
|
14975
15043
|
now: string;
|
|
14976
15044
|
}): AssignmentRecord {
|
|
15045
|
+
if (!assignment.itemLineageId) {
|
|
15046
|
+
throw new Error(`Assignment ${assignment.id} has no content item lineage; copywriting must continue the same content item.`);
|
|
15047
|
+
}
|
|
14977
15048
|
const type = decision === "brief" ? "brief" : "article";
|
|
14978
15049
|
const assignmentTypeKey = type === "brief" ? "copywriting.brief-draft" : "copywriting.article-draft";
|
|
14979
15050
|
const section = assignment.sectionKey ?? assignment.sectionId ?? "unsectioned";
|
|
@@ -15026,6 +15097,7 @@ function buildUiCopywritingAssignment({ actorLabel, assignment, decision, messag
|
|
|
15026
15097
|
createdAt: now,
|
|
15027
15098
|
updatedAt: now,
|
|
15028
15099
|
newsroomFeedKey: "assignment#open",
|
|
15100
|
+
itemLineageId: assignment.itemLineageId,
|
|
15029
15101
|
metadata,
|
|
15030
15102
|
};
|
|
15031
15103
|
}
|
|
@@ -15187,14 +15259,14 @@ function normalizeKnowledgeQueryAnchorKind(value: string | null | undefined): Kn
|
|
|
15187
15259
|
|
|
15188
15260
|
function normalizeNewsroomFromHref(value: string | null | undefined): string | null {
|
|
15189
15261
|
if (!value) return null;
|
|
15190
|
-
const trimmed = value.trim();
|
|
15262
|
+
const trimmed = browserPathToInternalNewsroomPath(value.trim());
|
|
15191
15263
|
if (!trimmed.startsWith("/newsroom")) return null;
|
|
15192
|
-
return trimmed;
|
|
15264
|
+
return toPublicNewsroomPath(trimmed);
|
|
15193
15265
|
}
|
|
15194
15266
|
|
|
15195
15267
|
function currentNewsroomOriginHref(fallback: string | null): string | null {
|
|
15196
15268
|
if (typeof window === "undefined") return fallback;
|
|
15197
|
-
const current = `${window.location.pathname}${window.location.search}
|
|
15269
|
+
const current = browserPathToInternalNewsroomPath(`${window.location.pathname}${window.location.search}`);
|
|
15198
15270
|
return current.startsWith("/newsroom/search") ? fallback : normalizeNewsroomFromHref(current);
|
|
15199
15271
|
}
|
|
15200
15272
|
|
|
@@ -15228,7 +15300,7 @@ function buildNewsroomSearchHref(request: NewsroomSearchRequest, demo?: boolean)
|
|
|
15228
15300
|
if (request.from) params.set("from", request.from);
|
|
15229
15301
|
if (demo) params.set("demo", "1");
|
|
15230
15302
|
const queryString = params.toString();
|
|
15231
|
-
return queryString ? `/newsroom/search?${queryString}` : "/newsroom/search";
|
|
15303
|
+
return toPublicNewsroomPath(queryString ? `/newsroom/search?${queryString}` : "/newsroom/search");
|
|
15232
15304
|
}
|
|
15233
15305
|
|
|
15234
15306
|
function focusNewsroomSearchForm() {
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState, type RefObject } from "react";
|
|
4
|
+
import type { BlogHeaderObstacle } from "./blog-page-background";
|
|
5
|
+
|
|
6
|
+
const HEADER_OBSTACLE_SELECTORS = [
|
|
7
|
+
".presentation-header__eyebrow",
|
|
8
|
+
".presentation-header__word",
|
|
9
|
+
".presentation-header__meta",
|
|
10
|
+
".presentation-header__subtitle",
|
|
11
|
+
".presentation-header__date",
|
|
12
|
+
".presentation-header__tagline",
|
|
13
|
+
".presentation-section-nav a",
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
function measureObstacleRect(element: HTMLElement): DOMRect {
|
|
17
|
+
const range = document.createRange();
|
|
18
|
+
range.selectNodeContents(element);
|
|
19
|
+
const contentRect = range.getBoundingClientRect();
|
|
20
|
+
range.detach();
|
|
21
|
+
if (contentRect.width > 0 && contentRect.height > 0) return contentRect;
|
|
22
|
+
return element.getBoundingClientRect();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Measures the rendered header text (eyebrow, masthead words, meta, tagline,
|
|
27
|
+
* section links) as rectangles in the coordinates of `.blog-page-background`,
|
|
28
|
+
* so a brand's background artwork can keep clear of them. Block-level header
|
|
29
|
+
* elements span the full page width even when their text ends mid-line, so
|
|
30
|
+
* the text range is measured instead of the element box.
|
|
31
|
+
*/
|
|
32
|
+
export function useBlogHeaderObstacles(
|
|
33
|
+
pageRef: RefObject<HTMLElement | null>,
|
|
34
|
+
paintBuffer: number,
|
|
35
|
+
): BlogHeaderObstacle[] {
|
|
36
|
+
const [obstacles, setObstacles] = useState<BlogHeaderObstacle[]>([]);
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
const page = pageRef.current;
|
|
40
|
+
if (!page) return;
|
|
41
|
+
|
|
42
|
+
const update = () => {
|
|
43
|
+
const background = page.querySelector<HTMLElement>(".blog-page-background");
|
|
44
|
+
const backgroundRect = background?.getBoundingClientRect();
|
|
45
|
+
if (!backgroundRect) return;
|
|
46
|
+
|
|
47
|
+
const next: BlogHeaderObstacle[] = [];
|
|
48
|
+
for (const selector of HEADER_OBSTACLE_SELECTORS) {
|
|
49
|
+
for (const element of Array.from(page.querySelectorAll<HTMLElement>(selector))) {
|
|
50
|
+
const rect = measureObstacleRect(element);
|
|
51
|
+
const intersectionTop = Math.max(rect.top, backgroundRect.top);
|
|
52
|
+
const intersectionBottom = Math.min(rect.bottom, backgroundRect.bottom);
|
|
53
|
+
const intersectionLeft = Math.max(rect.left, backgroundRect.left);
|
|
54
|
+
const intersectionRight = Math.min(rect.right, backgroundRect.right);
|
|
55
|
+
const width = intersectionRight - intersectionLeft;
|
|
56
|
+
const height = intersectionBottom - intersectionTop;
|
|
57
|
+
if (width <= 0 || height <= 0) continue;
|
|
58
|
+
next.push({
|
|
59
|
+
x: intersectionLeft - backgroundRect.left,
|
|
60
|
+
y: intersectionTop - backgroundRect.top,
|
|
61
|
+
width,
|
|
62
|
+
height: height + paintBuffer * 2,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
setObstacles(next);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
update();
|
|
70
|
+
const observer = new ResizeObserver(update);
|
|
71
|
+
observer.observe(page);
|
|
72
|
+
window.addEventListener("resize", update);
|
|
73
|
+
void document.fonts?.ready.then(update);
|
|
74
|
+
return () => {
|
|
75
|
+
observer.disconnect();
|
|
76
|
+
window.removeEventListener("resize", update);
|
|
77
|
+
};
|
|
78
|
+
}, [pageRef, paintBuffer]);
|
|
79
|
+
|
|
80
|
+
return obstacles;
|
|
81
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import { listBrandFontFamilies } from "../lib/brand-fonts";
|
|
5
|
+
import { watchBrandFontsLoaded } from "../lib/brand-fonts-watch";
|
|
6
|
+
import { clearPretextMeasurementCache } from "../lib/pretext-layout";
|
|
7
|
+
import { SITE_BRAND } from "../lib/site-brand";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Returns a number that increments each time the brand's declared font
|
|
11
|
+
* families finish loading. Pretext's width cache is cleared first, so
|
|
12
|
+
* measurements taken with fallback metrics are re-run with the real glyph
|
|
13
|
+
* widths. Stays 0 for brands without a font slot, and on the server.
|
|
14
|
+
*/
|
|
15
|
+
export function useBrandFontsReadyVersion(): number {
|
|
16
|
+
const [version, setVersion] = useState(0);
|
|
17
|
+
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
return watchBrandFontsLoaded({
|
|
20
|
+
fontFaceSet: typeof document === "undefined" ? undefined : document.fonts,
|
|
21
|
+
families: listBrandFontFamilies(SITE_BRAND.fonts),
|
|
22
|
+
clearMeasurementCaches: clearPretextMeasurementCache,
|
|
23
|
+
onFontsSettled: () => setVersion((current) => current + 1),
|
|
24
|
+
});
|
|
25
|
+
}, []);
|
|
26
|
+
|
|
27
|
+
return version;
|
|
28
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState, type RefObject } from "react";
|
|
4
|
+
|
|
5
|
+
export function useNearViewport(targetRef: RefObject<Element | null>, rootMargin: string): boolean {
|
|
6
|
+
const [isNearViewport, setIsNearViewport] = useState(false);
|
|
7
|
+
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
if (isNearViewport) return;
|
|
10
|
+
const target = targetRef.current;
|
|
11
|
+
if (!target) return;
|
|
12
|
+
if (typeof IntersectionObserver === "undefined") {
|
|
13
|
+
setIsNearViewport(true);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const observer = new IntersectionObserver(
|
|
17
|
+
(entries) => {
|
|
18
|
+
if (entries.some((entry) => entry.isIntersecting)) {
|
|
19
|
+
setIsNearViewport(true);
|
|
20
|
+
observer.disconnect();
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
{ rootMargin },
|
|
24
|
+
);
|
|
25
|
+
observer.observe(target);
|
|
26
|
+
return () => observer.disconnect();
|
|
27
|
+
}, [isNearViewport, rootMargin, targetRef]);
|
|
28
|
+
|
|
29
|
+
return isNearViewport;
|
|
30
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
|
|
5
|
+
export function useRhythmOverlay(): boolean {
|
|
6
|
+
const [showRhythmOverlay, setShowRhythmOverlay] = useState(false);
|
|
7
|
+
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
10
|
+
if (isEditableEventTarget(event.target)) return;
|
|
11
|
+
if ((event.key === "=" || event.code === "Equal") && event.ctrlKey && !event.metaKey && !event.altKey) {
|
|
12
|
+
event.preventDefault();
|
|
13
|
+
setShowRhythmOverlay((current) => !current);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
18
|
+
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
19
|
+
}, []);
|
|
20
|
+
|
|
21
|
+
return showRhythmOverlay;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function isEditableEventTarget(target: EventTarget | null): boolean {
|
|
25
|
+
if (!(target instanceof HTMLElement)) return false;
|
|
26
|
+
const tagName = target.tagName.toLowerCase();
|
|
27
|
+
if (tagName === "input" || tagName === "textarea" || tagName === "select") return true;
|
|
28
|
+
return target.isContentEditable || target.closest("[contenteditable='true']") !== null;
|
|
29
|
+
}
|