@alpaca-editor/core 1.0.4086 → 1.0.4089

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 (149) hide show
  1. package/dist/components/ui/card.d.ts +1 -1
  2. package/dist/config/config.js +10 -3
  3. package/dist/config/config.js.map +1 -1
  4. package/dist/config/types.d.ts +4 -0
  5. package/dist/editor/ContentTree.js +43 -21
  6. package/dist/editor/ContentTree.js.map +1 -1
  7. package/dist/editor/FieldListField.js +12 -1
  8. package/dist/editor/FieldListField.js.map +1 -1
  9. package/dist/editor/ai/AgentTerminal.d.ts +3 -1
  10. package/dist/editor/ai/AgentTerminal.js +96 -74
  11. package/dist/editor/ai/AgentTerminal.js.map +1 -1
  12. package/dist/editor/ai/Agents.js +50 -2
  13. package/dist/editor/ai/Agents.js.map +1 -1
  14. package/dist/editor/ai/AiResponseMessage.js +171 -75
  15. package/dist/editor/ai/AiResponseMessage.js.map +1 -1
  16. package/dist/editor/ai/AiTerminal.js +27 -14
  17. package/dist/editor/ai/AiTerminal.js.map +1 -1
  18. package/dist/editor/client/EditorShell.js +121 -17
  19. package/dist/editor/client/EditorShell.js.map +1 -1
  20. package/dist/editor/client/editContext.d.ts +4 -0
  21. package/dist/editor/client/editContext.js.map +1 -1
  22. package/dist/editor/client/hooks/useSocketMessageHandler.d.ts +1 -0
  23. package/dist/editor/client/hooks/useSocketMessageHandler.js +54 -20
  24. package/dist/editor/client/hooks/useSocketMessageHandler.js.map +1 -1
  25. package/dist/editor/client/hooks/useWorkbox.d.ts +1 -1
  26. package/dist/editor/client/hooks/useWorkbox.js +4 -4
  27. package/dist/editor/client/hooks/useWorkbox.js.map +1 -1
  28. package/dist/editor/client/itemsRepository.d.ts +13 -1
  29. package/dist/editor/client/itemsRepository.js +34 -21
  30. package/dist/editor/client/itemsRepository.js.map +1 -1
  31. package/dist/editor/client/pageModelBuilder.js +1 -1
  32. package/dist/editor/client/pageModelBuilder.js.map +1 -1
  33. package/dist/editor/control-center/Setup.js +12 -223
  34. package/dist/editor/control-center/Setup.js.map +1 -1
  35. package/dist/editor/control-center/setup-steps/AiSetupStep.d.ts +2 -0
  36. package/dist/editor/control-center/setup-steps/AiSetupStep.js +287 -0
  37. package/dist/editor/control-center/setup-steps/AiSetupStep.js.map +1 -0
  38. package/dist/editor/control-center/setup-steps/DbSetupStep.d.ts +2 -0
  39. package/dist/editor/control-center/setup-steps/DbSetupStep.js +46 -0
  40. package/dist/editor/control-center/setup-steps/DbSetupStep.js.map +1 -0
  41. package/dist/editor/control-center/setup-steps/IndexSetupStep.d.ts +2 -0
  42. package/dist/editor/control-center/setup-steps/IndexSetupStep.js +34 -0
  43. package/dist/editor/control-center/setup-steps/IndexSetupStep.js.map +1 -0
  44. package/dist/editor/control-center/setup-steps/SettingsSetupStep.d.ts +2 -0
  45. package/dist/editor/control-center/setup-steps/SettingsSetupStep.js +104 -0
  46. package/dist/editor/control-center/setup-steps/SettingsSetupStep.js.map +1 -0
  47. package/dist/editor/field-types/ImageFieldEditor.js +1 -1
  48. package/dist/editor/field-types/ImageFieldEditor.js.map +1 -1
  49. package/dist/editor/field-types/MultiLineText.js +1 -1
  50. package/dist/editor/field-types/MultiLineText.js.map +1 -1
  51. package/dist/editor/field-types/PictureFieldEditor.js +1 -1
  52. package/dist/editor/field-types/PictureFieldEditor.js.map +1 -1
  53. package/dist/editor/field-types/RawEditor.js +1 -1
  54. package/dist/editor/field-types/RawEditor.js.map +1 -1
  55. package/dist/editor/field-types/RichTextEditorComponent.js +1 -1
  56. package/dist/editor/field-types/RichTextEditorComponent.js.map +1 -1
  57. package/dist/editor/field-types/SingleLineText.js +1 -1
  58. package/dist/editor/field-types/SingleLineText.js.map +1 -1
  59. package/dist/editor/field-types/richtext/components/ReactSlate.js +2 -2
  60. package/dist/editor/field-types/richtext/components/ReactSlate.js.map +1 -1
  61. package/dist/editor/field-types/richtext/utils/profileServiceCache.d.ts +1 -1
  62. package/dist/editor/field-types/richtext/utils/profileServiceCache.js +16 -14
  63. package/dist/editor/field-types/richtext/utils/profileServiceCache.js.map +1 -1
  64. package/dist/editor/menubar/toolbar-sections/CompareControls.js +1 -1
  65. package/dist/editor/menubar/toolbar-sections/CompareControls.js.map +1 -1
  66. package/dist/editor/menubar/toolbar-sections/EditControls.js +1 -1
  67. package/dist/editor/menubar/toolbar-sections/EditControls.js.map +1 -1
  68. package/dist/editor/menubar/toolbar-sections/ViewportControls.js +1 -1
  69. package/dist/editor/menubar/toolbar-sections/ViewportControls.js.map +1 -1
  70. package/dist/editor/page-editor-chrome/InlineEditor.js +25 -6
  71. package/dist/editor/page-editor-chrome/InlineEditor.js.map +1 -1
  72. package/dist/editor/page-viewer/EditorForm.js +9 -2
  73. package/dist/editor/page-viewer/EditorForm.js.map +1 -1
  74. package/dist/editor/page-viewer/PageViewerFrame.js +6 -1
  75. package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
  76. package/dist/editor/pageModel.d.ts +1 -0
  77. package/dist/editor/reviews/Comment.js +1 -1
  78. package/dist/editor/reviews/Comment.js.map +1 -1
  79. package/dist/editor/reviews/CommentDisplayPopover.js +3 -24
  80. package/dist/editor/reviews/CommentDisplayPopover.js.map +1 -1
  81. package/dist/editor/reviews/CommentPopover.js +3 -23
  82. package/dist/editor/reviews/CommentPopover.js.map +1 -1
  83. package/dist/editor/reviews/CommentView.js +2 -1
  84. package/dist/editor/reviews/CommentView.js.map +1 -1
  85. package/dist/editor/reviews/Comments.js +88 -37
  86. package/dist/editor/reviews/Comments.js.map +1 -1
  87. package/dist/editor/reviews/commentAi.js +3 -0
  88. package/dist/editor/reviews/commentAi.js.map +1 -1
  89. package/dist/editor/sidebar/Debug.js +1 -5
  90. package/dist/editor/sidebar/Debug.js.map +1 -1
  91. package/dist/editor/sidebar/ViewSelector.js +72 -6
  92. package/dist/editor/sidebar/ViewSelector.js.map +1 -1
  93. package/dist/editor/ui/Icons.d.ts +5 -0
  94. package/dist/editor/ui/Icons.js +14 -0
  95. package/dist/editor/ui/Icons.js.map +1 -1
  96. package/dist/revision.d.ts +2 -2
  97. package/dist/revision.js +2 -2
  98. package/dist/splash-screen/SplashScreen.js +2 -2
  99. package/dist/splash-screen/SplashScreen.js.map +1 -1
  100. package/dist/styles.css +0 -5
  101. package/dist/types.d.ts +2 -0
  102. package/package.json +1 -1
  103. package/src/components/ui/card.tsx +1 -1
  104. package/src/config/config.tsx +9 -2
  105. package/src/config/types.ts +5 -0
  106. package/src/editor/ContentTree.tsx +48 -23
  107. package/src/editor/FieldListField.tsx +13 -2
  108. package/src/editor/ai/AgentTerminal.tsx +118 -71
  109. package/src/editor/ai/Agents.tsx +56 -1
  110. package/src/editor/ai/AiResponseMessage.tsx +234 -78
  111. package/src/editor/ai/AiTerminal.tsx +30 -14
  112. package/src/editor/client/EditorShell.tsx +140 -25
  113. package/src/editor/client/editContext.ts +1 -0
  114. package/src/editor/client/hooks/useSocketMessageHandler.ts +70 -21
  115. package/src/editor/client/hooks/useWorkbox.ts +4 -4
  116. package/src/editor/client/itemsRepository.ts +56 -25
  117. package/src/editor/client/pageModelBuilder.ts +1 -1
  118. package/src/editor/control-center/Setup.tsx +14 -420
  119. package/src/editor/control-center/setup-steps/AiSetupStep.tsx +462 -0
  120. package/src/editor/control-center/setup-steps/DbSetupStep.tsx +84 -0
  121. package/src/editor/control-center/setup-steps/IndexSetupStep.tsx +56 -0
  122. package/src/editor/control-center/setup-steps/SettingsSetupStep.tsx +176 -0
  123. package/src/editor/field-types/ImageFieldEditor.tsx +0 -1
  124. package/src/editor/field-types/MultiLineText.tsx +0 -1
  125. package/src/editor/field-types/PictureFieldEditor.tsx +0 -1
  126. package/src/editor/field-types/RawEditor.tsx +0 -1
  127. package/src/editor/field-types/RichTextEditorComponent.tsx +0 -1
  128. package/src/editor/field-types/SingleLineText.tsx +0 -1
  129. package/src/editor/field-types/richtext/components/ReactSlate.tsx +14 -6
  130. package/src/editor/field-types/richtext/utils/profileServiceCache.ts +42 -32
  131. package/src/editor/menubar/toolbar-sections/CompareControls.tsx +1 -0
  132. package/src/editor/menubar/toolbar-sections/EditControls.tsx +1 -0
  133. package/src/editor/menubar/toolbar-sections/ViewportControls.tsx +1 -0
  134. package/src/editor/page-editor-chrome/InlineEditor.tsx +29 -6
  135. package/src/editor/page-viewer/EditorForm.tsx +13 -2
  136. package/src/editor/page-viewer/PageViewerFrame.tsx +5 -1
  137. package/src/editor/pageModel.ts +1 -0
  138. package/src/editor/reviews/Comment.tsx +1 -1
  139. package/src/editor/reviews/CommentDisplayPopover.tsx +2 -22
  140. package/src/editor/reviews/CommentPopover.tsx +3 -24
  141. package/src/editor/reviews/CommentView.tsx +3 -2
  142. package/src/editor/reviews/Comments.tsx +162 -35
  143. package/src/editor/reviews/commentAi.ts +5 -0
  144. package/src/editor/sidebar/Debug.tsx +1 -5
  145. package/src/editor/sidebar/ViewSelector.tsx +144 -28
  146. package/src/editor/ui/Icons.tsx +55 -0
  147. package/src/revision.ts +2 -2
  148. package/src/splash-screen/SplashScreen.tsx +5 -6
  149. package/src/types.ts +3 -0
@@ -11,10 +11,7 @@ import {
11
11
  } from "../client/editContext";
12
12
  import type { SelectionRange } from "../client/editContext";
13
13
  import type { FieldDescriptor } from "../../types";
14
- import {
15
- createOrUpdateComment,
16
- getAvailableCommentTags,
17
- } from "../services/reviewsService";
14
+ import { createOrUpdateComment } from "../services/reviewsService";
18
15
  import {
19
16
  Popover,
20
17
  PopoverContent,
@@ -41,11 +38,9 @@ export function CommentPopover({
41
38
  }) {
42
39
  const [isOpen, setIsOpen] = useState(!!position);
43
40
  const [saving, setSaving] = useState(false);
44
- const [availableTags, setAvailableTags] = useState<
45
- { label: string; color: string }[]
46
- >([]);
47
41
  const contextFromHook = useEditContext();
48
42
  const editContext = externalEditContext || contextFromHook;
43
+ const availableTags = editContext?.availableCommentTags || [];
49
44
  const fieldsContext = useFieldsEditContext();
50
45
  const contentRef = React.useRef<HTMLDivElement | null>(null);
51
46
 
@@ -53,23 +48,7 @@ export function CommentPopover({
53
48
  if (position) setIsOpen(true);
54
49
  }, [position?.x, position?.y]);
55
50
 
56
- useEffect(() => {
57
- let cancelled = false;
58
- const loadTags = async () => {
59
- try {
60
- const descriptor = editContext?.contentEditorItem?.descriptor;
61
- if (!descriptor) return;
62
- const tags = await getAvailableCommentTags(descriptor);
63
- if (!cancelled) setAvailableTags(tags || []);
64
- } catch {
65
- if (!cancelled) setAvailableTags([]);
66
- }
67
- };
68
- loadTags();
69
- return () => {
70
- cancelled = true;
71
- };
72
- }, [editContext?.contentEditorItem?.descriptor?.id]);
51
+ // Tags are now provided by editContext.availableCommentTags
73
52
 
74
53
  const handleSave = async (text: string, tags: string[]) => {
75
54
  if (!editContext || !text || saving) return;
@@ -43,7 +43,8 @@ export function CommentView({
43
43
  const [resolvePopoverOpen, setResolvePopoverOpen] = React.useState(false);
44
44
 
45
45
  const renderTags = () => {
46
- const tagLabels = (comment.tags || "")
46
+ // Canonical: parse labels from comment.tags (comma-separated string)
47
+ const tagLabels: string[] = (comment.tags || "")
47
48
  .split(",")
48
49
  .map((t) => t.trim())
49
50
  .filter(Boolean);
@@ -192,7 +193,7 @@ export function CommentView({
192
193
  >
193
194
  <Check
194
195
  className="h-3.5 w-3.5 text-green-500"
195
- strokeWidth={2}
196
+ strokeWidth={1}
196
197
  />
197
198
  </button>
198
199
  </PopoverTrigger>
@@ -5,12 +5,21 @@ import { SuggestedEditComponent } from "./SuggestedEdit";
5
5
  import { Comment as CommentType, SuggestedEdit } from "../../types";
6
6
  import { SimpleToolbar } from "../ui/SimpleToolbar";
7
7
  import { SimpleIconButton } from "../ui/SimpleIconButton";
8
+ import { Button } from "../../components/ui/button";
9
+ import {
10
+ Popover,
11
+ PopoverContent,
12
+ PopoverTrigger,
13
+ } from "../../components/ui/popover";
8
14
  import {
9
15
  FileDiff,
10
16
  SquarePen,
11
17
  CheckCircle,
12
18
  CheckCircle2,
13
19
  MessageSquareMore,
20
+ Plus,
21
+ GitBranch,
22
+ Tags,
14
23
  } from "lucide-react";
15
24
 
16
25
  // Define a union type for feedback items:
@@ -21,9 +30,29 @@ export function Comments() {
21
30
  const [feedbackItems, setFeedbackItems] = useState<FeedbackItem[]>([]);
22
31
  const [hideAppliedSuggestions, setHideAppliedSuggestions] =
23
32
  useState<boolean>(true);
24
- const [availableTags, setAvailableTags] = useState<
25
- { label: string; color: string }[]
26
- >([]);
33
+ const [selectedTagsFilter, setSelectedTagsFilter] = useState<string[]>(() => {
34
+ try {
35
+ const raw =
36
+ typeof window !== "undefined"
37
+ ? localStorage.getItem("editor.comments.tagsFilter")
38
+ : null;
39
+ return raw ? (JSON.parse(raw) as string[]) : [];
40
+ } catch {
41
+ return [];
42
+ }
43
+ });
44
+ const [scope, setScope] = useState<"all" | "itemOnly">(() => {
45
+ try {
46
+ const raw =
47
+ typeof window !== "undefined"
48
+ ? localStorage.getItem("editor.comments.scope")
49
+ : null;
50
+ return raw === "itemOnly" ? "itemOnly" : "all";
51
+ } catch {
52
+ return "all";
53
+ }
54
+ });
55
+ const availableTags = editContext?.availableCommentTags || [];
27
56
 
28
57
  useEffect(() => {
29
58
  // Retrieve your list of comments (and ensure there's an array of suggested edits,
@@ -36,47 +65,69 @@ export function Comments() {
36
65
  ? suggestedEdits.filter((edit) => edit.status !== "applied")
37
66
  : suggestedEdits;
38
67
 
68
+ // Start with combined list
39
69
  const combined: FeedbackItem[] = [...comments, ...filteredSuggestedEdits];
40
70
 
71
+ // Apply scope filter (item-only vs item + children)
72
+ const baseItemId =
73
+ (editContext?.selection && editContext.selection[0]) ||
74
+ editContext?.currentItemDescriptor?.id ||
75
+ undefined;
76
+
77
+ const scoped =
78
+ scope === "itemOnly" && baseItemId
79
+ ? combined.filter((item: any) => item.itemId === baseItemId)
80
+ : combined;
81
+
82
+ // Apply tag filter to comments only (suggested edits do not have tags)
83
+ const tagFiltered = selectedTagsFilter.length
84
+ ? scoped.filter((item: any) => {
85
+ // Keep suggested edits
86
+ if ("oldValue" in item && "newValue" in item) return true;
87
+ const tags = (item.tags || "")
88
+ .split(",")
89
+ .map((t: string) => t.trim())
90
+ .filter(Boolean);
91
+ // OR logic: include if any selected tag matches
92
+ return selectedTagsFilter.some((t) => tags.includes(t));
93
+ })
94
+ : scoped;
95
+
41
96
  // Sort by creation date. Adjust the comparison as needed if the date properties differ.
42
- combined.sort(
97
+ tagFiltered.sort(
43
98
  (a, b) =>
44
99
  new Date(b.created || "").getTime() -
45
100
  new Date(a.created || "").getTime(),
46
101
  );
47
102
 
48
- setFeedbackItems(combined);
49
- }, [editContext, hideAppliedSuggestions]);
103
+ setFeedbackItems(tagFiltered);
104
+ }, [editContext, hideAppliedSuggestions, scope, selectedTagsFilter]);
50
105
 
51
- // Load available tags once per context for color mapping in child comments
52
106
  useEffect(() => {
53
- let cancelled = false;
54
- const loadTags = async () => {
55
- try {
56
- const descriptor = editContext?.contentEditorItem?.descriptor;
57
- if (!descriptor) {
58
- if (!cancelled) setAvailableTags([]);
59
- return;
60
- }
61
- const { getAvailableCommentTags } = await import(
62
- "../services/reviewsService"
107
+ try {
108
+ if (typeof window !== "undefined")
109
+ localStorage.setItem(
110
+ "editor.comments.tagsFilter",
111
+ JSON.stringify(selectedTagsFilter),
63
112
  );
64
- const tags = await getAvailableCommentTags(descriptor);
65
- if (!cancelled) setAvailableTags(tags || []);
66
- } catch {
67
- if (!cancelled) setAvailableTags([]);
68
- }
69
- };
70
- loadTags();
71
- return () => {
72
- cancelled = true;
73
- };
74
- }, [editContext?.contentEditorItem?.descriptor?.id]);
113
+ } catch {}
114
+ }, [selectedTagsFilter]);
115
+
116
+ useEffect(() => {
117
+ try {
118
+ if (typeof window !== "undefined")
119
+ localStorage.setItem("editor.comments.scope", scope);
120
+ } catch {}
121
+ }, [scope]);
122
+
123
+ // Tags are provided once via editContext.availableCommentTags
75
124
 
76
125
  return (
77
126
  <div className="flex h-full flex-col">
78
127
  <div className="border-gray-3 flex-1 overflow-auto border-b">
79
128
  <div className="h-fill-available p-4">
129
+ {/* Filters moved to bottom toolbar */}
130
+
80
131
  {feedbackItems.map((item) => {
81
132
  // Use a discriminator check. Here we assume suggested edits have the properties oldValue and newValue.
82
133
  if ("oldValue" in item && "newValue" in item) {
@@ -100,7 +151,7 @@ export function Comments() {
100
151
  <SimpleToolbar>
101
152
  <SimpleIconButton
102
153
  id="add-comment"
103
- icon="pi pi-plus"
154
+ icon={<Plus size={16} strokeWidth={1} className="p-0.5" />}
104
155
  label="Add Comment"
105
156
  onClick={() => {
106
157
  editContext?.addComment();
@@ -108,7 +159,9 @@ export function Comments() {
108
159
  />
109
160
  <SimpleIconButton
110
161
  selected={editContext?.showComments}
111
- icon={<MessageSquareMore size={16} className="p-0.5" />}
162
+ icon={
163
+ <MessageSquareMore size={16} strokeWidth={1} className="p-0.5" />
164
+ }
112
165
  label="Show Comments"
113
166
  onClick={() => {
114
167
  editContext?.setShowComments((x) => !x);
@@ -116,7 +169,7 @@ export function Comments() {
116
169
  />
117
170
  <SimpleIconButton
118
171
  selected={editContext?.showSuggestedEdits}
119
- icon={<SquarePen size={16} className="p-0.5" />}
172
+ icon={<SquarePen size={16} strokeWidth={1} className="p-0.5" />}
120
173
  label="Show Suggestions"
121
174
  onClick={() => {
122
175
  editContext?.setShowSuggestedEdits((x) => !x);
@@ -124,7 +177,7 @@ export function Comments() {
124
177
  />
125
178
  <SimpleIconButton
126
179
  selected={!!editContext?.showResolvedComments}
127
- icon={<CheckCircle2 size={16} className="p-0.5" />}
180
+ icon={<CheckCircle2 size={16} strokeWidth={1} className="p-0.5" />}
128
181
  label={
129
182
  editContext?.showResolvedComments
130
183
  ? "Hide Resolved Comments"
@@ -137,7 +190,7 @@ export function Comments() {
137
190
  {editContext?.showSuggestedEdits && (
138
191
  <SimpleIconButton
139
192
  selected={editContext?.showSuggestedEditsDiff}
140
- icon={<FileDiff size={16} className="p-0.5" />}
193
+ icon={<FileDiff size={16} strokeWidth={1} className="p-0.5" />}
141
194
  label="Show Suggestions Diff"
142
195
  onClick={() => {
143
196
  editContext?.setShowSuggestedEditsDiff((x) => !x);
@@ -148,9 +201,9 @@ export function Comments() {
148
201
  selected={hideAppliedSuggestions}
149
202
  icon={
150
203
  hideAppliedSuggestions ? (
151
- <CheckCircle2 size={16} className="p-0.5" />
204
+ <CheckCircle2 size={16} strokeWidth={1} className="p-0.5" />
152
205
  ) : (
153
- <CheckCircle size={16} className="p-0.5" />
206
+ <CheckCircle size={16} strokeWidth={1} className="p-0.5" />
154
207
  )
155
208
  }
156
209
  label={
@@ -162,6 +215,80 @@ export function Comments() {
162
215
  setHideAppliedSuggestions((x) => !x);
163
216
  }}
164
217
  />
218
+ <div className="ml-auto flex items-center gap-2">
219
+ <SimpleIconButton
220
+ selected={scope === "all"}
221
+ icon={<GitBranch size={16} strokeWidth={1} className="p-0.5" />}
222
+ label={
223
+ scope === "all"
224
+ ? "Include Children (on)"
225
+ : "Include Children (off)"
226
+ }
227
+ onClick={() => setScope(scope === "all" ? "itemOnly" : "all")}
228
+ />
229
+
230
+ {availableTags.length > 0 && (
231
+ <Popover>
232
+ <PopoverTrigger asChild>
233
+ <SimpleIconButton
234
+ showTooltip={false}
235
+ selected={selectedTagsFilter.length > 0}
236
+ icon={<Tags size={16} strokeWidth={1} className="p-0.5" />}
237
+ label={
238
+ selectedTagsFilter.length > 0
239
+ ? `Tags (${selectedTagsFilter.length})`
240
+ : "Tags"
241
+ }
242
+ onClick={() => {}}
243
+ />
244
+ </PopoverTrigger>
245
+ <PopoverContent className="w-72 p-3" align="start">
246
+ <div className="mb-2 text-xs text-gray-600">Filter by tags</div>
247
+ <div className="flex flex-wrap gap-2">
248
+ {availableTags.map((tag) => {
249
+ const isSelected = selectedTagsFilter.includes(tag.label);
250
+ return (
251
+ <Button
252
+ key={tag.label}
253
+ type="button"
254
+ size="sm"
255
+ variant={isSelected ? "secondary" : "outline"}
256
+ className="h-6 px-2 text-xs"
257
+ onClick={() => {
258
+ setSelectedTagsFilter((prev) =>
259
+ prev.includes(tag.label)
260
+ ? prev.filter((l) => l !== tag.label)
261
+ : [...prev, tag.label],
262
+ );
263
+ }}
264
+ style={{
265
+ borderColor: isSelected ? tag.color : undefined,
266
+ backgroundColor: isSelected
267
+ ? tag.color + "22"
268
+ : undefined,
269
+ }}
270
+ >
271
+ {tag.label}
272
+ </Button>
273
+ );
274
+ })}
275
+ </div>
276
+ {selectedTagsFilter.length > 0 && (
277
+ <div className="mt-3 text-right">
278
+ <Button
279
+ size="sm"
280
+ variant="ghost"
281
+ className="h-6 px-2 text-xs"
282
+ onClick={() => setSelectedTagsFilter([])}
283
+ >
284
+ Clear
285
+ </Button>
286
+ </div>
287
+ )}
288
+ </PopoverContent>
289
+ </Popover>
290
+ )}
291
+ </div>
165
292
  </SimpleToolbar>
166
293
  </div>
167
294
  );
@@ -88,6 +88,11 @@ export function openAiAgentForComment(
88
88
  rangeEnd: comment.rangeEnd,
89
89
  },
90
90
  },
91
+ // Seed profile from editor settings if available
92
+ profileId:
93
+ editContext.editorSettings?.commentResolveProfileId || undefined,
94
+ profileName:
95
+ editContext.editorSettings?.commentResolveProfileName || undefined,
91
96
  },
92
97
  } as any;
93
98
 
@@ -34,11 +34,7 @@ export function Debug({}: {}) {
34
34
  setPageLayout(layout);
35
35
  };
36
36
  loadLayout();
37
- }, [
38
- editContext.contentEditorItem,
39
- editContext.itemsRepository.revision,
40
- pageViewContext.isHeadless,
41
- ]);
37
+ }, [editContext.contentEditorItem, pageViewContext.isHeadless]);
42
38
 
43
39
  const page = pageLayout; // editContext.page;
44
40
  let component = null;
@@ -17,7 +17,14 @@ import {
17
17
  TooltipProvider,
18
18
  TooltipTrigger,
19
19
  } from "../../components/ui/tooltip";
20
+ import {
21
+ ContextMenu,
22
+ ContextMenuContent,
23
+ ContextMenuItem,
24
+ ContextMenuTrigger,
25
+ } from "../../components/ui/context-menu";
20
26
  import { DrawingPinFilledIcon, DrawingPinIcon } from "@radix-ui/react-icons";
27
+ import { Pin, PinOff } from "lucide-react";
21
28
 
22
29
  function ViewSelectorComponent() {
23
30
  const editContext = useEditContext();
@@ -51,6 +58,7 @@ function ViewSelectorComponent() {
51
58
  );
52
59
 
53
60
  const [isPopoverOpen, setIsPopoverOpen] = useState(false);
61
+ const [isDragging, setIsDragging] = useState(false);
54
62
 
55
63
  const views = useMemo(() => {
56
64
  return (
@@ -71,6 +79,22 @@ function ViewSelectorComponent() {
71
79
  // Use visibleViews from editContext instead of calculating locally
72
80
  const visibleViews = editContext?.visibleViews ?? [];
73
81
 
82
+ // Order visible views so pinned ones follow the user's pinnedViews order
83
+ const orderedVisibleViews = useMemo(() => {
84
+ const shown = visibleViews.filter((x) => x.icon);
85
+ const pinnedOrder = contextValues.pinnedViews;
86
+ const pinnedSet = new Set(pinnedOrder);
87
+
88
+ const pinnedOrdered = pinnedOrder
89
+ .map((name) => shown.find((v) => v.name === name))
90
+ .filter((v): v is NonNullable<typeof v> => Boolean(v));
91
+
92
+ const selected = shown.find((v) => v.name === contextValues.viewName);
93
+ if (selected && !pinnedSet.has(selected.name)) pinnedOrdered.push(selected);
94
+
95
+ return pinnedOrdered;
96
+ }, [visibleViews, contextValues.pinnedViews, contextValues.viewName]);
97
+
74
98
  const togglePin = useCallback(
75
99
  (viewName: string) => {
76
100
  const newPinnedViews = contextValues.pinnedViews.includes(viewName)
@@ -103,11 +127,12 @@ function ViewSelectorComponent() {
103
127
 
104
128
  const handleViewClick = useCallback(
105
129
  (viewName: string) => {
130
+ if (isDragging) return;
106
131
  if (viewName !== contextValues.viewName) {
107
132
  contextValues.switchView?.(viewName);
108
133
  }
109
134
  },
110
- [contextValues.viewName, contextValues.switchView],
135
+ [isDragging, contextValues.viewName, contextValues.switchView],
111
136
  );
112
137
 
113
138
  const handleViewClickAndClosePopover = useCallback(
@@ -126,41 +151,132 @@ function ViewSelectorComponent() {
126
151
  [togglePin],
127
152
  );
128
153
 
154
+ const handleDragStart = useCallback(
155
+ (e: React.DragEvent<HTMLDivElement>, viewName: string) => {
156
+ setIsDragging(true);
157
+ e.dataTransfer.setData("text/alpaca-view", viewName);
158
+ e.dataTransfer.effectAllowed = "move";
159
+ },
160
+ [],
161
+ );
162
+
163
+ const handleDragEnd = useCallback(() => {
164
+ setIsDragging(false);
165
+ }, []);
166
+
167
+ const handleDragOver = useCallback((e: React.DragEvent<HTMLDivElement>) => {
168
+ // Allow drop
169
+ e.preventDefault();
170
+ e.dataTransfer.dropEffect = "move";
171
+ }, []);
172
+
173
+ const handleDrop = useCallback(
174
+ (e: React.DragEvent<HTMLDivElement>, targetViewName: string) => {
175
+ e.preventDefault();
176
+ const sourceViewName =
177
+ e.dataTransfer.getData("text/alpaca-view") ||
178
+ e.dataTransfer.getData("text/plain");
179
+
180
+ if (!sourceViewName || sourceViewName === targetViewName) return;
181
+
182
+ const pinned = contextValues.pinnedViews;
183
+ const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
184
+ const before = e.clientY < rect.top + rect.height / 2;
185
+
186
+ // Remove source first, then insert relative to target (even if not currently pinned)
187
+ const newPinned = pinned.filter((n) => n !== sourceViewName);
188
+ const targetIndex = newPinned.indexOf(targetViewName);
189
+ const baseIndex = targetIndex >= 0 ? targetIndex : newPinned.length - 1;
190
+ const insertIndex = before
191
+ ? Math.max(0, baseIndex)
192
+ : Math.max(0, baseIndex + 1);
193
+
194
+ newPinned.splice(insertIndex, 0, sourceViewName);
195
+ contextValues.setUserPreferences?.({ pinnedViews: newPinned });
196
+ },
197
+ [contextValues.pinnedViews, contextValues.setUserPreferences],
198
+ );
199
+
200
+ // Drop between helpers
201
+ const handleDropBetween = useCallback(
202
+ (
203
+ e: React.DragEvent<HTMLDivElement>,
204
+ beforeIndex: number, // insert at this index
205
+ ) => {
206
+ e.preventDefault();
207
+ const sourceViewName =
208
+ e.dataTransfer.getData("text/alpaca-view") ||
209
+ e.dataTransfer.getData("text/plain");
210
+ if (!sourceViewName) return;
211
+
212
+ const pinned = contextValues.pinnedViews;
213
+ const newPinned = pinned.filter((n) => n !== sourceViewName);
214
+ const clamped = Math.max(0, Math.min(beforeIndex, newPinned.length));
215
+ newPinned.splice(clamped, 0, sourceViewName);
216
+ contextValues.setUserPreferences?.({ pinnedViews: newPinned });
217
+ },
218
+ [contextValues.pinnedViews, contextValues.setUserPreferences],
219
+ );
220
+
129
221
  return (
130
222
  <TooltipProvider delayDuration={500} skipDelayDuration={0}>
131
223
  {/* Visible views (pinned + selected) */}
132
- {visibleViews
133
- .filter((x) => x.icon)
134
- .map((view, i) => (
224
+ {orderedVisibleViews.map((view, i) => {
225
+ const isPinned = contextValues.pinnedViews.includes(view.name);
226
+ return (
135
227
  <Tooltip key={`visible-${i}`} delayDuration={500}>
136
- <TooltipTrigger asChild>
137
- <div
138
- className={classNames(
139
- contextValues.viewName === view.name
140
- ? "active bg-theme-secondary-light text-theme-secondary rounded-sm"
141
- : "text-dark hover:bg-gray-5",
142
- "relative flex cursor-pointer flex-col items-center justify-center",
143
- contextValues.showViewNames ? "p-1" : "p-1.5",
144
- contextValues.isMobile ? "flex-shrink-0" : "",
145
- )}
146
- data-sidebarview-name={view.name}
147
- onClick={() => handleViewClick(view.name)}
148
- >
149
- <div className={contextValues.showViewNames ? "p-0.5" : ""}>
150
- {renderViewIcon(view)}
151
- </div>
152
- {contextValues.showViewNames && (
153
- <span className="mt-1 max-w-[60px] text-center text-xs leading-tight break-words">
154
- {view.title || view.name}
155
- </span>
156
- )}
157
- </div>
158
- </TooltipTrigger>
228
+ <ContextMenu>
229
+ <ContextMenuTrigger asChild>
230
+ <TooltipTrigger asChild>
231
+ <div
232
+ className={classNames(
233
+ contextValues.viewName === view.name
234
+ ? "active bg-theme-secondary-light text-theme-secondary rounded-sm"
235
+ : "text-dark hover:bg-gray-5",
236
+ "relative flex cursor-pointer flex-col items-center justify-center select-none",
237
+ contextValues.showViewNames ? "p-1" : "p-1.5",
238
+ contextValues.isMobile ? "flex-shrink-0" : "",
239
+ )}
240
+ data-sidebarview-name={view.name}
241
+ onDragOver={handleDragOver}
242
+ onDrop={(e) => handleDrop(e, view.name)}
243
+ draggable={true}
244
+ onDragStart={(e) => handleDragStart(e, view.name)}
245
+ onDragEnd={handleDragEnd}
246
+ onClick={() => handleViewClick(view.name)}
247
+ >
248
+ <div className={contextValues.showViewNames ? "p-0.5" : ""}>
249
+ {renderViewIcon(view)}
250
+ </div>
251
+ {contextValues.showViewNames && (
252
+ <span className="mt-1 max-w-[60px] text-center text-xs leading-tight break-words">
253
+ {view.title || view.name}
254
+ </span>
255
+ )}
256
+ </div>
257
+ </TooltipTrigger>
258
+ </ContextMenuTrigger>
259
+ <ContextMenuContent>
260
+ <ContextMenuItem
261
+ onSelect={() => {
262
+ togglePin(view.name);
263
+ }}
264
+ >
265
+ {isPinned ? (
266
+ <PinOff className="size-4" strokeWidth={1} />
267
+ ) : (
268
+ <Pin className="size-4" strokeWidth={1} />
269
+ )}
270
+ {isPinned ? "Unpin view" : "Pin view"}
271
+ </ContextMenuItem>
272
+ </ContextMenuContent>
273
+ </ContextMenu>
159
274
  <TooltipContent side="right">
160
275
  {view.title || view.name}
161
276
  </TooltipContent>
162
277
  </Tooltip>
163
- ))}
278
+ );
279
+ })}
164
280
 
165
281
  {/* Three dots menu for non-selected views */}
166
282
  {views.length > 0 && (
@@ -921,3 +921,58 @@ export function SecretAgentIcon({
921
921
  </svg>
922
922
  );
923
923
  }
924
+
925
+ export function AnimatedSunIcon({
926
+ size = 36,
927
+ style,
928
+ ...props
929
+ }: {
930
+ size?: number;
931
+ style?: React.CSSProperties;
932
+ props?: React.SVGProps<SVGSVGElement>;
933
+ }) {
934
+ return (
935
+ <svg
936
+ width={size}
937
+ height={size}
938
+ viewBox="0 0 24 24"
939
+ fill="none"
940
+ role="img"
941
+ aria-label="Animated Sun"
942
+ stroke="currentColor"
943
+ strokeWidth={1.6}
944
+ strokeLinecap="round"
945
+ strokeLinejoin="round"
946
+ style={style}
947
+ {...props}
948
+ >
949
+ <style>{`
950
+ @media (prefers-reduced-motion: reduce) {
951
+ .sun-spin { animation: none !important; }
952
+ }
953
+ .sun-spin {
954
+ transform-origin: 12px 12px;
955
+ animation: sun-rotate 8s linear infinite;
956
+ }
957
+ @keyframes sun-rotate {
958
+ to { transform: rotate(360deg); }
959
+ }
960
+ `}</style>
961
+
962
+ {/* Rays spin */}
963
+ <g className="sun-spin">
964
+ <line x1="12" y1="2.5" x2="12" y2="4.2" />
965
+ <line x1="12" y1="19.8" x2="12" y2="21.5" />
966
+ <line x1="4.2" y1="12" x2="2.5" y2="12" />
967
+ <line x1="21.5" y1="12" x2="19.8" y2="12" />
968
+ <line x1="5.6" y1="5.6" x2="4.4" y2="4.4" />
969
+ <line x1="19.6" y1="19.6" x2="18.4" y2="18.4" />
970
+ <line x1="5.6" y1="18.4" x2="4.4" y2="19.6" />
971
+ <line x1="19.6" y1="4.4" x2="18.4" y2="5.6" />
972
+ </g>
973
+
974
+ {/* Core stays still */}
975
+ <circle cx="12" cy="12" r="4.2" />
976
+ </svg>
977
+ );
978
+ }
package/src/revision.ts CHANGED
@@ -1,2 +1,2 @@
1
- export const version = "1.0.4086";
2
- export const buildDate = "2025-09-12 01:46:21";
1
+ export const version = "1.0.4089";
2
+ export const buildDate = "2025-09-16 09:23:08";