@anthusai/papyrus 0.0.0 → 1.0.0-next.2
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 +2037 -0
- package/amplify/data/resource.ts +2148 -0
- package/amplify/data/schema.js +1850 -0
- package/amplify/data/schema.ts +1953 -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/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 +115 -0
- package/amplify/functions/shared/python-bundle.ts +141 -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/site-backend-config.js +1 -0
- package/amplify/site-backend-config.ts +30 -0
- package/amplify/site-backend.js +587 -0
- package/amplify/site-backend.ts +704 -0
- package/amplify/storage/resource.js +18 -0
- package/amplify/storage/resource.ts +19 -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 +10329 -0
- package/app/layout.tsx +146 -0
- package/app/newsroom/[section]/[...selection]/page.tsx +50 -0
- package/app/newsroom/[section]/page.tsx +47 -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/settings/page.tsx +14 -0
- package/app/tailwind.css +65 -0
- package/bin/papyrus-app.mjs +10 -0
- package/bin/papyrus-infra.mjs +39 -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 +47 -0
- package/components/blog-page-background.tsx +20 -0
- package/components/dev-sandbox-editor-sign-in.tsx +60 -0
- package/components/news-desk-client-provider.tsx +211 -0
- package/components/news-desk-page.tsx +119 -0
- package/components/news-desk-taxonomy-client.ts +3684 -0
- package/components/newspaper.tsx +1929 -0
- package/components/newsroom-assignments-view.tsx +664 -0
- package/components/newsroom-ops-overview.tsx +226 -0
- package/components/newsroom-ops-shell.tsx +319 -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/topic-steering-workspace.tsx +18063 -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 +109 -0
- package/infra/amplify-app-shell.ts +155 -0
- package/infra/index.js +12 -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 +89 -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-load-timing.ts +23 -0
- package/lib/content-repository.ts +40 -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/excerpts.ts +8 -0
- package/lib/graphql-content-repository.ts +986 -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-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-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-nav.ts +42 -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 +41 -0
- package/lib/site-brand.ts +179 -0
- package/lib/site-renderer.ts +17 -0
- package/lib/site-stack.ts +195 -0
- package/lib/themed-image.ts +30 -0
- package/lib/utils.ts +6 -0
- package/middleware.ts +78 -0
- package/package.json +296 -1
- package/publications/anth_us/brand.ts +68 -0
- package/publications/anth_us/theme.css +66 -0
- package/publications/papyrus/theme.css +11 -0
- package/publications/pilobol_us/brand.ts +37 -0
- package/publications/pilobol_us/theme.css +140 -0
- package/publications/threat_intelligence/blog-defense/graph.ts +636 -0
- package/publications/threat_intelligence/blog-defense/layout.ts +934 -0
- package/publications/threat_intelligence/blog-defense/page-background.tsx +590 -0
- package/publications/threat_intelligence/brand.ts +40 -0
- package/publications/threat_intelligence/pictograms/art.tsx +608 -0
- package/publications/threat_intelligence/pictograms/figure.tsx +82 -0
- package/publications/threat_intelligence/pictograms/registry.ts +30 -0
- package/publications/threat_intelligence/pictograms/system.tsx +305 -0
- package/publications/threat_intelligence/seed/seed-edition-content.json +629 -0
- package/publications/threat_intelligence/theme.css +1440 -0
- package/renderers/markus/index.ts +25 -0
- package/renderers/markus/stubs.tsx +33 -0
- package/renderers/pretext/article-page.tsx +174 -0
- package/renderers/pretext/index.ts +30 -0
- package/renderers/pretext/layouts.ts +16 -0
- package/renderers/pretext/presentation-shell.tsx +477 -0
- package/routes.manifest.json +182 -0
- package/scripts/fixtures/pilobol-sample.ast.json +234 -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 +47 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export type NewsroomKnowledgeQueryAnchor = {
|
|
2
|
+
kind: "assignment" | "category" | "categorySet" | "item" | "message" | "newsroomSection" | "reference" | "semanticNode";
|
|
3
|
+
id: string;
|
|
4
|
+
lineageId?: string | null;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export type NewsroomKnowledgeQueryTarget = {
|
|
8
|
+
anchor: NewsroomKnowledgeQueryAnchor;
|
|
9
|
+
title: string;
|
|
10
|
+
subtitle?: string | null;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
type BuildKnowledgeQueryInputOptions = {
|
|
14
|
+
profile?: "researcher" | "reporter" | "editor" | "reviewer" | "chat";
|
|
15
|
+
semanticSearch?: boolean;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export function buildNewsroomKnowledgeQueryInput(
|
|
19
|
+
target: NewsroomKnowledgeQueryTarget | null,
|
|
20
|
+
semanticText: string,
|
|
21
|
+
maxTokens: number,
|
|
22
|
+
options: BuildKnowledgeQueryInputOptions = {},
|
|
23
|
+
): Record<string, unknown> {
|
|
24
|
+
const request: Record<string, unknown> = {
|
|
25
|
+
semanticQuery: semanticText.trim(),
|
|
26
|
+
scope: {
|
|
27
|
+
depth: 1,
|
|
28
|
+
topK: target ? 18 : 24,
|
|
29
|
+
relatedRecordLimit: target ? 8 : 10,
|
|
30
|
+
semanticSearch: options.semanticSearch ?? true,
|
|
31
|
+
},
|
|
32
|
+
profile: options.profile ?? "editor",
|
|
33
|
+
ranking: {
|
|
34
|
+
profile: "balanced",
|
|
35
|
+
diversity: target ? "balanced" : "broad",
|
|
36
|
+
},
|
|
37
|
+
output: {
|
|
38
|
+
format: "markdown",
|
|
39
|
+
maxTokens,
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
if (target) {
|
|
43
|
+
const anchor: Record<string, unknown> = {
|
|
44
|
+
kind: target.anchor.kind,
|
|
45
|
+
id: target.anchor.id,
|
|
46
|
+
};
|
|
47
|
+
if (target.anchor.lineageId) anchor.lineageId = target.anchor.lineageId;
|
|
48
|
+
request.anchors = [anchor];
|
|
49
|
+
}
|
|
50
|
+
return request;
|
|
51
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { cn } from "./utils";
|
|
2
|
+
|
|
3
|
+
/** List-row selection: dark copy on moss/paper tint in light mode; light copy on wash in dark mode. Never invert to primary-foreground. */
|
|
4
|
+
export const NEWSROOM_LIST_ROW_BASE =
|
|
5
|
+
"w-full rounded-xl border bg-card p-4 text-left text-foreground transition-colors";
|
|
6
|
+
|
|
7
|
+
export const NEWSROOM_LIST_ROW_INACTIVE =
|
|
8
|
+
"border-border hover:border-primary/30 hover:bg-primary/5";
|
|
9
|
+
|
|
10
|
+
export const NEWSROOM_LIST_ROW_ACTIVE =
|
|
11
|
+
"border-primary/40 bg-primary/10 text-foreground ring-1 ring-primary/15";
|
|
12
|
+
|
|
13
|
+
export const NEWSROOM_INBOX_ROW_BASE =
|
|
14
|
+
"flex items-start gap-3 rounded-xl border bg-card p-3 text-foreground transition-colors";
|
|
15
|
+
|
|
16
|
+
export const NEWSROOM_INBOX_ROW_INACTIVE =
|
|
17
|
+
"border-border hover:border-primary/30 hover:bg-primary/5";
|
|
18
|
+
|
|
19
|
+
export const NEWSROOM_INBOX_ROW_ACTIVE =
|
|
20
|
+
"border-primary/40 bg-primary/10 text-foreground ring-1 ring-primary/15";
|
|
21
|
+
|
|
22
|
+
export const NEWSROOM_SIDEBAR_NAV_ACTIVE =
|
|
23
|
+
"bg-primary/10 text-foreground shadow-none hover:bg-primary/15";
|
|
24
|
+
|
|
25
|
+
export const NEWSROOM_SIDEBAR_NAV_INACTIVE =
|
|
26
|
+
"text-muted-foreground hover:bg-muted/50 hover:text-foreground";
|
|
27
|
+
|
|
28
|
+
export function newsroomListRowClassName(active: boolean, className?: string): string {
|
|
29
|
+
return cn(
|
|
30
|
+
NEWSROOM_LIST_ROW_BASE,
|
|
31
|
+
active ? NEWSROOM_LIST_ROW_ACTIVE : NEWSROOM_LIST_ROW_INACTIVE,
|
|
32
|
+
className,
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function newsroomInboxRowClassName(active: boolean, className?: string): string {
|
|
37
|
+
return cn(
|
|
38
|
+
NEWSROOM_INBOX_ROW_BASE,
|
|
39
|
+
active ? NEWSROOM_INBOX_ROW_ACTIVE : NEWSROOM_INBOX_ROW_INACTIVE,
|
|
40
|
+
className,
|
|
41
|
+
);
|
|
42
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { newsroomHref, toPublicNewsroomPath } from "./newsroom-base-path";
|
|
2
|
+
|
|
3
|
+
export type NewsroomNavTabId =
|
|
4
|
+
| "overview"
|
|
5
|
+
| "messages"
|
|
6
|
+
| "insights"
|
|
7
|
+
| "assignments"
|
|
8
|
+
| "references"
|
|
9
|
+
| "topics"
|
|
10
|
+
| "concepts"
|
|
11
|
+
| "administration"
|
|
12
|
+
| "search"
|
|
13
|
+
| "desks";
|
|
14
|
+
|
|
15
|
+
export type NewsroomNavItem = {
|
|
16
|
+
id: NewsroomNavTabId;
|
|
17
|
+
label: string;
|
|
18
|
+
detail: string;
|
|
19
|
+
href: string;
|
|
20
|
+
mobilePrimary?: boolean;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const NEWSROOM_OPS_NAV: NewsroomNavItem[] = [
|
|
24
|
+
{ id: "overview", label: "Overview", detail: "Desk home", href: newsroomHref(), mobilePrimary: true },
|
|
25
|
+
{ id: "assignments", label: "Assignments", detail: "Work queue", href: newsroomHref("assignments"), mobilePrimary: true },
|
|
26
|
+
{ id: "references", label: "References", detail: "Knowledge base", href: newsroomHref("references"), mobilePrimary: true },
|
|
27
|
+
{ id: "messages", label: "Messages", detail: "Forum", href: newsroomHref("messages") },
|
|
28
|
+
{ id: "insights", label: "Insights", detail: "Research threads", href: newsroomHref("insights") },
|
|
29
|
+
{ id: "topics", label: "Topics", detail: "Taxonomy", href: newsroomHref("topics"), mobilePrimary: true },
|
|
30
|
+
{ id: "concepts", label: "Concepts", detail: "Ontology", href: newsroomHref("concepts") },
|
|
31
|
+
{ id: "administration", label: "Administration", detail: "Users & policies", href: newsroomHref("administration") },
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
export const NEWSROOM_MOBILE_PRIMARY_NAV = NEWSROOM_OPS_NAV.filter((item) => item.mobilePrimary);
|
|
35
|
+
|
|
36
|
+
export function getNewsroomNavHref(href: string, demo?: boolean): string {
|
|
37
|
+
const path = toPublicNewsroomPath(href);
|
|
38
|
+
if (!demo) return path;
|
|
39
|
+
const url = new URL(path, "http://localhost");
|
|
40
|
+
url.searchParams.set("demo", "1");
|
|
41
|
+
return `${url.pathname}${url.search}`;
|
|
42
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import type { ReferenceRecord } from "./category-repository";
|
|
2
|
+
import { normalizeReferenceCurationStatus } from "./reference-policy";
|
|
3
|
+
|
|
4
|
+
export type ReferenceCurationAction = "accept" | "reject" | "archive";
|
|
5
|
+
|
|
6
|
+
export type ReferenceStatusFilter = "all" | "pending" | "accepted" | "rejected" | "archived";
|
|
7
|
+
|
|
8
|
+
export function referenceLineageId(reference: ReferenceRecord): string {
|
|
9
|
+
return reference.lineageId ?? reference.id;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function normalizeReferenceStatus(status: string | null | undefined): string {
|
|
13
|
+
return normalizeReferenceCurationStatus(status);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function referenceSortDate(reference: ReferenceRecord): string {
|
|
17
|
+
return reference.sourcePublishedAt
|
|
18
|
+
?? reference.sourceUpdatedAt
|
|
19
|
+
?? reference.retrievedAt
|
|
20
|
+
?? reference.importedAt
|
|
21
|
+
?? reference.updatedAt
|
|
22
|
+
?? "";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function compareReferencesByRecency(left: ReferenceRecord, right: ReferenceRecord): number {
|
|
26
|
+
const dateDiff = referenceSortDate(right).localeCompare(referenceSortDate(left));
|
|
27
|
+
if (dateDiff !== 0) return dateDiff;
|
|
28
|
+
return (left.title ?? left.externalItemId).localeCompare(right.title ?? right.externalItemId);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function compareReferencesForCanonicalChoice(left: ReferenceRecord, right: ReferenceRecord): number {
|
|
32
|
+
const leftCurrent = left.versionState === "current" ? 1 : 0;
|
|
33
|
+
const rightCurrent = right.versionState === "current" ? 1 : 0;
|
|
34
|
+
if (leftCurrent !== rightCurrent) return rightCurrent - leftCurrent;
|
|
35
|
+
|
|
36
|
+
const leftVersion = Number.isFinite(left.versionNumber) ? Number(left.versionNumber) : 0;
|
|
37
|
+
const rightVersion = Number.isFinite(right.versionNumber) ? Number(right.versionNumber) : 0;
|
|
38
|
+
if (leftVersion !== rightVersion) return rightVersion - leftVersion;
|
|
39
|
+
|
|
40
|
+
return compareReferencesByRecency(left, right);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function selectCanonicalReferenceRecords(references: ReferenceRecord[]): ReferenceRecord[] {
|
|
44
|
+
const byLineage = new Map<string, ReferenceRecord>();
|
|
45
|
+
for (const reference of references) {
|
|
46
|
+
const key = referenceLineageId(reference);
|
|
47
|
+
const current = byLineage.get(key);
|
|
48
|
+
if (!current || compareReferencesForCanonicalChoice(reference, current) < 0) {
|
|
49
|
+
byLineage.set(key, reference);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return Array.from(byLineage.values()).sort(compareReferencesByRecency);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function selectedReferenceRecordByLineage(
|
|
56
|
+
references: ReferenceRecord[],
|
|
57
|
+
requestedLineageId: string | null | undefined,
|
|
58
|
+
): ReferenceRecord | null {
|
|
59
|
+
if (!requestedLineageId) return null;
|
|
60
|
+
const lineageMatches = references.filter((reference) => referenceLineageId(reference) === requestedLineageId);
|
|
61
|
+
if (!lineageMatches.length) return null;
|
|
62
|
+
return lineageMatches.reduce((best, current) => (
|
|
63
|
+
compareReferencesForCanonicalChoice(current, best) < 0 ? current : best
|
|
64
|
+
));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function countReferencesByStatus(references: ReferenceRecord[]): Record<string, number> {
|
|
68
|
+
const counts: Record<string, number> = {};
|
|
69
|
+
for (const reference of references) {
|
|
70
|
+
const status = normalizeReferenceStatus(reference.curationStatus);
|
|
71
|
+
counts[status] = (counts[status] ?? 0) + 1;
|
|
72
|
+
}
|
|
73
|
+
return counts;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function filterReferencesByStatus(
|
|
77
|
+
references: ReferenceRecord[],
|
|
78
|
+
statusFilter: ReferenceStatusFilter,
|
|
79
|
+
): ReferenceRecord[] {
|
|
80
|
+
const canonical = selectCanonicalReferenceRecords(references);
|
|
81
|
+
if (statusFilter === "all") return canonical;
|
|
82
|
+
return canonical.filter((reference) => normalizeReferenceStatus(reference.curationStatus) === statusFilter);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function formatReferenceListDate(reference: ReferenceRecord): string {
|
|
86
|
+
const value = referenceSortDate(reference);
|
|
87
|
+
if (!value) return "Undated";
|
|
88
|
+
const timestamp = Date.parse(value);
|
|
89
|
+
if (Number.isNaN(timestamp)) return value;
|
|
90
|
+
return new Intl.DateTimeFormat("en-US", {
|
|
91
|
+
month: "short",
|
|
92
|
+
day: "numeric",
|
|
93
|
+
year: "numeric",
|
|
94
|
+
timeZone: "UTC",
|
|
95
|
+
}).format(new Date(timestamp));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function referenceDisplayTitle(reference: ReferenceRecord): string {
|
|
99
|
+
return reference.title?.trim() || reference.externalItemId;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function referenceStatusLabel(status: string): string {
|
|
103
|
+
if (status === "accepted") return "Accepted";
|
|
104
|
+
if (status === "rejected") return "Rejected";
|
|
105
|
+
if (status === "archived") return "Archived";
|
|
106
|
+
return "Pending";
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function referenceStatusBadgeVariant(
|
|
110
|
+
status: string,
|
|
111
|
+
): "default" | "secondary" | "outline" | "muted" {
|
|
112
|
+
if (status === "accepted") return "default";
|
|
113
|
+
if (status === "rejected") return "outline";
|
|
114
|
+
if (status === "archived") return "secondary";
|
|
115
|
+
return "muted";
|
|
116
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { NewsroomSectionRecord } from "./category-repository";
|
|
2
|
+
import { getNewsroomNavHref } from "./newsroom-nav";
|
|
3
|
+
|
|
4
|
+
const SECTION_SHORT_TITLE_FALLBACKS: Record<string, string> = {
|
|
5
|
+
news: "News",
|
|
6
|
+
business: "Business",
|
|
7
|
+
technology: "Technology",
|
|
8
|
+
science: "Science",
|
|
9
|
+
methods: "Methods",
|
|
10
|
+
history: "History",
|
|
11
|
+
opinion: "Opinion",
|
|
12
|
+
world: "World",
|
|
13
|
+
arts: "Arts",
|
|
14
|
+
sports: "Sports",
|
|
15
|
+
education: "Education",
|
|
16
|
+
health: "Health",
|
|
17
|
+
security: "Security",
|
|
18
|
+
"law-policy": "Law & Policy",
|
|
19
|
+
labor: "Labor",
|
|
20
|
+
"field-notes": "Field Notes",
|
|
21
|
+
"strangler-control": "Host encirclement",
|
|
22
|
+
"mycelial-signals": "Network routing",
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export function normalizeNewsroomSectionType(value: string | null | undefined): "canonical" | "floating" {
|
|
26
|
+
return value === "floating" || value === "rotating" ? "floating" : "canonical";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function isEnabledNewsroomSection(section: NewsroomSectionRecord): boolean {
|
|
30
|
+
return section.enabled !== false && section.enabledStatus !== "disabled";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function sortNewsroomSections(sections: NewsroomSectionRecord[]): NewsroomSectionRecord[] {
|
|
34
|
+
return [...sections].sort((left, right) => {
|
|
35
|
+
const typeDiff = (normalizeNewsroomSectionType(left.type) === "canonical" ? 0 : 1)
|
|
36
|
+
- (normalizeNewsroomSectionType(right.type) === "canonical" ? 0 : 1);
|
|
37
|
+
if (typeDiff !== 0) return typeDiff;
|
|
38
|
+
const orderDiff = (left.sortOrder ?? 999999) - (right.sortOrder ?? 999999);
|
|
39
|
+
if (orderDiff !== 0) return orderDiff;
|
|
40
|
+
return left.title.localeCompare(right.title);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function displayNewsroomSectionShortTitle(section: NewsroomSectionRecord): string {
|
|
45
|
+
const explicit = section.shortTitle?.trim();
|
|
46
|
+
if (explicit) return explicit;
|
|
47
|
+
return SECTION_SHORT_TITLE_FALLBACKS[section.id] ?? section.id.replace(/-/g, " ");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function buildNewsroomSectionHref(sectionId: string, demo?: boolean): string {
|
|
51
|
+
return getNewsroomNavHref(`/newsroom/sections/${encodeURIComponent(sectionId)}`, demo);
|
|
52
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import type { CategorySteeringProposal } from "./category-repository";
|
|
2
|
+
|
|
3
|
+
export type TopicProposalReviewAction = "accept" | "reject" | "defer" | "edit";
|
|
4
|
+
|
|
5
|
+
export type TopicProposalStatusFilter = "all" | "proposed" | "deferred" | "reviewed";
|
|
6
|
+
|
|
7
|
+
const TOPIC_PROPOSAL_BLOCKED_APPLY_KINDS = new Set([
|
|
8
|
+
"merge-category",
|
|
9
|
+
"merge-categories",
|
|
10
|
+
"split-category",
|
|
11
|
+
"archive-category",
|
|
12
|
+
"deprecate-category",
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
export function isCategoryTopicProposal(proposal: CategorySteeringProposal): boolean {
|
|
16
|
+
return proposal.steeringDomain === "category";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function filterTopicProposals(
|
|
20
|
+
proposals: CategorySteeringProposal[],
|
|
21
|
+
statusFilter: TopicProposalStatusFilter,
|
|
22
|
+
): CategorySteeringProposal[] {
|
|
23
|
+
const categoryProposals = proposals.filter(isCategoryTopicProposal);
|
|
24
|
+
if (statusFilter === "all") return categoryProposals;
|
|
25
|
+
if (statusFilter === "reviewed") {
|
|
26
|
+
return categoryProposals.filter((proposal) => (
|
|
27
|
+
proposal.status === "accepted" || proposal.status === "rejected"
|
|
28
|
+
));
|
|
29
|
+
}
|
|
30
|
+
return categoryProposals.filter((proposal) => proposal.status === statusFilter);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function countTopicProposalsByStatus(
|
|
34
|
+
proposals: CategorySteeringProposal[],
|
|
35
|
+
): Record<TopicProposalStatusFilter, number> {
|
|
36
|
+
const categoryProposals = proposals.filter(isCategoryTopicProposal);
|
|
37
|
+
return {
|
|
38
|
+
all: categoryProposals.length,
|
|
39
|
+
proposed: categoryProposals.filter((proposal) => proposal.status === "proposed").length,
|
|
40
|
+
deferred: categoryProposals.filter((proposal) => proposal.status === "deferred").length,
|
|
41
|
+
reviewed: categoryProposals.filter((proposal) => (
|
|
42
|
+
proposal.status === "accepted" || proposal.status === "rejected"
|
|
43
|
+
)).length,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function compareTopicProposals(
|
|
48
|
+
left: CategorySteeringProposal,
|
|
49
|
+
right: CategorySteeringProposal,
|
|
50
|
+
): number {
|
|
51
|
+
const statusWeight = new Map([
|
|
52
|
+
["proposed", 0],
|
|
53
|
+
["deferred", 1],
|
|
54
|
+
["accepted", 2],
|
|
55
|
+
["rejected", 3],
|
|
56
|
+
]);
|
|
57
|
+
const statusDiff = (statusWeight.get(left.status) ?? 9) - (statusWeight.get(right.status) ?? 9);
|
|
58
|
+
if (statusDiff !== 0) return statusDiff;
|
|
59
|
+
const dateDiff = (right.proposedAt ?? right.updatedAt ?? "").localeCompare(left.proposedAt ?? left.updatedAt ?? "");
|
|
60
|
+
if (dateDiff !== 0) return dateDiff;
|
|
61
|
+
return proposalDisplayTitle(left).localeCompare(proposalDisplayTitle(right));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function sortTopicProposals(proposals: CategorySteeringProposal[]): CategorySteeringProposal[] {
|
|
65
|
+
return [...proposals].sort(compareTopicProposals);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function proposalDisplayTitle(proposal: CategorySteeringProposal): string {
|
|
69
|
+
return proposal.displayName?.trim() || proposal.title?.trim() || proposal.categoryKey || proposal.id;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function proposalStatusLabel(status: string): string {
|
|
73
|
+
if (status === "accepted") return "Accepted";
|
|
74
|
+
if (status === "rejected") return "Rejected";
|
|
75
|
+
if (status === "deferred") return "Deferred";
|
|
76
|
+
return "Proposed";
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function proposalStatusBadgeVariant(
|
|
80
|
+
status: string,
|
|
81
|
+
): "default" | "secondary" | "outline" | "muted" {
|
|
82
|
+
if (status === "accepted") return "default";
|
|
83
|
+
if (status === "rejected") return "outline";
|
|
84
|
+
if (status === "deferred") return "secondary";
|
|
85
|
+
return "muted";
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function formatProposalListDate(proposal: CategorySteeringProposal): string {
|
|
89
|
+
const value = proposal.proposedAt ?? proposal.reviewedAt ?? proposal.updatedAt ?? "";
|
|
90
|
+
if (!value) return "Undated";
|
|
91
|
+
const timestamp = Date.parse(value);
|
|
92
|
+
if (Number.isNaN(timestamp)) return value;
|
|
93
|
+
return new Intl.DateTimeFormat("en-US", {
|
|
94
|
+
month: "short",
|
|
95
|
+
day: "numeric",
|
|
96
|
+
year: "numeric",
|
|
97
|
+
timeZone: "UTC",
|
|
98
|
+
}).format(new Date(timestamp));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function selectedTopicProposalById(
|
|
102
|
+
proposals: CategorySteeringProposal[],
|
|
103
|
+
proposalId: string | null | undefined,
|
|
104
|
+
): CategorySteeringProposal | null {
|
|
105
|
+
if (!proposalId) return null;
|
|
106
|
+
return proposals.find((proposal) => proposal.id === proposalId) ?? null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function topicProposalReviewBlockedReason(
|
|
110
|
+
proposal: CategorySteeringProposal,
|
|
111
|
+
action: TopicProposalReviewAction,
|
|
112
|
+
): string | null {
|
|
113
|
+
if ((action === "accept" || action === "edit") && TOPIC_PROPOSAL_BLOCKED_APPLY_KINDS.has(proposal.proposalKind)) {
|
|
114
|
+
return `${proposal.proposalKind} apply is not implemented yet.`;
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type PapyrusObjectKind =
|
|
2
|
+
| "assignment"
|
|
3
|
+
| "category"
|
|
4
|
+
| "categorySet"
|
|
5
|
+
| "item"
|
|
6
|
+
| "message"
|
|
7
|
+
| "newsroomSection"
|
|
8
|
+
| "reference"
|
|
9
|
+
| "semanticNode"
|
|
10
|
+
| "semanticRelation"
|
|
11
|
+
| "steeringProposal";
|
|
12
|
+
|
|
13
|
+
export const PAPYRUS_OBJECT_KINDS = new Set<PapyrusObjectKind>([
|
|
14
|
+
"assignment",
|
|
15
|
+
"category",
|
|
16
|
+
"categorySet",
|
|
17
|
+
"item",
|
|
18
|
+
"message",
|
|
19
|
+
"newsroomSection",
|
|
20
|
+
"reference",
|
|
21
|
+
"semanticNode",
|
|
22
|
+
"semanticRelation",
|
|
23
|
+
"steeringProposal",
|
|
24
|
+
]);
|