@anthusai/papyrus 1.10.0 → 1.12.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/data/resource.js +70 -3
- package/amplify/data/resource.ts +75 -3
- package/amplify/data/schema.js +67 -0
- package/amplify/data/schema.ts +72 -0
- package/amplify/functions/category-action/handler.ts +190 -20
- package/amplify/functions/console-chat-responder/src/main.rs +1 -1
- package/amplify/functions/content-actions/handler.py +23 -0
- package/amplify/functions/content-actions/requirements.txt +1 -1
- package/amplify/functions/content-actions/resource.js +1 -1
- package/amplify/functions/content-actions/resource.ts +1 -1
- package/amplify/functions/renditions/requirements.txt +1 -1
- package/amplify/functions/shared/curation-delegation.ts +67 -0
- package/amplify/functions/shared/reference-curation-metadata.ts +20 -0
- package/amplify/functions/shared/slack-events.ts +2 -2
- package/amplify/publication/renditions-function.js +1 -1
- package/amplify/publication/renditions-function.ts +1 -1
- package/app/api/local-preview/derive/route.ts +28 -0
- package/app/api/local-preview/media/route.ts +25 -0
- package/app/globals.css +28 -28
- package/app/layout.tsx +8 -5
- package/app/newsroom/[section]/[...selection]/page.tsx +0 -1
- package/app/newsroom/[section]/page.tsx +0 -1
- package/app/newsroom/insights/[threadId]/page.tsx +5 -16
- package/app/newsroom/insights/page.tsx +4 -12
- package/components/administration-delegations-panel.tsx +204 -0
- package/components/board-card-excerpts.tsx +45 -0
- package/components/curation-delegation-client.ts +81 -0
- package/components/markus-ir-preview.tsx +47 -2
- package/components/news-desk-page.tsx +0 -1
- package/components/news-desk-taxonomy-client.ts +32 -220
- package/components/newsroom-article-editor.tsx +135 -39
- package/components/newsroom-assignment-detail.tsx +35 -1
- package/components/newsroom-board-view.tsx +29 -7
- package/components/newsroom-newsprint-board.css +9 -2
- package/components/newsroom-ops-overview.tsx +27 -5
- package/components/newsroom-organizer-shell.tsx +25 -7
- package/components/newsroom-organizer.css +218 -4
- package/components/newsroom-references-view.tsx +56 -21
- package/components/priming-proof-page.tsx +17 -8
- package/components/prospectus-account-chooser.tsx +98 -0
- package/components/reference-cyclotron-status.tsx +5 -5
- package/components/reference-cyclotron-suggestion.tsx +143 -0
- package/components/reference-live-agreement.tsx +62 -0
- package/components/story-reporting-trail-client.ts +91 -0
- package/components/story-reporting-trail.tsx +115 -0
- package/components/topic-steering-workspace.tsx +203 -674
- package/components/ui/sheet.tsx +1 -0
- package/components/wiki-concepts-view.tsx +153 -0
- package/components/wiki-index-client.ts +51 -0
- package/lib/content-actions-client.ts +3 -0
- package/lib/content-actions-demo.ts +55 -3
- package/lib/content-item-lifecycle.ts +4 -2
- package/lib/content-source-context.ts +9 -1
- package/lib/curation-delegation-helpers.ts +59 -0
- package/lib/cyclotron-suggestions.ts +263 -0
- package/lib/front-matter-fields.ts +42 -0
- package/lib/graphql-content-repository.ts +42 -7
- package/lib/local-preview-seed.ts +174 -3
- package/lib/local-preview-tenant.ts +27 -0
- package/lib/newsroom-articles.ts +2 -0
- package/lib/newsroom-board.ts +4 -0
- package/lib/newsroom-forum-routes.ts +2 -43
- package/lib/newsroom-index-filters.ts +3 -40
- package/lib/newsroom-media-backend.ts +5 -0
- package/lib/newsroom-nav.ts +1 -3
- package/lib/newsroom-organizer-nav.ts +5 -12
- package/lib/newsroom-section-routing.ts +1 -1
- package/lib/newsroom-sections.ts +12 -0
- package/lib/papyrus-web-locations.ts +3 -24
- package/lib/priming-proof-report.ts +1 -0
- package/lib/prospectus-tenant.ts +22 -0
- package/lib/relevance-decisions.ts +29 -0
- package/lib/semantic-graph.ts +5 -5
- package/lib/site-brand.ts +9 -0
- package/lib/story-reporting-trail.ts +106 -0
- package/lib/tenant-keys.ts +32 -0
- package/lib/wiki.ts +56 -0
- package/package.json +21 -1
- package/routes.manifest.json +23 -7
package/app/globals.css
CHANGED
|
@@ -6275,7 +6275,7 @@ img {
|
|
|
6275
6275
|
padding-top: calc(var(--paper-rhythm) - 1px);
|
|
6276
6276
|
}
|
|
6277
6277
|
|
|
6278
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] [data-reference-topic-workflow] + [data-reference-
|
|
6278
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] [data-reference-topic-workflow] + [data-reference-note-workflow] {
|
|
6279
6279
|
border-top: 0;
|
|
6280
6280
|
padding-top: 0;
|
|
6281
6281
|
}
|
|
@@ -6476,7 +6476,7 @@ img {
|
|
|
6476
6476
|
|
|
6477
6477
|
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-workflow__input button,
|
|
6478
6478
|
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-workflow__header > button,
|
|
6479
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-
|
|
6479
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-note-shell > button {
|
|
6480
6480
|
background: transparent;
|
|
6481
6481
|
border: 1px solid rgb(var(--rule-rgb) / 0.42);
|
|
6482
6482
|
box-sizing: border-box;
|
|
@@ -6498,7 +6498,7 @@ img {
|
|
|
6498
6498
|
}
|
|
6499
6499
|
|
|
6500
6500
|
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-workflow__header > button:disabled,
|
|
6501
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-
|
|
6501
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-note-shell > button:disabled {
|
|
6502
6502
|
cursor: not-allowed;
|
|
6503
6503
|
opacity: 0.45;
|
|
6504
6504
|
}
|
|
@@ -6521,26 +6521,26 @@ img {
|
|
|
6521
6521
|
margin-right: calc(var(--paper-rhythm) * 0.5);
|
|
6522
6522
|
}
|
|
6523
6523
|
|
|
6524
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-
|
|
6524
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-note-form {
|
|
6525
6525
|
display: grid;
|
|
6526
6526
|
gap: 0;
|
|
6527
6527
|
width: 100%;
|
|
6528
6528
|
}
|
|
6529
6529
|
|
|
6530
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-
|
|
6530
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-note-shell {
|
|
6531
6531
|
display: grid;
|
|
6532
6532
|
gap: 0;
|
|
6533
6533
|
}
|
|
6534
6534
|
|
|
6535
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-
|
|
6535
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-note-shell > * + * {
|
|
6536
6536
|
margin-top: var(--paper-rhythm);
|
|
6537
6537
|
}
|
|
6538
6538
|
|
|
6539
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-
|
|
6539
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-note-form > * + * {
|
|
6540
6540
|
margin-top: var(--paper-rhythm);
|
|
6541
6541
|
}
|
|
6542
6542
|
|
|
6543
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-
|
|
6543
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-note-shell > button {
|
|
6544
6544
|
align-items: center;
|
|
6545
6545
|
appearance: none;
|
|
6546
6546
|
display: inline-flex;
|
|
@@ -6550,7 +6550,7 @@ img {
|
|
|
6550
6550
|
vertical-align: top;
|
|
6551
6551
|
}
|
|
6552
6552
|
|
|
6553
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] [data-reference-
|
|
6553
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] [data-reference-note-workflow] .news-desk-reference-workflow__header > button {
|
|
6554
6554
|
align-items: center;
|
|
6555
6555
|
appearance: none;
|
|
6556
6556
|
display: inline-flex;
|
|
@@ -6562,37 +6562,37 @@ img {
|
|
|
6562
6562
|
vertical-align: top;
|
|
6563
6563
|
}
|
|
6564
6564
|
|
|
6565
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] [data-reference-
|
|
6565
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] [data-reference-note-workflow] .news-desk-reference-workflow__header {
|
|
6566
6566
|
align-items: flex-start;
|
|
6567
6567
|
}
|
|
6568
6568
|
|
|
6569
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-
|
|
6569
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-note-list {
|
|
6570
6570
|
display: grid;
|
|
6571
6571
|
gap: 0;
|
|
6572
6572
|
}
|
|
6573
6573
|
|
|
6574
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-
|
|
6574
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-note-list .news-desk-reference-topic-state-row {
|
|
6575
6575
|
border-left: 0;
|
|
6576
6576
|
gap: 0;
|
|
6577
6577
|
padding-left: 0;
|
|
6578
6578
|
}
|
|
6579
6579
|
|
|
6580
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-
|
|
6580
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-note-list .news-desk-reference-topic-state-row + .news-desk-reference-topic-state-row {
|
|
6581
6581
|
margin-top: var(--paper-rhythm);
|
|
6582
6582
|
}
|
|
6583
6583
|
|
|
6584
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-
|
|
6585
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-
|
|
6584
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-note-list .news-desk-reference-topic-state-row__headline,
|
|
6585
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-note-list .news-desk-reference-topic-state-row__meta {
|
|
6586
6586
|
gap: 0;
|
|
6587
6587
|
}
|
|
6588
6588
|
|
|
6589
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-
|
|
6590
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-
|
|
6589
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-note-list .news-desk-reference-topic-state-row strong,
|
|
6590
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-note-list .news-desk-reference-topic-state-row span {
|
|
6591
6591
|
line-height: var(--paper-rhythm);
|
|
6592
6592
|
min-height: var(--paper-rhythm);
|
|
6593
6593
|
}
|
|
6594
6594
|
|
|
6595
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-
|
|
6595
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-note-form .news-desk-reference-curation-note {
|
|
6596
6596
|
display: grid;
|
|
6597
6597
|
gap: 0;
|
|
6598
6598
|
grid-template-rows: var(--paper-rhythm) auto;
|
|
@@ -6600,7 +6600,7 @@ img {
|
|
|
6600
6600
|
min-width: 0;
|
|
6601
6601
|
}
|
|
6602
6602
|
|
|
6603
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-
|
|
6603
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-note-form .news-desk-reference-curation-note span {
|
|
6604
6604
|
color: var(--muted-strong);
|
|
6605
6605
|
font-family: Arial, Helvetica, sans-serif;
|
|
6606
6606
|
font-size: 0.66rem;
|
|
@@ -6611,13 +6611,13 @@ img {
|
|
|
6611
6611
|
text-transform: uppercase;
|
|
6612
6612
|
}
|
|
6613
6613
|
|
|
6614
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-
|
|
6614
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-note-shell button {
|
|
6615
6615
|
line-height: var(--paper-rhythm);
|
|
6616
6616
|
min-height: var(--news-desk-detail-control-height);
|
|
6617
6617
|
}
|
|
6618
6618
|
|
|
6619
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-
|
|
6620
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-
|
|
6619
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-note-form .news-desk-reference-curation-note input,
|
|
6620
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-note-form .news-desk-reference-curation-note textarea {
|
|
6621
6621
|
box-sizing: border-box;
|
|
6622
6622
|
display: block;
|
|
6623
6623
|
font-family: Arial, Helvetica, sans-serif;
|
|
@@ -6627,7 +6627,7 @@ img {
|
|
|
6627
6627
|
padding: 0 calc(var(--paper-rhythm) * 0.5);
|
|
6628
6628
|
}
|
|
6629
6629
|
|
|
6630
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-
|
|
6630
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-note-form textarea {
|
|
6631
6631
|
background: rgb(var(--paper-rgb) / 0.42);
|
|
6632
6632
|
border: 1px solid rgb(var(--rule-rgb) / 0.32);
|
|
6633
6633
|
color: var(--ink);
|
|
@@ -6637,7 +6637,7 @@ img {
|
|
|
6637
6637
|
resize: vertical;
|
|
6638
6638
|
}
|
|
6639
6639
|
|
|
6640
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-
|
|
6640
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-note-form__actions {
|
|
6641
6641
|
align-items: center;
|
|
6642
6642
|
column-gap: calc(var(--paper-rhythm) * 0.5);
|
|
6643
6643
|
display: flex;
|
|
@@ -6646,7 +6646,7 @@ img {
|
|
|
6646
6646
|
min-height: var(--paper-rhythm);
|
|
6647
6647
|
}
|
|
6648
6648
|
|
|
6649
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-
|
|
6649
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-note-form__actions button {
|
|
6650
6650
|
background: transparent;
|
|
6651
6651
|
border: 1px solid rgb(var(--rule-rgb) / 0.42);
|
|
6652
6652
|
box-sizing: border-box;
|
|
@@ -6662,7 +6662,7 @@ img {
|
|
|
6662
6662
|
text-transform: uppercase;
|
|
6663
6663
|
}
|
|
6664
6664
|
|
|
6665
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-
|
|
6665
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-note-form__actions button:disabled {
|
|
6666
6666
|
cursor: not-allowed;
|
|
6667
6667
|
opacity: 0.45;
|
|
6668
6668
|
}
|
|
@@ -6895,11 +6895,11 @@ img {
|
|
|
6895
6895
|
justify-self: stretch;
|
|
6896
6896
|
}
|
|
6897
6897
|
|
|
6898
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-
|
|
6898
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-note-shell > button {
|
|
6899
6899
|
justify-self: stretch;
|
|
6900
6900
|
}
|
|
6901
6901
|
|
|
6902
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] [data-reference-
|
|
6902
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] [data-reference-note-workflow] .news-desk-reference-workflow__header {
|
|
6903
6903
|
align-items: flex-start;
|
|
6904
6904
|
flex-direction: row;
|
|
6905
6905
|
gap: var(--paper-rhythm);
|
package/app/layout.tsx
CHANGED
|
@@ -5,7 +5,7 @@ import { AmplifyClientProvider } from "../components/amplify-client-provider";
|
|
|
5
5
|
import { PapyrusConsoleShell } from "../components/papyrus-console-shell";
|
|
6
6
|
import { TooltipProvider } from "../components/ui/tooltip";
|
|
7
7
|
import { resolveBrandFontAssets } from "../lib/brand-fonts";
|
|
8
|
-
import { getPresentationChoices, getSiteBrand } from "../lib/site-brand";
|
|
8
|
+
import { DEFAULT_SITE_BRAND_ICONS, getPresentationChoices, getSiteBrand } from "../lib/site-brand";
|
|
9
9
|
import { getSiteStack } from "../lib/site-stack";
|
|
10
10
|
import { isIndexable, showStagingBanner } from "../lib/site-env";
|
|
11
11
|
import { StagingBanner } from "../components/staging-banner";
|
|
@@ -34,9 +34,10 @@ export async function generateMetadata(): Promise<Metadata> {
|
|
|
34
34
|
...(isIndexable() ? {} : { robots: { index: false, follow: false } }),
|
|
35
35
|
icons: {
|
|
36
36
|
icon: [
|
|
37
|
-
{ url:
|
|
38
|
-
{ url:
|
|
37
|
+
{ url: `${siteBrand.icons?.light ?? DEFAULT_SITE_BRAND_ICONS.light}?v=${faviconVersion}`, type: "image/png" },
|
|
38
|
+
...(siteBrand.icons ? [] : [{ url: `${DEFAULT_SITE_BRAND_ICONS.fallback}?v=${faviconVersion}`, type: "image/png" }]),
|
|
39
39
|
],
|
|
40
|
+
...(siteBrand.icons?.appleTouch ? { apple: [{ url: `${siteBrand.icons.appleTouch}?v=${faviconVersion}`, type: "image/png" }] } : {}),
|
|
40
41
|
},
|
|
41
42
|
};
|
|
42
43
|
}
|
|
@@ -46,6 +47,8 @@ export default async function RootLayout({ children }: Readonly<{ children: Reac
|
|
|
46
47
|
const presentationChoices = getPresentationChoices(siteBrand);
|
|
47
48
|
const siteStack = getSiteStack(siteBrand);
|
|
48
49
|
const brandFonts = resolveBrandFontAssets(siteBrand.fonts);
|
|
50
|
+
const lightIconPath = siteBrand.icons?.light ?? DEFAULT_SITE_BRAND_ICONS.light;
|
|
51
|
+
const darkIconPath = siteBrand.icons ? siteBrand.icons.dark ?? siteBrand.icons.light : DEFAULT_SITE_BRAND_ICONS.dark;
|
|
49
52
|
|
|
50
53
|
return (
|
|
51
54
|
<html
|
|
@@ -78,8 +81,8 @@ export default async function RootLayout({ children }: Readonly<{ children: Reac
|
|
|
78
81
|
<Script id="papyrus-favicon-color-scheme" strategy="beforeInteractive">
|
|
79
82
|
{`(() => {
|
|
80
83
|
const version = "${faviconVersion}";
|
|
81
|
-
const lightHref = "
|
|
82
|
-
const darkHref = "
|
|
84
|
+
const lightHref = ${JSON.stringify(lightIconPath)} + "?v=" + version;
|
|
85
|
+
const darkHref = ${JSON.stringify(darkIconPath)} + "?v=" + version;
|
|
83
86
|
const settingsStorageKey = "papyrus:reader-settings";
|
|
84
87
|
const defaultTheme = "${defaultTheme}";
|
|
85
88
|
|
|
@@ -1,22 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { permanentRedirect } from "next/navigation";
|
|
2
|
+
import { toPublicNewsroomPath } from "../../../../lib/newsroom-base-path";
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
type NewsroomInsightThreadPageProps = {
|
|
4
|
+
type NewsroomMessageThreadRedirectPageProps = {
|
|
6
5
|
params: Promise<{ threadId: string }>;
|
|
7
|
-
searchParams?: Promise<{
|
|
8
|
-
demo?: string | string[];
|
|
9
|
-
domain?: string | string[];
|
|
10
|
-
}>;
|
|
11
6
|
};
|
|
12
7
|
|
|
13
|
-
export default async function
|
|
8
|
+
export default async function NewsroomMessageThreadRedirectPage({ params }: NewsroomMessageThreadRedirectPageProps) {
|
|
14
9
|
const { threadId } = await params;
|
|
15
|
-
|
|
16
|
-
<NewsDeskPage
|
|
17
|
-
section="insights"
|
|
18
|
-
selectionPath={[threadId]}
|
|
19
|
-
searchParams={searchParams}
|
|
20
|
-
/>
|
|
21
|
-
);
|
|
10
|
+
permanentRedirect(toPublicNewsroomPath(`/newsroom/messages/${encodeURIComponent(threadId)}`));
|
|
22
11
|
}
|
|
@@ -1,14 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { permanentRedirect } from "next/navigation";
|
|
2
|
+
import { toPublicNewsroomPath } from "../../../lib/newsroom-base-path";
|
|
2
3
|
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
type NewsroomInsightsPageProps = {
|
|
6
|
-
searchParams?: Promise<{
|
|
7
|
-
demo?: string | string[];
|
|
8
|
-
domain?: string | string[];
|
|
9
|
-
}>;
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
export default async function NewsroomInsightsPage({ searchParams }: NewsroomInsightsPageProps) {
|
|
13
|
-
return <NewsDeskPage section="insights" searchParams={searchParams} />;
|
|
4
|
+
export default function NewsroomMessagesRedirectPage() {
|
|
5
|
+
permanentRedirect(toPublicNewsroomPath("/newsroom/messages"));
|
|
14
6
|
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef, useState } from "react";
|
|
4
|
+
import { fetchAuthSession } from "aws-amplify/auth";
|
|
5
|
+
import { isLocalPreviewEnabled } from "../lib/local-preview";
|
|
6
|
+
import {
|
|
7
|
+
DEFAULT_DELEGATION_HOURS,
|
|
8
|
+
MAXIMUM_DELEGATION_HOURS,
|
|
9
|
+
MINIMUM_DELEGATION_HOURS,
|
|
10
|
+
buildDelegatedCommandLine,
|
|
11
|
+
delegationExpiryText,
|
|
12
|
+
describeDelegationError,
|
|
13
|
+
validateDelegationInput,
|
|
14
|
+
type CurationDelegationRecord,
|
|
15
|
+
} from "../lib/curation-delegation-helpers";
|
|
16
|
+
import { createCurationDelegation, listCurationDelegations, revokeCurationDelegation, type CreatedCurationDelegation } from "./curation-delegation-client";
|
|
17
|
+
|
|
18
|
+
type IssuedToken = { agentLabel: string; token: string };
|
|
19
|
+
|
|
20
|
+
async function readSignedInEmail(): Promise<string> {
|
|
21
|
+
if (isLocalPreviewEnabled()) return "ryan@example.com";
|
|
22
|
+
try {
|
|
23
|
+
const session = await fetchAuthSession();
|
|
24
|
+
const email = session.tokens?.idToken?.payload?.email;
|
|
25
|
+
return typeof email === "string" ? email : "";
|
|
26
|
+
} catch {
|
|
27
|
+
return "";
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function AdministrationDelegationsPanel({ disabled }: { disabled: boolean }) {
|
|
32
|
+
const [agentLabelText, setAgentLabelText] = useState("");
|
|
33
|
+
const [hoursText, setHoursText] = useState(String(DEFAULT_DELEGATION_HOURS));
|
|
34
|
+
const [busy, setBusy] = useState(false);
|
|
35
|
+
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
|
36
|
+
const [issued, setIssued] = useState<IssuedToken | null>(null);
|
|
37
|
+
const [copied, setCopied] = useState<"token" | "command" | null>(null);
|
|
38
|
+
const [email, setEmail] = useState("");
|
|
39
|
+
const [delegations, setDelegations] = useState<CurationDelegationRecord[]>([]);
|
|
40
|
+
const [confirmingRevokeId, setConfirmingRevokeId] = useState<string | null>(null);
|
|
41
|
+
const [nowMilliseconds, setNowMilliseconds] = useState(() => Date.now());
|
|
42
|
+
const mounted = useRef(true);
|
|
43
|
+
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
mounted.current = true;
|
|
46
|
+
void readSignedInEmail().then((value) => {
|
|
47
|
+
if (mounted.current) setEmail(value);
|
|
48
|
+
});
|
|
49
|
+
void refreshDelegations();
|
|
50
|
+
const timer = window.setInterval(() => setNowMilliseconds(Date.now()), 30_000);
|
|
51
|
+
return () => {
|
|
52
|
+
mounted.current = false;
|
|
53
|
+
window.clearInterval(timer);
|
|
54
|
+
setIssued(null);
|
|
55
|
+
};
|
|
56
|
+
}, []);
|
|
57
|
+
|
|
58
|
+
async function refreshDelegations() {
|
|
59
|
+
try {
|
|
60
|
+
const listed = await listCurationDelegations();
|
|
61
|
+
if (mounted.current) setDelegations(listed);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
if (mounted.current) setErrorMessage(describeDelegationError(error));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function handleCreate() {
|
|
68
|
+
const validation = validateDelegationInput(agentLabelText, hoursText);
|
|
69
|
+
if (!validation.ok) {
|
|
70
|
+
setErrorMessage(validation.message);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
setBusy(true);
|
|
74
|
+
setErrorMessage(null);
|
|
75
|
+
try {
|
|
76
|
+
const created: CreatedCurationDelegation = await createCurationDelegation(validation.agentLabel, validation.hours);
|
|
77
|
+
setIssued({ agentLabel: created.agentLabel, token: created.token });
|
|
78
|
+
setCopied(null);
|
|
79
|
+
await refreshDelegations();
|
|
80
|
+
setAgentLabelText("");
|
|
81
|
+
setNowMilliseconds(Date.now());
|
|
82
|
+
} catch (error) {
|
|
83
|
+
setErrorMessage(describeDelegationError(error));
|
|
84
|
+
} finally {
|
|
85
|
+
setBusy(false);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function handleRevoke(delegationId: string) {
|
|
90
|
+
setBusy(true);
|
|
91
|
+
setErrorMessage(null);
|
|
92
|
+
try {
|
|
93
|
+
await revokeCurationDelegation(delegationId);
|
|
94
|
+
await refreshDelegations();
|
|
95
|
+
setConfirmingRevokeId(null);
|
|
96
|
+
} catch (error) {
|
|
97
|
+
setErrorMessage(describeDelegationError(error));
|
|
98
|
+
} finally {
|
|
99
|
+
setBusy(false);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function copyText(kind: "token" | "command", text: string) {
|
|
104
|
+
try {
|
|
105
|
+
await navigator.clipboard.writeText(text);
|
|
106
|
+
setCopied(kind);
|
|
107
|
+
} catch {
|
|
108
|
+
setErrorMessage("Copying was blocked by the browser. Select the text and copy it by hand.");
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const commandLine = issued ? buildDelegatedCommandLine({ email, agentLabel: issued.agentLabel, token: issued.token }) : "";
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<div data-news-desk-admin-panel="delegations">
|
|
116
|
+
<article className="news-desk-doctrine-card">
|
|
117
|
+
<div className="news-desk-doctrine-card__header">
|
|
118
|
+
<div>
|
|
119
|
+
<h3>Agent delegations</h3>
|
|
120
|
+
<span>Let one agent approve or reject References for you, for a short time</span>
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
<p className="news-desk-delegation-note">
|
|
124
|
+
A delegation is a token you hand to one named agent. While it lasts, that agent's approvals and rejections are recorded under your name and marked as relayed. You can revoke it at any moment.
|
|
125
|
+
</p>
|
|
126
|
+
<p className="news-desk-delegation-note" data-delegation-words-note>
|
|
127
|
+
The agent must pass your actual words, in quotes, as the instruction on every approval or rejection. Papyrus keeps those words with the record. An agent that paraphrases or invents them is not following this delegation.
|
|
128
|
+
</p>
|
|
129
|
+
<div className="news-desk-delegation-form">
|
|
130
|
+
<label className="news-desk-doctrine-card__field">
|
|
131
|
+
<span>Agent name</span>
|
|
132
|
+
<input value={agentLabelText} onChange={(event) => setAgentLabelText(event.target.value)} placeholder="curator-agent" autoComplete="off" disabled={disabled || busy} />
|
|
133
|
+
</label>
|
|
134
|
+
<label className="news-desk-doctrine-card__field">
|
|
135
|
+
<span>{`Hours (${MINIMUM_DELEGATION_HOURS} to ${MAXIMUM_DELEGATION_HOURS})`}</span>
|
|
136
|
+
<input value={hoursText} onChange={(event) => setHoursText(event.target.value)} inputMode="numeric" disabled={disabled || busy} />
|
|
137
|
+
</label>
|
|
138
|
+
<button type="button" className="news-desk-primary-action-button" data-delegation-create onClick={() => void handleCreate()} disabled={disabled || busy}>
|
|
139
|
+
{busy && !issued ? "Creating" : "Create delegation"}
|
|
140
|
+
</button>
|
|
141
|
+
</div>
|
|
142
|
+
{errorMessage ? <p className="news-desk-delegation-error" role="alert" data-delegation-error>{errorMessage}</p> : null}
|
|
143
|
+
</article>
|
|
144
|
+
|
|
145
|
+
{issued ? (
|
|
146
|
+
<article className="news-desk-delegation-token" data-delegation-token-box aria-live="polite">
|
|
147
|
+
<h3>Copy this token now</h3>
|
|
148
|
+
<p>This token is shown once. Papyrus does not keep it, and it cannot be shown again. If you lose it, revoke the delegation and create a new one.</p>
|
|
149
|
+
<div className="news-desk-delegation-token__row">
|
|
150
|
+
<code data-delegation-token>{issued.token}</code>
|
|
151
|
+
<button type="button" onClick={() => void copyText("token", issued.token)}>{copied === "token" ? "Copied" : "Copy token"}</button>
|
|
152
|
+
</div>
|
|
153
|
+
<p className="story-label">Command line for {issued.agentLabel}</p>
|
|
154
|
+
<div className="news-desk-delegation-token__row">
|
|
155
|
+
<code data-delegation-command>{commandLine}</code>
|
|
156
|
+
<button type="button" onClick={() => void copyText("command", commandLine)}>{copied === "command" ? "Copied" : "Copy command"}</button>
|
|
157
|
+
</div>
|
|
158
|
+
<div className="news-desk-doctrine-card__actions">
|
|
159
|
+
<button type="button" className="news-desk-primary-action-button" data-delegation-done onClick={() => { setIssued(null); setCopied(null); }}>Done</button>
|
|
160
|
+
</div>
|
|
161
|
+
</article>
|
|
162
|
+
) : null}
|
|
163
|
+
|
|
164
|
+
<article className="news-desk-doctrine-card">
|
|
165
|
+
<div className="news-desk-doctrine-card__header">
|
|
166
|
+
<div>
|
|
167
|
+
<h3>Delegations</h3>
|
|
168
|
+
<span>Granted by you, newest first</span>
|
|
169
|
+
</div>
|
|
170
|
+
</div>
|
|
171
|
+
{delegations.length === 0 ? (
|
|
172
|
+
<p className="news-desk-delegation-note" data-delegation-empty>You have not granted any delegations.</p>
|
|
173
|
+
) : (
|
|
174
|
+
<ul className="news-desk-delegation-list">
|
|
175
|
+
{delegations.map((entry) => {
|
|
176
|
+
const stateText = delegationExpiryText(entry.expiresAt, entry.revokedAt, nowMilliseconds);
|
|
177
|
+
const live = !entry.revokedAt && stateText !== "Expired";
|
|
178
|
+
return (
|
|
179
|
+
<li key={entry.delegationId} data-delegation-row={entry.delegationId} data-delegation-live={live || undefined}>
|
|
180
|
+
<div>
|
|
181
|
+
<strong>{entry.agentLabel}</strong>
|
|
182
|
+
<span>{`Granted by ${entry.grantorLabel ?? "you"}`}</span>
|
|
183
|
+
</div>
|
|
184
|
+
<span className="news-desk-delegation-state">{stateText}</span>
|
|
185
|
+
{live ? (
|
|
186
|
+
confirmingRevokeId === entry.delegationId ? (
|
|
187
|
+
<span className="news-desk-delegation-confirm">
|
|
188
|
+
<span>{`Stop ${entry.agentLabel} from curating for you?`}</span>
|
|
189
|
+
<button type="button" data-delegation-revoke-confirm onClick={() => void handleRevoke(entry.delegationId)} disabled={busy}>Revoke now</button>
|
|
190
|
+
<button type="button" onClick={() => setConfirmingRevokeId(null)} disabled={busy}>Keep</button>
|
|
191
|
+
</span>
|
|
192
|
+
) : (
|
|
193
|
+
<button type="button" data-delegation-revoke onClick={() => setConfirmingRevokeId(entry.delegationId)} disabled={disabled || busy}>Revoke</button>
|
|
194
|
+
)
|
|
195
|
+
) : null}
|
|
196
|
+
</li>
|
|
197
|
+
);
|
|
198
|
+
})}
|
|
199
|
+
</ul>
|
|
200
|
+
)}
|
|
201
|
+
</article>
|
|
202
|
+
</div>
|
|
203
|
+
);
|
|
204
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import { createPortal } from "react-dom";
|
|
5
|
+
|
|
6
|
+
type Excerpt = { issueId: string; text: string };
|
|
7
|
+
|
|
8
|
+
function CardExcerpt({ text }: { text: string }) {
|
|
9
|
+
const [expanded, setExpanded] = useState(false);
|
|
10
|
+
return <div className="board-card-excerpt" data-board-card-excerpt data-expanded={expanded}>
|
|
11
|
+
<p>{text}</p>
|
|
12
|
+
{text.length > 130 && <button aria-expanded={expanded} onClick={event => { event.stopPropagation(); setExpanded(open => !open); }} onKeyDown={event => event.stopPropagation()} type="button">{expanded ? "Less" : "More"}</button>}
|
|
13
|
+
</div>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Adds a short expandable excerpt under each card's headline. The Kanbus card has no excerpt slot,
|
|
18
|
+
* so each excerpt is rendered into the card's content block once the card is on the page.
|
|
19
|
+
*/
|
|
20
|
+
export function BoardCardExcerpts({ excerpts, scope }: { excerpts: readonly Excerpt[]; scope: HTMLElement | null }) {
|
|
21
|
+
const [hosts, setHosts] = useState<{ host: HTMLElement; text: string }[]>([]);
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (!scope) return;
|
|
24
|
+
const collect = () => {
|
|
25
|
+
const found = excerpts.flatMap(({ issueId, text }) => {
|
|
26
|
+
const card = Array.from(scope.querySelectorAll<HTMLElement>(".issue-card")).find(candidate => candidate.dataset.issueId === issueId);
|
|
27
|
+
const content = card?.querySelector<HTMLElement>(".issue-card-content");
|
|
28
|
+
if (!content) return [];
|
|
29
|
+
let host = content.querySelector<HTMLElement>(":scope > [data-board-card-excerpt-host]");
|
|
30
|
+
if (!host) {
|
|
31
|
+
host = document.createElement("div");
|
|
32
|
+
host.dataset.boardCardExcerptHost = "true";
|
|
33
|
+
content.querySelector("h3")?.after(host);
|
|
34
|
+
}
|
|
35
|
+
return [{ host, text }];
|
|
36
|
+
});
|
|
37
|
+
setHosts(previous => previous.length === found.length && previous.every((entry, index) => entry.host === found[index].host && entry.text === found[index].text) ? previous : found);
|
|
38
|
+
};
|
|
39
|
+
collect();
|
|
40
|
+
const observer = new MutationObserver(collect);
|
|
41
|
+
observer.observe(scope, { childList: true, subtree: true });
|
|
42
|
+
return () => observer.disconnect();
|
|
43
|
+
}, [excerpts, scope]);
|
|
44
|
+
return <>{hosts.map(({ host, text }, index) => createPortal(<CardExcerpt text={text} />, host, `${index}`))}</>;
|
|
45
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { generateClient } from "aws-amplify/data";
|
|
4
|
+
import { isLocalPreviewEnabled } from "../lib/local-preview";
|
|
5
|
+
import type { CurationDelegationRecord } from "../lib/curation-delegation-helpers";
|
|
6
|
+
import { configureAmplifyClient } from "./amplify-client-provider";
|
|
7
|
+
|
|
8
|
+
const USER_POOL_AUTH_MODE = "userPool";
|
|
9
|
+
|
|
10
|
+
export type CreatedCurationDelegation = CurationDelegationRecord & { token: string };
|
|
11
|
+
|
|
12
|
+
const CREATE_MUTATION = `
|
|
13
|
+
mutation CreateCurationDelegation($agentLabel: String!, $hours: Int) {
|
|
14
|
+
createCurationDelegation(agentLabel: $agentLabel, hours: $hours) {
|
|
15
|
+
delegationId token agentLabel grantorLabel expiresAt revokedAt
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
`;
|
|
19
|
+
|
|
20
|
+
const REVOKE_MUTATION = `
|
|
21
|
+
mutation RevokeCurationDelegation($delegationId: ID!) {
|
|
22
|
+
revokeCurationDelegation(delegationId: $delegationId) {
|
|
23
|
+
delegationId agentLabel grantorLabel expiresAt revokedAt
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
`;
|
|
27
|
+
|
|
28
|
+
const LIST_QUERY = `
|
|
29
|
+
query ListCurationDelegations {
|
|
30
|
+
listCurationDelegations {
|
|
31
|
+
delegationId agentLabel grantorLabel expiresAt revokedAt
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
`;
|
|
35
|
+
|
|
36
|
+
const previewDelegations: CurationDelegationRecord[] = [];
|
|
37
|
+
|
|
38
|
+
async function runGraphql<T>(query: string, variables: Record<string, unknown>): Promise<T> {
|
|
39
|
+
configureAmplifyClient();
|
|
40
|
+
const client = generateClient() as unknown as {
|
|
41
|
+
graphql: (options: Record<string, unknown>) => Promise<{ data?: T | null; errors?: Array<{ message?: string }> | null }>;
|
|
42
|
+
};
|
|
43
|
+
const response = await client.graphql({ query, variables, authMode: USER_POOL_AUTH_MODE });
|
|
44
|
+
if (response.errors?.length) throw Object.assign(new Error(response.errors[0]?.message ?? "Request failed."), { errors: response.errors });
|
|
45
|
+
if (!response.data) throw new Error("The server returned no result.");
|
|
46
|
+
return response.data;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function createCurationDelegation(agentLabel: string, hours: number): Promise<CreatedCurationDelegation> {
|
|
50
|
+
if (isLocalPreviewEnabled()) {
|
|
51
|
+
const delegationId = `curation-delegation-preview-${previewDelegations.length + 1}`;
|
|
52
|
+
const record: CurationDelegationRecord = {
|
|
53
|
+
delegationId,
|
|
54
|
+
agentLabel,
|
|
55
|
+
grantorLabel: "ryan@example.com",
|
|
56
|
+
expiresAt: new Date(Date.now() + hours * 3_600_000).toISOString(),
|
|
57
|
+
revokedAt: null,
|
|
58
|
+
};
|
|
59
|
+
previewDelegations.push(record);
|
|
60
|
+
return { ...record, token: `${delegationId}~preview-token-not-valid-anywhere` };
|
|
61
|
+
}
|
|
62
|
+
const data = await runGraphql<{ createCurationDelegation: CreatedCurationDelegation }>(CREATE_MUTATION, { agentLabel, hours });
|
|
63
|
+
return data.createCurationDelegation;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function revokeCurationDelegation(delegationId: string): Promise<CurationDelegationRecord> {
|
|
67
|
+
if (isLocalPreviewEnabled()) {
|
|
68
|
+
const record = previewDelegations.find((entry) => entry.delegationId === delegationId);
|
|
69
|
+
if (!record) throw new Error("That delegation was not found.");
|
|
70
|
+
record.revokedAt = record.revokedAt ?? new Date().toISOString();
|
|
71
|
+
return { ...record };
|
|
72
|
+
}
|
|
73
|
+
const data = await runGraphql<{ revokeCurationDelegation: CurationDelegationRecord }>(REVOKE_MUTATION, { delegationId });
|
|
74
|
+
return data.revokeCurationDelegation;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function listCurationDelegations(): Promise<CurationDelegationRecord[]> {
|
|
78
|
+
if (isLocalPreviewEnabled()) return previewDelegations.map((entry) => ({ ...entry })).reverse();
|
|
79
|
+
const data = await runGraphql<{ listCurationDelegations: CurationDelegationRecord[] }>(LIST_QUERY, {});
|
|
80
|
+
return data.listCurationDelegations ?? [];
|
|
81
|
+
}
|