@anthusai/papyrus 1.10.0 → 1.11.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 +3 -3
- package/amplify/data/resource.ts +3 -3
- package/amplify/functions/category-action/handler.ts +18 -18
- 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/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 +9 -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 +103 -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/front-matter-fields.ts +42 -0
- package/lib/local-preview-seed.ts +33 -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/semantic-graph.ts +5 -5
- package/lib/site-brand.ts +9 -0
- package/lib/story-reporting-trail.ts +106 -0
- package/lib/wiki.ts +56 -0
- package/package.json +12 -1
- package/routes.manifest.json +23 -7
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { WikiConceptsView } from "./wiki-concepts-view";
|
|
3
4
|
import { Hub } from "aws-amplify/utils";
|
|
4
5
|
import { fetchAuthSession } from "aws-amplify/auth";
|
|
5
6
|
import { gsap } from "gsap";
|
|
@@ -34,17 +35,13 @@ import {
|
|
|
34
35
|
loadEditorProcedureData,
|
|
35
36
|
loadEditorSemanticRelationsData,
|
|
36
37
|
loadEditionForumThreads,
|
|
37
|
-
loadInsightForumThreadById,
|
|
38
|
-
loadInsightForumThreads,
|
|
39
38
|
loadNewsroomAssignmentPage,
|
|
40
39
|
loadNewsroomMessagePage,
|
|
41
40
|
loadNewsroomReferencePage,
|
|
42
41
|
loadNewsroomSemanticNodePage,
|
|
43
42
|
appendForumThreadMessageRecord,
|
|
44
|
-
appendInsightThreadReplyRecord,
|
|
45
43
|
createSectionForumThreadRecord,
|
|
46
44
|
deleteForumThreadMessageRecord,
|
|
47
|
-
deleteInsightThreadMessageRecord,
|
|
48
45
|
ensureEditionForumThreadRecord,
|
|
49
46
|
publishProcedureVersionRecord,
|
|
50
47
|
runNewsroomKnowledgeQuery,
|
|
@@ -68,11 +65,8 @@ import {
|
|
|
68
65
|
effectiveReferencesIndexFilters,
|
|
69
66
|
normalizeReferenceIndexOrder,
|
|
70
67
|
readAssignmentsIndexFilters,
|
|
71
|
-
readInsightsIndexFilters,
|
|
72
68
|
readMessagesIndexFilters,
|
|
73
69
|
readReferencesIndexFilters,
|
|
74
|
-
effectiveInsightsIndexFilters,
|
|
75
|
-
parseInsightThreadIdFromNewsroomPathname,
|
|
76
70
|
referencesStatusFromUrl,
|
|
77
71
|
referencesStatusToUrl,
|
|
78
72
|
type ReferenceIndexOrder,
|
|
@@ -201,12 +195,10 @@ import {
|
|
|
201
195
|
} from "../lib/reporting-story-budget";
|
|
202
196
|
import {
|
|
203
197
|
buildForumThreadUrl,
|
|
204
|
-
buildInsightThreadUrl,
|
|
205
198
|
getForumMessageAnchorId,
|
|
206
199
|
isForumThreadId,
|
|
207
200
|
parseForumMessageAnchorFromHash,
|
|
208
201
|
pushForumThreadUrl,
|
|
209
|
-
pushInsightThreadUrl,
|
|
210
202
|
readCurrentForumRoute,
|
|
211
203
|
} from "../lib/newsroom-forum-routes";
|
|
212
204
|
|
|
@@ -247,7 +239,7 @@ type ReportingPacketReviewDecision = "select" | "merge" | "brief" | "hold" | "ki
|
|
|
247
239
|
type AssignmentDeskViewMode = "queue" | "budget";
|
|
248
240
|
type UserRoleAction = "grant" | "revoke";
|
|
249
241
|
type AdministrationPanel = "users" | "policies" | "sections" | "procedures" | "signups";
|
|
250
|
-
export type NewsDeskTab = "overview" | "desks" | "topics" | "concepts" | "references" | "
|
|
242
|
+
export type NewsDeskTab = "overview" | "desks" | "topics" | "concepts" | "references" | "messages" | "assignments" | "administration" | "search";
|
|
251
243
|
type LexicalRuleScope = "publication" | "corpus" | "classifier" | "category";
|
|
252
244
|
type AnalysisReindexMode = "online-update" | "classifier-retrain" | "scoped-topic-rebuild" | "entity-graph-rebuild" | "generated-analysis-rebuild";
|
|
253
245
|
type ReferenceProcessingStatus = "created" | "processable" | "processed" | "blocked";
|
|
@@ -324,7 +316,7 @@ type NewsroomDetailAction = {
|
|
|
324
316
|
onSelect: () => void;
|
|
325
317
|
};
|
|
326
318
|
|
|
327
|
-
type
|
|
319
|
+
type NoteTarget = {
|
|
328
320
|
kind: "assignment" | "category" | "item" | "newsroomSection" | "reference" | "semanticNode";
|
|
329
321
|
id: string;
|
|
330
322
|
lineageId: string;
|
|
@@ -333,7 +325,7 @@ type InsightTarget = {
|
|
|
333
325
|
versionNumber?: number | null;
|
|
334
326
|
};
|
|
335
327
|
|
|
336
|
-
type
|
|
328
|
+
type NoteComposerControl = {
|
|
337
329
|
action: NewsroomDetailAction;
|
|
338
330
|
dialog: ReactNode;
|
|
339
331
|
error: string | null;
|
|
@@ -460,7 +452,7 @@ type ReferenceQualityResponse = {
|
|
|
460
452
|
errors?: Array<{ message?: string | null } | string | null> | null;
|
|
461
453
|
};
|
|
462
454
|
|
|
463
|
-
type
|
|
455
|
+
type ReferenceNoteResponse = {
|
|
464
456
|
data?: {
|
|
465
457
|
ok?: boolean | null;
|
|
466
458
|
referenceId?: string | null;
|
|
@@ -927,9 +919,6 @@ function NewsDeskDashboard({
|
|
|
927
919
|
overview: 0,
|
|
928
920
|
desks: summaryCountFromRecord(summary, "categories"),
|
|
929
921
|
messages: summaryCountFromRecord(summary, "messages"),
|
|
930
|
-
insights: summary?.facets?.messages?.byKind?.insight
|
|
931
|
-
?? summary?.messageKindCounts?.insight
|
|
932
|
-
?? null,
|
|
933
922
|
assignments: summaryCountFromRecord(summary, "assignments"),
|
|
934
923
|
references: summaryCountFromRecord(summary, "references")
|
|
935
924
|
?? (hasHydratedReferences ? references.length : null),
|
|
@@ -1895,11 +1884,11 @@ function NewsDeskDashboard({
|
|
|
1895
1884
|
});
|
|
1896
1885
|
}
|
|
1897
1886
|
|
|
1898
|
-
async function
|
|
1887
|
+
async function createNote(target: NoteTarget, summary: string, body: string): Promise<void> {
|
|
1899
1888
|
const cleanSummary = summary.trim();
|
|
1900
1889
|
const cleanBody = body.trim();
|
|
1901
|
-
if (!cleanSummary) throw new Error("
|
|
1902
|
-
if (!cleanBody) throw new Error("
|
|
1890
|
+
if (!cleanSummary) throw new Error("Note summary is required.");
|
|
1891
|
+
if (!cleanBody) throw new Error("Note body is required.");
|
|
1903
1892
|
const now = new Date().toISOString();
|
|
1904
1893
|
const subjectSeed = [
|
|
1905
1894
|
target.kind,
|
|
@@ -1908,10 +1897,10 @@ function NewsDeskDashboard({
|
|
|
1908
1897
|
cleanBody,
|
|
1909
1898
|
now,
|
|
1910
1899
|
];
|
|
1911
|
-
const messageId = `message-
|
|
1900
|
+
const messageId = `message-note-${safeUiId(target.kind)}-${safeUiId(target.lineageId)}-${hashUiKey(subjectSeed)}`;
|
|
1912
1901
|
const message: MessageRecord = {
|
|
1913
1902
|
id: messageId,
|
|
1914
|
-
messageKind: "
|
|
1903
|
+
messageKind: "note",
|
|
1915
1904
|
messageDomain: "knowledge",
|
|
1916
1905
|
status: "active",
|
|
1917
1906
|
summary: cleanSummary,
|
|
@@ -1929,18 +1918,18 @@ function NewsDeskDashboard({
|
|
|
1929
1918
|
targetLineageId: target.lineageId,
|
|
1930
1919
|
},
|
|
1931
1920
|
};
|
|
1932
|
-
const relation =
|
|
1933
|
-
setActionState({ id: messageId, message: "
|
|
1921
|
+
const relation = buildUiNoteRelation(message, target, now);
|
|
1922
|
+
setActionState({ id: messageId, message: "note pending", tone: "pending" });
|
|
1934
1923
|
|
|
1935
1924
|
if (dashboard.isDemo) {
|
|
1936
1925
|
setMessages((current) => [message, ...current.filter((entry) => entry.id !== message.id)]);
|
|
1937
1926
|
setSemanticRelations((current) => [relation, ...current.filter((entry) => entry.id !== relation.id)]);
|
|
1938
|
-
setActionState({ id: messageId, message: "
|
|
1927
|
+
setActionState({ id: messageId, message: "note saved", tone: "ok" });
|
|
1939
1928
|
return;
|
|
1940
1929
|
}
|
|
1941
1930
|
|
|
1942
1931
|
if (target.kind === "reference") {
|
|
1943
|
-
const response = await dataClient.mutations.
|
|
1932
|
+
const response = await dataClient.mutations.createReferenceNote(
|
|
1944
1933
|
{
|
|
1945
1934
|
referenceId: target.id,
|
|
1946
1935
|
summary: cleanSummary,
|
|
@@ -1949,7 +1938,7 @@ function NewsDeskDashboard({
|
|
|
1949
1938
|
},
|
|
1950
1939
|
{ authMode: USER_POOL_AUTH_MODE },
|
|
1951
1940
|
);
|
|
1952
|
-
const created =
|
|
1941
|
+
const created = assertReferenceNoteMutationSucceeded(response as ReferenceNoteResponse, target.id);
|
|
1953
1942
|
const persistedMessageId = created.messageId ?? messageId;
|
|
1954
1943
|
const persistedRelationId = created.relationId ?? relation.id;
|
|
1955
1944
|
const persistedMessage: MessageRecord = {
|
|
@@ -1966,7 +1955,7 @@ function NewsDeskDashboard({
|
|
|
1966
1955
|
};
|
|
1967
1956
|
setMessages((current) => [persistedMessage, ...current.filter((entry) => entry.id !== persistedMessage.id)]);
|
|
1968
1957
|
setSemanticRelations((current) => [persistedRelation, ...current.filter((entry) => entry.id !== persistedRelation.id)]);
|
|
1969
|
-
setActionState({ id: persistedMessageId, message: "
|
|
1958
|
+
setActionState({ id: persistedMessageId, message: "note saved", tone: "ok" });
|
|
1970
1959
|
return;
|
|
1971
1960
|
}
|
|
1972
1961
|
|
|
@@ -2004,7 +1993,7 @@ function NewsDeskDashboard({
|
|
|
2004
1993
|
assertNoGraphQLErrors(relationResponse.errors);
|
|
2005
1994
|
setMessages((current) => [message, ...current.filter((entry) => entry.id !== message.id)]);
|
|
2006
1995
|
setSemanticRelations((current) => [relation, ...current.filter((entry) => entry.id !== relation.id)]);
|
|
2007
|
-
setActionState({ id: messageId, message: "
|
|
1996
|
+
setActionState({ id: messageId, message: "note saved", tone: "ok" });
|
|
2008
1997
|
}
|
|
2009
1998
|
|
|
2010
1999
|
function createLexicalSteeringRule(draft: LexicalRuleDraft) {
|
|
@@ -3073,7 +3062,7 @@ function NewsDeskDashboard({
|
|
|
3073
3062
|
|
|
3074
3063
|
const shellTabCounts = useMemo(() => {
|
|
3075
3064
|
const entries: Partial<Record<string, NewsroomNavCount>> = {};
|
|
3076
|
-
for (const tab of ["overview", "messages", "
|
|
3065
|
+
for (const tab of ["overview", "messages", "assignments", "references", "topics", "concepts", "administration"] as NewsDeskTab[]) {
|
|
3077
3066
|
if (tab === "administration") continue;
|
|
3078
3067
|
entries[tab] = {
|
|
3079
3068
|
count: tabCounts[tab],
|
|
@@ -3087,7 +3076,9 @@ function NewsDeskDashboard({
|
|
|
3087
3076
|
? mastheadTitle
|
|
3088
3077
|
: activeTab === "overview"
|
|
3089
3078
|
? "Overview"
|
|
3090
|
-
:
|
|
3079
|
+
: activeTab === "administration" && administrationPanel === "sections"
|
|
3080
|
+
? "Sections"
|
|
3081
|
+
: formatDeskSectionHeadline(activeTab);
|
|
3091
3082
|
|
|
3092
3083
|
return (
|
|
3093
3084
|
<>
|
|
@@ -3124,8 +3115,8 @@ function NewsDeskDashboard({
|
|
|
3124
3115
|
>
|
|
3125
3116
|
{activeTab !== "overview" && activeTab !== "assignments" && activeTab !== "messages" && activeTab !== "references" && activeTab !== "topics" && activeTab !== "concepts" && activeTab !== "search" ? (
|
|
3126
3117
|
<NewsroomOpsSectionIntro
|
|
3127
|
-
description={formatDeskSectionLede(activeTab)}
|
|
3128
|
-
title={formatDeskSectionHeadline(activeTab)}
|
|
3118
|
+
description={activeTab === "administration" && administrationPanel === "sections" ? "Organize the publication's sections and the ontology that classifies its coverage." : formatDeskSectionLede(activeTab)}
|
|
3119
|
+
title={activeTab === "administration" && administrationPanel === "sections" ? "Sections" : formatDeskSectionHeadline(activeTab)}
|
|
3129
3120
|
/>
|
|
3130
3121
|
) : null}
|
|
3131
3122
|
|
|
@@ -3145,7 +3136,7 @@ function NewsDeskDashboard({
|
|
|
3145
3136
|
<DeepNewsroomSectionView
|
|
3146
3137
|
demo={Boolean(dashboard.isDemo)}
|
|
3147
3138
|
disabled={controlsDisabled}
|
|
3148
|
-
|
|
3139
|
+
onCreateNote={createNote}
|
|
3149
3140
|
section={activeNewsroomSection}
|
|
3150
3141
|
sectionId={sectionPageId ?? ""}
|
|
3151
3142
|
sectionsLoaded={loadedSections.sections}
|
|
@@ -3171,6 +3162,12 @@ function NewsDeskDashboard({
|
|
|
3171
3162
|
semanticNodes={semanticNodes}
|
|
3172
3163
|
/>
|
|
3173
3164
|
) : null}
|
|
3165
|
+
{!isSectionPage && (activeTab === "desks" || activeTab === "topics") ? (
|
|
3166
|
+
<nav aria-label="Taxonomy views" className="taxonomy-view-switch" data-taxonomy-view-switch>
|
|
3167
|
+
<Link aria-current={activeTab === "desks" ? "page" : undefined} href={getNewsroomNavHref("/newsroom/desks", Boolean(dashboard.isDemo))}>Categories</Link>
|
|
3168
|
+
<Link aria-current={activeTab === "topics" ? "page" : undefined} href={getNewsroomNavHref("/newsroom/topics", Boolean(dashboard.isDemo))}>Topic proposals</Link>
|
|
3169
|
+
</nav>
|
|
3170
|
+
) : null}
|
|
3174
3171
|
{!isSectionPage && activeTab === "desks" ? (
|
|
3175
3172
|
<DesksDeskView
|
|
3176
3173
|
assignments={assignments}
|
|
@@ -3198,14 +3195,10 @@ function NewsDeskDashboard({
|
|
|
3198
3195
|
) : null}
|
|
3199
3196
|
{!isSectionPage && activeTab === "concepts" ? (
|
|
3200
3197
|
<ConceptsDeskView
|
|
3201
|
-
categories={mergeCategoryRecords(categorys, activeCategoryTreeNodes)}
|
|
3202
|
-
disabled={controlsDisabled}
|
|
3203
3198
|
graph={graph}
|
|
3204
|
-
initialCategoryLineageId={initialSelection.category}
|
|
3205
3199
|
initialNodeLineageId={initialSelection.node}
|
|
3206
|
-
|
|
3200
|
+
isDemo={Boolean(dashboard.isDemo)}
|
|
3207
3201
|
semanticNodes={semanticNodes}
|
|
3208
|
-
summary={summary}
|
|
3209
3202
|
/>
|
|
3210
3203
|
) : null}
|
|
3211
3204
|
{!isSectionPage && activeTab === "references" ? (
|
|
@@ -3241,15 +3234,6 @@ function NewsDeskDashboard({
|
|
|
3241
3234
|
references={references}
|
|
3242
3235
|
/>
|
|
3243
3236
|
) : null}
|
|
3244
|
-
{!isSectionPage && activeTab === "insights" ? (
|
|
3245
|
-
<InsightsDeskView
|
|
3246
|
-
graph={graph}
|
|
3247
|
-
initialThreadId={initialSelection.forumThread}
|
|
3248
|
-
isDemo={Boolean(dashboard.isDemo)}
|
|
3249
|
-
messages={messages}
|
|
3250
|
-
summary={summary}
|
|
3251
|
-
/>
|
|
3252
|
-
) : null}
|
|
3253
3237
|
{!isSectionPage && activeTab === "messages" ? (
|
|
3254
3238
|
<MessagesDeskView
|
|
3255
3239
|
assignments={assignments}
|
|
@@ -4193,20 +4177,20 @@ function RotatingSectionTriangleIcon({ expanded }: { expanded: boolean }) {
|
|
|
4193
4177
|
function DeepNewsroomSectionView({
|
|
4194
4178
|
demo,
|
|
4195
4179
|
disabled,
|
|
4196
|
-
|
|
4180
|
+
onCreateNote,
|
|
4197
4181
|
section,
|
|
4198
4182
|
sectionId,
|
|
4199
4183
|
sectionsLoaded,
|
|
4200
4184
|
}: {
|
|
4201
4185
|
demo: boolean;
|
|
4202
4186
|
disabled: boolean;
|
|
4203
|
-
|
|
4187
|
+
onCreateNote: (target: NoteTarget, summary: string, body: string) => Promise<void>;
|
|
4204
4188
|
section: NewsroomSectionRecord | null;
|
|
4205
4189
|
sectionId: string;
|
|
4206
4190
|
sectionsLoaded: boolean;
|
|
4207
4191
|
}) {
|
|
4208
|
-
const
|
|
4209
|
-
const
|
|
4192
|
+
const noteTarget = section ? noteTargetForNewsroomSection(section) : null;
|
|
4193
|
+
const noteComposer = useNewsroomNoteComposer(noteTarget, disabled, onCreateNote);
|
|
4210
4194
|
|
|
4211
4195
|
if (!section) {
|
|
4212
4196
|
return (
|
|
@@ -4235,13 +4219,13 @@ function DeepNewsroomSectionView({
|
|
|
4235
4219
|
<h2 data-newsroom-deep-section-title>{section.title}</h2>
|
|
4236
4220
|
<p>{displayNewsroomSectionShortTitle(section)}</p>
|
|
4237
4221
|
<div className="newsroom-deep-section__header-actions">
|
|
4238
|
-
<button type="button" disabled={
|
|
4239
|
-
{
|
|
4222
|
+
<button type="button" disabled={noteComposer.action.disabled} onClick={noteComposer.action.onSelect}>
|
|
4223
|
+
{noteComposer.loading ? "Saving Note" : "Add Note"}
|
|
4240
4224
|
</button>
|
|
4241
4225
|
<Link href={getNewsDeskTabHref("/newsroom", demo)}>Back to Newsroom</Link>
|
|
4242
4226
|
</div>
|
|
4243
4227
|
</header>
|
|
4244
|
-
{
|
|
4228
|
+
{noteComposer.error ? <div className="category-steering-alert" role="status">{noteComposer.error}</div> : null}
|
|
4245
4229
|
<div className="newsroom-deep-section__body">
|
|
4246
4230
|
<article className="newsroom-deep-section__field newsroom-deep-section__field--lead">
|
|
4247
4231
|
<p className="story-label">Mission</p>
|
|
@@ -4258,7 +4242,7 @@ function DeepNewsroomSectionView({
|
|
|
4258
4242
|
</article>
|
|
4259
4243
|
))}
|
|
4260
4244
|
</div>
|
|
4261
|
-
{
|
|
4245
|
+
{noteComposer.dialog}
|
|
4262
4246
|
</section>
|
|
4263
4247
|
);
|
|
4264
4248
|
}
|
|
@@ -4449,9 +4433,9 @@ function AdministrationDeskView({
|
|
|
4449
4433
|
<div className="news-desk-columns news-desk-columns--administration" data-news-desk-section="administration">
|
|
4450
4434
|
<div className="news-desk-main-column">
|
|
4451
4435
|
<section className="category-steering-section category-steering-section--lead" aria-labelledby="newsroom-administration-title">
|
|
4452
|
-
<SectionHeader title="Administration" detail="Settings for users, policies,
|
|
4453
|
-
<div className="news-desk-settings-shell">
|
|
4454
|
-
<nav className="news-desk-settings-nav" aria-label="Administration settings">
|
|
4436
|
+
<SectionHeader title={administrationPanel === "sections" ? "Sections" : "Administration"} detail={administrationPanel === "sections" ? "The publication's sections and ontology" : "Settings for users, policies, and procedures"} />
|
|
4437
|
+
<div className="news-desk-settings-shell" data-settings-nav={administrationPanel === "sections" ? "hidden" : undefined}>
|
|
4438
|
+
{administrationPanel === "sections" ? null : <nav className="news-desk-settings-nav" aria-label="Administration settings">
|
|
4455
4439
|
<Link
|
|
4456
4440
|
href={administrationPanelHref("users", isDemo)}
|
|
4457
4441
|
data-news-desk-admin-nav="users"
|
|
@@ -4470,15 +4454,6 @@ function AdministrationDeskView({
|
|
|
4470
4454
|
<strong>Policies</strong>
|
|
4471
4455
|
<span>Publication and category doctrine</span>
|
|
4472
4456
|
</Link>
|
|
4473
|
-
<Link
|
|
4474
|
-
href={administrationPanelHref("sections", isDemo)}
|
|
4475
|
-
data-news-desk-admin-nav="sections"
|
|
4476
|
-
data-active={administrationPanel === "sections" || undefined}
|
|
4477
|
-
onClick={() => onPanelChange("sections")}
|
|
4478
|
-
>
|
|
4479
|
-
<strong>Sections</strong>
|
|
4480
|
-
<span>Canonical and floating newspaper sections</span>
|
|
4481
|
-
</Link>
|
|
4482
4457
|
<Link
|
|
4483
4458
|
href={administrationPanelHref("procedures", isDemo)}
|
|
4484
4459
|
data-news-desk-admin-nav="procedures"
|
|
@@ -4499,7 +4474,7 @@ function AdministrationDeskView({
|
|
|
4499
4474
|
<span>Addresses from the site's signup forms</span>
|
|
4500
4475
|
</Link>
|
|
4501
4476
|
) : null}
|
|
4502
|
-
</nav>
|
|
4477
|
+
</nav>}
|
|
4503
4478
|
<div className="news-desk-settings-panel">
|
|
4504
4479
|
{administrationPanel === "signups" ? <SignupEntriesPanel /> : null}
|
|
4505
4480
|
{administrationPanel === "users" ? (
|
|
@@ -4987,7 +4962,7 @@ function AdministrationSectionsPanel({
|
|
|
4987
4962
|
|
|
4988
4963
|
return (
|
|
4989
4964
|
<div data-news-desk-admin-panel="sections">
|
|
4990
|
-
<SectionHeader title="
|
|
4965
|
+
<SectionHeader title="Sections" detail={`${sortedSections.length} configured sections`} />
|
|
4991
4966
|
<div className="news-desk-administration-category-layout">
|
|
4992
4967
|
<div className="news-desk-administration-category-list" data-news-desk-section-list>
|
|
4993
4968
|
{sortedSections.length ? (
|
|
@@ -5824,7 +5799,7 @@ function DesksDeskView({
|
|
|
5824
5799
|
}, [initialRootKey, roots, selectedRoot, selectedRootKey]);
|
|
5825
5800
|
|
|
5826
5801
|
return (
|
|
5827
|
-
<div className="news-desk-columns" data-news-desk-section="desks">
|
|
5802
|
+
<div className="news-desk-columns news-desk-columns--administration news-desk-columns--taxonomy" data-news-desk-section="desks">
|
|
5828
5803
|
<div className="news-desk-main-column">
|
|
5829
5804
|
<section className="category-steering-section category-steering-section--lead" aria-labelledby="newsroom-desks-title">
|
|
5830
5805
|
<SectionHeader title="News Desks" detail={`${roots.length} root topic desks`} />
|
|
@@ -6374,148 +6349,27 @@ function TopicProposalEditModal({
|
|
|
6374
6349
|
}
|
|
6375
6350
|
|
|
6376
6351
|
function ConceptsDeskView({
|
|
6377
|
-
categories,
|
|
6378
|
-
disabled,
|
|
6379
6352
|
graph,
|
|
6380
|
-
initialCategoryLineageId,
|
|
6381
6353
|
initialNodeLineageId,
|
|
6382
|
-
|
|
6354
|
+
isDemo,
|
|
6383
6355
|
semanticNodes,
|
|
6384
|
-
summary,
|
|
6385
6356
|
}: {
|
|
6386
|
-
categories: CategorySteeringCategory[];
|
|
6387
|
-
disabled: boolean;
|
|
6388
6357
|
graph: SemanticGraph;
|
|
6389
|
-
initialCategoryLineageId?: string | null;
|
|
6390
6358
|
initialNodeLineageId?: string | null;
|
|
6391
|
-
|
|
6359
|
+
isDemo: boolean;
|
|
6392
6360
|
semanticNodes: SemanticNodeRecord[];
|
|
6393
|
-
summary?: NewsroomSummaryRecord | null;
|
|
6394
6361
|
}) {
|
|
6395
|
-
const [nodeKindFilter, setNodeKindFilter] = useState("");
|
|
6396
|
-
const [nodeStatusFilter, setNodeStatusFilter] = useState("");
|
|
6397
|
-
const [selectedNodeLineageId, setSelectedNodeLineageId] = useState(initialNodeLineageId ?? "");
|
|
6398
|
-
const [isConceptDetailOpen, setIsConceptDetailOpen] = useState(Boolean(initialNodeLineageId));
|
|
6399
|
-
const categoryContext = useMemo(() => buildCategoryDrilldownContext(categories, initialCategoryLineageId), [categories, initialCategoryLineageId]);
|
|
6400
6362
|
const feed = useNewsroomPagedRows({
|
|
6401
6363
|
initialItems: semanticNodes,
|
|
6402
|
-
enabled:
|
|
6403
|
-
resetKey:
|
|
6404
|
-
loadPage: (nextToken) => loadNewsroomSemanticNodePage({
|
|
6405
|
-
nodeKind: nodeKindFilter,
|
|
6406
|
-
status: nodeStatusFilter,
|
|
6407
|
-
nextToken,
|
|
6408
|
-
}),
|
|
6364
|
+
enabled: true,
|
|
6365
|
+
resetKey: "semanticNodes:wiki",
|
|
6366
|
+
loadPage: (nextToken) => loadNewsroomSemanticNodePage({ nodeKind: "", status: "", nextToken }),
|
|
6409
6367
|
});
|
|
6410
|
-
|
|
6411
|
-
|
|
6412
|
-
|
|
6413
|
-
|
|
6414
|
-
|
|
6415
|
-
), [categoryContext, graph]);
|
|
6416
|
-
const feedNodes = useMemo(() => (
|
|
6417
|
-
feed.items.length
|
|
6418
|
-
? feed.items
|
|
6419
|
-
: (feed.isLoading || Boolean(feed.error))
|
|
6420
|
-
? semanticNodes
|
|
6421
|
-
: feed.items
|
|
6422
|
-
), [feed.error, feed.isLoading, feed.items, semanticNodes]);
|
|
6423
|
-
const visibleNodes = useMemo(() => (
|
|
6424
|
-
categoryConceptLineages
|
|
6425
|
-
? semanticNodes.filter((node) => categoryConceptLineages.has(node.lineageId ?? node.id))
|
|
6426
|
-
: feedNodes
|
|
6427
|
-
), [categoryConceptLineages, feedNodes, semanticNodes]);
|
|
6428
|
-
const fallbackConceptCategories = useMemo(() => (
|
|
6429
|
-
visibleNodes.length
|
|
6430
|
-
? []
|
|
6431
|
-
: categories.filter((category) => category.status !== "deprecated" && category.status !== "archived")
|
|
6432
|
-
), [categories, visibleNodes.length]);
|
|
6433
|
-
const requestedNodeLineageId = selectedNodeLineageId || initialNodeLineageId || "";
|
|
6434
|
-
const selected = selectSemanticNodeSummary(graph, visibleNodes, requestedNodeLineageId)
|
|
6435
|
-
?? (requestedNodeLineageId ? selectSemanticNodeSummary(graph, semanticNodes, requestedNodeLineageId) : null)
|
|
6436
|
-
?? (requestedNodeLineageId ? selectCategorySummary(graph, categories, requestedNodeLineageId) : null)
|
|
6437
|
-
?? categoryFilter
|
|
6438
|
-
?? selectSemanticNodeSummary(graph, visibleNodes)
|
|
6439
|
-
?? selectCategorySummary(graph, fallbackConceptCategories);
|
|
6440
|
-
const conceptKnowledgeQuery = useNewsroomKnowledgeContext(selected ? {
|
|
6441
|
-
anchor: {
|
|
6442
|
-
kind: selected.kind === "category" ? "category" : "semanticNode",
|
|
6443
|
-
id: selected.id ?? selected.lineageId,
|
|
6444
|
-
lineageId: selected.lineageId,
|
|
6445
|
-
},
|
|
6446
|
-
title: selected.label,
|
|
6447
|
-
subtitle: selected.subtitle,
|
|
6448
|
-
} : null);
|
|
6449
|
-
const conceptInsight = useNewsroomInsightComposer(
|
|
6450
|
-
selected && (selected.kind === "category" || selected.kind === "semanticNode") ? insightTargetForSemanticObject(selected) : null,
|
|
6451
|
-
disabled,
|
|
6452
|
-
onCreateInsight,
|
|
6453
|
-
);
|
|
6454
|
-
const detail = categoryFilter
|
|
6455
|
-
? `${visibleNodes.length} graph nodes associated with ${categoryFilter.label}`
|
|
6456
|
-
: visibleNodes.length
|
|
6457
|
-
? `${summaryCountFromRecord(summary, "semanticNodes") || visibleNodes.length} graph nodes`
|
|
6458
|
-
: `${fallbackConceptCategories.length} taxonomy concepts`;
|
|
6459
|
-
const nodeKindCounts = categoryFilter ? countSemanticNodesByKind(visibleNodes) : summary?.facets?.semanticNodes?.byNodeKind ?? countSemanticNodesByKind(visibleNodes);
|
|
6460
|
-
const nodeStatusCounts = categoryFilter ? countSemanticNodesByStatus(visibleNodes) : summary?.facets?.semanticNodes?.byStatus ?? countSemanticNodesByStatus(visibleNodes);
|
|
6461
|
-
const nodeKinds = sortedCountOptions(nodeKindCounts).slice(0, 8);
|
|
6462
|
-
const cards = visibleNodes.length
|
|
6463
|
-
? visibleNodes.map((node, index) => semanticNodeToNewsroomCard(node, index))
|
|
6464
|
-
: fallbackConceptCategories.map((category, index) => categoryToConceptNewsroomCard(category, index));
|
|
6465
|
-
const cardIds = useMemo(() => new Set(cards.map((card) => card.id)), [cards]);
|
|
6466
|
-
const selectedLineageId = selected && cardIds.has(selected.lineageId) ? selected.lineageId : null;
|
|
6467
|
-
const selectNode = (lineageId: string) => {
|
|
6468
|
-
setSelectedNodeLineageId(lineageId);
|
|
6469
|
-
setIsConceptDetailOpen(true);
|
|
6470
|
-
pushNewsroomDetailUrl("concepts", lineageId, false);
|
|
6471
|
-
};
|
|
6472
|
-
return (
|
|
6473
|
-
<>
|
|
6474
|
-
<NewsroomListDetailShell
|
|
6475
|
-
animatedDetail
|
|
6476
|
-
sectionKey="concepts"
|
|
6477
|
-
canExpandDetail={Boolean(selected)}
|
|
6478
|
-
detailOpen={isConceptDetailOpen}
|
|
6479
|
-
selectionScrollKey={selectedLineageId}
|
|
6480
|
-
actions={[conceptInsight.action]}
|
|
6481
|
-
utilityActions={[conceptKnowledgeQuery.action]}
|
|
6482
|
-
lede={(
|
|
6483
|
-
<NewsroomDeskSectionLede headingId="semantic-concepts-title" section="concepts" />
|
|
6484
|
-
)}
|
|
6485
|
-
list={(
|
|
6486
|
-
<section className="category-steering-section category-steering-section--lead" aria-label={detail}>
|
|
6487
|
-
<NewsroomCardGrid
|
|
6488
|
-
cards={cards}
|
|
6489
|
-
emptyLabel={feed.isLoading ? "Loading semantic concepts" : feed.error ?? "No semantic nodes imported"}
|
|
6490
|
-
filterLabel="Concept kind"
|
|
6491
|
-
filterOptions={[
|
|
6492
|
-
{ key: "", label: "All concept kinds", count: summaryCountFromRecord(summary, "semanticNodes") || visibleNodes.length },
|
|
6493
|
-
...nodeKinds.map((option) => ({ key: option.key, label: formatAssignmentTypeLabel(option.key), count: option.count })),
|
|
6494
|
-
]}
|
|
6495
|
-
filterValue={nodeKindFilter}
|
|
6496
|
-
footerLabel={feed.error ?? undefined}
|
|
6497
|
-
hasMore={!categoryFilter && feed.hasMore}
|
|
6498
|
-
isLoadingMore={feed.isLoadingMore}
|
|
6499
|
-
metricValue={nodeStatusFilter}
|
|
6500
|
-
metrics={[
|
|
6501
|
-
{ key: "", label: "All", count: summaryCountFromRecord(summary, "semanticNodes") || visibleNodes.length },
|
|
6502
|
-
...sortedCountOptions(nodeStatusCounts).map((option) => ({ key: option.key, label: option.key, count: option.count })),
|
|
6503
|
-
]}
|
|
6504
|
-
onFilterChange={setNodeKindFilter}
|
|
6505
|
-
onLoadMore={feed.loadMore}
|
|
6506
|
-
onMetricChange={setNodeStatusFilter}
|
|
6507
|
-
onSelect={selectNode}
|
|
6508
|
-
selectedId={selectedLineageId}
|
|
6509
|
-
/>
|
|
6510
|
-
</section>
|
|
6511
|
-
)}
|
|
6512
|
-
onCloseDetail={() => setIsConceptDetailOpen(false)}
|
|
6513
|
-
detail={<SemanticDetailPanel graph={graph} selected={selected} knowledgeQuery={conceptKnowledgeQuery} />}
|
|
6514
|
-
/>
|
|
6515
|
-
{conceptKnowledgeQuery.dialog}
|
|
6516
|
-
{conceptInsight.dialog}
|
|
6517
|
-
</>
|
|
6518
|
-
);
|
|
6368
|
+
useEffect(() => {
|
|
6369
|
+
if (feed.hasMore && !feed.isLoadingMore) feed.loadMore();
|
|
6370
|
+
}, [feed]);
|
|
6371
|
+
const nodes = feed.items.length ? feed.items : semanticNodes;
|
|
6372
|
+
return <WikiConceptsView demo={isDemo} graph={graph} initialNodeLineageId={initialNodeLineageId} nodes={nodes} />;
|
|
6519
6373
|
}
|
|
6520
6374
|
|
|
6521
6375
|
function ReferencesDeskView({
|
|
@@ -6529,7 +6383,7 @@ function ReferencesDeskView({
|
|
|
6529
6383
|
initialReferenceLineageId,
|
|
6530
6384
|
isDemo,
|
|
6531
6385
|
deepLinkFetchEnabled = true,
|
|
6532
|
-
|
|
6386
|
+
onCreateNote,
|
|
6533
6387
|
onHydrateReference,
|
|
6534
6388
|
onMoveCorpus,
|
|
6535
6389
|
onReview,
|
|
@@ -6554,7 +6408,7 @@ function ReferencesDeskView({
|
|
|
6554
6408
|
initialReferenceLineageId?: string | null;
|
|
6555
6409
|
isDemo?: boolean;
|
|
6556
6410
|
deepLinkFetchEnabled?: boolean;
|
|
6557
|
-
|
|
6411
|
+
onCreateNote: (target: NoteTarget, summary: string, body: string) => Promise<void>;
|
|
6558
6412
|
onHydrateReference?: (reference: ReferenceRecord) => void;
|
|
6559
6413
|
onMoveCorpus: (reference: ReferenceRecord, corpusId: string) => void;
|
|
6560
6414
|
onReview: (reference: ReferenceRecord, action: ReferenceCurationAction, note?: string, reasonCode?: ReferenceRejectionReasonCode | null) => void;
|
|
@@ -6958,7 +6812,7 @@ function ReferencesDeskView({
|
|
|
6958
6812
|
corpora={corpora}
|
|
6959
6813
|
disabled={disabled}
|
|
6960
6814
|
graph={graph}
|
|
6961
|
-
|
|
6815
|
+
onCreateNote={onCreateNote}
|
|
6962
6816
|
onMoveCorpus={onMoveCorpus}
|
|
6963
6817
|
onReview={runReferenceAction}
|
|
6964
6818
|
onReviewTopicLabel={onReviewTopicLabel}
|
|
@@ -7010,365 +6864,6 @@ function NewsroomDeskSectionLede({
|
|
|
7010
6864
|
);
|
|
7011
6865
|
}
|
|
7012
6866
|
|
|
7013
|
-
function InsightsDeskView({
|
|
7014
|
-
graph,
|
|
7015
|
-
initialThreadId = null,
|
|
7016
|
-
isDemo = false,
|
|
7017
|
-
messages,
|
|
7018
|
-
summary,
|
|
7019
|
-
}: {
|
|
7020
|
-
graph: SemanticGraph;
|
|
7021
|
-
initialThreadId?: string | null;
|
|
7022
|
-
isDemo?: boolean;
|
|
7023
|
-
messages: MessageRecord[];
|
|
7024
|
-
summary?: NewsroomSummaryRecord | null;
|
|
7025
|
-
}) {
|
|
7026
|
-
const pathname = usePathname();
|
|
7027
|
-
const pathnameThreadId = useMemo(
|
|
7028
|
-
() => parseInsightThreadIdFromNewsroomPathname(pathname),
|
|
7029
|
-
[pathname],
|
|
7030
|
-
);
|
|
7031
|
-
const resolvedInitialThreadId = initialThreadId ?? pathnameThreadId ?? "";
|
|
7032
|
-
const forumMessageAnchorId = useForumMessageAnchorId();
|
|
7033
|
-
const [domainFilter, setDomainFilter] = useState(() => {
|
|
7034
|
-
if (typeof window === "undefined") return "";
|
|
7035
|
-
return readInsightsIndexFilters(new URLSearchParams(window.location.search)).domain;
|
|
7036
|
-
});
|
|
7037
|
-
const [threads, setThreads] = useState<ForumThreadWithMessages[]>([]);
|
|
7038
|
-
const [threadsLoading, setThreadsLoading] = useState(false);
|
|
7039
|
-
const [threadsError, setThreadsError] = useState<string | null>(null);
|
|
7040
|
-
const [nextToken, setNextToken] = useState<string | null>(null);
|
|
7041
|
-
const [selectedThreadId, setSelectedThreadId] = useState(resolvedInitialThreadId);
|
|
7042
|
-
const [forumView, setForumView] = useState<ForumViewState>(
|
|
7043
|
-
resolvedInitialThreadId ? { mode: "thread", threadId: resolvedInitialThreadId } : { mode: "index" },
|
|
7044
|
-
);
|
|
7045
|
-
const [replyParentId, setReplyParentId] = useState("");
|
|
7046
|
-
const [composeSummary, setComposeSummary] = useState("");
|
|
7047
|
-
const [composeContent, setComposeContent] = useState("");
|
|
7048
|
-
const [composeError, setComposeError] = useState<string | null>(null);
|
|
7049
|
-
const [deletingMessageId, setDeletingMessageId] = useState("");
|
|
7050
|
-
|
|
7051
|
-
const syncInsightsIndexUrl = useCallback((nextDomain: string, replace = true) => {
|
|
7052
|
-
if (isDemo || forumView.mode === "thread") return;
|
|
7053
|
-
syncBrowserNewsroomIndexUrl("insights", effectiveInsightsIndexFilters({ domain: nextDomain }), { replace });
|
|
7054
|
-
}, [forumView.mode, isDemo]);
|
|
7055
|
-
|
|
7056
|
-
useEffect(() => {
|
|
7057
|
-
if (isDemo || forumView.mode === "thread") return;
|
|
7058
|
-
syncInsightsIndexUrl(domainFilter, true);
|
|
7059
|
-
}, [domainFilter, forumView.mode, isDemo, syncInsightsIndexUrl]);
|
|
7060
|
-
|
|
7061
|
-
const refreshThreads = useCallback(async (options?: { append?: boolean; token?: string | null }) => {
|
|
7062
|
-
if (isDemo) {
|
|
7063
|
-
const demoThreads = messages
|
|
7064
|
-
.filter((message) => message.messageKind === "insight")
|
|
7065
|
-
.filter((message) => !domainFilter || message.messageDomain === domainFilter)
|
|
7066
|
-
.map((message) => buildDemoInsightForumThread(message, messages));
|
|
7067
|
-
setThreads(demoThreads);
|
|
7068
|
-
setThreadsError(null);
|
|
7069
|
-
setNextToken(null);
|
|
7070
|
-
return;
|
|
7071
|
-
}
|
|
7072
|
-
setThreadsLoading(true);
|
|
7073
|
-
try {
|
|
7074
|
-
const result = await loadInsightForumThreads({
|
|
7075
|
-
domain: domainFilter,
|
|
7076
|
-
nextToken: options?.token ?? null,
|
|
7077
|
-
includeMessages: true,
|
|
7078
|
-
});
|
|
7079
|
-
setThreads((current) => (options?.append ? [...current, ...result.threads] : result.threads));
|
|
7080
|
-
setNextToken(result.nextToken);
|
|
7081
|
-
setThreadsError(null);
|
|
7082
|
-
setSelectedThreadId((current) => (current && result.threads.some((thread) => thread.id === current) ? current : current));
|
|
7083
|
-
} catch (error) {
|
|
7084
|
-
setThreads([]);
|
|
7085
|
-
setThreadsError(error instanceof Error ? error.message : "Could not load insight threads.");
|
|
7086
|
-
setNextToken(null);
|
|
7087
|
-
} finally {
|
|
7088
|
-
setThreadsLoading(false);
|
|
7089
|
-
}
|
|
7090
|
-
}, [domainFilter, isDemo, messages]);
|
|
7091
|
-
|
|
7092
|
-
useEffect(() => {
|
|
7093
|
-
void refreshThreads();
|
|
7094
|
-
}, [refreshThreads]);
|
|
7095
|
-
|
|
7096
|
-
useEffect(() => {
|
|
7097
|
-
if (!resolvedInitialThreadId || threadsLoading) return;
|
|
7098
|
-
if (!threads.some((thread) => thread.id === resolvedInitialThreadId)) return;
|
|
7099
|
-
if (selectedThreadId === resolvedInitialThreadId && forumView.mode === "thread") return;
|
|
7100
|
-
setSelectedThreadId(resolvedInitialThreadId);
|
|
7101
|
-
setForumView({ mode: "thread", threadId: resolvedInitialThreadId });
|
|
7102
|
-
}, [forumView.mode, resolvedInitialThreadId, selectedThreadId, threads, threadsLoading]);
|
|
7103
|
-
|
|
7104
|
-
useEffect(() => {
|
|
7105
|
-
const handlePopState = () => {
|
|
7106
|
-
const route = readCurrentForumRoute();
|
|
7107
|
-
if (route.surface === "insight_forum" && route.threadId) {
|
|
7108
|
-
setSelectedThreadId(route.threadId);
|
|
7109
|
-
setForumView({ mode: "thread", threadId: route.threadId });
|
|
7110
|
-
return;
|
|
7111
|
-
}
|
|
7112
|
-
setSelectedThreadId("");
|
|
7113
|
-
setForumView({ mode: "index" });
|
|
7114
|
-
};
|
|
7115
|
-
window.addEventListener("popstate", handlePopState);
|
|
7116
|
-
return () => window.removeEventListener("popstate", handlePopState);
|
|
7117
|
-
}, []);
|
|
7118
|
-
|
|
7119
|
-
const selectedThread = threads.find((thread) => thread.id === selectedThreadId) ?? null;
|
|
7120
|
-
const domainOptions = useMemo(() => {
|
|
7121
|
-
const domains = new Set<string>();
|
|
7122
|
-
for (const message of messages) {
|
|
7123
|
-
if (message.messageKind === "insight" && message.messageDomain) domains.add(message.messageDomain);
|
|
7124
|
-
}
|
|
7125
|
-
for (const thread of threads) {
|
|
7126
|
-
if (thread.sectionKey) domains.add(thread.sectionKey);
|
|
7127
|
-
}
|
|
7128
|
-
return sortedCountOptions(
|
|
7129
|
-
Object.fromEntries([...domains].map((domain) => [domain, threads.filter((thread) => thread.sectionKey === domain).length])),
|
|
7130
|
-
);
|
|
7131
|
-
}, [messages, threads]);
|
|
7132
|
-
|
|
7133
|
-
const openThread = useCallback((threadId: string, options?: { messageId?: string | null; replace?: boolean }) => {
|
|
7134
|
-
setSelectedThreadId(threadId);
|
|
7135
|
-
setForumView({ mode: "thread", threadId });
|
|
7136
|
-
pushInsightThreadUrl(threadId, {
|
|
7137
|
-
demo: isDemo,
|
|
7138
|
-
messageId: options?.messageId,
|
|
7139
|
-
replace: options?.replace,
|
|
7140
|
-
});
|
|
7141
|
-
}, [isDemo]);
|
|
7142
|
-
|
|
7143
|
-
const closeThread = useCallback(() => {
|
|
7144
|
-
setSelectedThreadId("");
|
|
7145
|
-
setForumView({ mode: "index" });
|
|
7146
|
-
pushInsightThreadUrl(null, { demo: isDemo, replace: true });
|
|
7147
|
-
}, [isDemo]);
|
|
7148
|
-
|
|
7149
|
-
const postThreadMessage = useCallback(async () => {
|
|
7150
|
-
if (!selectedThread) return;
|
|
7151
|
-
const content = composeContent.trim();
|
|
7152
|
-
if (!content) {
|
|
7153
|
-
setComposeError("Message content is required.");
|
|
7154
|
-
return;
|
|
7155
|
-
}
|
|
7156
|
-
const summary = composeSummary.trim() || content.slice(0, 120);
|
|
7157
|
-
setComposeError(null);
|
|
7158
|
-
try {
|
|
7159
|
-
await appendInsightThreadReplyRecord({
|
|
7160
|
-
threadId: selectedThread.id,
|
|
7161
|
-
summary,
|
|
7162
|
-
content,
|
|
7163
|
-
role: "human",
|
|
7164
|
-
authorLabel: "human-editor",
|
|
7165
|
-
parentMessageId: replyParentId || undefined,
|
|
7166
|
-
});
|
|
7167
|
-
setComposeSummary("");
|
|
7168
|
-
setComposeContent("");
|
|
7169
|
-
setReplyParentId("");
|
|
7170
|
-
await refreshThreads();
|
|
7171
|
-
const reloaded = await loadInsightForumThreadById(selectedThread.id, { includeMessages: true });
|
|
7172
|
-
if (reloaded) {
|
|
7173
|
-
setThreads((current) => current.map((thread) => (thread.id === reloaded.id ? reloaded : thread)));
|
|
7174
|
-
}
|
|
7175
|
-
setForumView({ mode: "thread", threadId: selectedThread.id });
|
|
7176
|
-
} catch (error) {
|
|
7177
|
-
setComposeError(error instanceof Error ? error.message : "Could not post thread message.");
|
|
7178
|
-
}
|
|
7179
|
-
}, [composeContent, composeSummary, refreshThreads, replyParentId, selectedThread]);
|
|
7180
|
-
|
|
7181
|
-
const deleteThreadMessage = useCallback(async (threadId: string, messageId: string) => {
|
|
7182
|
-
setDeletingMessageId(messageId);
|
|
7183
|
-
try {
|
|
7184
|
-
await deleteInsightThreadMessageRecord({ threadId, messageId });
|
|
7185
|
-
if (replyParentId === messageId) setReplyParentId("");
|
|
7186
|
-
await refreshThreads();
|
|
7187
|
-
const reloaded = await loadInsightForumThreadById(threadId, { includeMessages: true });
|
|
7188
|
-
if (reloaded) {
|
|
7189
|
-
setThreads((current) => current.map((thread) => (thread.id === reloaded.id ? reloaded : thread)));
|
|
7190
|
-
}
|
|
7191
|
-
setComposeError(null);
|
|
7192
|
-
} catch (error) {
|
|
7193
|
-
setComposeError(error instanceof Error ? error.message : "Could not delete message.");
|
|
7194
|
-
} finally {
|
|
7195
|
-
setDeletingMessageId("");
|
|
7196
|
-
}
|
|
7197
|
-
}, [refreshThreads, replyParentId]);
|
|
7198
|
-
|
|
7199
|
-
const insightKnowledgeQuery = useNewsroomKnowledgeContext(selectedThread ? {
|
|
7200
|
-
anchor: { kind: "message", id: selectedThread.id, lineageId: selectedThread.id },
|
|
7201
|
-
title: selectedThread.title ?? "Insight thread",
|
|
7202
|
-
subtitle: selectedThread.sectionKey ?? "insight",
|
|
7203
|
-
} : null);
|
|
7204
|
-
|
|
7205
|
-
return (
|
|
7206
|
-
<>
|
|
7207
|
-
<NewsroomListDetailShell
|
|
7208
|
-
sectionKey="insights"
|
|
7209
|
-
canExpandDetail={forumView.mode === "thread" && Boolean(forumView.threadId)}
|
|
7210
|
-
detailOpen={forumView.mode === "thread" && Boolean(forumView.threadId)}
|
|
7211
|
-
selectionScrollKey={forumView.mode === "thread" ? forumView.threadId : null}
|
|
7212
|
-
utilityActions={[insightKnowledgeQuery.action]}
|
|
7213
|
-
lede={(
|
|
7214
|
-
<NewsroomDeskSectionLede
|
|
7215
|
-
headingId="insights-forum-title"
|
|
7216
|
-
section="insights"
|
|
7217
|
-
headline="Insights"
|
|
7218
|
-
lede="Recent research insights in reverse chronological order. Each insight opens a forum thread for discussion and refinement."
|
|
7219
|
-
controls={(
|
|
7220
|
-
<div className="news-desk-reference-sort-controls" role="group" aria-label="Insight domain filter">
|
|
7221
|
-
<span className="news-desk-reference-sort-controls__label">Domain</span>
|
|
7222
|
-
<button
|
|
7223
|
-
type="button"
|
|
7224
|
-
data-active={!domainFilter || undefined}
|
|
7225
|
-
onClick={() => {
|
|
7226
|
-
setDomainFilter("");
|
|
7227
|
-
syncInsightsIndexUrl("", true);
|
|
7228
|
-
}}
|
|
7229
|
-
>
|
|
7230
|
-
All
|
|
7231
|
-
</button>
|
|
7232
|
-
<button
|
|
7233
|
-
type="button"
|
|
7234
|
-
data-active={domainFilter === "knowledge" || undefined}
|
|
7235
|
-
onClick={() => {
|
|
7236
|
-
setDomainFilter("knowledge");
|
|
7237
|
-
syncInsightsIndexUrl("knowledge", true);
|
|
7238
|
-
}}
|
|
7239
|
-
>
|
|
7240
|
-
Knowledge
|
|
7241
|
-
</button>
|
|
7242
|
-
<button
|
|
7243
|
-
type="button"
|
|
7244
|
-
data-active={domainFilter === "assignment_work" || undefined}
|
|
7245
|
-
onClick={() => {
|
|
7246
|
-
setDomainFilter("assignment_work");
|
|
7247
|
-
syncInsightsIndexUrl("assignment_work", true);
|
|
7248
|
-
}}
|
|
7249
|
-
>
|
|
7250
|
-
Assignment work
|
|
7251
|
-
</button>
|
|
7252
|
-
</div>
|
|
7253
|
-
)}
|
|
7254
|
-
/>
|
|
7255
|
-
)}
|
|
7256
|
-
list={(
|
|
7257
|
-
<section className="category-steering-section category-steering-section--lead" aria-label="Insight forum threads">
|
|
7258
|
-
<ForumThreadIndex
|
|
7259
|
-
threads={threads}
|
|
7260
|
-
sections={[]}
|
|
7261
|
-
isLoading={threadsLoading}
|
|
7262
|
-
error={threadsError}
|
|
7263
|
-
emptyLabel="No insight threads match this filter."
|
|
7264
|
-
variant="insight"
|
|
7265
|
-
toolbar={domainOptions.length > 2 ? (
|
|
7266
|
-
<div className="news-desk-forum-toolbar">
|
|
7267
|
-
<select
|
|
7268
|
-
aria-label="Insight domain"
|
|
7269
|
-
value={domainFilter}
|
|
7270
|
-
onChange={(event) => {
|
|
7271
|
-
setDomainFilter(event.target.value);
|
|
7272
|
-
syncInsightsIndexUrl(event.target.value, true);
|
|
7273
|
-
}}
|
|
7274
|
-
>
|
|
7275
|
-
<option value="">All domains ({threads.length})</option>
|
|
7276
|
-
{domainOptions.map((option) => (
|
|
7277
|
-
<option key={option.key} value={option.key}>{option.key} ({option.count})</option>
|
|
7278
|
-
))}
|
|
7279
|
-
</select>
|
|
7280
|
-
{nextToken ? (
|
|
7281
|
-
<button type="button" disabled={threadsLoading} onClick={() => void refreshThreads({ append: true, token: nextToken })}>
|
|
7282
|
-
Load more
|
|
7283
|
-
</button>
|
|
7284
|
-
) : null}
|
|
7285
|
-
</div>
|
|
7286
|
-
) : nextToken ? (
|
|
7287
|
-
<div className="news-desk-forum-toolbar">
|
|
7288
|
-
<button type="button" disabled={threadsLoading} onClick={() => void refreshThreads({ append: true, token: nextToken })}>
|
|
7289
|
-
Load more
|
|
7290
|
-
</button>
|
|
7291
|
-
</div>
|
|
7292
|
-
) : null}
|
|
7293
|
-
onOpenThread={(threadId) => openThread(threadId)}
|
|
7294
|
-
/>
|
|
7295
|
-
</section>
|
|
7296
|
-
)}
|
|
7297
|
-
detail={forumView.mode === "thread" && selectedThread ? (
|
|
7298
|
-
<ForumThreadView
|
|
7299
|
-
thread={selectedThread}
|
|
7300
|
-
sections={[]}
|
|
7301
|
-
replyParentId={replyParentId}
|
|
7302
|
-
composeSummary={composeSummary}
|
|
7303
|
-
composeContent={composeContent}
|
|
7304
|
-
composeError={composeError}
|
|
7305
|
-
focusMessageId={forumMessageAnchorId}
|
|
7306
|
-
isDemo={isDemo}
|
|
7307
|
-
variant="insight"
|
|
7308
|
-
onBack={closeThread}
|
|
7309
|
-
onReplyTarget={(messageId) => {
|
|
7310
|
-
setReplyParentId(messageId);
|
|
7311
|
-
pushInsightThreadUrl(selectedThread.id, { demo: isDemo, messageId, replace: true });
|
|
7312
|
-
}}
|
|
7313
|
-
onClearReplyTarget={() => {
|
|
7314
|
-
setReplyParentId("");
|
|
7315
|
-
pushInsightThreadUrl(selectedThread.id, { demo: isDemo, replace: true });
|
|
7316
|
-
}}
|
|
7317
|
-
onSummaryChange={setComposeSummary}
|
|
7318
|
-
onContentChange={setComposeContent}
|
|
7319
|
-
onSubmit={() => void postThreadMessage()}
|
|
7320
|
-
onDeleteMessage={(messageId) => {
|
|
7321
|
-
if (!selectedThread || deletingMessageId === messageId) return;
|
|
7322
|
-
void deleteThreadMessage(selectedThread.id, messageId);
|
|
7323
|
-
}}
|
|
7324
|
-
deletingMessageId={deletingMessageId}
|
|
7325
|
-
/>
|
|
7326
|
-
) : forumView.mode === "thread" ? (
|
|
7327
|
-
<section className="category-steering-section">
|
|
7328
|
-
<SectionHeader title="Thread Unavailable" detail="Insights" />
|
|
7329
|
-
<EmptyRow label="That insight thread is not available. Return to the index or adjust filters." />
|
|
7330
|
-
</section>
|
|
7331
|
-
) : null}
|
|
7332
|
-
/>
|
|
7333
|
-
{insightKnowledgeQuery.dialog}
|
|
7334
|
-
</>
|
|
7335
|
-
);
|
|
7336
|
-
}
|
|
7337
|
-
|
|
7338
|
-
function buildDemoInsightForumThread(root: MessageRecord, allMessages: MessageRecord[]): ForumThreadWithMessages {
|
|
7339
|
-
const replies = allMessages.filter((message) => (
|
|
7340
|
-
message.threadId === root.id
|
|
7341
|
-
&& message.id !== root.id
|
|
7342
|
-
&& String(message.status || "active") === "active"
|
|
7343
|
-
));
|
|
7344
|
-
const messages = [root, ...replies].sort(
|
|
7345
|
-
(left, right) => Number(left.sequenceNumber ?? 0) - Number(right.sequenceNumber ?? 0),
|
|
7346
|
-
);
|
|
7347
|
-
const lastMessage = messages.at(-1) ?? root;
|
|
7348
|
-
return {
|
|
7349
|
-
id: root.id,
|
|
7350
|
-
threadKind: "insight_forum",
|
|
7351
|
-
status: "active",
|
|
7352
|
-
title: root.summary ?? "Insight",
|
|
7353
|
-
summary: root.messageDomain ?? null,
|
|
7354
|
-
primaryAnchorKind: "message",
|
|
7355
|
-
primaryAnchorId: root.id,
|
|
7356
|
-
primaryAnchorLineageId: root.id,
|
|
7357
|
-
primaryAnchorKey: `message#${root.id}`,
|
|
7358
|
-
createdByLabel: root.authorLabel ?? null,
|
|
7359
|
-
messageCount: messages.length,
|
|
7360
|
-
lastMessageId: lastMessage.id,
|
|
7361
|
-
lastMessageAt: lastMessage.updatedAt ?? lastMessage.createdAt,
|
|
7362
|
-
metadata: metadataRecord(root.metadata),
|
|
7363
|
-
createdAt: root.createdAt,
|
|
7364
|
-
updatedAt: lastMessage.updatedAt ?? lastMessage.createdAt,
|
|
7365
|
-
newsroomFeedKey: "messages",
|
|
7366
|
-
messages,
|
|
7367
|
-
scope: "insight",
|
|
7368
|
-
sectionKey: root.messageDomain ?? null,
|
|
7369
|
-
};
|
|
7370
|
-
}
|
|
7371
|
-
|
|
7372
6867
|
function MessagesDeskView({
|
|
7373
6868
|
assignments,
|
|
7374
6869
|
graph,
|
|
@@ -7975,7 +7470,6 @@ function ForumThreadIndex({
|
|
|
7975
7470
|
emptyLabel,
|
|
7976
7471
|
toolbar,
|
|
7977
7472
|
composer,
|
|
7978
|
-
variant = "edition",
|
|
7979
7473
|
onOpenThread,
|
|
7980
7474
|
}: {
|
|
7981
7475
|
threads: ForumThreadWithMessages[];
|
|
@@ -7985,7 +7479,6 @@ function ForumThreadIndex({
|
|
|
7985
7479
|
emptyLabel: string;
|
|
7986
7480
|
toolbar?: ReactNode;
|
|
7987
7481
|
composer?: ReactNode;
|
|
7988
|
-
variant?: "edition" | "insight";
|
|
7989
7482
|
onOpenThread: (threadId: string) => void;
|
|
7990
7483
|
}) {
|
|
7991
7484
|
return (
|
|
@@ -7997,7 +7490,7 @@ function ForumThreadIndex({
|
|
|
7997
7490
|
<thead>
|
|
7998
7491
|
<tr>
|
|
7999
7492
|
<th>Topic</th>
|
|
8000
|
-
<th>
|
|
7493
|
+
<th>Scope</th>
|
|
8001
7494
|
<th>Replies</th>
|
|
8002
7495
|
<th>Last Post</th>
|
|
8003
7496
|
</tr>
|
|
@@ -8010,11 +7503,9 @@ function ForumThreadIndex({
|
|
|
8010
7503
|
);
|
|
8011
7504
|
const lastMessage = sortedMessages.at(-1) ?? null;
|
|
8012
7505
|
const replies = Math.max(activeMessages.length - 1, 0);
|
|
8013
|
-
const scopeLabel = thread.scope === "
|
|
8014
|
-
?
|
|
8015
|
-
: thread
|
|
8016
|
-
? "Edition"
|
|
8017
|
-
: `Section: ${normalizeForumThreadSectionLabel(thread, sections)}`;
|
|
7506
|
+
const scopeLabel = thread.scope === "edition"
|
|
7507
|
+
? "Edition"
|
|
7508
|
+
: `Section: ${normalizeForumThreadSectionLabel(thread, sections)}`;
|
|
8018
7509
|
return (
|
|
8019
7510
|
<tr key={thread.id}>
|
|
8020
7511
|
<td>
|
|
@@ -8128,7 +7619,6 @@ function ForumThreadView({
|
|
|
8128
7619
|
deletingMessageId,
|
|
8129
7620
|
focusMessageId = null,
|
|
8130
7621
|
isDemo = false,
|
|
8131
|
-
variant = "edition",
|
|
8132
7622
|
onBack,
|
|
8133
7623
|
onReplyTarget,
|
|
8134
7624
|
onDeleteMessage,
|
|
@@ -8146,7 +7636,6 @@ function ForumThreadView({
|
|
|
8146
7636
|
deletingMessageId?: string;
|
|
8147
7637
|
focusMessageId?: string | null;
|
|
8148
7638
|
isDemo?: boolean;
|
|
8149
|
-
variant?: "edition" | "insight";
|
|
8150
7639
|
onBack: () => void;
|
|
8151
7640
|
onReplyTarget: (messageId: string) => void;
|
|
8152
7641
|
onDeleteMessage: (messageId: string) => void;
|
|
@@ -8159,9 +7648,7 @@ function ForumThreadView({
|
|
|
8159
7648
|
(left, right) => Number(left.sequenceNumber ?? 0) - Number(right.sequenceNumber ?? 0),
|
|
8160
7649
|
);
|
|
8161
7650
|
const visibleCount = messages.length;
|
|
8162
|
-
const threadShareUrl =
|
|
8163
|
-
? buildInsightThreadUrl(thread.id, { demo: isDemo })
|
|
8164
|
-
: buildForumThreadUrl(thread.id, { demo: isDemo });
|
|
7651
|
+
const threadShareUrl = buildForumThreadUrl(thread.id, { demo: isDemo });
|
|
8165
7652
|
|
|
8166
7653
|
useEffect(() => {
|
|
8167
7654
|
if (!focusMessageId || typeof window === "undefined") return;
|
|
@@ -8190,11 +7677,9 @@ function ForumThreadView({
|
|
|
8190
7677
|
Back to Threads
|
|
8191
7678
|
</button>
|
|
8192
7679
|
<p className="story-label">
|
|
8193
|
-
{thread.scope === "
|
|
8194
|
-
?
|
|
8195
|
-
: thread
|
|
8196
|
-
? "Edition Thread"
|
|
8197
|
-
: `Section Thread: ${normalizeForumThreadSectionLabel(thread, sections)}`}
|
|
7680
|
+
{thread.scope === "edition"
|
|
7681
|
+
? "Edition Thread"
|
|
7682
|
+
: `Section Thread: ${normalizeForumThreadSectionLabel(thread, sections)}`}
|
|
8198
7683
|
</p>
|
|
8199
7684
|
<h3>{thread.title}</h3>
|
|
8200
7685
|
</div>
|
|
@@ -8234,9 +7719,7 @@ function ForumThreadView({
|
|
|
8234
7719
|
</DropdownMenuItem>
|
|
8235
7720
|
<DropdownMenuItem
|
|
8236
7721
|
onSelect={() => {
|
|
8237
|
-
const url =
|
|
8238
|
-
? buildInsightThreadUrl(thread.id, { messageId: message.id, demo: isDemo })
|
|
8239
|
-
: buildForumThreadUrl(thread.id, { messageId: message.id, demo: isDemo });
|
|
7722
|
+
const url = buildForumThreadUrl(thread.id, { messageId: message.id, demo: isDemo });
|
|
8240
7723
|
void copyShareLink(url);
|
|
8241
7724
|
}}
|
|
8242
7725
|
>
|
|
@@ -8539,7 +8022,7 @@ function NewsroomListDetailShell({
|
|
|
8539
8022
|
list: ReactNode;
|
|
8540
8023
|
onCloseDetail?: () => void;
|
|
8541
8024
|
selectionScrollKey?: string | null;
|
|
8542
|
-
sectionKey: "assignments" | "concepts" | "
|
|
8025
|
+
sectionKey: "assignments" | "concepts" | "messages" | "references" | "topics";
|
|
8543
8026
|
utilityActions?: NewsroomDetailAction[];
|
|
8544
8027
|
}) {
|
|
8545
8028
|
const [detailMode, setDetailMode] = useState<"split" | "full">("split");
|
|
@@ -9051,7 +8534,7 @@ function LibraryBigIcon() {
|
|
|
9051
8534
|
);
|
|
9052
8535
|
}
|
|
9053
8536
|
|
|
9054
|
-
function
|
|
8537
|
+
function NoteIcon() {
|
|
9055
8538
|
return (
|
|
9056
8539
|
<svg
|
|
9057
8540
|
aria-hidden="true"
|
|
@@ -9512,7 +8995,7 @@ function SemanticDetailPanel({
|
|
|
9512
8995
|
}
|
|
9513
8996
|
|
|
9514
8997
|
const messages = graph.messagesFor(selected.kind, selected.lineageId);
|
|
9515
|
-
const
|
|
8998
|
+
const notes = graph.notesFor(selected.kind, selected.lineageId);
|
|
9516
8999
|
const attachments = selected.kind === "reference" ? graph.attachmentsForReference(selected.lineageId) : [];
|
|
9517
9000
|
const neighborGroups = graph.neighbors(selected.kind, selected.lineageId);
|
|
9518
9001
|
const selectedReference = selected.kind === "reference" ? selected.record as ReferenceRecord : null;
|
|
@@ -9586,8 +9069,8 @@ function SemanticDetailPanel({
|
|
|
9586
9069
|
) : null}
|
|
9587
9070
|
</div>
|
|
9588
9071
|
) : null}
|
|
9589
|
-
{
|
|
9590
|
-
<
|
|
9072
|
+
{notes.length ? (
|
|
9073
|
+
<NoteMessageBlock notes={notes} />
|
|
9591
9074
|
) : null}
|
|
9592
9075
|
{messages.length ? (
|
|
9593
9076
|
<div className="news-desk-detail-block">
|
|
@@ -9791,11 +9274,11 @@ function useNewsroomKnowledgeContext(target: KnowledgeQueryTarget | null) {
|
|
|
9791
9274
|
};
|
|
9792
9275
|
}
|
|
9793
9276
|
|
|
9794
|
-
function
|
|
9795
|
-
target:
|
|
9277
|
+
function useNewsroomNoteComposer(
|
|
9278
|
+
target: NoteTarget | null,
|
|
9796
9279
|
disabled: boolean,
|
|
9797
|
-
onCreate: (target:
|
|
9798
|
-
):
|
|
9280
|
+
onCreate: (target: NoteTarget, summary: string, body: string) => Promise<void>,
|
|
9281
|
+
): NoteComposerControl {
|
|
9799
9282
|
const [dialogOpen, setDialogOpen] = useState(false);
|
|
9800
9283
|
const [summary, setSummary] = useState("");
|
|
9801
9284
|
const [body, setBody] = useState("");
|
|
@@ -9804,7 +9287,7 @@ function useNewsroomInsightComposer(
|
|
|
9804
9287
|
loading: false,
|
|
9805
9288
|
targetKey: null,
|
|
9806
9289
|
});
|
|
9807
|
-
const targetKey = target ?
|
|
9290
|
+
const targetKey = target ? noteTargetKey(target) : null;
|
|
9808
9291
|
|
|
9809
9292
|
useEffect(() => {
|
|
9810
9293
|
setDialogOpen(false);
|
|
@@ -9818,7 +9301,7 @@ function useNewsroomInsightComposer(
|
|
|
9818
9301
|
const cleanSummary = summary.trim();
|
|
9819
9302
|
const cleanBody = body.trim();
|
|
9820
9303
|
if (!cleanSummary || !cleanBody) {
|
|
9821
|
-
setState({ error: "
|
|
9304
|
+
setState({ error: "Note summary and body are required.", loading: false, targetKey });
|
|
9822
9305
|
return;
|
|
9823
9306
|
}
|
|
9824
9307
|
setState({ error: null, loading: true, targetKey });
|
|
@@ -9830,7 +9313,7 @@ function useNewsroomInsightComposer(
|
|
|
9830
9313
|
setBody("");
|
|
9831
9314
|
} catch (error) {
|
|
9832
9315
|
setState({
|
|
9833
|
-
error: error instanceof Error ? error.message : "Could not save
|
|
9316
|
+
error: error instanceof Error ? error.message : "Could not save note.",
|
|
9834
9317
|
loading: false,
|
|
9835
9318
|
targetKey,
|
|
9836
9319
|
});
|
|
@@ -9842,15 +9325,15 @@ function useNewsroomInsightComposer(
|
|
|
9842
9325
|
|
|
9843
9326
|
return {
|
|
9844
9327
|
action: {
|
|
9845
|
-
ariaLabel: "Add
|
|
9328
|
+
ariaLabel: "Add note",
|
|
9846
9329
|
disabled: disabled || !target || loading,
|
|
9847
|
-
icon: <
|
|
9848
|
-
key: "add-
|
|
9849
|
-
label: loading ? "Saving
|
|
9330
|
+
icon: <NoteIcon />,
|
|
9331
|
+
key: "add-note",
|
|
9332
|
+
label: loading ? "Saving Note" : "Add Note",
|
|
9850
9333
|
onSelect: () => setDialogOpen(true),
|
|
9851
9334
|
} satisfies NewsroomDetailAction,
|
|
9852
9335
|
dialog: target ? (
|
|
9853
|
-
<
|
|
9336
|
+
<NoteComposerDialog
|
|
9854
9337
|
body={body}
|
|
9855
9338
|
disabled={disabled || loading}
|
|
9856
9339
|
error={error}
|
|
@@ -10189,7 +9672,7 @@ function KnowledgeQueryDialog({
|
|
|
10189
9672
|
);
|
|
10190
9673
|
}
|
|
10191
9674
|
|
|
10192
|
-
function
|
|
9675
|
+
function NoteComposerDialog({
|
|
10193
9676
|
body,
|
|
10194
9677
|
disabled,
|
|
10195
9678
|
error,
|
|
@@ -10210,7 +9693,7 @@ function InsightComposerDialog({
|
|
|
10210
9693
|
onSummaryChange: (value: string) => void;
|
|
10211
9694
|
open: boolean;
|
|
10212
9695
|
summary: string;
|
|
10213
|
-
target:
|
|
9696
|
+
target: NoteTarget;
|
|
10214
9697
|
}) {
|
|
10215
9698
|
useEffect(() => {
|
|
10216
9699
|
if (!open) return;
|
|
@@ -10227,16 +9710,16 @@ function InsightComposerDialog({
|
|
|
10227
9710
|
return (
|
|
10228
9711
|
<div
|
|
10229
9712
|
className="news-desk-modal"
|
|
10230
|
-
data-news-desk-
|
|
9713
|
+
data-news-desk-note-modal
|
|
10231
9714
|
onMouseDown={(event) => {
|
|
10232
9715
|
if (event.target === event.currentTarget && !disabled) onClose();
|
|
10233
9716
|
}}
|
|
10234
9717
|
>
|
|
10235
|
-
<div className="news-desk-modal__dialog news-desk-modal__dialog--knowledge-query" role="dialog" aria-modal="true" aria-labelledby="
|
|
9718
|
+
<div className="news-desk-modal__dialog news-desk-modal__dialog--knowledge-query" role="dialog" aria-modal="true" aria-labelledby="note-composer-title">
|
|
10236
9719
|
<header className="news-desk-modal__header">
|
|
10237
9720
|
<div>
|
|
10238
|
-
<p className="story-label">
|
|
10239
|
-
<h3 id="
|
|
9721
|
+
<p className="story-label">Note</p>
|
|
9722
|
+
<h3 id="note-composer-title">{target.title}</h3>
|
|
10240
9723
|
<span>{target.kind} / {target.lineageId}</span>
|
|
10241
9724
|
</div>
|
|
10242
9725
|
<button type="button" disabled={disabled} onClick={onClose}>Close</button>
|
|
@@ -10251,7 +9734,7 @@ function InsightComposerDialog({
|
|
|
10251
9734
|
/>
|
|
10252
9735
|
</label>
|
|
10253
9736
|
<label>
|
|
10254
|
-
<span>
|
|
9737
|
+
<span>Note Body</span>
|
|
10255
9738
|
<textarea
|
|
10256
9739
|
disabled={disabled}
|
|
10257
9740
|
rows={8}
|
|
@@ -10264,7 +9747,7 @@ function InsightComposerDialog({
|
|
|
10264
9747
|
<footer className="news-desk-modal__actions">
|
|
10265
9748
|
<button type="button" disabled={disabled} onClick={onClose}>Cancel</button>
|
|
10266
9749
|
<button type="button" className="news-desk-assignment-create-button" disabled={disabled || !summary.trim() || !body.trim()} onClick={onRun}>
|
|
10267
|
-
{disabled ? "Saving" : "Save
|
|
9750
|
+
{disabled ? "Saving" : "Save Note"}
|
|
10268
9751
|
</button>
|
|
10269
9752
|
</footer>
|
|
10270
9753
|
</div>
|
|
@@ -11011,14 +10494,14 @@ function PayloadDetailBlock({
|
|
|
11011
10494
|
);
|
|
11012
10495
|
}
|
|
11013
10496
|
|
|
11014
|
-
function
|
|
10497
|
+
function NoteMessageBlock({ notes }: { notes: MessageRecord[] }) {
|
|
11015
10498
|
return (
|
|
11016
10499
|
<div className="news-desk-detail-block">
|
|
11017
|
-
<p className="story-label">
|
|
11018
|
-
{
|
|
10500
|
+
<p className="story-label">Notes</p>
|
|
10501
|
+
{notes.slice(0, 6).map((message) => (
|
|
11019
10502
|
<div className="news-desk-detail-line" key={message.id}>
|
|
11020
10503
|
<span>{message.authorLabel ?? "knowledge"}</span>
|
|
11021
|
-
<strong>{message.summary ?? "Stored
|
|
10504
|
+
<strong>{message.summary ?? "Stored note"}</strong>
|
|
11022
10505
|
</div>
|
|
11023
10506
|
))}
|
|
11024
10507
|
</div>
|
|
@@ -11032,7 +10515,7 @@ function ReferenceDetailPanel({
|
|
|
11032
10515
|
curation,
|
|
11033
10516
|
disabled,
|
|
11034
10517
|
graph,
|
|
11035
|
-
|
|
10518
|
+
onCreateNote,
|
|
11036
10519
|
onMoveCorpus,
|
|
11037
10520
|
onReview,
|
|
11038
10521
|
onReviewTopicLabel,
|
|
@@ -11050,7 +10533,7 @@ function ReferenceDetailPanel({
|
|
|
11050
10533
|
curation: ReferenceCurationDisplayState | null;
|
|
11051
10534
|
disabled: boolean;
|
|
11052
10535
|
graph: SemanticGraph;
|
|
11053
|
-
|
|
10536
|
+
onCreateNote: (target: NoteTarget, summary: string, body: string) => Promise<void>;
|
|
11054
10537
|
onMoveCorpus: (reference: ReferenceRecord, corpusId: string) => void;
|
|
11055
10538
|
onReview: (action: ReferenceCurationAction) => void;
|
|
11056
10539
|
onReviewTopicLabel: (input: { action: TopicLabelAction; category: CategorySteeringCategory; note?: string | null; reference: ReferenceRecord; sourceRelationId?: string | null }) => void;
|
|
@@ -11329,7 +10812,7 @@ function ReferenceDetailPanel({
|
|
|
11329
10812
|
|
|
11330
10813
|
const lineageId = referenceLineageId;
|
|
11331
10814
|
const messages = graph.messagesFor("reference", lineageId);
|
|
11332
|
-
const
|
|
10815
|
+
const notes = graph.notesFor("reference", lineageId);
|
|
11333
10816
|
const neighborGroups = graph.neighbors("reference", lineageId);
|
|
11334
10817
|
const fallbackOutgoingCitationGroup = neighborGroups.find((group) => group.direction === "outgoing" && group.predicate === "cites") ?? null;
|
|
11335
10818
|
const fallbackIncomingCitationGroup = neighborGroups.find((group) => group.direction === "incoming" && group.predicate === "cites") ?? null;
|
|
@@ -11448,10 +10931,10 @@ function ReferenceDetailPanel({
|
|
|
11448
10931
|
reference={reference}
|
|
11449
10932
|
semanticRelations={semanticRelations}
|
|
11450
10933
|
/>
|
|
11451
|
-
<
|
|
10934
|
+
<ReferenceNotePanel
|
|
11452
10935
|
disabled={disabled}
|
|
11453
|
-
|
|
11454
|
-
|
|
10936
|
+
notes={notes}
|
|
10937
|
+
onCreateNote={onCreateNote}
|
|
11455
10938
|
reference={reference}
|
|
11456
10939
|
/>
|
|
11457
10940
|
<div data-news-desk-reference-metadata-expander>
|
|
@@ -12341,49 +11824,49 @@ function ReferenceCurationStatusPanel({ status }: { status: ReferenceCurationRun
|
|
|
12341
11824
|
);
|
|
12342
11825
|
}
|
|
12343
11826
|
|
|
12344
|
-
function
|
|
11827
|
+
function ReferenceNotePanel({
|
|
12345
11828
|
disabled,
|
|
12346
|
-
|
|
12347
|
-
|
|
11829
|
+
notes,
|
|
11830
|
+
onCreateNote,
|
|
12348
11831
|
reference,
|
|
12349
11832
|
}: {
|
|
12350
11833
|
disabled: boolean;
|
|
12351
|
-
|
|
12352
|
-
|
|
11834
|
+
notes: MessageRecord[];
|
|
11835
|
+
onCreateNote: (target: NoteTarget, summary: string, body: string) => Promise<void>;
|
|
12353
11836
|
reference: ReferenceRecord;
|
|
12354
11837
|
}) {
|
|
12355
11838
|
const [composerOpen, setComposerOpen] = useState(false);
|
|
12356
11839
|
const [body, setBody] = useState("");
|
|
12357
11840
|
const [error, setError] = useState<string | null>(null);
|
|
12358
11841
|
const [saving, setSaving] = useState(false);
|
|
12359
|
-
const target = useMemo(() =>
|
|
12360
|
-
const
|
|
11842
|
+
const target = useMemo(() => noteTargetForReference(reference), [reference]);
|
|
11843
|
+
const hasNotes = notes.length > 0;
|
|
12361
11844
|
|
|
12362
11845
|
return (
|
|
12363
|
-
<section className="news-desk-reference-workflow" data-reference-
|
|
11846
|
+
<section className="news-desk-reference-workflow" data-reference-note-workflow>
|
|
12364
11847
|
<header className="news-desk-reference-workflow__header">
|
|
12365
|
-
<p className="story-label">
|
|
12366
|
-
{!
|
|
11848
|
+
<p className="story-label">Notes</p>
|
|
11849
|
+
{!hasNotes && !composerOpen ? (
|
|
12367
11850
|
<button
|
|
12368
11851
|
type="button"
|
|
12369
|
-
data-news-desk-reference-
|
|
11852
|
+
data-news-desk-reference-note-trigger
|
|
12370
11853
|
disabled={disabled || saving}
|
|
12371
11854
|
onClick={() => {
|
|
12372
11855
|
setComposerOpen(true);
|
|
12373
11856
|
setError(null);
|
|
12374
11857
|
}}
|
|
12375
11858
|
>
|
|
12376
|
-
Add
|
|
11859
|
+
Add note
|
|
12377
11860
|
</button>
|
|
12378
11861
|
) : null}
|
|
12379
11862
|
</header>
|
|
12380
|
-
<div className="news-desk-reference-
|
|
12381
|
-
{
|
|
12382
|
-
<div className="news-desk-reference-
|
|
12383
|
-
{
|
|
11863
|
+
<div className="news-desk-reference-note-shell" data-reference-note-input>
|
|
11864
|
+
{hasNotes ? (
|
|
11865
|
+
<div className="news-desk-reference-note-list" data-reference-note-state>
|
|
11866
|
+
{notes.map((message) => (
|
|
12384
11867
|
<article className="news-desk-reference-topic-state-row" key={message.id}>
|
|
12385
11868
|
<div className="news-desk-reference-topic-state-row__headline">
|
|
12386
|
-
<strong>{message.summary ?? "Stored
|
|
11869
|
+
<strong>{message.summary ?? "Stored note"}</strong>
|
|
12387
11870
|
<span>{message.authorLabel ?? message.source ?? "knowledge"}</span>
|
|
12388
11871
|
</div>
|
|
12389
11872
|
<div className="news-desk-reference-topic-state-row__meta">
|
|
@@ -12393,35 +11876,35 @@ function ReferenceInsightPanel({
|
|
|
12393
11876
|
))}
|
|
12394
11877
|
</div>
|
|
12395
11878
|
) : null}
|
|
12396
|
-
{
|
|
11879
|
+
{hasNotes && !composerOpen ? (
|
|
12397
11880
|
<button
|
|
12398
11881
|
type="button"
|
|
12399
|
-
data-news-desk-reference-
|
|
11882
|
+
data-news-desk-reference-note-trigger
|
|
12400
11883
|
disabled={disabled || saving}
|
|
12401
11884
|
onClick={() => {
|
|
12402
11885
|
setComposerOpen(true);
|
|
12403
11886
|
setError(null);
|
|
12404
11887
|
}}
|
|
12405
11888
|
>
|
|
12406
|
-
Add
|
|
11889
|
+
Add note
|
|
12407
11890
|
</button>
|
|
12408
11891
|
) : null}
|
|
12409
11892
|
{composerOpen ? (
|
|
12410
11893
|
<form
|
|
12411
|
-
className="news-desk-reference-
|
|
12412
|
-
data-news-desk-reference-
|
|
11894
|
+
className="news-desk-reference-note-form"
|
|
11895
|
+
data-news-desk-reference-note-form
|
|
12413
11896
|
onSubmit={(event) => {
|
|
12414
11897
|
event.preventDefault();
|
|
12415
11898
|
if (disabled || saving) return;
|
|
12416
11899
|
const cleanBody = body.trim();
|
|
12417
11900
|
if (!cleanBody) {
|
|
12418
|
-
setError("
|
|
11901
|
+
setError("Note text is required.");
|
|
12419
11902
|
return;
|
|
12420
11903
|
}
|
|
12421
|
-
const cleanSummary =
|
|
11904
|
+
const cleanSummary = summarizeNoteForStorage(cleanBody);
|
|
12422
11905
|
setSaving(true);
|
|
12423
11906
|
setError(null);
|
|
12424
|
-
void
|
|
11907
|
+
void onCreateNote(target, cleanSummary, cleanBody)
|
|
12425
11908
|
.then(() => {
|
|
12426
11909
|
setSaving(false);
|
|
12427
11910
|
setBody("");
|
|
@@ -12429,12 +11912,12 @@ function ReferenceInsightPanel({
|
|
|
12429
11912
|
})
|
|
12430
11913
|
.catch((submitError) => {
|
|
12431
11914
|
setSaving(false);
|
|
12432
|
-
setError(submitError instanceof Error ? submitError.message : "Could not save
|
|
11915
|
+
setError(submitError instanceof Error ? submitError.message : "Could not save note.");
|
|
12433
11916
|
});
|
|
12434
11917
|
}}
|
|
12435
11918
|
>
|
|
12436
11919
|
<label className="news-desk-reference-curation-note">
|
|
12437
|
-
<span>
|
|
11920
|
+
<span>Note</span>
|
|
12438
11921
|
<textarea
|
|
12439
11922
|
disabled={disabled || saving}
|
|
12440
11923
|
value={body}
|
|
@@ -12442,9 +11925,9 @@ function ReferenceInsightPanel({
|
|
|
12442
11925
|
/>
|
|
12443
11926
|
</label>
|
|
12444
11927
|
{error ? <p className="news-desk-detail-copy">{error}</p> : null}
|
|
12445
|
-
<div className="news-desk-reference-
|
|
11928
|
+
<div className="news-desk-reference-note-form__actions">
|
|
12446
11929
|
<button type="submit" disabled={disabled || saving || !body.trim()}>
|
|
12447
|
-
{saving ? "Saving" : "Save
|
|
11930
|
+
{saving ? "Saving" : "Save note"}
|
|
12448
11931
|
</button>
|
|
12449
11932
|
<button
|
|
12450
11933
|
type="button"
|
|
@@ -12465,9 +11948,9 @@ function ReferenceInsightPanel({
|
|
|
12465
11948
|
);
|
|
12466
11949
|
}
|
|
12467
11950
|
|
|
12468
|
-
function
|
|
11951
|
+
function summarizeNoteForStorage(body: string): string {
|
|
12469
11952
|
const normalized = body.trim().replace(/\s+/g, " ");
|
|
12470
|
-
if (!normalized) return "
|
|
11953
|
+
if (!normalized) return "Note";
|
|
12471
11954
|
const firstSentenceMatch = normalized.match(/(.+?[.!?])(?:\s|$)/);
|
|
12472
11955
|
const candidate = (firstSentenceMatch?.[1] ?? normalized).trim();
|
|
12473
11956
|
if (candidate.length <= 120) return candidate;
|
|
@@ -13332,7 +12815,7 @@ function buildUiAnalysisReindexAssignmentPlan({
|
|
|
13332
12815
|
return { assignment, event, relation, metadata };
|
|
13333
12816
|
}
|
|
13334
12817
|
|
|
13335
|
-
function
|
|
12818
|
+
function noteTargetForReference(reference: ReferenceRecord): NoteTarget {
|
|
13336
12819
|
return {
|
|
13337
12820
|
kind: "reference",
|
|
13338
12821
|
id: reference.id,
|
|
@@ -13343,7 +12826,7 @@ function insightTargetForReference(reference: ReferenceRecord): InsightTarget {
|
|
|
13343
12826
|
};
|
|
13344
12827
|
}
|
|
13345
12828
|
|
|
13346
|
-
function
|
|
12829
|
+
function noteTargetForNewsroomSection(section: NewsroomSectionRecord): NoteTarget {
|
|
13347
12830
|
return {
|
|
13348
12831
|
kind: "newsroomSection",
|
|
13349
12832
|
id: section.id,
|
|
@@ -13354,7 +12837,7 @@ function insightTargetForNewsroomSection(section: NewsroomSectionRecord): Insigh
|
|
|
13354
12837
|
};
|
|
13355
12838
|
}
|
|
13356
12839
|
|
|
13357
|
-
function
|
|
12840
|
+
function noteTargetForSemanticObject(object: SemanticObjectSummary): NoteTarget | null {
|
|
13358
12841
|
if (object.kind !== "category" && object.kind !== "semanticNode") return null;
|
|
13359
12842
|
return {
|
|
13360
12843
|
kind: object.kind,
|
|
@@ -13366,21 +12849,21 @@ function insightTargetForSemanticObject(object: SemanticObjectSummary): InsightT
|
|
|
13366
12849
|
};
|
|
13367
12850
|
}
|
|
13368
12851
|
|
|
13369
|
-
function
|
|
12852
|
+
function noteTargetKey(target: NoteTarget): string {
|
|
13370
12853
|
return `${target.kind}#${target.lineageId}`;
|
|
13371
12854
|
}
|
|
13372
12855
|
|
|
13373
|
-
function
|
|
12856
|
+
function buildUiNoteRelation(message: MessageRecord, target: NoteTarget, now: string): SemanticRelationRecord {
|
|
13374
12857
|
const subjectStateKey = semanticStateKey("message", message.id);
|
|
13375
12858
|
const objectStateKey = semanticStateKey(target.kind, target.lineageId);
|
|
13376
12859
|
const subjectVersionKey = semanticVersionKey("message", message.id);
|
|
13377
12860
|
const objectVersionKey = semanticVersionKey(target.kind, target.id);
|
|
13378
12861
|
return {
|
|
13379
|
-
id: `semantic-relation-${hashUiKey([subjectVersionKey, "
|
|
12862
|
+
id: `semantic-relation-${hashUiKey([subjectVersionKey, "note_about", objectVersionKey])}`,
|
|
13380
12863
|
relationState: "current",
|
|
13381
|
-
predicate: "
|
|
13382
|
-
relationTypeId: "semantic-relation-type-
|
|
13383
|
-
relationTypeKey: "
|
|
12864
|
+
predicate: "note_about",
|
|
12865
|
+
relationTypeId: "semantic-relation-type-note-about",
|
|
12866
|
+
relationTypeKey: "note_about",
|
|
13384
12867
|
relationDomain: "knowledge",
|
|
13385
12868
|
subjectKind: "message",
|
|
13386
12869
|
subjectId: message.id,
|
|
@@ -13393,7 +12876,7 @@ function buildUiInsightRelation(message: MessageRecord, target: InsightTarget, n
|
|
|
13393
12876
|
subjectStateKey,
|
|
13394
12877
|
objectStateKey,
|
|
13395
12878
|
objectSubjectStateKey: `${objectStateKey}#message`,
|
|
13396
|
-
predicateObjectStateKey: `
|
|
12879
|
+
predicateObjectStateKey: `note_about#${objectStateKey}`,
|
|
13397
12880
|
subjectVersionKey,
|
|
13398
12881
|
objectVersionKey,
|
|
13399
12882
|
score: null,
|
|
@@ -13890,10 +13373,10 @@ function compareDoctrineCategories(left: DoctrineCategory, right: DoctrineCatego
|
|
|
13890
13373
|
|
|
13891
13374
|
function formatDeskSectionHeadline(section: NewsDeskTab): string {
|
|
13892
13375
|
if (section === "search") return "Search";
|
|
13893
|
-
if (section === "
|
|
13376
|
+
if (section === "desks") return "Taxonomy";
|
|
13377
|
+
if (section === "topics") return "Topic proposals";
|
|
13894
13378
|
if (section === "concepts") return "Concepts";
|
|
13895
13379
|
if (section === "references") return "References";
|
|
13896
|
-
if (section === "insights") return "Insights";
|
|
13897
13380
|
if (section === "messages") return "Messages";
|
|
13898
13381
|
if (section === "assignments") return "Assignments";
|
|
13899
13382
|
if (section === "administration") return "Administration";
|
|
@@ -13902,13 +13385,13 @@ function formatDeskSectionHeadline(section: NewsDeskTab): string {
|
|
|
13902
13385
|
|
|
13903
13386
|
function formatDeskSectionLede(section: NewsDeskTab): string {
|
|
13904
13387
|
if (section === "search") return "Search the newsroom knowledge base with semantic and ontology context.";
|
|
13905
|
-
if (section === "
|
|
13388
|
+
if (section === "desks") return "The desks and topic categories that organize coverage, with each desk's mission and policies.";
|
|
13389
|
+
if (section === "topics") return "Review suggested changes to the subject areas the newsroom covers.";
|
|
13906
13390
|
if (section === "concepts") return "Browse people, organizations, places, and ideas found in the source material.";
|
|
13907
13391
|
if (section === "references") return "Review source materials before they become usable evidence.";
|
|
13908
|
-
if (section === "insights") return "Browse recent research insights and discuss each one in a forum thread to refine the synthesis.";
|
|
13909
13392
|
if (section === "messages") return "Read notes, rationales, forum threads, curation decisions, and other work products from people and agents.";
|
|
13910
13393
|
if (section === "assignments") return "Create, filter, claim, and complete newsroom work.";
|
|
13911
|
-
if (section === "administration") return "Manage users, roles, doctrine,
|
|
13394
|
+
if (section === "administration") return "Manage users, roles, doctrine, and newsroom procedures.";
|
|
13912
13395
|
return "";
|
|
13913
13396
|
}
|
|
13914
13397
|
|
|
@@ -16537,21 +16020,21 @@ function assertReferenceQualityMutationSucceeded(
|
|
|
16537
16020
|
return response.data;
|
|
16538
16021
|
}
|
|
16539
16022
|
|
|
16540
|
-
function
|
|
16541
|
-
response:
|
|
16023
|
+
function assertReferenceNoteMutationSucceeded(
|
|
16024
|
+
response: ReferenceNoteResponse,
|
|
16542
16025
|
referenceId: string,
|
|
16543
|
-
): NonNullable<
|
|
16026
|
+
): NonNullable<ReferenceNoteResponse["data"]> {
|
|
16544
16027
|
if (response.errors?.length) {
|
|
16545
16028
|
throw new Error(response.errors.map(formatGraphQLError).join("; "));
|
|
16546
16029
|
}
|
|
16547
16030
|
if (!response.data?.ok) {
|
|
16548
|
-
throw new Error(`Reference
|
|
16031
|
+
throw new Error(`Reference note was not saved for ${referenceId}.`);
|
|
16549
16032
|
}
|
|
16550
16033
|
if (response.data.referenceId && response.data.referenceId !== referenceId) {
|
|
16551
|
-
throw new Error(`Reference
|
|
16034
|
+
throw new Error(`Reference note response did not match ${referenceId}.`);
|
|
16552
16035
|
}
|
|
16553
16036
|
if (!response.data.messageId || !response.data.relationId) {
|
|
16554
|
-
throw new Error(`Reference
|
|
16037
|
+
throw new Error(`Reference note saved without required audit relations for ${referenceId}.`);
|
|
16555
16038
|
}
|
|
16556
16039
|
return response.data;
|
|
16557
16040
|
}
|