@anthusai/papyrus 1.4.0-next.4 → 1.4.0-next.6
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 +13 -3
- package/amplify/data/resource.ts +13 -3
- package/amplify/data/schema.js +10 -0
- package/amplify/data/schema.ts +10 -0
- package/amplify/functions/category-action/handler.ts +20 -19
- 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/reference-curation-metadata.ts +15 -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/board-card-excerpts.tsx +45 -0
- package/components/markus-ir-preview.tsx +47 -2
- package/components/news-desk-page.tsx +0 -1
- package/components/news-desk-taxonomy-client.ts +25 -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 +10 -2
- package/components/newsroom-ops-overview.tsx +27 -5
- package/components/newsroom-organizer-shell.tsx +25 -7
- package/components/newsroom-organizer.css +196 -4
- package/components/priming-proof-page.tsx +17 -8
- package/components/prospectus-account-chooser.tsx +98 -0
- package/components/story-reporting-trail-client.ts +91 -0
- package/components/story-reporting-trail.tsx +115 -0
- package/components/topic-steering-workspace.tsx +154 -671
- 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/front-matter-fields.ts +42 -0
- package/lib/graphql-content-repository.ts +42 -7
- package/lib/local-preview-seed.ts +55 -2
- 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/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 +15 -1
- package/routes.manifest.json +23 -7
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { NextResponse } from "next/server";
|
|
4
|
+
|
|
5
|
+
export const dynamic = "force-dynamic";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Local preview only: derives a story's Markus body through the real Python engine so directives render.
|
|
9
|
+
* Set PAPYRUS_PREVIEW_PYTHON to a Python that has Papyrus and its Markus extra installed.
|
|
10
|
+
*/
|
|
11
|
+
export async function POST(request: Request) {
|
|
12
|
+
if (process.env.NEXT_PUBLIC_PAPYRUS_LOCAL_PREVIEW !== "1") return new NextResponse(null, { status: 404 });
|
|
13
|
+
const python = process.env.PAPYRUS_PREVIEW_PYTHON?.trim();
|
|
14
|
+
if (!python) return NextResponse.json({ error: "PAPYRUS_PREVIEW_PYTHON is not set." }, { status: 501 });
|
|
15
|
+
const input = await request.text();
|
|
16
|
+
const script = path.join(process.cwd(), "scripts", "local-preview-derive.py");
|
|
17
|
+
const output = await new Promise<string>((resolve, reject) => {
|
|
18
|
+
const child = spawn(python, [script], { env: process.env });
|
|
19
|
+
let stdout = "";
|
|
20
|
+
let stderr = "";
|
|
21
|
+
child.stdout.on("data", chunk => { stdout += chunk; });
|
|
22
|
+
child.stderr.on("data", chunk => { stderr += chunk; });
|
|
23
|
+
child.on("error", reject);
|
|
24
|
+
child.on("close", code => code === 0 ? resolve(stdout) : reject(new Error(stderr || `derive exited with ${code}`)));
|
|
25
|
+
child.stdin.end(input);
|
|
26
|
+
}).catch((reason: unknown) => JSON.stringify({ error: reason instanceof Error ? reason.message : "derive failed" }));
|
|
27
|
+
return new NextResponse(output, { headers: { "content-type": "application/json" } });
|
|
28
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { NextResponse } from "next/server";
|
|
3
|
+
|
|
4
|
+
export const dynamic = "force-dynamic";
|
|
5
|
+
|
|
6
|
+
const CONTENT_TYPES: Record<string, string> = { jpg: "image/jpeg", jpeg: "image/jpeg", png: "image/png", gif: "image/gif", webp: "image/webp", svg: "image/svg+xml", avif: "image/avif" };
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Local preview only: streams one story image from the publication's storage bucket so the previews show real figures.
|
|
10
|
+
* Set PAPYRUS_PREVIEW_MEDIA_BUCKET to the bucket; the server's AWS credentials do the reading.
|
|
11
|
+
*/
|
|
12
|
+
export async function GET(request: Request) {
|
|
13
|
+
if (process.env.NEXT_PUBLIC_PAPYRUS_LOCAL_PREVIEW !== "1") return new NextResponse(null, { status: 404 });
|
|
14
|
+
const bucket = process.env.PAPYRUS_PREVIEW_MEDIA_BUCKET?.trim();
|
|
15
|
+
const key = new URL(request.url).searchParams.get("key") ?? "";
|
|
16
|
+
if (!bucket) return NextResponse.json({ error: "PAPYRUS_PREVIEW_MEDIA_BUCKET is not set." }, { status: 501 });
|
|
17
|
+
if (!/^media\/[A-Za-z0-9_\-./]+$/.test(key) || key.includes("..")) return new NextResponse(null, { status: 400 });
|
|
18
|
+
const extension = key.split(".").pop()?.toLowerCase() ?? "";
|
|
19
|
+
const child = spawn("aws", ["s3", "cp", `s3://${bucket}/${key}`, "-"], { env: process.env });
|
|
20
|
+
const chunks: Buffer[] = [];
|
|
21
|
+
child.stdout.on("data", chunk => chunks.push(chunk));
|
|
22
|
+
const exitCode = await new Promise<number>(resolve => { child.on("close", code => resolve(code ?? 1)); child.on("error", () => resolve(1)); });
|
|
23
|
+
if (exitCode !== 0) return new NextResponse(null, { status: 404 });
|
|
24
|
+
return new NextResponse(Buffer.concat(chunks), { headers: { "content-type": CONTENT_TYPES[extension] ?? "application/octet-stream", "cache-control": "private, max-age=300" } });
|
|
25
|
+
}
|
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,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
|
+
}
|
|
@@ -21,6 +21,8 @@ type PreviewContext = {
|
|
|
21
21
|
sidecar: PreviewSidecar;
|
|
22
22
|
citationNumbers: Map<string, number>;
|
|
23
23
|
imageUrls: Record<string, string>;
|
|
24
|
+
dynamicDirectives: Record<string, (attributes: Record<string, unknown>) => ReactNode>;
|
|
25
|
+
renderLink?: (href: string, children: ReactNode) => ReactNode | null;
|
|
24
26
|
};
|
|
25
27
|
|
|
26
28
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
@@ -62,7 +64,7 @@ function imagePlaceholder(token: string, context: PreviewContext): ReactNode {
|
|
|
62
64
|
// eslint-disable-next-line @next/next/no-img-element
|
|
63
65
|
<img
|
|
64
66
|
alt={alt}
|
|
65
|
-
className="my-3 h-auto max-w-full
|
|
67
|
+
className="my-3 h-auto max-w-full"
|
|
66
68
|
data-markus-preview-image-loaded
|
|
67
69
|
loading="lazy"
|
|
68
70
|
src={url}
|
|
@@ -112,7 +114,9 @@ function renderInlines(inlines: MarkusInline[], context: PreviewContext): ReactN
|
|
|
112
114
|
return <s key={index}>{renderInlines(node.children, context)}</s>;
|
|
113
115
|
case "code_span":
|
|
114
116
|
return <code className="rounded bg-muted px-1 py-0.5 font-mono text-[0.85em]" key={index}>{node.code}</code>;
|
|
115
|
-
case "link":
|
|
117
|
+
case "link": {
|
|
118
|
+
const custom = node.href ? context.renderLink?.(node.href, renderInlines(node.children, context)) : null;
|
|
119
|
+
if (custom) return <span key={index}>{custom}</span>;
|
|
116
120
|
return node.href ? (
|
|
117
121
|
<a className="text-primary underline" href={node.href} key={index} rel="noopener noreferrer" target="_blank">
|
|
118
122
|
{renderInlines(node.children, context)}
|
|
@@ -120,6 +124,7 @@ function renderInlines(inlines: MarkusInline[], context: PreviewContext): ReactN
|
|
|
120
124
|
) : (
|
|
121
125
|
<span key={index}>{renderInlines(node.children, context)}</span>
|
|
122
126
|
);
|
|
127
|
+
}
|
|
123
128
|
case "image":
|
|
124
129
|
return (
|
|
125
130
|
<span className="rounded border border-dashed border-border px-1 text-xs text-muted-foreground" key={index}>
|
|
@@ -228,6 +233,36 @@ function renderBlock(block: MarkusBlock, index: number, context: PreviewContext)
|
|
|
228
233
|
}
|
|
229
234
|
}
|
|
230
235
|
|
|
236
|
+
function normalizedFigureSource(src: string): string {
|
|
237
|
+
return src.replace(/^(\.\.?\/)+/, "");
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function renderFigure(directive: MarkusDirective, index: number, context: PreviewContext): ReactNode {
|
|
241
|
+
const attribute = (name: string) => typeof directive.attributes[name] === "string" ? String(directive.attributes[name]) : "";
|
|
242
|
+
const src = attribute("src");
|
|
243
|
+
const alt = attribute("alt");
|
|
244
|
+
const caption = attribute("caption");
|
|
245
|
+
const credit = attribute("credit");
|
|
246
|
+
const url = context.imageUrls[src] ?? context.imageUrls[normalizedFigureSource(src)];
|
|
247
|
+
return (
|
|
248
|
+
<figure className="my-4" data-markus-preview-directive="figure" key={index}>
|
|
249
|
+
{url ? (
|
|
250
|
+
// eslint-disable-next-line @next/next/no-img-element
|
|
251
|
+
<img alt={alt} className="h-auto max-w-full" data-markus-preview-image-loaded loading="lazy" src={url} />
|
|
252
|
+
) : (
|
|
253
|
+
<div className="border border-dashed border-border bg-muted/30 px-3 py-6 text-center text-xs text-muted-foreground" data-markus-preview-image>
|
|
254
|
+
{`Figure: ${src}${alt ? ` (${alt})` : ""}`}
|
|
255
|
+
</div>
|
|
256
|
+
)}
|
|
257
|
+
{caption || credit ? (
|
|
258
|
+
<figcaption className="mt-1 text-sm text-muted-foreground">
|
|
259
|
+
{caption}{caption && credit ? " " : ""}{credit ? <span className="text-xs">{credit}</span> : null}
|
|
260
|
+
</figcaption>
|
|
261
|
+
) : null}
|
|
262
|
+
</figure>
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
231
266
|
function renderDirective(directive: MarkusDirective, index: number, context: PreviewContext): ReactNode {
|
|
232
267
|
if (directive.name === "pull-quote") {
|
|
233
268
|
const attribution = directive.attributes.attribution;
|
|
@@ -244,6 +279,9 @@ function renderDirective(directive: MarkusDirective, index: number, context: Pre
|
|
|
244
279
|
</blockquote>
|
|
245
280
|
);
|
|
246
281
|
}
|
|
282
|
+
if (directive.name === "figure") return renderFigure(directive, index, context);
|
|
283
|
+
const dynamicRenderer = context.dynamicDirectives[directive.name];
|
|
284
|
+
if (dynamicRenderer) return <div data-markus-preview-directive={directive.name} key={index}>{dynamicRenderer(directive.attributes)}</div>;
|
|
247
285
|
const attributeEntries = Object.entries(directive.attributes).filter(([, value]) => value !== null && value !== "");
|
|
248
286
|
return (
|
|
249
287
|
<section
|
|
@@ -275,9 +313,14 @@ function renderBlocks(nodes: MarkusNode[], context: PreviewContext): ReactNode[]
|
|
|
275
313
|
export function MarkusIrPreview({
|
|
276
314
|
bodyIr,
|
|
277
315
|
imageUrls = {},
|
|
316
|
+
dynamicDirectives = {},
|
|
317
|
+
renderLink,
|
|
278
318
|
}: {
|
|
279
319
|
bodyIr: unknown;
|
|
280
320
|
imageUrls?: Record<string, string>;
|
|
321
|
+
/** Live components for leaf directives whose content the host app computes, keyed by directive name. */
|
|
322
|
+
dynamicDirectives?: PreviewContext["dynamicDirectives"];
|
|
323
|
+
renderLink?: PreviewContext["renderLink"];
|
|
281
324
|
}) {
|
|
282
325
|
try {
|
|
283
326
|
const envelope = typeof bodyIr === "string" ? JSON.parse(bodyIr) : bodyIr;
|
|
@@ -288,6 +331,8 @@ export function MarkusIrPreview({
|
|
|
288
331
|
sidecar,
|
|
289
332
|
citationNumbers: numberCitationsByFirstAppearance(envelope.document, sidecar),
|
|
290
333
|
imageUrls,
|
|
334
|
+
dynamicDirectives,
|
|
335
|
+
renderLink,
|
|
291
336
|
};
|
|
292
337
|
return <div data-markus-preview>{renderBlocks(document.children, context)}</div>;
|
|
293
338
|
} catch (error) {
|
|
@@ -86,7 +86,6 @@ function parseRouteSelection(tab: NewsDeskTab, selectionPath: string[] | null |
|
|
|
86
86
|
if (segments[0] === "forum" && segments[1]) return { forumThread: segments[1] };
|
|
87
87
|
return { message: segments[0] ?? null };
|
|
88
88
|
}
|
|
89
|
-
if (tab === "insights") return { forumThread: segments[0] ?? null };
|
|
90
89
|
if (tab === "assignments") return { assignment: segments[0] ?? null };
|
|
91
90
|
if (tab === "overview" && segments[0] === "forum" && segments[1]) return { forumThread: segments[1] };
|
|
92
91
|
return {};
|