astro-tractstack 2.0.0-rc.65 → 2.0.0-rc.67
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/templates/src/client/view.js +9 -3
- package/templates/src/components/codehooks/EpinetWrapper.tsx +1 -0
- package/templates/src/components/edit/SettingsPanel.tsx +1 -1
- package/templates/src/components/edit/state/SaveModal.tsx +6 -0
- package/templates/src/components/edit/widgets/InteractiveDisclosureWidget.tsx +76 -33
- package/templates/src/components/edit/widgets/ToggleWidget.tsx +9 -16
- package/templates/src/pages/[...slug]/edit.astro +1 -1
- package/templates/src/pages/context/[...contextSlug]/edit.astro +2 -8
package/package.json
CHANGED
|
@@ -327,7 +327,7 @@ function processStoryfragmentUpdate(update, config) {
|
|
|
327
327
|
`Target pane element for scrolling not found: #pane-${update.gotoPaneId}`
|
|
328
328
|
);
|
|
329
329
|
}
|
|
330
|
-
},
|
|
330
|
+
}, 350);
|
|
331
331
|
}
|
|
332
332
|
}
|
|
333
333
|
|
|
@@ -361,6 +361,7 @@ function initializeCurrentView() {
|
|
|
361
361
|
|
|
362
362
|
function resetViewState() {
|
|
363
363
|
log('Resetting view state before new page preparation.');
|
|
364
|
+
flushPendingPaneEvents();
|
|
364
365
|
isPageInitialized = false;
|
|
365
366
|
paneViewTimes.clear();
|
|
366
367
|
}
|
|
@@ -385,8 +386,13 @@ if (!window.tractstackViewLifecycleListenersAttached) {
|
|
|
385
386
|
|
|
386
387
|
let beliefValue;
|
|
387
388
|
if (target.type === 'checkbox') {
|
|
388
|
-
|
|
389
|
-
|
|
389
|
+
const onVerb = target.getAttribute('data-verb');
|
|
390
|
+
const offVerb = target.getAttribute('data-off-verb');
|
|
391
|
+
if (onVerb && offVerb) {
|
|
392
|
+
beliefValue = target.checked ? onVerb : offVerb;
|
|
393
|
+
} else {
|
|
394
|
+
beliefValue = target.checked ? 'BELIEVES_YES' : 'BELIEVES_NO';
|
|
395
|
+
}
|
|
390
396
|
} else {
|
|
391
397
|
beliefValue = target.value;
|
|
392
398
|
}
|
|
@@ -27,7 +27,7 @@ const SettingsPanel = ({ config, availableCodeHooks }: SettingsPanelProps) => {
|
|
|
27
27
|
|
|
28
28
|
return (
|
|
29
29
|
<div
|
|
30
|
-
className="bg-mydarkgrey flex h-full max-w-sm flex-col rounded-xl bg-opacity-20 p-0.5 backdrop-blur-sm"
|
|
30
|
+
className="bg-mydarkgrey min-w-xs flex h-full max-w-sm flex-col rounded-xl bg-opacity-20 p-0.5 backdrop-blur-sm"
|
|
31
31
|
style={
|
|
32
32
|
{
|
|
33
33
|
animation: window.matchMedia(
|
|
@@ -931,6 +931,12 @@ export default function SaveModal({
|
|
|
931
931
|
>
|
|
932
932
|
Keep Editing
|
|
933
933
|
</button>
|
|
934
|
+
<a
|
|
935
|
+
href="/storykeep/content"
|
|
936
|
+
className={`rounded bg-black px-4 py-2 text-white transition-colors hover:bg-white hover:text-black`}
|
|
937
|
+
>
|
|
938
|
+
Dashboard
|
|
939
|
+
</a>
|
|
934
940
|
</>
|
|
935
941
|
)}
|
|
936
942
|
{stage === 'ERROR' && (
|
|
@@ -38,10 +38,7 @@ interface WidgetStyles {
|
|
|
38
38
|
bgColor: string;
|
|
39
39
|
bgOpacity: number;
|
|
40
40
|
}
|
|
41
|
-
type StoredDisclosureItem = Omit<
|
|
42
|
-
DisclosureItem,
|
|
43
|
-
'id' | 'isDisabled' | 'isCustom'
|
|
44
|
-
>;
|
|
41
|
+
type StoredDisclosureItem = Omit<DisclosureItem, 'id' | 'isDisabled'>;
|
|
45
42
|
interface InteractiveDisclosureWidgetProps {
|
|
46
43
|
node: FlatNode;
|
|
47
44
|
onUpdate: (params: string[]) => void;
|
|
@@ -91,6 +88,7 @@ const IconSelector = ({
|
|
|
91
88
|
<Combobox.Input
|
|
92
89
|
className="w-full rounded-md border-gray-300 py-1.5 pl-3 pr-10 shadow-sm"
|
|
93
90
|
placeholder="Search icons..."
|
|
91
|
+
autoFocus
|
|
94
92
|
/>
|
|
95
93
|
<Combobox.Trigger className="absolute inset-y-0 right-0 flex items-center pr-2">
|
|
96
94
|
<i className={`bi bi-${value} mr-2 text-lg`}></i>
|
|
@@ -141,6 +139,13 @@ const DisclosureItemEditor = ({
|
|
|
141
139
|
isFirst: boolean;
|
|
142
140
|
isLast: boolean;
|
|
143
141
|
}) => {
|
|
142
|
+
const [isEditingIcon, setIsEditingIcon] = useState(false);
|
|
143
|
+
|
|
144
|
+
const handleIconChange = (newIcon: string) => {
|
|
145
|
+
onUpdate({ icon: newIcon });
|
|
146
|
+
setIsEditingIcon(false);
|
|
147
|
+
};
|
|
148
|
+
|
|
144
149
|
return (
|
|
145
150
|
<div
|
|
146
151
|
className={`space-y-4 rounded-lg border bg-white p-4 shadow-sm transition-opacity ${
|
|
@@ -169,9 +174,11 @@ const DisclosureItemEditor = ({
|
|
|
169
174
|
</div>
|
|
170
175
|
<h4 className="font-bold text-gray-800">
|
|
171
176
|
{item.title}{' '}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
177
|
+
{!item.isCustom && (
|
|
178
|
+
<span className="text-xs font-normal text-gray-500">
|
|
179
|
+
(Key: {item.beliefValue})
|
|
180
|
+
</span>
|
|
181
|
+
)}
|
|
175
182
|
</h4>
|
|
176
183
|
</div>
|
|
177
184
|
<button
|
|
@@ -189,13 +196,6 @@ const DisclosureItemEditor = ({
|
|
|
189
196
|
</button>
|
|
190
197
|
</div>
|
|
191
198
|
<fieldset disabled={item.isDisabled} className="space-y-4">
|
|
192
|
-
{item.isCustom && (
|
|
193
|
-
<SingleParam
|
|
194
|
-
label="Key / Value"
|
|
195
|
-
value={item.beliefValue}
|
|
196
|
-
onChange={(value) => onUpdate({ beliefValue: value })}
|
|
197
|
-
/>
|
|
198
|
-
)}
|
|
199
199
|
<SingleParam
|
|
200
200
|
label="Display Title"
|
|
201
201
|
value={item.title}
|
|
@@ -206,10 +206,29 @@ const DisclosureItemEditor = ({
|
|
|
206
206
|
value={item.description || ''}
|
|
207
207
|
onChange={(value) => onUpdate({ description: value })}
|
|
208
208
|
/>
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
209
|
+
|
|
210
|
+
{isEditingIcon ? (
|
|
211
|
+
<IconSelector value={item.icon} onChange={handleIconChange} />
|
|
212
|
+
) : (
|
|
213
|
+
<div>
|
|
214
|
+
<label className="block text-xs font-bold text-gray-600">
|
|
215
|
+
Icon
|
|
216
|
+
</label>
|
|
217
|
+
<div className="mt-1 flex items-center justify-between rounded-md border border-gray-300 bg-white px-3 py-1.5 shadow-sm">
|
|
218
|
+
<div className="flex items-center gap-2">
|
|
219
|
+
<i className={`bi bi-${item.icon} text-lg`}></i>
|
|
220
|
+
<span className="text-sm">{item.icon}</span>
|
|
221
|
+
</div>
|
|
222
|
+
<button
|
|
223
|
+
type="button"
|
|
224
|
+
onClick={() => setIsEditingIcon(true)}
|
|
225
|
+
className="text-sm font-bold text-cyan-600 hover:text-cyan-800"
|
|
226
|
+
>
|
|
227
|
+
Change
|
|
228
|
+
</button>
|
|
229
|
+
</div>
|
|
230
|
+
</div>
|
|
231
|
+
)}
|
|
213
232
|
|
|
214
233
|
{item.isCustom ? (
|
|
215
234
|
<div className="relative rounded-md border p-3">
|
|
@@ -243,8 +262,8 @@ export default function InteractiveDisclosureWidget({
|
|
|
243
262
|
const [selectedBeliefTag, setSelectedBeliefTag] = useState<string>('');
|
|
244
263
|
const [disclosures, setDisclosures] = useState<DisclosureItem[]>([]);
|
|
245
264
|
const [widgetStyles, setWidgetStyles] = useState<WidgetStyles>({
|
|
246
|
-
textColor: '',
|
|
247
|
-
bgColor: '',
|
|
265
|
+
textColor: '#000000',
|
|
266
|
+
bgColor: '#ffffff',
|
|
248
267
|
bgOpacity: 100,
|
|
249
268
|
});
|
|
250
269
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
@@ -268,7 +287,11 @@ export default function InteractiveDisclosureWidget({
|
|
|
268
287
|
try {
|
|
269
288
|
const parsed = JSON.parse(payloadJson);
|
|
270
289
|
setWidgetStyles(
|
|
271
|
-
parsed.styles || {
|
|
290
|
+
parsed.styles || {
|
|
291
|
+
textColor: '#000000',
|
|
292
|
+
bgColor: '#ffffff',
|
|
293
|
+
bgOpacity: 100,
|
|
294
|
+
}
|
|
272
295
|
);
|
|
273
296
|
const loadedDisclosures =
|
|
274
297
|
(parsed.disclosures as StoredDisclosureItem[]) || [];
|
|
@@ -290,6 +313,7 @@ export default function InteractiveDisclosureWidget({
|
|
|
290
313
|
const isFromScale = scaleKeys.some(
|
|
291
314
|
(sk) => sk.slug === loadedItem.beliefValue
|
|
292
315
|
);
|
|
316
|
+
|
|
293
317
|
return {
|
|
294
318
|
...loadedItem,
|
|
295
319
|
id: generateId(),
|
|
@@ -308,7 +332,7 @@ export default function InteractiveDisclosureWidget({
|
|
|
308
332
|
beliefValue: slug,
|
|
309
333
|
title: name,
|
|
310
334
|
description: '',
|
|
311
|
-
icon: '
|
|
335
|
+
icon: 'chat-heart-fill',
|
|
312
336
|
actionLisp: `(${actionCommand} ${beliefTag} ${quoteIfNecessary(actionCommand, slug)})`,
|
|
313
337
|
isCustom: false,
|
|
314
338
|
isDisabled: true,
|
|
@@ -321,7 +345,11 @@ export default function InteractiveDisclosureWidget({
|
|
|
321
345
|
}
|
|
322
346
|
} else {
|
|
323
347
|
setDisclosures([]);
|
|
324
|
-
setWidgetStyles({
|
|
348
|
+
setWidgetStyles({
|
|
349
|
+
textColor: '#000000',
|
|
350
|
+
bgColor: '#ffffff',
|
|
351
|
+
bgOpacity: 100,
|
|
352
|
+
});
|
|
325
353
|
}
|
|
326
354
|
setIsDataLoaded(true);
|
|
327
355
|
}, [node, beliefs]);
|
|
@@ -352,13 +380,18 @@ export default function InteractiveDisclosureWidget({
|
|
|
352
380
|
const handleUpdate = () => {
|
|
353
381
|
const disclosuresToStore: StoredDisclosureItem[] = disclosures
|
|
354
382
|
.filter((d) => !d.isDisabled)
|
|
355
|
-
.map(({ id,
|
|
383
|
+
.map(({ id, isDisabled, ...rest }) => rest);
|
|
356
384
|
const payload = { styles: widgetStyles, disclosures: disclosuresToStore };
|
|
357
385
|
onUpdate([selectedBeliefTag, JSON.stringify(payload)]);
|
|
358
386
|
};
|
|
359
387
|
|
|
360
388
|
const handleBeliefChange = (tag: string) => {
|
|
361
389
|
setSelectedBeliefTag(tag);
|
|
390
|
+
setWidgetStyles({
|
|
391
|
+
textColor: '#000000',
|
|
392
|
+
bgColor: '#ffffff',
|
|
393
|
+
bgOpacity: 100,
|
|
394
|
+
});
|
|
362
395
|
const belief = beliefs.find((b) => b.slug === tag);
|
|
363
396
|
let newDisclosures: DisclosureItem[] = [];
|
|
364
397
|
if (belief) {
|
|
@@ -375,7 +408,7 @@ export default function InteractiveDisclosureWidget({
|
|
|
375
408
|
beliefValue: slug,
|
|
376
409
|
title: name,
|
|
377
410
|
description: '',
|
|
378
|
-
icon: '
|
|
411
|
+
icon: 'chat-heart-fill',
|
|
379
412
|
actionLisp: `(${actionCommand} ${tag} ${quoteIfNecessary(actionCommand, slug)})`,
|
|
380
413
|
isCustom: false,
|
|
381
414
|
isDisabled: false,
|
|
@@ -399,10 +432,12 @@ export default function InteractiveDisclosureWidget({
|
|
|
399
432
|
const addCustomDisclosure = () => {
|
|
400
433
|
const newItem: DisclosureItem = {
|
|
401
434
|
id: generateId(),
|
|
402
|
-
beliefValue: `custom
|
|
435
|
+
beliefValue: `custom-${Date.now()}-${Math.random()
|
|
436
|
+
.toString(36)
|
|
437
|
+
.substring(2, 6)}`,
|
|
403
438
|
title: 'New Custom Item',
|
|
404
439
|
description: '',
|
|
405
|
-
icon: '
|
|
440
|
+
icon: 'chat-heart-fill',
|
|
406
441
|
actionLisp: '',
|
|
407
442
|
isCustom: true,
|
|
408
443
|
isDisabled: false,
|
|
@@ -418,12 +453,20 @@ export default function InteractiveDisclosureWidget({
|
|
|
418
453
|
const updateWidgetStyles = (updates: Partial<WidgetStyles>) =>
|
|
419
454
|
setWidgetStyles((prev) => ({ ...prev, ...updates }));
|
|
420
455
|
|
|
421
|
-
const toggleDisclosure = (id: string) =>
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
456
|
+
const toggleDisclosure = (id: string) => {
|
|
457
|
+
const itemToToggle = disclosures.find((d) => d.id === id);
|
|
458
|
+
if (!itemToToggle) return;
|
|
459
|
+
|
|
460
|
+
if (itemToToggle.isCustom) {
|
|
461
|
+
setDisclosures(disclosures.filter((d) => d.id !== id));
|
|
462
|
+
} else {
|
|
463
|
+
setDisclosures(
|
|
464
|
+
disclosures.map((d) =>
|
|
465
|
+
d.id === id ? { ...d, isDisabled: !d.isDisabled } : d
|
|
466
|
+
)
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
};
|
|
427
470
|
|
|
428
471
|
const handleColorChange = (
|
|
429
472
|
key: 'textColor' | 'bgColor',
|
|
@@ -12,28 +12,27 @@ export default function ToggleWidget({ node, onUpdate }: ToggleWidgetProps) {
|
|
|
12
12
|
const [beliefs, setBeliefs] = useState<BeliefNode[]>([]);
|
|
13
13
|
const [selectedBeliefTag, setSelectedBeliefTag] = useState<string>('');
|
|
14
14
|
const [currentPrompt, setCurrentPrompt] = useState<string>('');
|
|
15
|
+
const [currentScale, setCurrentScale] = useState<string>('');
|
|
15
16
|
const [isInitialized, setIsInitialized] = useState(false);
|
|
16
17
|
|
|
17
|
-
// Get parameter metadata from the widgetMeta constant
|
|
18
18
|
const widgetInfo = widgetMeta.toggle;
|
|
19
19
|
|
|
20
20
|
const params = node.codeHookParams || [];
|
|
21
21
|
const beliefTag = String(params[0] || '');
|
|
22
22
|
const prompt = String(params[1] || '');
|
|
23
|
+
const scale = String(params[2] || '');
|
|
23
24
|
|
|
24
|
-
// Check if beliefTag is the placeholder value
|
|
25
25
|
const isPlaceholder = beliefTag === 'BeliefTag';
|
|
26
26
|
|
|
27
|
-
// Update local state when props change
|
|
28
27
|
useEffect(() => {
|
|
29
28
|
if (!isPlaceholder && beliefTag) {
|
|
30
29
|
setSelectedBeliefTag(beliefTag);
|
|
31
30
|
}
|
|
32
31
|
setCurrentPrompt(prompt);
|
|
32
|
+
setCurrentScale(scale);
|
|
33
33
|
setIsInitialized(true);
|
|
34
|
-
}, [beliefTag, prompt, isPlaceholder]);
|
|
34
|
+
}, [beliefTag, prompt, scale, isPlaceholder]);
|
|
35
35
|
|
|
36
|
-
// Fetch beliefs using new Go backend pattern
|
|
37
36
|
useEffect(() => {
|
|
38
37
|
const fetchData = async () => {
|
|
39
38
|
try {
|
|
@@ -41,7 +40,6 @@ export default function ToggleWidget({ node, onUpdate }: ToggleWidgetProps) {
|
|
|
41
40
|
import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080';
|
|
42
41
|
const tenantId = import.meta.env.PUBLIC_TENANTID || 'default';
|
|
43
42
|
|
|
44
|
-
// Step 1: Get all belief IDs
|
|
45
43
|
const idsResponse = await fetch(`${goBackend}/api/v1/nodes/beliefs`, {
|
|
46
44
|
headers: {
|
|
47
45
|
'X-Tenant-ID': tenantId,
|
|
@@ -60,7 +58,6 @@ export default function ToggleWidget({ node, onUpdate }: ToggleWidgetProps) {
|
|
|
60
58
|
return;
|
|
61
59
|
}
|
|
62
60
|
|
|
63
|
-
// Step 2: Get belief data by IDs
|
|
64
61
|
const beliefsResponse = await fetch(
|
|
65
62
|
`${goBackend}/api/v1/nodes/beliefs`,
|
|
66
63
|
{
|
|
@@ -92,34 +89,30 @@ export default function ToggleWidget({ node, onUpdate }: ToggleWidgetProps) {
|
|
|
92
89
|
const handleBeliefChange = (selectedValue: string) => {
|
|
93
90
|
if (!isInitialized) return;
|
|
94
91
|
setSelectedBeliefTag(selectedValue);
|
|
95
|
-
|
|
92
|
+
const selectedBelief = beliefs.find((b) => b.slug === selectedValue);
|
|
93
|
+
const newScale = selectedBelief ? selectedBelief.scale || '' : '';
|
|
94
|
+
setCurrentScale(newScale);
|
|
95
|
+
onUpdate([selectedValue, currentPrompt, newScale]);
|
|
96
96
|
};
|
|
97
97
|
|
|
98
98
|
const handlePromptChange = (value: string) => {
|
|
99
99
|
if (!isInitialized) return;
|
|
100
|
-
// Sanitize the input value (remove newlines and pipe characters)
|
|
101
100
|
const sanitizedValue = value.replace(/[\n\r|]/g, '');
|
|
102
101
|
setCurrentPrompt(sanitizedValue);
|
|
103
|
-
|
|
104
|
-
// Use the actual selected tag (from state) or the original belief tag as fallback
|
|
105
102
|
const tagToUse = selectedBeliefTag || (isPlaceholder ? '' : beliefTag);
|
|
106
|
-
onUpdate([tagToUse, sanitizedValue]);
|
|
103
|
+
onUpdate([tagToUse, sanitizedValue, currentScale]);
|
|
107
104
|
};
|
|
108
105
|
|
|
109
|
-
// Show beliefs that can be selected for the toggle
|
|
110
106
|
const filteredBeliefs = beliefs.filter(
|
|
111
107
|
(b) => b.scale === 'yn' || b.scale === 'tf'
|
|
112
108
|
);
|
|
113
109
|
|
|
114
|
-
// Find the selected belief (if any)
|
|
115
110
|
const selectedBelief = beliefs.find(
|
|
116
111
|
(b) => b.slug === (selectedBeliefTag || (isPlaceholder ? '' : beliefTag))
|
|
117
112
|
);
|
|
118
113
|
|
|
119
|
-
// Determine if we have a real selection - either from state or props
|
|
120
114
|
const hasRealSelection = !!selectedBelief || (!isPlaceholder && !!beliefTag);
|
|
121
115
|
|
|
122
|
-
// Calculate the current value to show in the select dropdown
|
|
123
116
|
const selectValue = selectedBeliefTag || (isPlaceholder ? '' : beliefTag);
|
|
124
117
|
|
|
125
118
|
return (
|
|
@@ -171,7 +171,7 @@ for (const [key, value] of Astro.url.searchParams) {
|
|
|
171
171
|
<main id="mainContent" class="relative flex-1 overflow-x-auto">
|
|
172
172
|
<div class="bg-myblue/20 bg-mylightgrey h-full p-1.5">
|
|
173
173
|
<div
|
|
174
|
-
class="h-fit min-h-screen pb-
|
|
174
|
+
class="h-fit min-h-screen pb-96"
|
|
175
175
|
style={{
|
|
176
176
|
backgroundImage:
|
|
177
177
|
'repeating-linear-gradient(135deg, transparent, transparent 10px, rgba(0,0,0,0.05) 10px, rgba(0,0,0,0.05) 20px)',
|
|
@@ -159,16 +159,10 @@ for (const [key, value] of Astro.url.searchParams) {
|
|
|
159
159
|
<StoryKeepToolMode isContext={true} client:only="react" />
|
|
160
160
|
|
|
161
161
|
<!-- Main Content Area -->
|
|
162
|
-
<main
|
|
163
|
-
id="mainContent"
|
|
164
|
-
class="relative flex-1 overflow-x-auto"
|
|
165
|
-
style={{
|
|
166
|
-
paddingBottom: 'var(--bottom-right-controls-bottom-offset, 16px)',
|
|
167
|
-
}}
|
|
168
|
-
>
|
|
162
|
+
<main id="mainContent" class="relative flex-1 overflow-x-auto">
|
|
169
163
|
<div class="bg-myblue/20 bg-mylightgrey h-full p-1.5">
|
|
170
164
|
<div
|
|
171
|
-
class="h-fit min-h-screen"
|
|
165
|
+
class="h-fit min-h-screen pb-96"
|
|
172
166
|
style={{
|
|
173
167
|
backgroundImage:
|
|
174
168
|
'repeating-linear-gradient(135deg, transparent, transparent 10px, rgba(0,0,0,0.05) 10px, rgba(0,0,0,0.05) 20px)',
|