@alpaca-editor/core 1.0.4104 → 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 (90) hide show
  1. package/dist/config/config.js +7 -0
  2. package/dist/config/config.js.map +1 -1
  3. package/dist/editor/MainLayout.js +1 -1
  4. package/dist/editor/MainLayout.js.map +1 -1
  5. package/dist/editor/Terminal.js +2 -2
  6. package/dist/editor/Terminal.js.map +1 -1
  7. package/dist/editor/ai/AgentTerminal.js +5 -3
  8. package/dist/editor/ai/AgentTerminal.js.map +1 -1
  9. package/dist/editor/ai/Agents.js +9 -1
  10. package/dist/editor/ai/Agents.js.map +1 -1
  11. package/dist/editor/ai/AiResponseMessage.js +4 -2
  12. package/dist/editor/ai/AiResponseMessage.js.map +1 -1
  13. package/dist/editor/client/EditorShell.js +2 -0
  14. package/dist/editor/client/EditorShell.js.map +1 -1
  15. package/dist/editor/control-center/IndexOverview.js +51 -32
  16. package/dist/editor/control-center/IndexOverview.js.map +1 -1
  17. package/dist/editor/control-center/LatestFeedback.d.ts +1 -0
  18. package/dist/editor/control-center/LatestFeedback.js +134 -0
  19. package/dist/editor/control-center/LatestFeedback.js.map +1 -0
  20. package/dist/editor/control-center/WebSocketMessages.js +3 -3
  21. package/dist/editor/control-center/WebSocketMessages.js.map +1 -1
  22. package/dist/editor/field-types/richtext/contextMenuFactory.js +11 -0
  23. package/dist/editor/field-types/richtext/contextMenuFactory.js.map +1 -1
  24. package/dist/editor/media-selector/MediaFolderBrowser.js +2 -2
  25. package/dist/editor/media-selector/MediaFolderBrowser.js.map +1 -1
  26. package/dist/editor/menubar/ActiveUsers.js +3 -2
  27. package/dist/editor/menubar/ActiveUsers.js.map +1 -1
  28. package/dist/editor/menubar/toolbar-sections/EditControls.js +1 -2
  29. package/dist/editor/menubar/toolbar-sections/EditControls.js.map +1 -1
  30. package/dist/editor/menubar/toolbar-sections/ViewportControls.js +3 -2
  31. package/dist/editor/menubar/toolbar-sections/ViewportControls.js.map +1 -1
  32. package/dist/editor/reviews/Comment.js +12 -2
  33. package/dist/editor/reviews/Comment.js.map +1 -1
  34. package/dist/editor/reviews/CommentView.d.ts +3 -1
  35. package/dist/editor/reviews/CommentView.js +9 -6
  36. package/dist/editor/reviews/CommentView.js.map +1 -1
  37. package/dist/editor/reviews/Comments.js +64 -76
  38. package/dist/editor/reviews/Comments.js.map +1 -1
  39. package/dist/editor/reviews/SuggestedEdit.js +8 -6
  40. package/dist/editor/reviews/SuggestedEdit.js.map +1 -1
  41. package/dist/editor/reviews/SuggestionDisplayPopover.js +7 -5
  42. package/dist/editor/reviews/SuggestionDisplayPopover.js.map +1 -1
  43. package/dist/editor/services/reviewsService.d.ts +1 -0
  44. package/dist/editor/services/reviewsService.js +8 -0
  45. package/dist/editor/services/reviewsService.js.map +1 -1
  46. package/dist/editor/services/suggestedEditsService.d.ts +1 -0
  47. package/dist/editor/services/suggestedEditsService.js +8 -0
  48. package/dist/editor/services/suggestedEditsService.js.map +1 -1
  49. package/dist/editor/sidebar/Completions.js +2 -1
  50. package/dist/editor/sidebar/Completions.js.map +1 -1
  51. package/dist/editor/ui/Icons.d.ts +2 -1
  52. package/dist/editor/ui/Icons.js +2 -2
  53. package/dist/editor/ui/Icons.js.map +1 -1
  54. package/dist/editor/ui/SimpleTable.js +1 -1
  55. package/dist/editor/ui/SimpleTable.js.map +1 -1
  56. package/dist/editor/utils.d.ts +12 -1
  57. package/dist/editor/utils.js +60 -12
  58. package/dist/editor/utils.js.map +1 -1
  59. package/dist/revision.d.ts +2 -2
  60. package/dist/revision.js +2 -2
  61. package/dist/types.d.ts +4 -0
  62. package/package.json +1 -1
  63. package/src/config/config.tsx +7 -0
  64. package/src/editor/MainLayout.tsx +1 -1
  65. package/src/editor/Terminal.tsx +2 -2
  66. package/src/editor/ai/AgentTerminal.tsx +8 -20
  67. package/src/editor/ai/Agents.tsx +14 -2
  68. package/src/editor/ai/AiResponseMessage.tsx +7 -5
  69. package/src/editor/client/EditorShell.tsx +2 -0
  70. package/src/editor/control-center/IndexOverview.tsx +70 -35
  71. package/src/editor/control-center/LatestFeedback.tsx +198 -0
  72. package/src/editor/control-center/WebSocketMessages.tsx +3 -5
  73. package/src/editor/field-types/richtext/contextMenuFactory.tsx +14 -0
  74. package/src/editor/media-selector/MediaFolderBrowser.tsx +2 -2
  75. package/src/editor/menubar/ActiveUsers.tsx +4 -3
  76. package/src/editor/menubar/toolbar-sections/EditControls.tsx +2 -3
  77. package/src/editor/menubar/toolbar-sections/ViewportControls.tsx +19 -1
  78. package/src/editor/reviews/Comment.tsx +16 -1
  79. package/src/editor/reviews/CommentView.tsx +24 -24
  80. package/src/editor/reviews/Comments.tsx +122 -145
  81. package/src/editor/reviews/SuggestedEdit.tsx +16 -10
  82. package/src/editor/reviews/SuggestionDisplayPopover.tsx +14 -9
  83. package/src/editor/services/reviewsService.ts +10 -0
  84. package/src/editor/services/suggestedEditsService.ts +10 -0
  85. package/src/editor/sidebar/Completions.tsx +2 -1
  86. package/src/editor/ui/Icons.tsx +3 -0
  87. package/src/editor/ui/SimpleTable.tsx +2 -2
  88. package/src/editor/utils.ts +73 -15
  89. package/src/revision.ts +2 -2
  90. package/src/types.ts +4 -0
@@ -11,14 +11,16 @@ import {
11
11
  PopoverContent,
12
12
  PopoverTrigger,
13
13
  } from "../../components/ui/popover";
14
+ import {
15
+ Tooltip,
16
+ TooltipContent,
17
+ TooltipTrigger,
18
+ } from "../../components/ui/tooltip";
14
19
  import {
15
20
  FileDiff,
16
- SquarePen,
17
21
  CheckCircle,
18
22
  CheckCircle2,
19
- MessageSquareMore,
20
23
  Plus,
21
- GitBranch,
22
24
  Tags,
23
25
  Lightbulb,
24
26
  } from "lucide-react";
@@ -42,67 +44,70 @@ export function Comments() {
42
44
  return [];
43
45
  }
44
46
  });
45
- const [scope, setScope] = useState<"all" | "itemOnly">(() => {
46
- try {
47
- const raw =
48
- typeof window !== "undefined"
49
- ? localStorage.getItem("editor.comments.scope")
50
- : null;
51
- return raw === "itemOnly" ? "itemOnly" : "all";
52
- } catch {
53
- return "all";
54
- }
55
- });
56
47
  const availableTags = editContext?.availableCommentTags || [];
57
48
 
58
49
  useEffect(() => {
59
- // Retrieve your list of comments (and ensure there's an array of suggested edits,
60
- // for instance from your editContext)
61
- const comments: CommentType[] = editContext?.comments || [];
62
- const suggestedEdits: SuggestedEdit[] = editContext?.suggestedEdits || [];
50
+ let cancelled = false;
51
+
52
+ const load = async () => {
53
+ // Base lists from context
54
+ const mainComments: CommentType[] = editContext?.comments || [];
55
+ const suggestedEdits: SuggestedEdit[] = editContext?.suggestedEdits || [];
56
+
57
+ // Filter out applied suggestions if hideAppliedSuggestions is true
58
+ const filteredSuggestedEdits = hideAppliedSuggestions
59
+ ? suggestedEdits.filter((edit) => edit.status !== "applied")
60
+ : suggestedEdits;
63
61
 
64
- // Filter out applied suggestions if hideAppliedSuggestions is true
65
- const filteredSuggestedEdits = hideAppliedSuggestions
66
- ? suggestedEdits.filter((edit) => edit.status !== "applied")
67
- : suggestedEdits;
62
+ // Start with main page comments and suggestions
63
+ let allComments: CommentType[] = [...mainComments];
68
64
 
69
- // Start with combined list
70
- const combined: FeedbackItem[] = [...comments, ...filteredSuggestedEdits];
65
+ // Base page context
66
+ const basePageId = editContext?.currentItemDescriptor?.id;
71
67
 
72
- // Apply scope filter (item-only vs item + children)
73
- const baseItemId =
74
- (editContext?.selection && editContext.selection[0]) ||
75
- editContext?.currentItemDescriptor?.id ||
76
- undefined;
68
+ // Combine with suggestions
69
+ let combined: FeedbackItem[] = [
70
+ ...allComments,
71
+ ...filteredSuggestedEdits,
72
+ ];
77
73
 
78
- const scoped =
79
- scope === "itemOnly" && baseItemId
80
- ? combined.filter((item: any) => item.itemId === baseItemId)
74
+ // Only keep items whose mainItemId is the current page
75
+ if (basePageId) {
76
+ combined = combined.filter(
77
+ (item: any) => item.mainItemId === basePageId,
78
+ );
79
+ }
80
+
81
+ // Apply tag filter to comments only (suggested edits do not have tags)
82
+ const tagFiltered = selectedTagsFilter.length
83
+ ? combined.filter((item: any) => {
84
+ // Keep suggested edits
85
+ if ("oldValue" in item && "newValue" in item) return true;
86
+ const tags = (item.tags || "")
87
+ .split(",")
88
+ .map((t: string) => t.trim())
89
+ .filter(Boolean);
90
+ // OR logic: include if any selected tag matches
91
+ return selectedTagsFilter.some((t) => tags.includes(t));
92
+ })
81
93
  : combined;
82
94
 
83
- // Apply tag filter to comments only (suggested edits do not have tags)
84
- const tagFiltered = selectedTagsFilter.length
85
- ? scoped.filter((item: any) => {
86
- // Keep suggested edits
87
- if ("oldValue" in item && "newValue" in item) return true;
88
- const tags = (item.tags || "")
89
- .split(",")
90
- .map((t: string) => t.trim())
91
- .filter(Boolean);
92
- // OR logic: include if any selected tag matches
93
- return selectedTagsFilter.some((t) => tags.includes(t));
94
- })
95
- : scoped;
95
+ // Sort by creation date
96
+ tagFiltered.sort(
97
+ (a, b) =>
98
+ new Date(b.created || "").getTime() -
99
+ new Date(a.created || "").getTime(),
100
+ );
101
+
102
+ if (!cancelled) setFeedbackItems(tagFiltered);
103
+ };
96
104
 
97
- // Sort by creation date. Adjust the comparison as needed if the date properties differ.
98
- tagFiltered.sort(
99
- (a, b) =>
100
- new Date(b.created || "").getTime() -
101
- new Date(a.created || "").getTime(),
102
- );
105
+ load();
103
106
 
104
- setFeedbackItems(tagFiltered);
105
- }, [editContext, hideAppliedSuggestions, scope, selectedTagsFilter]);
107
+ return () => {
108
+ cancelled = true;
109
+ };
110
+ }, [editContext, hideAppliedSuggestions, selectedTagsFilter]);
106
111
 
107
112
  useEffect(() => {
108
113
  try {
@@ -114,13 +119,6 @@ export function Comments() {
114
119
  } catch {}
115
120
  }, [selectedTagsFilter]);
116
121
 
117
- useEffect(() => {
118
- try {
119
- if (typeof window !== "undefined")
120
- localStorage.setItem("editor.comments.scope", scope);
121
- } catch {}
122
- }, [scope]);
123
-
124
122
  // Tags are provided once via editContext.availableCommentTags
125
123
 
126
124
  return (
@@ -158,17 +156,6 @@ export function Comments() {
158
156
  editContext?.addComment();
159
157
  }}
160
158
  />
161
- <SimpleIconButton
162
- selected={editContext?.showComments}
163
- icon={
164
- <MessageSquareMore size={16} strokeWidth={1} className="p-0.5" />
165
- }
166
- label="Show Comments"
167
- onClick={() => {
168
- editContext?.setShowComments((x) => !x);
169
- }}
170
- />
171
-
172
159
  <SimpleIconButton
173
160
  selected={!!editContext?.showResolvedComments}
174
161
  icon={<CheckCircle2 size={16} strokeWidth={1} className="p-0.5" />}
@@ -181,15 +168,7 @@ export function Comments() {
181
168
  editContext?.setShowResolvedComments((x) => !x);
182
169
  }}
183
170
  />
184
- <SimpleIconButton
185
- selected={editContext?.showSuggestedEdits}
186
- icon={<Lightbulb size={16} strokeWidth={1} className="p-0.5" />}
187
- label="Show Suggestions"
188
- data-testid="show-suggestions-button"
189
- onClick={() => {
190
- editContext?.setShowSuggestedEdits((x) => !x);
191
- }}
192
- />
171
+
193
172
  {editContext?.showSuggestedEdits && (
194
173
  <SimpleIconButton
195
174
  selected={editContext?.showSuggestedEditsDiff}
@@ -221,77 +200,75 @@ export function Comments() {
221
200
  }}
222
201
  />
223
202
  <div className="ml-auto flex items-center gap-2">
224
- <SimpleIconButton
225
- selected={scope === "all"}
226
- icon={<GitBranch size={16} strokeWidth={1} className="p-0.5" />}
227
- label={
228
- scope === "all"
229
- ? "Include Children (on)"
230
- : "Include Children (off)"
231
- }
232
- onClick={() => setScope(scope === "all" ? "itemOnly" : "all")}
233
- />
234
-
235
203
  {availableTags.length > 0 && (
236
- <Popover>
237
- <PopoverTrigger asChild>
238
- <SimpleIconButton
239
- showTooltip={false}
240
- selected={selectedTagsFilter.length > 0}
241
- icon={<Tags size={16} strokeWidth={1} className="p-0.5" />}
242
- label={
243
- selectedTagsFilter.length > 0
244
- ? `Tags (${selectedTagsFilter.length})`
245
- : "Tags"
246
- }
247
- onClick={() => {}}
248
- />
249
- </PopoverTrigger>
250
- <PopoverContent className="w-72 p-3" align="start">
251
- <div className="mb-2 text-xs text-gray-600">Filter by tags</div>
252
- <div className="flex flex-wrap gap-2">
253
- {availableTags.map((tag) => {
254
- const isSelected = selectedTagsFilter.includes(tag.label);
255
- return (
204
+ <Tooltip delayDuration={500}>
205
+ <Popover>
206
+ <PopoverTrigger asChild>
207
+ <TooltipTrigger asChild>
208
+ <SimpleIconButton
209
+ showTooltip={false}
210
+ selected={selectedTagsFilter.length > 0}
211
+ icon={
212
+ <Tags size={16} strokeWidth={1} className="p-0.5" />
213
+ }
214
+ label={
215
+ selectedTagsFilter.length > 0
216
+ ? `Tags (${selectedTagsFilter.length})`
217
+ : "Tags"
218
+ }
219
+ onClick={() => {}}
220
+ />
221
+ </TooltipTrigger>
222
+ </PopoverTrigger>
223
+ <PopoverContent className="w-72 p-3" align="start">
224
+ <div className="mb-2 text-xs text-gray-600">
225
+ Filter by tags
226
+ </div>
227
+ <div className="flex flex-wrap gap-2">
228
+ {availableTags.map((tag) => {
229
+ const isSelected = selectedTagsFilter.includes(tag.label);
230
+ return (
231
+ <Button
232
+ key={tag.label}
233
+ type="button"
234
+ size="sm"
235
+ variant={isSelected ? "secondary" : "outline"}
236
+ className="h-6 px-2 text-xs"
237
+ onClick={() => {
238
+ setSelectedTagsFilter((prev) =>
239
+ prev.includes(tag.label)
240
+ ? prev.filter((l) => l !== tag.label)
241
+ : [...prev, tag.label],
242
+ );
243
+ }}
244
+ style={{
245
+ borderColor: isSelected ? tag.color : undefined,
246
+ backgroundColor: isSelected
247
+ ? tag.color + "22"
248
+ : undefined,
249
+ }}
250
+ >
251
+ {tag.label}
252
+ </Button>
253
+ );
254
+ })}
255
+ </div>
256
+ {selectedTagsFilter.length > 0 && (
257
+ <div className="mt-3 text-right">
256
258
  <Button
257
- key={tag.label}
258
- type="button"
259
259
  size="sm"
260
- variant={isSelected ? "secondary" : "outline"}
260
+ variant="ghost"
261
261
  className="h-6 px-2 text-xs"
262
- onClick={() => {
263
- setSelectedTagsFilter((prev) =>
264
- prev.includes(tag.label)
265
- ? prev.filter((l) => l !== tag.label)
266
- : [...prev, tag.label],
267
- );
268
- }}
269
- style={{
270
- borderColor: isSelected ? tag.color : undefined,
271
- backgroundColor: isSelected
272
- ? tag.color + "22"
273
- : undefined,
274
- }}
262
+ onClick={() => setSelectedTagsFilter([])}
275
263
  >
276
- {tag.label}
264
+ Clear
277
265
  </Button>
278
- );
279
- })}
280
- </div>
281
- {selectedTagsFilter.length > 0 && (
282
- <div className="mt-3 text-right">
283
- <Button
284
- size="sm"
285
- variant="ghost"
286
- className="h-6 px-2 text-xs"
287
- onClick={() => setSelectedTagsFilter([])}
288
- >
289
- Clear
290
- </Button>
291
- </div>
292
- )}
293
- </PopoverContent>
294
- </Popover>
266
+ </div>
267
+ )}
268
+ </PopoverContent>
269
+ </Popover>
270
+ <TooltipContent>Filter by tags</TooltipContent>
271
+ </Tooltip>
295
272
  )}
296
273
  </div>
297
274
  </SimpleToolbar>
@@ -1,5 +1,5 @@
1
1
  import { SuggestedEdit as SuggestedEditType } from "../../types";
2
- import { useEffect, useRef, useState } from "react";
2
+ import React, { useEffect, useRef, useState } from "react";
3
3
  import { useEditContext, useFieldsEditContext } from "../client/editContext";
4
4
  import {
5
5
  deleteSuggestedEdit,
@@ -100,21 +100,27 @@ export function SuggestedEditComponent({ edit }: { edit: SuggestedEditType }) {
100
100
 
101
101
  // Render contextual info by retrieving item and field names from the loaded item.
102
102
  const renderContextInfo = () => {
103
- const itemName = item ? item.name : null;
103
+ const pageName = editContext?.currentItemDescriptor?.name;
104
+ const itemName = item ? item.name : undefined;
104
105
  const fieldName =
105
106
  item && item.fields
106
107
  ? item.fields.find(
107
108
  (f: { id: string; name?: string }) => f.id === edit.fieldId,
108
- )?.name
109
- : null;
110
- if (!itemName && !fieldName) return null;
109
+ )?.name || undefined
110
+ : undefined;
111
+
112
+ const segments = [pageName, itemName, fieldName].filter(
113
+ (x): x is string => !!x && x.length > 0,
114
+ );
115
+ if (segments.length === 0) return null;
111
116
  return (
112
117
  <div className="mt-3 flex items-center border-t pt-3 text-xs">
113
- {itemName && <div className="text-2xs text-gray-500">{itemName}</div>}
114
- {fieldName && itemName && (
115
- <div className="text-2xs mx-2 text-gray-500">&gt;</div>
116
- )}
117
- {fieldName && <div className="text-2xs text-gray-500">{fieldName}</div>}
118
+ {segments.map((seg, idx) => (
119
+ <React.Fragment key={idx}>
120
+ {idx > 0 && <div className="text-2xs mx-2 text-gray-500">&gt;</div>}
121
+ <div className="text-2xs text-gray-500">{seg}</div>
122
+ </React.Fragment>
123
+ ))}
118
124
  </div>
119
125
  );
120
126
  };
@@ -208,23 +208,28 @@ export function SuggestionDisplayPopover({
208
208
  };
209
209
 
210
210
  const renderContextInfo = () => {
211
- const itemName = item ? item.name : null;
211
+ const pageName = editContext?.currentItemDescriptor?.name;
212
+ const itemName = item ? item.name : undefined;
212
213
  const fieldName =
213
214
  item && item.fields
214
215
  ? item.fields.find(
215
216
  (f: { id: string; name?: string }) => f.id === suggestion.fieldId,
216
- )?.name
217
- : null;
217
+ )?.name || undefined
218
+ : undefined;
218
219
 
219
- if (!itemName && !fieldName) return null;
220
+ const segments = [pageName, itemName, fieldName].filter(
221
+ (x): x is string => !!x && x.length > 0,
222
+ );
223
+ if (segments.length === 0) return null;
220
224
 
221
225
  return (
222
226
  <div className="mt-3 flex items-center border-t pt-3 text-xs">
223
- {itemName && <div className="text-xs text-gray-500">{itemName}</div>}
224
- {fieldName && itemName && (
225
- <div className="mx-2 text-xs text-gray-500">&gt;</div>
226
- )}
227
- {fieldName && <div className="text-xs text-gray-500">{fieldName}</div>}
227
+ {segments.map((seg, idx) => (
228
+ <React.Fragment key={idx}>
229
+ {idx > 0 && <div className="mx-2 text-xs text-gray-500">&gt;</div>}
230
+ <div className="text-xs text-gray-500">{seg}</div>
231
+ </React.Fragment>
232
+ ))}
228
233
  </div>
229
234
  );
230
235
  };
@@ -12,6 +12,16 @@ export function getComments(itemId: string, language: string, version: number) {
12
12
  );
13
13
  }
14
14
 
15
+ // Latest comments across the system, already filtered by backend for user access.
16
+ export function getLatestComments(take?: number) {
17
+ const params = new URLSearchParams();
18
+ if (take && take > 0) params.set("take", take.toString());
19
+ const query = params.toString();
20
+ return get<Comment[]>(
21
+ `/alpaca/editor/comments/latest${query ? `?${query}` : ""}`,
22
+ );
23
+ }
24
+
15
25
  export function resolveComment(comment: Comment) {
16
26
  return post(`/alpaca/editor/comments/resolve?commentId=${comment.id}`, {});
17
27
  }
@@ -21,6 +21,16 @@ export function getSuggestedEdits(
21
21
  );
22
22
  }
23
23
 
24
+ // Latest suggested edits across the system, already filtered by backend for user access.
25
+ export function getLatestSuggestedEdits(take?: number) {
26
+ const params = new URLSearchParams();
27
+ if (take && take > 0) params.set("take", take.toString());
28
+ const query = params.toString();
29
+ return get<SuggestedEdit[]>(
30
+ `/alpaca/editor/suggested-edits/latest${query ? `?${query}` : ""}`,
31
+ );
32
+ }
33
+
24
34
  /**
25
35
  * Retrieves a single suggested edit by its unique identifier.
26
36
  */
@@ -4,6 +4,7 @@ import { Switch } from "../../components/ui/switch";
4
4
  import { Button } from "../../components/ui/button";
5
5
  import { Badge } from "../../components/ui/badge";
6
6
  import { Loader2, RefreshCw, FileText } from "lucide-react";
7
+ import { formatTime } from "../utils";
7
8
 
8
9
  import {
9
10
  generatePageContext,
@@ -120,7 +121,7 @@ export function Completions() {
120
121
  {pageContext.abstract}
121
122
  </p>
122
123
  <div className="text-xs text-gray-400">
123
- Generated: {pageContext.lastGenerated.toLocaleTimeString()}
124
+ {`Generated: ${formatTime(pageContext.lastGenerated)}`}
124
125
  </div>
125
126
  </div>
126
127
  ) : (
@@ -891,10 +891,12 @@ export function SecretAgentIcon({
891
891
  title,
892
892
  strokeWidth = 1,
893
893
  size = 20,
894
+ className,
894
895
  }: {
895
896
  title?: string;
896
897
  strokeWidth?: number;
897
898
  size?: number;
899
+ className?: string;
898
900
  }) {
899
901
  return (
900
902
  <svg
@@ -909,6 +911,7 @@ export function SecretAgentIcon({
909
911
  strokeLinejoin="round"
910
912
  role="img"
911
913
  aria-label={title}
914
+ className={className}
912
915
  >
913
916
  <path d="M3 10h18" />
914
917
  <path d="M7 10l2-5h6l2 5" />
@@ -23,10 +23,10 @@ export function SimpleTable<T>({
23
23
  }) {
24
24
  return (
25
25
  <table className="text-surface min-w-full table-auto text-left text-xs font-light">
26
- <thead className="border-b border-neutral-200 font-medium">
26
+ <thead className="border-b border-neutral-200">
27
27
  <tr>
28
28
  {columns.map((col, index) => (
29
- <th key={index} className="px-1.5 py-1.5">
29
+ <th key={index} className="px-1.5 py-1.5 font-medium">
30
30
  {col.header}
31
31
  </th>
32
32
  ))}
@@ -504,23 +504,81 @@ export function normalizeGuid(id: string) {
504
504
  return "{" + id + "}";
505
505
  }
506
506
 
507
- const dateOptions: Intl.DateTimeFormatOptions = {
508
- year: "numeric",
509
- month: "numeric",
510
- day: "numeric",
511
- hour: "numeric",
512
- minute: "numeric",
513
- second: "numeric",
514
- hour12: false,
515
- };
507
+ function getLocale() {
508
+ try {
509
+ if (typeof navigator !== "undefined" && navigator.language)
510
+ return navigator.language;
511
+ } catch {}
512
+ return "en";
513
+ }
514
+
515
+ function getUserTimeZone() {
516
+ try {
517
+ const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
518
+ return tz || "UTC";
519
+ } catch {
520
+ return "UTC";
521
+ }
522
+ }
523
+
524
+ export function formatDate(
525
+ date: Date,
526
+ options?: Intl.DateTimeFormatOptions & { timeZone?: string },
527
+ ) {
528
+ const locale = getLocale();
529
+ const timeZone = options?.timeZone || getUserTimeZone();
530
+ const fmt = new Intl.DateTimeFormat(locale, {
531
+ year: "numeric",
532
+ month: "numeric",
533
+ day: "numeric",
534
+ hour: "numeric",
535
+ minute: "numeric",
536
+ second: "numeric",
537
+ hour12: false,
538
+ timeZone,
539
+ ...options,
540
+ });
541
+ return fmt.format(date);
542
+ }
516
543
 
517
- const dateFormat = Intl.DateTimeFormat(
518
- typeof navigator !== "undefined" ? navigator.language : "en",
519
- dateOptions,
520
- );
544
+ export function formatDateOnly(
545
+ date: Date,
546
+ options?: Intl.DateTimeFormatOptions & { timeZone?: string },
547
+ ) {
548
+ const locale = getLocale();
549
+ const timeZone = options?.timeZone || getUserTimeZone();
550
+ const fmt = new Intl.DateTimeFormat(locale, {
551
+ year: "numeric",
552
+ month: "numeric",
553
+ day: "numeric",
554
+ timeZone,
555
+ ...options,
556
+ });
557
+ return fmt.format(date);
558
+ }
521
559
 
522
- export function formatDate(date: Date) {
523
- return dateFormat.format(date);
560
+ export function formatTime(
561
+ date: Date,
562
+ options?: Intl.DateTimeFormatOptions & { timeZone?: string },
563
+ ) {
564
+ const locale = getLocale();
565
+ const timeZone = options?.timeZone || getUserTimeZone();
566
+ const fmt = new Intl.DateTimeFormat(locale, {
567
+ hour: "numeric",
568
+ minute: "numeric",
569
+ second: "numeric",
570
+ hour12: false,
571
+ timeZone,
572
+ ...options,
573
+ });
574
+ return fmt.format(date);
575
+ }
576
+
577
+ export function formatDateTime(
578
+ date: Date,
579
+ options?: Intl.DateTimeFormatOptions & { timeZone?: string },
580
+ ) {
581
+ return formatDate(date, options);
524
582
  }
525
583
 
526
584
  export function findClosestFieldElement(node: Node | null): HTMLElement | null {
package/src/revision.ts CHANGED
@@ -1,2 +1,2 @@
1
- export const version = "1.0.4104";
2
- export const buildDate = "2025-09-23 14:05:05";
1
+ export const version = "1.0.4106";
2
+ export const buildDate = "2025-09-23 20:25:39";
package/src/types.ts CHANGED
@@ -260,6 +260,7 @@ export type IndexStatus = {
260
260
  rebuilding: boolean;
261
261
  batches: EmbeddingBatch[];
262
262
  latestEmbeddedItems?: LatestEmbeddedItem[];
263
+ messages?: SystemStatusMessage[];
263
264
  };
264
265
 
265
266
  export type EmbeddingBatch = {
@@ -337,6 +338,7 @@ export type Comment = {
337
338
  isNew: boolean;
338
339
  itemId: string;
339
340
  itemName?: string;
341
+ mainItemName?: string;
340
342
  mainItemId: string;
341
343
  language: string;
342
344
  version: number;
@@ -408,7 +410,9 @@ export interface SuggestedEdit {
408
410
  mainItemId: string;
409
411
  mainItemLanguage: string;
410
412
  mainItemVersion: number;
413
+ mainItemName?: string;
411
414
  itemId: string;
415
+ itemName?: string;
412
416
  fieldId: string;
413
417
  oldValue: string;
414
418
  newValue: string;