@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
@@ -11,19 +11,19 @@ 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";
25
- import { getChildren } from "../services/contentService";
26
- import { getComments as getCommentsApi } from "../services/reviewsService";
27
27
 
28
28
  // Define a union type for feedback items:
29
29
  export type FeedbackItem = CommentType | SuggestedEdit;
@@ -44,17 +44,6 @@ export function Comments() {
44
44
  return [];
45
45
  }
46
46
  });
47
- const [scope, setScope] = useState<"all" | "itemOnly">(() => {
48
- try {
49
- const raw =
50
- typeof window !== "undefined"
51
- ? localStorage.getItem("editor.comments.scope")
52
- : null;
53
- return raw === "itemOnly" ? "itemOnly" : "all";
54
- } catch {
55
- return "all";
56
- }
57
- });
58
47
  const availableTags = editContext?.availableCommentTags || [];
59
48
 
60
49
  useEffect(() => {
@@ -73,77 +62,8 @@ export function Comments() {
73
62
  // Start with main page comments and suggestions
74
63
  let allComments: CommentType[] = [...mainComments];
75
64
 
76
- // If including children, fetch comments for descendant pages and merge
65
+ // Base page context
77
66
  const basePageId = editContext?.currentItemDescriptor?.id;
78
- const baseLanguage = editContext?.currentItemDescriptor?.language;
79
- const baseVersion = editContext?.currentItemDescriptor?.version;
80
-
81
- if (scope === "all" && basePageId && baseLanguage && baseVersion) {
82
- try {
83
- const sessionId = editContext?.sessionId || "";
84
-
85
- // BFS to collect all descendant nodes
86
- const queue: string[] = [basePageId];
87
- const visited = new Set<string>();
88
- const descendantPages: {
89
- id: string;
90
- language: string;
91
- version: number;
92
- hasChildren: boolean;
93
- }[] = [];
94
-
95
- while (queue.length > 0) {
96
- const parentId = queue.shift()!;
97
- if (visited.has(parentId)) continue;
98
- visited.add(parentId);
99
-
100
- const children = await getChildren(
101
- parentId,
102
- sessionId,
103
- [],
104
- false,
105
- baseLanguage,
106
- undefined,
107
- );
108
-
109
- for (const child of children) {
110
- // Enqueue for deeper traversal when it has children
111
- if (child.hasChildren) queue.push(child.id);
112
- // Only pages (hasLayout) contribute comments
113
- if (child.hasLayout) {
114
- descendantPages.push({
115
- id: child.id,
116
- language: child.language,
117
- version: child.version,
118
- hasChildren: child.hasChildren,
119
- });
120
- }
121
- }
122
- }
123
-
124
- // Fetch comments for all descendant pages in parallel
125
- const results = await Promise.all(
126
- descendantPages.map((p) =>
127
- getCommentsApi(p.id, p.language, p.version),
128
- ),
129
- );
130
-
131
- const childComments = results
132
- .map((r) => (r.data || []) as CommentType[])
133
- .flat();
134
-
135
- // Merge unique by id
136
- const existingIds = new Set(allComments.map((c) => c.id));
137
- for (const c of childComments) {
138
- if (!existingIds.has(c.id)) {
139
- allComments.push(c);
140
- existingIds.add(c.id);
141
- }
142
- }
143
- } catch {
144
- // Ignore child-loading errors to avoid blocking main list
145
- }
146
- }
147
67
 
148
68
  // Combine with suggestions
149
69
  let combined: FeedbackItem[] = [
@@ -151,8 +71,8 @@ export function Comments() {
151
71
  ...filteredSuggestedEdits,
152
72
  ];
153
73
 
154
- // Apply scope filter for item-only: keep only items whose mainItemId is the current page
155
- if (scope === "itemOnly" && basePageId) {
74
+ // Only keep items whose mainItemId is the current page
75
+ if (basePageId) {
156
76
  combined = combined.filter(
157
77
  (item: any) => item.mainItemId === basePageId,
158
78
  );
@@ -187,7 +107,7 @@ export function Comments() {
187
107
  return () => {
188
108
  cancelled = true;
189
109
  };
190
- }, [editContext, hideAppliedSuggestions, scope, selectedTagsFilter]);
110
+ }, [editContext, hideAppliedSuggestions, selectedTagsFilter]);
191
111
 
192
112
  useEffect(() => {
193
113
  try {
@@ -199,13 +119,6 @@ export function Comments() {
199
119
  } catch {}
200
120
  }, [selectedTagsFilter]);
201
121
 
202
- useEffect(() => {
203
- try {
204
- if (typeof window !== "undefined")
205
- localStorage.setItem("editor.comments.scope", scope);
206
- } catch {}
207
- }, [scope]);
208
-
209
122
  // Tags are provided once via editContext.availableCommentTags
210
123
 
211
124
  return (
@@ -243,17 +156,6 @@ export function Comments() {
243
156
  editContext?.addComment();
244
157
  }}
245
158
  />
246
- <SimpleIconButton
247
- selected={editContext?.showComments}
248
- icon={
249
- <MessageSquareMore size={16} strokeWidth={1} className="p-0.5" />
250
- }
251
- label="Show Comments"
252
- onClick={() => {
253
- editContext?.setShowComments((x) => !x);
254
- }}
255
- />
256
-
257
159
  <SimpleIconButton
258
160
  selected={!!editContext?.showResolvedComments}
259
161
  icon={<CheckCircle2 size={16} strokeWidth={1} className="p-0.5" />}
@@ -266,15 +168,7 @@ export function Comments() {
266
168
  editContext?.setShowResolvedComments((x) => !x);
267
169
  }}
268
170
  />
269
- <SimpleIconButton
270
- selected={editContext?.showSuggestedEdits}
271
- icon={<Lightbulb size={16} strokeWidth={1} className="p-0.5" />}
272
- label="Show Suggestions"
273
- data-testid="show-suggestions-button"
274
- onClick={() => {
275
- editContext?.setShowSuggestedEdits((x) => !x);
276
- }}
277
- />
171
+
278
172
  {editContext?.showSuggestedEdits && (
279
173
  <SimpleIconButton
280
174
  selected={editContext?.showSuggestedEditsDiff}
@@ -306,77 +200,75 @@ export function Comments() {
306
200
  }}
307
201
  />
308
202
  <div className="ml-auto flex items-center gap-2">
309
- <SimpleIconButton
310
- selected={scope === "all"}
311
- icon={<GitBranch size={16} strokeWidth={1} className="p-0.5" />}
312
- label={
313
- scope === "all"
314
- ? "Include Children (on)"
315
- : "Include Children (off)"
316
- }
317
- onClick={() => setScope(scope === "all" ? "itemOnly" : "all")}
318
- />
319
-
320
203
  {availableTags.length > 0 && (
321
- <Popover>
322
- <PopoverTrigger asChild>
323
- <SimpleIconButton
324
- showTooltip={false}
325
- selected={selectedTagsFilter.length > 0}
326
- icon={<Tags size={16} strokeWidth={1} className="p-0.5" />}
327
- label={
328
- selectedTagsFilter.length > 0
329
- ? `Tags (${selectedTagsFilter.length})`
330
- : "Tags"
331
- }
332
- onClick={() => {}}
333
- />
334
- </PopoverTrigger>
335
- <PopoverContent className="w-72 p-3" align="start">
336
- <div className="mb-2 text-xs text-gray-600">Filter by tags</div>
337
- <div className="flex flex-wrap gap-2">
338
- {availableTags.map((tag) => {
339
- const isSelected = selectedTagsFilter.includes(tag.label);
340
- 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">
341
258
  <Button
342
- key={tag.label}
343
- type="button"
344
259
  size="sm"
345
- variant={isSelected ? "secondary" : "outline"}
260
+ variant="ghost"
346
261
  className="h-6 px-2 text-xs"
347
- onClick={() => {
348
- setSelectedTagsFilter((prev) =>
349
- prev.includes(tag.label)
350
- ? prev.filter((l) => l !== tag.label)
351
- : [...prev, tag.label],
352
- );
353
- }}
354
- style={{
355
- borderColor: isSelected ? tag.color : undefined,
356
- backgroundColor: isSelected
357
- ? tag.color + "22"
358
- : undefined,
359
- }}
262
+ onClick={() => setSelectedTagsFilter([])}
360
263
  >
361
- {tag.label}
264
+ Clear
362
265
  </Button>
363
- );
364
- })}
365
- </div>
366
- {selectedTagsFilter.length > 0 && (
367
- <div className="mt-3 text-right">
368
- <Button
369
- size="sm"
370
- variant="ghost"
371
- className="h-6 px-2 text-xs"
372
- onClick={() => setSelectedTagsFilter([])}
373
- >
374
- Clear
375
- </Button>
376
- </div>
377
- )}
378
- </PopoverContent>
379
- </Popover>
266
+ </div>
267
+ )}
268
+ </PopoverContent>
269
+ </Popover>
270
+ <TooltipContent>Filter by tags</TooltipContent>
271
+ </Tooltip>
380
272
  )}
381
273
  </div>
382
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
  */
package/src/revision.ts CHANGED
@@ -1,2 +1,2 @@
1
- export const version = "1.0.4105";
2
- export const buildDate = "2025-09-23 15:04:32";
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;