@alpaca-editor/core 1.0.4105 → 1.0.4106

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.
Files changed (49) hide show
  1. package/dist/config/config.js +7 -0
  2. package/dist/config/config.js.map +1 -1
  3. package/dist/editor/control-center/IndexOverview.js +48 -30
  4. package/dist/editor/control-center/IndexOverview.js.map +1 -1
  5. package/dist/editor/control-center/LatestFeedback.d.ts +1 -0
  6. package/dist/editor/control-center/LatestFeedback.js +134 -0
  7. package/dist/editor/control-center/LatestFeedback.js.map +1 -0
  8. package/dist/editor/field-types/richtext/contextMenuFactory.js +11 -0
  9. package/dist/editor/field-types/richtext/contextMenuFactory.js.map +1 -1
  10. package/dist/editor/menubar/toolbar-sections/EditControls.js +1 -2
  11. package/dist/editor/menubar/toolbar-sections/EditControls.js.map +1 -1
  12. package/dist/editor/menubar/toolbar-sections/ViewportControls.js +3 -2
  13. package/dist/editor/menubar/toolbar-sections/ViewportControls.js.map +1 -1
  14. package/dist/editor/reviews/Comment.js +12 -2
  15. package/dist/editor/reviews/Comment.js.map +1 -1
  16. package/dist/editor/reviews/CommentView.d.ts +3 -1
  17. package/dist/editor/reviews/CommentView.js +9 -6
  18. package/dist/editor/reviews/CommentView.js.map +1 -1
  19. package/dist/editor/reviews/Comments.js +21 -94
  20. package/dist/editor/reviews/Comments.js.map +1 -1
  21. package/dist/editor/reviews/SuggestedEdit.js +8 -6
  22. package/dist/editor/reviews/SuggestedEdit.js.map +1 -1
  23. package/dist/editor/reviews/SuggestionDisplayPopover.js +7 -5
  24. package/dist/editor/reviews/SuggestionDisplayPopover.js.map +1 -1
  25. package/dist/editor/services/reviewsService.d.ts +1 -0
  26. package/dist/editor/services/reviewsService.js +8 -0
  27. package/dist/editor/services/reviewsService.js.map +1 -1
  28. package/dist/editor/services/suggestedEditsService.d.ts +1 -0
  29. package/dist/editor/services/suggestedEditsService.js +8 -0
  30. package/dist/editor/services/suggestedEditsService.js.map +1 -1
  31. package/dist/revision.d.ts +2 -2
  32. package/dist/revision.js +2 -2
  33. package/dist/types.d.ts +4 -0
  34. package/package.json +1 -1
  35. package/src/config/config.tsx +7 -0
  36. package/src/editor/control-center/IndexOverview.tsx +67 -33
  37. package/src/editor/control-center/LatestFeedback.tsx +198 -0
  38. package/src/editor/field-types/richtext/contextMenuFactory.tsx +14 -0
  39. package/src/editor/menubar/toolbar-sections/EditControls.tsx +2 -3
  40. package/src/editor/menubar/toolbar-sections/ViewportControls.tsx +19 -1
  41. package/src/editor/reviews/Comment.tsx +16 -1
  42. package/src/editor/reviews/CommentView.tsx +24 -24
  43. package/src/editor/reviews/Comments.tsx +73 -181
  44. package/src/editor/reviews/SuggestedEdit.tsx +16 -10
  45. package/src/editor/reviews/SuggestionDisplayPopover.tsx +14 -9
  46. package/src/editor/services/reviewsService.ts +10 -0
  47. package/src/editor/services/suggestedEditsService.ts +10 -0
  48. package/src/revision.ts +2 -2
  49. package/src/types.ts +4 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alpaca-editor/core",
3
- "version": "1.0.4105",
3
+ "version": "1.0.4106",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -79,6 +79,7 @@ import { Info } from "../editor/control-center/Info";
79
79
  import { QuotaInfo } from "../editor/control-center/QuotaInfo";
80
80
  import { About } from "../editor/control-center/About";
81
81
  import { WebSocketMessages } from "../editor/control-center/WebSocketMessages";
82
+ import { LatestFeedback } from "../editor/control-center/LatestFeedback";
82
83
  import { DbSetupStep } from "../editor/control-center/setup-steps/DbSetupStep";
83
84
  import { SettingsSetupStep } from "../editor/control-center/setup-steps/SettingsSetupStep";
84
85
  import { AiSetupStep } from "../editor/control-center/setup-steps/AiSetupStep";
@@ -579,6 +580,12 @@ export const getConfiguration = (): EditorConfiguration => {
579
580
  initialSize: 100,
580
581
  noOverflow: true,
581
582
  },
583
+ {
584
+ name: "latest-feedback",
585
+ title: "Latest Feedback",
586
+ content: <LatestFeedback />,
587
+ initialSize: 50,
588
+ },
582
589
  ],
583
590
  },
584
591
  ...pageEditorViewBase,
@@ -1,13 +1,14 @@
1
- import { useEffect, useState } from "react";
1
+ import { useCallback, useEffect, useState } from "react";
2
2
  import {
3
3
  RefreshCw,
4
+ ExternalLink,
4
5
  Database,
5
6
  Clock,
6
7
  CheckCircle,
7
8
  AlertCircle,
8
9
  Trash2,
9
10
  } from "lucide-react";
10
-
11
+ import { Button } from "../../components/ui/button";
11
12
  import { classNames } from "primereact/utils";
12
13
  import { formatDateOnly, formatDateTime } from "../utils";
13
14
  import {
@@ -26,6 +27,8 @@ import {
26
27
  startDirectGeneration,
27
28
  } from "../services/indexService";
28
29
 
30
+ import { useEditContext } from "../client/editContext";
31
+
29
32
  export function IndexOverview() {
30
33
  const [indexStatus, setIndexStatus] = useState<IndexStatus | null>(null);
31
34
  const [isLoading, setIsLoading] = useState(false);
@@ -191,6 +194,54 @@ export function IndexOverview() {
191
194
  }
192
195
  };
193
196
 
197
+ const editContext = useEditContext();
198
+ const goToSettings = useCallback(() => {
199
+ // Ensure URL reflects the target view and panel before switching views
200
+ editContext?.updateUrl({ view: "control-center", ccpanel: "setup" });
201
+ editContext?.switchView("control-center");
202
+ }, [editContext]);
203
+
204
+
205
+ const getIndexStatusInfo = (
206
+ isCollecting: boolean,
207
+ isSubmitting: boolean,
208
+ indexStatus: IndexStatus | null,
209
+ importStatus: ImportStatus | null,
210
+ centroidsStatus: CentroidsStatus | null,
211
+ ) => {
212
+ const batches = indexStatus?.batches || [];
213
+ const inProgress = batches.some((b) => {
214
+ const s = (b.status || "").toLowerCase();
215
+ return (
216
+ s === "submitted" || s === "processing" || s === "queued"
217
+ );
218
+ });
219
+
220
+ if (isCollecting) {
221
+ return { statusLabel: "Collecting items", statusClass: "text-indigo-600", hasSettingsIssue: false};
222
+ } else if (isSubmitting || inProgress) {
223
+ return { statusLabel: "Generating Embeddings", statusClass: "text-blue-600", hasSettingsIssue: false };
224
+ } else if (importStatus?.isImporting) {
225
+ return {
226
+ statusLabel: importStatus.status || "Importing embeddings",
227
+ statusClass: "text-purple-600",
228
+ hasSettingsIssue: false
229
+ };
230
+ } else if (centroidsStatus?.isPopulating) {
231
+ return {
232
+ statusLabel: centroidsStatus.status || "Populating centroids",
233
+ statusClass: "text-pink-600",
234
+ hasSettingsIssue: false
235
+ };
236
+ } else if (indexStatus?.rebuilding) {
237
+ return { statusLabel: "Rebuilding", statusClass: "text-orange-600", hasSettingsIssue: false };
238
+ } else if (indexStatus?.messages != null && indexStatus.messages.length > 0 && indexStatus.messages[0]?.message){
239
+ return { statusLabel: indexStatus.messages[0].message, statusClass: "text-red-600", hasSettingsIssue: true };
240
+ }else {
241
+ return { statusLabel: "Ready", statusClass: "text-green-600", hasSettingsIssue: false };
242
+ }
243
+ };
244
+
194
245
  return (
195
246
  <div className="flex h-full items-stretch">
196
247
  <div className="flex min-w-96 flex-col gap-4 p-4">
@@ -376,41 +427,24 @@ export function IndexOverview() {
376
427
  <div className="rounded bg-gray-50 p-3">
377
428
  <div className="text-sm text-gray-600">Status</div>
378
429
  {(() => {
379
- const batches = indexStatus?.batches || [];
380
- const inProgress = batches.some((b) => {
381
- const s = (b.status || "").toLowerCase();
382
- return (
383
- s === "submitted" || s === "processing" || s === "queued"
384
- );
385
- });
386
- const statusLabel = isCollecting
387
- ? "Collecting items"
388
- : isSubmitting || inProgress
389
- ? "Generating Embeddings"
390
- : importStatus?.isImporting
391
- ? importStatus.status || "Importing embeddings"
392
- : centroidsStatus?.isPopulating
393
- ? centroidsStatus.status || "Populating centroids"
394
- : indexStatus.rebuilding
395
- ? "Rebuilding"
396
- : "Ready";
397
- const statusClass =
398
- statusLabel === "Rebuilding"
399
- ? "text-orange-600"
400
- : statusLabel === "Generating Embeddings"
401
- ? "text-blue-600"
402
- : statusLabel === "Collecting items"
403
- ? "text-indigo-600"
404
- : importStatus?.isImporting
405
- ? "text-purple-600"
406
- : centroidsStatus?.isPopulating
407
- ? "text-pink-600"
408
- : "text-green-600";
430
+ const { statusLabel, statusClass, hasSettingsIssue } = getIndexStatusInfo(
431
+ isCollecting,
432
+ isSubmitting,
433
+ indexStatus,
434
+ importStatus,
435
+ centroidsStatus,
436
+ );
409
437
  return (
410
438
  <div
411
- className={classNames("text-sm font-medium", statusClass)}
439
+ className={classNames("text-sm font-medium flex items-center gap-2", statusClass)}
412
440
  >
413
441
  {statusLabel}
442
+ {hasSettingsIssue && (
443
+ <Button size="sm" variant="outline" onClick={goToSettings}>
444
+ <ExternalLink className="h-4 w-4" strokeWidth={1} />
445
+ Settings
446
+ </Button>
447
+ )}
414
448
  </div>
415
449
  );
416
450
  })()}
@@ -0,0 +1,198 @@
1
+ import { useCallback, useEffect, useMemo, useState } from "react";
2
+ import { useEditContext } from "../client/editContext";
3
+ import { Comment, SuggestedEdit } from "../../types";
4
+ import { getLatestComments } from "../services/reviewsService";
5
+ import { getLatestSuggestedEdits } from "../services/suggestedEditsService";
6
+ import { Lightbulb, MessageSquare } from "lucide-react";
7
+
8
+ type FeedbackItem =
9
+ | (Comment & { kind: "comment" })
10
+ | (SuggestedEdit & { kind: "suggestion" });
11
+
12
+ export function LatestFeedback() {
13
+ const editContext = useEditContext();
14
+ const [items, setItems] = useState<FeedbackItem[]>([]);
15
+ const [isLoading, setIsLoading] = useState(false);
16
+ const [error, setError] = useState<string | null>(null);
17
+
18
+ // Cutoff is now computed server-side. Keep memo in case we later want client hints.
19
+ const sinceIso = useMemo(() => null, []);
20
+
21
+ const load = useCallback(async () => {
22
+ let cancelled = false;
23
+ setIsLoading(true);
24
+ setError(null);
25
+ try {
26
+ const take = 50;
27
+ const [cRes, sRes] = await Promise.all([
28
+ getLatestComments(take),
29
+ getLatestSuggestedEdits(take),
30
+ ]);
31
+
32
+ const comments = ((cRes.data || []) as Comment[]).map((c) => ({
33
+ ...(c as Comment),
34
+ kind: "comment" as const,
35
+ }));
36
+ const suggestions = ((sRes.data || []) as SuggestedEdit[]).map((s) => ({
37
+ ...(s as SuggestedEdit),
38
+ kind: "suggestion" as const,
39
+ }));
40
+
41
+ const merged = [...comments, ...suggestions].sort((a, b) => {
42
+ const ad = new Date((a as any).created || 0).getTime();
43
+ const bd = new Date((b as any).created || 0).getTime();
44
+ return bd - ad;
45
+ });
46
+
47
+ const latest = merged.slice(0, 50);
48
+ if (!cancelled) setItems(latest);
49
+ } catch (e: any) {
50
+ if (!cancelled) setError(e?.message || "Failed to load latest items");
51
+ } finally {
52
+ if (!cancelled) setIsLoading(false);
53
+ }
54
+ return () => {
55
+ cancelled = true;
56
+ };
57
+ }, [sinceIso]);
58
+
59
+ useEffect(() => {
60
+ let disposed: any;
61
+ (async () => {
62
+ disposed = await load();
63
+ })();
64
+ load();
65
+ return () => {
66
+ try {
67
+ disposed?.();
68
+ } catch {}
69
+ };
70
+ }, [load]);
71
+
72
+ useEffect(() => {
73
+ const typesToRefresh = new Set([
74
+ "comment-updated",
75
+ "comment-deleted",
76
+ "suggested-edit-updated",
77
+ "suggested-edit-deleted",
78
+ ]);
79
+ const dispose = editContext?.addSocketMessageListener?.((message: any) => {
80
+ try {
81
+ if (typesToRefresh.has(message?.type)) {
82
+ void load();
83
+ }
84
+ } catch {}
85
+ });
86
+ return () => {
87
+ try {
88
+ dispose?.();
89
+ } catch {}
90
+ };
91
+ }, [editContext?.addSocketMessageListener, load]);
92
+
93
+ const onOpenItem = async (item: FeedbackItem) => {
94
+ try {
95
+ const mainId =
96
+ item.kind === "comment" ? item.mainItemId : item.mainItemId;
97
+ const lang =
98
+ item.kind === "comment" ? item.language : item.mainItemLanguage;
99
+ const ver = item.kind === "comment" ? item.version : item.mainItemVersion;
100
+ await editContext?.loadItem({ id: mainId, language: lang, version: ver });
101
+ editContext?.setSelectedComment(
102
+ item.kind === "comment" ? (item as Comment) : undefined,
103
+ );
104
+ if (item.kind === "comment") {
105
+ editContext?.setScrollIntoView((item as Comment).itemId);
106
+ }
107
+ editContext?.switchView("comments");
108
+ } catch {}
109
+ };
110
+
111
+ const Row = ({ item }: { item: FeedbackItem }) => {
112
+ const isComment = item.kind === "comment";
113
+ const created = (item as any).created
114
+ ? new Date((item as any).created)
115
+ : undefined;
116
+ const dateText = created
117
+ ? `${created.toLocaleDateString()} ${created.toLocaleTimeString()}`
118
+ : "";
119
+ const comment = isComment ? (item as Comment) : undefined;
120
+ const pageName = isComment
121
+ ? comment?.mainItemName ||
122
+ (comment?.relatedItems || []).find(
123
+ (ri) => ri.itemId === comment?.mainItemId,
124
+ )?.itemName
125
+ : (item as SuggestedEdit).mainItemName;
126
+ const componentName = isComment
127
+ ? comment?.itemName
128
+ : (item as SuggestedEdit).itemName;
129
+ const fieldName = isComment ? comment?.fieldName : undefined;
130
+ const user = (item as any).authorDisplayName || (item as any).author || "";
131
+ const label = isComment ? "Comment" : "Suggestion";
132
+
133
+ return (
134
+ <button
135
+ className="w-full cursor-pointer rounded p-1 text-left hover:bg-gray-100"
136
+ onClick={() => onOpenItem(item)}
137
+ >
138
+ <div className="flex items-center justify-between gap-2 text-xs">
139
+ <div className="flex items-center gap-2 truncate">
140
+ <span
141
+ className="inline-flex items-center justify-center rounded py-0.5 text-gray-700"
142
+ aria-label={label}
143
+ title={label}
144
+ >
145
+ {isComment ? (
146
+ <MessageSquare className="h-3.5 w-3.5" strokeWidth={1} />
147
+ ) : (
148
+ <Lightbulb className="h-3.5 w-3.5" strokeWidth={1} />
149
+ )}
150
+ </span>
151
+ <div className="flex items-center">
152
+ {[
153
+ pageName,
154
+ componentName,
155
+ fieldName ||
156
+ (item.kind === "suggestion" ? item.fieldId : undefined),
157
+ ]
158
+ .filter((x): x is string => !!x && x.length > 0)
159
+ .map((seg, idx, arr) => (
160
+ <span key={idx} className="truncate">
161
+ {idx > 0 && (
162
+ <span className="mx-1 text-gray-400">&gt;</span>
163
+ )}
164
+ <span
165
+ className={
166
+ idx === 0
167
+ ? "font-medium text-gray-900"
168
+ : "text-gray-500"
169
+ }
170
+ >
171
+ {seg}
172
+ </span>
173
+ </span>
174
+ ))}
175
+ </div>
176
+ {user && <span className="text-gray-500"> · {user}</span>}
177
+ </div>
178
+ <div className="shrink-0 text-[10px] text-gray-500">{dateText}</div>
179
+ </div>
180
+ </button>
181
+ );
182
+ };
183
+
184
+ return (
185
+ <div className="h-full overflow-auto p-2">
186
+ {isLoading && <div className="p-2 text-xs text-gray-500">Loading…</div>}
187
+ {error && <div className="p-2 text-xs text-red-600">{error}</div>}
188
+ {!isLoading && !error && items.length === 0 && (
189
+ <div className="p-2 text-xs text-gray-500">No recent activity</div>
190
+ )}
191
+ <div className="divide-y">
192
+ {items.map((it) => (
193
+ <Row key={`${it.kind}-${(it as any).id}`} item={it} />
194
+ ))}
195
+ </div>
196
+ </div>
197
+ );
198
+ }
@@ -3,6 +3,7 @@ import { Field } from "../../pageModel";
3
3
  import { EditContextType } from "../../client/editContext";
4
4
  import { getRichTextProfile } from "../../services/contentService";
5
5
  import { getCachedParsedProfile } from "./utils/profileServiceCache";
6
+ import { getComponentById } from "../../componentTreeHelper";
6
7
  import {
7
8
  RichTextEditorProfile,
8
9
  SLATE_MARKS,
@@ -82,6 +83,19 @@ export async function createRichTextContextMenu(
82
83
  const richTextField = field as RichTextField;
83
84
  const profilePath = richTextField.customProperties?.profile;
84
85
 
86
+ // Hide formatting when layout components are disabled and this field belongs to a layout component
87
+ try {
88
+ if (editContext?.showLayoutComponents === false && editContext?.page) {
89
+ const owner = getComponentById(
90
+ field.descriptor.item.id,
91
+ editContext.page,
92
+ );
93
+ if (owner?.layoutId) {
94
+ return [];
95
+ }
96
+ }
97
+ } catch {}
98
+
85
99
  // Get the iframe document
86
100
  const iframeDocument =
87
101
  editContext.pageView?.editorIframe?.contentWindow?.document;
@@ -18,12 +18,11 @@ export function EditControls({
18
18
  if (!editContext) return null;
19
19
 
20
20
  // Calculate total count of comments and suggestions
21
- const commentsCount =
22
- editContext.comments?.filter((x) => !x.isResolved).length || 0;
21
+
23
22
  const suggestionsCount =
24
23
  editContext.suggestedEdits?.filter((x) => x.status === "pending").length ||
25
24
  0;
26
- const totalCount = commentsCount + suggestionsCount;
25
+ const totalCount = suggestionsCount;
27
26
 
28
27
  return (
29
28
  <>
@@ -1,7 +1,7 @@
1
1
  import { CompareIcon } from "../../ui/Icons";
2
2
  import { useEditContext } from "../../client/editContext";
3
3
  import { SimpleIconButton } from "../../ui/SimpleIconButton";
4
- import { Monitor, Smartphone, Table } from "lucide-react";
4
+ import { Monitor, Smartphone, Table, MessageSquareMore } from "lucide-react";
5
5
  import { Separator } from "../Separator";
6
6
 
7
7
  export function ViewportControls() {
@@ -12,6 +12,8 @@ export function ViewportControls() {
12
12
  const pageViewContext = editContext.pageView;
13
13
  const device = pageViewContext.device;
14
14
  const setDevice = pageViewContext.setDevice;
15
+ const commentsCount =
16
+ editContext.comments?.filter((x) => !x.isResolved).length || 0;
15
17
 
16
18
  return (
17
19
  <>
@@ -56,6 +58,22 @@ export function ViewportControls() {
56
58
  onClick={() => editContext.setCompareMode(!editContext.compareMode)}
57
59
  />
58
60
  )}
61
+ <Separator size="large" />{" "}
62
+ <SimpleIconButton
63
+ className="relative"
64
+ selected={!!editContext.showComments}
65
+ icon={<MessageSquareMore className="h-6 w-6 p-1" strokeWidth={1} />}
66
+ label={editContext.showComments ? "Hide comments" : "Show comments"}
67
+ size="large"
68
+ data-testid="toggle-comments-button"
69
+ onClick={() => editContext.setShowComments(!editContext.showComments)}
70
+ >
71
+ {commentsCount > 0 && (
72
+ <div className="bg-theme-secondary text-3xs absolute -top-1 -right-1 flex h-[16px] min-w-[16px] items-center justify-center rounded-full px-1 text-white">
73
+ {commentsCount > 99 ? "99+" : commentsCount}
74
+ </div>
75
+ )}
76
+ </SimpleIconButton>
59
77
  </>
60
78
  );
61
79
  }
@@ -136,7 +136,20 @@ export function Comment({
136
136
  className={`mb-3 cursor-pointer rounded-lg border-2 bg-white p-3 shadow-sm hover:bg-gray-50 ${
137
137
  isSelected ? "border-blue-500" : "border-transparent"
138
138
  }`}
139
- onClick={() => {
139
+ onClick={async () => {
140
+ const currentPageId = editContext?.currentItemDescriptor?.id;
141
+ if (
142
+ currentPageId &&
143
+ comment.mainItemId &&
144
+ comment.mainItemId !== currentPageId
145
+ ) {
146
+ await editContext?.loadItem({
147
+ id: comment.mainItemId,
148
+ language: comment.language,
149
+ version: comment.version,
150
+ });
151
+ }
152
+
140
153
  editContext?.setSelectedComment(comment);
141
154
  editContext?.setScrollIntoView(comment.itemId);
142
155
  editContext?.select([comment.itemId]);
@@ -161,6 +174,8 @@ export function Comment({
161
174
  canDelete={canDelete}
162
175
  canResolve={canResolve}
163
176
  availableTags={availableTags}
177
+ basePageName={editContext?.currentItemDescriptor?.name}
178
+ basePageId={editContext?.currentItemDescriptor?.id}
164
179
  onEdit={() => setIsEditing(true)}
165
180
  onDelete={handleDelete}
166
181
  onResolve={handleResolve}
@@ -16,6 +16,11 @@ export interface CommentViewProps {
16
16
  canDelete: boolean;
17
17
  canResolve: boolean;
18
18
  availableTags?: { label: string; color: string }[];
19
+ // The id of the page currently open in the editor. Used to decide when to
20
+ // show the page name for comments belonging to a different page (child page).
21
+ basePageId?: string;
22
+ // The name of the page to display in the breadcrumb
23
+ basePageName?: string;
19
24
  onEdit?: () => void;
20
25
  onDelete?: () => void;
21
26
  onResolve?: () => void;
@@ -31,6 +36,8 @@ export function CommentView({
31
36
  canDelete,
32
37
  canResolve,
33
38
  availableTags = [],
39
+ basePageId,
40
+ basePageName,
34
41
  onEdit,
35
42
  onDelete,
36
43
  onResolve,
@@ -73,36 +80,29 @@ export function CommentView({
73
80
  };
74
81
 
75
82
  const renderContextInfo = () => {
76
- const showItemName =
77
- comment.itemId && comment.mainItemId !== comment.itemId;
78
- const showFieldName = comment.fieldId;
83
+ const pageName = basePageName;
84
+ const isComponentDifferent =
85
+ !!comment.itemId && comment.mainItemId !== comment.itemId;
86
+ const componentName = isComponentDifferent ? comment.itemName : undefined;
87
+ const fieldName = comment.fieldName;
79
88
 
80
- if (!showItemName && !showFieldName) return null;
89
+ const segments = [pageName, componentName, fieldName].filter(
90
+ (x): x is string => !!x && x.length > 0,
91
+ );
92
+
93
+ if (segments.length === 0) return null;
81
94
 
82
95
  return (
83
96
  <div
84
97
  className={`${compact ? "mt-2" : "mt-3"} flex items-center border-t pt-${compact ? "2" : "3"} text-xs`}
85
98
  data-testid="comment-context-info"
86
99
  >
87
- {showItemName && (
88
- <div
89
- className="text-2xs text-gray-500"
90
- data-testid="comment-item-name"
91
- >
92
- {comment.itemName}
93
- </div>
94
- )}
95
- {showFieldName && showItemName && (
96
- <div className="text-2xs mx-2 text-gray-500">&gt;</div>
97
- )}
98
- {showFieldName && (
99
- <div
100
- className="text-2xs text-gray-500"
101
- data-testid="comment-field-name"
102
- >
103
- {comment.fieldName}
104
- </div>
105
- )}
100
+ {segments.map((seg, idx) => (
101
+ <React.Fragment key={idx}>
102
+ {idx > 0 && <div className="text-2xs mx-2 text-gray-500">&gt;</div>}
103
+ <div className="text-2xs text-gray-500">{seg}</div>
104
+ </React.Fragment>
105
+ ))}
106
106
  </div>
107
107
  );
108
108
  };
@@ -115,7 +115,7 @@ export function CommentView({
115
115
  >
116
116
  <div>
117
117
  <div
118
- className={`${compact ? "text-xs" : "text-sm"} font-bold text-gray-900`}
118
+ className={`text-dark text-xs font-medium`}
119
119
  title={comment.author}
120
120
  >
121
121
  {comment.authorDisplayName}