astro-tractstack 2.0.0-rc.9 → 2.0.1
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/LICENSE +8 -97
- package/README.md +7 -5
- package/bin/create-tractstack.js +31 -8
- package/dist/index.js +106 -29
- package/package.json +10 -5
- package/templates/css/frontend.css +1 -1
- package/templates/custom/minimal/CodeHook.astro +13 -12
- package/templates/custom/minimal/CustomRoutes.astro +25 -31
- package/templates/custom/with-examples/CodeHook.astro +22 -11
- package/templates/custom/with-examples/CustomRoutes.astro +4 -8
- package/templates/custom/with-examples/ProductCard.astro +29 -0
- package/templates/custom/with-examples/ProductCardWrapper.astro +43 -0
- package/templates/custom/with-examples/ProductGrid.astro +64 -0
- package/templates/custom/with-examples/pages/Collections.astro +58 -98
- package/templates/gitignore +42 -0
- package/templates/prettierignore +5 -0
- package/templates/prettierrc +19 -0
- package/templates/src/client/app.js +127 -0
- package/templates/src/client/htmx.min.js +3519 -0
- package/templates/src/client/view.js +429 -0
- package/templates/src/components/Footer.astro +4 -9
- package/templates/src/components/Header.astro +67 -60
- package/templates/src/components/Menu.tsx +188 -52
- package/templates/src/components/codehooks/BunnyVideoSetup.tsx +2 -2
- package/templates/src/components/codehooks/EpinetDurationSelector.tsx +9 -13
- package/templates/src/components/codehooks/EpinetTableView.tsx +11 -7
- package/templates/src/components/codehooks/EpinetWrapper.tsx +10 -9
- package/templates/src/components/codehooks/FeaturedArticle.astro +105 -0
- package/templates/src/components/codehooks/FeaturedArticleSetup.tsx +318 -0
- package/templates/src/components/codehooks/ListContent.astro +32 -162
- package/templates/src/components/codehooks/ListContentSetup.tsx +43 -138
- package/templates/src/components/codehooks/ProductCardSetup.tsx +152 -0
- package/templates/src/components/codehooks/ProductGridSetup.tsx +274 -0
- package/templates/src/components/codehooks/SearchWidget.tsx +453 -0
- package/templates/src/components/compositor/Node.tsx +3 -6
- package/templates/src/components/compositor/PanelVisibilityWrapper.tsx +21 -11
- package/templates/src/components/compositor/elements/BunnyVideo.tsx +21 -20
- package/templates/src/components/compositor/nodes/Pane.tsx +51 -21
- package/templates/src/components/compositor/nodes/RenderChildren.tsx +6 -1
- package/templates/src/components/compositor/nodes/Widget.tsx +16 -2
- package/templates/src/components/compositor/preview/FeaturedArticlePreview.tsx +155 -0
- package/templates/src/components/compositor/preview/PaneSnapshotGenerator.tsx +20 -1
- package/templates/src/components/edit/Header.tsx +10 -4
- package/templates/src/components/edit/PanelSwitch.tsx +11 -7
- package/templates/src/components/edit/SettingsPanel.tsx +29 -18
- package/templates/src/components/edit/ToolBar.tsx +1 -28
- package/templates/src/components/edit/ToolMode.tsx +45 -32
- package/templates/src/components/edit/pane/AddPanePanel_break.tsx +12 -2
- package/templates/src/components/edit/pane/AddPanePanel_codehook.tsx +8 -2
- package/templates/src/components/edit/pane/AddPanePanel_newAICopy_modal.tsx +1 -1
- package/templates/src/components/edit/pane/ConfigPanePanel.tsx +17 -27
- package/templates/src/components/edit/pane/PageGenSelector.tsx +16 -16
- package/templates/src/components/edit/pane/PageGenSpecial.tsx +26 -49
- package/templates/src/components/edit/pane/PageGen_preview.tsx +17 -2
- package/templates/src/components/edit/pane/PanePanel_path.tsx +2 -4
- package/templates/src/components/edit/pane/PanePanel_title.tsx +243 -76
- package/templates/src/components/edit/panels/StyleBreakPanel.tsx +17 -19
- package/templates/src/components/edit/panels/StyleCodeHookPanel.tsx +48 -37
- package/templates/src/components/edit/panels/StyleElementPanel_add.tsx +60 -55
- package/templates/src/components/edit/panels/StyleImagePanel_add.tsx +56 -50
- package/templates/src/components/edit/panels/StyleLiElementPanel_add.tsx +54 -47
- package/templates/src/components/edit/panels/StyleLinkPanel_add.tsx +54 -44
- package/templates/src/components/edit/panels/StyleLinkPanel_config.tsx +113 -138
- package/templates/src/components/edit/panels/StyleParentPanel_add.tsx +54 -40
- package/templates/src/components/edit/panels/StyleWidgetPanel.tsx +3 -3
- package/templates/src/components/edit/panels/StyleWidgetPanel_add.tsx +56 -49
- package/templates/src/components/edit/panels/StyleWidgetPanel_config.tsx +14 -5
- package/templates/src/components/edit/state/SaveModal.tsx +316 -169
- package/templates/src/components/edit/storyfragment/StoryFragmentPanel_og.tsx +1 -1
- package/templates/src/components/edit/storyfragment/StoryFragmentPanel_slug.tsx +56 -55
- package/templates/src/components/edit/widgets/BunnyWidget.tsx +538 -59
- package/templates/src/components/edit/widgets/InteractiveDisclosureWidget.tsx +656 -0
- package/templates/src/components/edit/widgets/ToggleWidget.tsx +9 -16
- package/templates/src/components/fields/ArtpackImage.tsx +4 -1
- package/templates/src/components/fields/BackgroundImage.tsx +1 -1
- package/templates/src/components/fields/BackgroundImageWrapper.tsx +127 -35
- package/templates/src/components/fields/ColorPickerCombo.tsx +66 -62
- package/templates/src/components/fields/ImageUpload.tsx +1 -1
- package/templates/src/components/fields/ViewportComboBox.tsx +59 -42
- package/templates/src/components/form/ActionBuilderBeliefSelector.tsx +117 -0
- package/templates/src/components/form/ActionBuilderField.tsx +306 -87
- package/templates/src/components/search/SearchModal.tsx +420 -0
- package/templates/src/components/search/SearchResults.tsx +367 -0
- package/templates/src/components/search/SearchWrapper.tsx +46 -0
- package/templates/src/components/storykeep/Dashboard_Advanced.tsx +1 -1
- package/templates/src/components/storykeep/Dashboard_Analytics.tsx +34 -8
- package/templates/src/components/storykeep/Dashboard_Branding.tsx +1 -1
- package/templates/src/components/storykeep/Dashboard_Content.tsx +6 -0
- package/templates/src/components/storykeep/StoryKeepBackdrop.astro +87 -0
- package/templates/src/components/storykeep/controls/content/BeliefForm.tsx +38 -34
- package/templates/src/components/storykeep/controls/content/KnownResourceForm.tsx +1 -1
- package/templates/src/components/storykeep/controls/content/MenuForm.tsx +56 -8
- package/templates/src/components/storykeep/controls/content/ResourceForm.tsx +18 -3
- package/templates/src/components/storykeep/controls/content/StoryFragmentTable.tsx +5 -8
- package/templates/src/components/storykeep/state/FetchAnalytics.tsx +274 -228
- package/templates/src/components/storykeep/widgets/Wizard.tsx +14 -7
- package/templates/src/components/widgets/ImpressionWrapper.tsx +0 -1
- package/templates/src/constants/shapes.ts +9 -0
- package/templates/src/constants.ts +2121 -16
- package/templates/src/hooks/useSearch.ts +228 -0
- package/templates/src/layouts/Layout.astro +213 -104
- package/templates/src/lib/storyData.ts +4 -1
- package/templates/src/pages/[...slug]/edit.astro +14 -14
- package/templates/src/pages/[...slug].astro +82 -21
- package/templates/src/pages/api/orphan-analysis.ts +0 -1
- package/templates/src/pages/api/tailwind.ts +23 -21
- package/templates/src/pages/context/[...contextSlug]/edit.astro +14 -14
- package/templates/src/pages/context/[...contextSlug].astro +7 -2
- package/templates/src/pages/storykeep/advanced.astro +5 -4
- package/templates/src/pages/storykeep/branding.astro +5 -4
- package/templates/src/pages/storykeep/content.astro +5 -4
- package/templates/src/pages/storykeep/init.astro +40 -1
- package/templates/src/pages/storykeep/login.astro +1 -1
- package/templates/src/pages/storykeep.astro +5 -4
- package/templates/src/stores/nodes.ts +59 -88
- package/templates/src/stores/orphanAnalysis.ts +19 -21
- package/templates/src/stores/storykeep.ts +7 -0
- package/templates/src/types/compositorTypes.ts +6 -0
- package/templates/src/types/tractstack.ts +17 -0
- package/templates/src/utils/actions/lispLexer.ts +2 -2
- package/templates/src/utils/actions/preParse_Action.ts +3 -0
- package/templates/src/utils/api/beliefHelpers.ts +12 -36
- package/templates/src/utils/api/menuHelpers.ts +2 -2
- package/templates/src/utils/api.ts +26 -0
- package/templates/src/utils/compositor/TemplateNodes.ts +7 -0
- package/templates/src/utils/compositor/allowInsert.ts +5 -3
- package/templates/src/utils/compositor/nodesHelper.ts +4 -0
- package/templates/src/utils/compositor/processMarkdown.ts +16 -2
- package/templates/src/utils/compositor/reduceNodesClassNames.ts +4 -0
- package/templates/src/utils/compositor/templateMarkdownStyles.ts +13 -13
- package/templates/src/utils/compositor/typeGuards.ts +1 -0
- package/templates/src/utils/customHelpers.ts +38 -0
- package/templates/src/utils/helpers.ts +2 -2
- package/templates/src/utils/layout.ts +65 -144
- package/utils/inject-files.ts +95 -18
- package/templates/src/client/analytics-events.js +0 -207
- package/templates/src/client/belief-events.js +0 -191
- package/templates/src/client/sse.js +0 -613
- package/templates/src/components/codehooks/FeaturedContent.astro +0 -273
- package/templates/src/components/codehooks/FeaturedContentSetup.tsx +0 -738
- package/templates/src/components/compositor/preview/FeaturedContentPreview.tsx +0 -128
- package/templates/src/components/edit/pane/PanePanel_slug.tsx +0 -219
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { useState, useEffect, useMemo } from 'react';
|
|
2
|
+
import type { BeliefNode } from '@/types/compositorTypes';
|
|
3
|
+
import { heldBeliefsScales } from '@/constants/beliefs';
|
|
4
|
+
|
|
5
|
+
interface ActionBuilderBeliefSelectorProps {
|
|
6
|
+
value: string;
|
|
7
|
+
onChange: (value: string) => void;
|
|
8
|
+
beliefs: BeliefNode[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const parseValue = (value: string): [string, string] => {
|
|
12
|
+
try {
|
|
13
|
+
const match = value.match(/\(([^)]+)\)/);
|
|
14
|
+
if (match) {
|
|
15
|
+
const parts = match[1].split(' ').filter(Boolean);
|
|
16
|
+
if (parts.length >= 2) {
|
|
17
|
+
return [parts[0], parts.slice(1).join(' ')];
|
|
18
|
+
}
|
|
19
|
+
if (parts.length === 1) {
|
|
20
|
+
return [parts[0], ''];
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
} catch (e) {
|
|
24
|
+
console.error('Error parsing belief selector value:', e);
|
|
25
|
+
}
|
|
26
|
+
return ['', ''];
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export default function ActionBuilderBeliefSelector({
|
|
30
|
+
value,
|
|
31
|
+
onChange,
|
|
32
|
+
beliefs,
|
|
33
|
+
}: ActionBuilderBeliefSelectorProps) {
|
|
34
|
+
const [selectedBeliefSlug, setSelectedBeliefSlug] = useState('');
|
|
35
|
+
const [selectedValue, setSelectedValue] = useState('');
|
|
36
|
+
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
const [slug, val] = parseValue(value);
|
|
39
|
+
setSelectedBeliefSlug(slug);
|
|
40
|
+
setSelectedValue(val);
|
|
41
|
+
}, [value]);
|
|
42
|
+
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (selectedBeliefSlug && selectedValue) {
|
|
45
|
+
const newValue = `(${selectedBeliefSlug} ${selectedValue})`;
|
|
46
|
+
if (value !== newValue) {
|
|
47
|
+
onChange(newValue);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}, [selectedBeliefSlug, selectedValue, onChange, value]);
|
|
51
|
+
|
|
52
|
+
const selectedBelief = useMemo(
|
|
53
|
+
() => beliefs.find((b) => b.slug === selectedBeliefSlug),
|
|
54
|
+
[selectedBeliefSlug, beliefs]
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const valueOptions = useMemo(() => {
|
|
58
|
+
if (!selectedBelief) return [];
|
|
59
|
+
if (selectedBelief.scale === 'custom' && selectedBelief.customValues) {
|
|
60
|
+
return selectedBelief.customValues.map((v) => ({ label: v, value: v }));
|
|
61
|
+
}
|
|
62
|
+
const scale =
|
|
63
|
+
heldBeliefsScales[selectedBelief.scale as keyof typeof heldBeliefsScales];
|
|
64
|
+
if (scale) {
|
|
65
|
+
return scale.map(({ slug, name }) => ({ label: name, value: slug }));
|
|
66
|
+
}
|
|
67
|
+
return [];
|
|
68
|
+
}, [selectedBelief]);
|
|
69
|
+
|
|
70
|
+
const handleBeliefChange = (slug: string) => {
|
|
71
|
+
setSelectedBeliefSlug(slug);
|
|
72
|
+
setSelectedValue('');
|
|
73
|
+
onChange(slug ? `(${slug} )` : '');
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const commonSelectClass =
|
|
77
|
+
'w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-cyan-700 focus:ring-cyan-700';
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<div className="space-y-4">
|
|
81
|
+
<div className="space-y-2">
|
|
82
|
+
<label className="block text-sm text-gray-700">Belief</label>
|
|
83
|
+
<select
|
|
84
|
+
value={selectedBeliefSlug}
|
|
85
|
+
onChange={(e) => handleBeliefChange(e.target.value)}
|
|
86
|
+
className={commonSelectClass}
|
|
87
|
+
>
|
|
88
|
+
<option value="">Select a belief...</option>
|
|
89
|
+
{beliefs.map((belief) => (
|
|
90
|
+
<option key={belief.id} value={belief.slug}>
|
|
91
|
+
{belief.title} ({belief.scale})
|
|
92
|
+
</option>
|
|
93
|
+
))}
|
|
94
|
+
</select>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
{selectedBelief && (
|
|
98
|
+
<div className="space-y-2">
|
|
99
|
+
<label className="block text-sm text-gray-700">Value</label>
|
|
100
|
+
<select
|
|
101
|
+
value={selectedValue}
|
|
102
|
+
onChange={(e) => setSelectedValue(e.target.value)}
|
|
103
|
+
className={commonSelectClass}
|
|
104
|
+
disabled={!selectedBelief || valueOptions.length === 0}
|
|
105
|
+
>
|
|
106
|
+
<option value="">Select a value...</option>
|
|
107
|
+
{valueOptions.map((option) => (
|
|
108
|
+
<option key={option.value} value={option.value}>
|
|
109
|
+
{option.label}
|
|
110
|
+
</option>
|
|
111
|
+
))}
|
|
112
|
+
</select>
|
|
113
|
+
</div>
|
|
114
|
+
)}
|
|
115
|
+
</div>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { useState, useEffect, useMemo } from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { TractStackAPI } from '@/utils/api';
|
|
3
|
+
import { GOTO_TARGETS, ACTION_COMMANDS, type ActionCommand } from '@/constants';
|
|
4
|
+
import XMarkIcon from '@heroicons/react/24/outline/XMarkIcon';
|
|
3
5
|
import ActionBuilderSlugSelector from './ActionBuilderSlugSelector';
|
|
6
|
+
import ActionBuilderBeliefSelector from './ActionBuilderBeliefSelector';
|
|
7
|
+
import BunnyMomentSelector from '@/components/fields/BunnyMomentSelector';
|
|
4
8
|
import type { FullContentMapItem } from '@/types/tractstack';
|
|
9
|
+
import type { BeliefNode } from '@/types/compositorTypes';
|
|
5
10
|
|
|
6
11
|
interface ActionBuilderFieldProps {
|
|
7
12
|
value: string;
|
|
@@ -12,86 +17,248 @@ interface ActionBuilderFieldProps {
|
|
|
12
17
|
slug?: string;
|
|
13
18
|
}
|
|
14
19
|
|
|
20
|
+
const parseActionLisp = (
|
|
21
|
+
value: string
|
|
22
|
+
): { command: ActionCommand; params: string } => {
|
|
23
|
+
try {
|
|
24
|
+
const match = value.match(/^\s*\(([\w-]+)\s+(.*)\)\s*$/);
|
|
25
|
+
if (match) {
|
|
26
|
+
const cmd = match[1] as ActionCommand;
|
|
27
|
+
const params = match[2];
|
|
28
|
+
if (ACTION_COMMANDS[cmd]) {
|
|
29
|
+
return { command: cmd, params };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
} catch (e) {
|
|
33
|
+
console.error('Error parsing actionLisp value:', e);
|
|
34
|
+
}
|
|
35
|
+
return { command: 'goto', params: '' };
|
|
36
|
+
};
|
|
37
|
+
|
|
15
38
|
export default function ActionBuilderField({
|
|
16
39
|
value,
|
|
17
40
|
onChange,
|
|
18
41
|
contentMap,
|
|
19
|
-
label = '
|
|
42
|
+
label = 'Action',
|
|
20
43
|
error,
|
|
21
44
|
slug,
|
|
22
45
|
}: ActionBuilderFieldProps) {
|
|
46
|
+
const [command, setCommand] = useState<ActionCommand>('goto');
|
|
47
|
+
const [params, setParams] = useState('');
|
|
48
|
+
const [beliefs, setBeliefs] = useState<BeliefNode[]>([]);
|
|
49
|
+
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
const fetchData = async () => {
|
|
52
|
+
try {
|
|
53
|
+
const api = new TractStackAPI();
|
|
54
|
+
const {
|
|
55
|
+
data: { beliefIds },
|
|
56
|
+
} = await api.get('/api/v1/nodes/beliefs');
|
|
57
|
+
if (!beliefIds?.length) return;
|
|
58
|
+
const {
|
|
59
|
+
data: { beliefs },
|
|
60
|
+
} = await api.post('/api/v1/nodes/beliefs', { beliefIds });
|
|
61
|
+
setBeliefs(beliefs || []);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.error('Error fetching beliefs for ActionBuilder:', error);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
fetchData();
|
|
67
|
+
}, []);
|
|
68
|
+
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
if (value) {
|
|
71
|
+
const { command: parsedCommand, params: parsedParams } =
|
|
72
|
+
parseActionLisp(value);
|
|
73
|
+
setCommand(parsedCommand);
|
|
74
|
+
setParams(parsedParams);
|
|
75
|
+
} else {
|
|
76
|
+
setCommand('goto');
|
|
77
|
+
setParams('');
|
|
78
|
+
}
|
|
79
|
+
}, [value]);
|
|
80
|
+
|
|
81
|
+
const handleParamChange = (newParams: string) => {
|
|
82
|
+
setParams(newParams);
|
|
83
|
+
const trimmedParams = newParams.trim();
|
|
84
|
+
|
|
85
|
+
if (!trimmedParams || trimmedParams === '()') {
|
|
86
|
+
onChange('');
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (command === 'identifyAs') {
|
|
91
|
+
const firstSpaceIndex = trimmedParams.indexOf(' ');
|
|
92
|
+
if (firstSpaceIndex === -1) {
|
|
93
|
+
// Handle case with only beliefId and no value
|
|
94
|
+
onChange(`(${command} ${trimmedParams})`);
|
|
95
|
+
} else {
|
|
96
|
+
const beliefId = trimmedParams.substring(0, firstSpaceIndex);
|
|
97
|
+
const value = trimmedParams.substring(firstSpaceIndex + 1);
|
|
98
|
+
const finalValue = value.includes(' ') ? `"${value}"` : value;
|
|
99
|
+
onChange(`(${command} ${beliefId} ${finalValue})`);
|
|
100
|
+
}
|
|
101
|
+
} else {
|
|
102
|
+
// Original behavior for all other commands
|
|
103
|
+
onChange(`(${command} ${trimmedParams})`);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const handleCommandChange = (newCommand: ActionCommand) => {
|
|
108
|
+
setCommand(newCommand);
|
|
109
|
+
setParams('');
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const handleClearAction = () => {
|
|
113
|
+
setCommand('goto');
|
|
114
|
+
setParams('');
|
|
115
|
+
onChange('');
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const hasAction = value && value.trim() !== '';
|
|
119
|
+
|
|
120
|
+
const renderBuilderForCommand = () => {
|
|
121
|
+
switch (command) {
|
|
122
|
+
case 'declare':
|
|
123
|
+
return (
|
|
124
|
+
<ActionBuilderBeliefSelector
|
|
125
|
+
value={params}
|
|
126
|
+
onChange={handleParamChange}
|
|
127
|
+
beliefs={beliefs.filter((b) => b.scale !== 'custom')}
|
|
128
|
+
/>
|
|
129
|
+
);
|
|
130
|
+
case 'identifyAs':
|
|
131
|
+
return (
|
|
132
|
+
<ActionBuilderBeliefSelector
|
|
133
|
+
value={params}
|
|
134
|
+
onChange={handleParamChange}
|
|
135
|
+
beliefs={beliefs.filter((b) => b.scale === 'custom')}
|
|
136
|
+
/>
|
|
137
|
+
);
|
|
138
|
+
case 'bunnyMoment':
|
|
139
|
+
return (
|
|
140
|
+
<BunnyMomentSelector value={params} onChange={handleParamChange} />
|
|
141
|
+
);
|
|
142
|
+
case 'goto':
|
|
143
|
+
default:
|
|
144
|
+
return (
|
|
145
|
+
<GotoBuilder
|
|
146
|
+
value={params}
|
|
147
|
+
onChange={handleParamChange}
|
|
148
|
+
contentMap={contentMap}
|
|
149
|
+
slug={slug}
|
|
150
|
+
/>
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
return (
|
|
156
|
+
<div className="space-y-4">
|
|
157
|
+
{label && (
|
|
158
|
+
<div className="flex items-center justify-between">
|
|
159
|
+
<label className="block text-sm font-bold text-gray-700">
|
|
160
|
+
{label}
|
|
161
|
+
</label>
|
|
162
|
+
{hasAction && (
|
|
163
|
+
<button
|
|
164
|
+
type="button"
|
|
165
|
+
onClick={handleClearAction}
|
|
166
|
+
className="rounded p-1 text-gray-500 hover:bg-gray-100 hover:text-red-600"
|
|
167
|
+
title="Clear action"
|
|
168
|
+
>
|
|
169
|
+
<XMarkIcon className="h-4 w-4" />
|
|
170
|
+
</button>
|
|
171
|
+
)}
|
|
172
|
+
</div>
|
|
173
|
+
)}
|
|
174
|
+
|
|
175
|
+
<div className="space-y-2">
|
|
176
|
+
<label className="block text-sm text-gray-700">Action Type</label>
|
|
177
|
+
<select
|
|
178
|
+
value={command}
|
|
179
|
+
onChange={(e) => handleCommandChange(e.target.value as ActionCommand)}
|
|
180
|
+
className="w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-cyan-700 focus:ring-cyan-700"
|
|
181
|
+
>
|
|
182
|
+
{Object.entries(ACTION_COMMANDS).map(([key, data]) => (
|
|
183
|
+
<option key={key} value={key}>
|
|
184
|
+
{data.name}
|
|
185
|
+
</option>
|
|
186
|
+
))}
|
|
187
|
+
</select>
|
|
188
|
+
<p className="mt-1 text-sm text-gray-500">
|
|
189
|
+
{ACTION_COMMANDS[command]?.description}
|
|
190
|
+
</p>
|
|
191
|
+
</div>
|
|
192
|
+
|
|
193
|
+
<div className="mt-4 border-t border-gray-200 pt-4">
|
|
194
|
+
{renderBuilderForCommand()}
|
|
195
|
+
</div>
|
|
196
|
+
|
|
197
|
+
{error && <p className="text-sm text-red-600">{error}</p>}
|
|
198
|
+
</div>
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function GotoBuilder({
|
|
203
|
+
value,
|
|
204
|
+
onChange,
|
|
205
|
+
contentMap,
|
|
206
|
+
slug,
|
|
207
|
+
}: Omit<ActionBuilderFieldProps, 'label' | 'error' | 'value' | 'onChange'> & {
|
|
208
|
+
value: string;
|
|
209
|
+
onChange: (v: string) => void;
|
|
210
|
+
}) {
|
|
23
211
|
const [selectedTarget, setSelectedTarget] = useState<string>('');
|
|
24
212
|
const [selectedSubcommand, setSelectedSubcommand] = useState<string>('');
|
|
25
213
|
const [param1, setParam1] = useState<string>('');
|
|
26
214
|
const [param2, setParam2] = useState<string>('');
|
|
27
|
-
//const [param3, setParam3] = useState<string>('');
|
|
28
215
|
|
|
29
|
-
// Parse existing value on mount/change
|
|
30
216
|
useEffect(() => {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (parts.length > 2) setParam2(parts[2]);
|
|
43
|
-
//if (parts.length > 3) setParam3(parts[3]);
|
|
44
|
-
}
|
|
217
|
+
try {
|
|
218
|
+
const match = value.match(/\(([^)]+)\)/);
|
|
219
|
+
if (match) {
|
|
220
|
+
const parts = match[1].split(' ').filter(Boolean);
|
|
221
|
+
if (parts.length > 0) {
|
|
222
|
+
setSelectedTarget(parts[0]);
|
|
223
|
+
if (GOTO_TARGETS[parts[0]]?.subcommands) {
|
|
224
|
+
if (parts.length > 1) setSelectedSubcommand(parts[1]);
|
|
225
|
+
} else {
|
|
226
|
+
if (parts.length > 1) setParam1(parts[1]);
|
|
227
|
+
if (parts.length > 2) setParam2(parts[2]);
|
|
45
228
|
}
|
|
46
229
|
}
|
|
47
|
-
}
|
|
48
|
-
|
|
230
|
+
} else {
|
|
231
|
+
setSelectedTarget('');
|
|
232
|
+
setSelectedSubcommand('');
|
|
233
|
+
setParam1('');
|
|
234
|
+
setParam2('');
|
|
49
235
|
}
|
|
236
|
+
} catch (e) {
|
|
237
|
+
console.error('Error parsing goto value:', e);
|
|
50
238
|
}
|
|
51
239
|
}, [value]);
|
|
52
240
|
|
|
53
|
-
const updateValue = (
|
|
54
|
-
target
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
) => {
|
|
60
|
-
let newValue = `(goto (${target}`;
|
|
241
|
+
const updateValue = (target: string, sub = '', p1 = '', p2 = '') => {
|
|
242
|
+
if (!target) {
|
|
243
|
+
onChange('');
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
let newValue = `(${target}`;
|
|
61
247
|
if (GOTO_TARGETS[target]?.subcommands) {
|
|
62
248
|
if (sub) newValue += ` ${sub}`;
|
|
63
249
|
} else {
|
|
64
250
|
if (p1) newValue += ` ${p1}`;
|
|
65
251
|
if (p2) newValue += ` ${p2}`;
|
|
66
|
-
if (p3) newValue += ` ${p3}`;
|
|
67
252
|
}
|
|
68
|
-
newValue += ')
|
|
253
|
+
newValue += ')';
|
|
69
254
|
onChange(newValue);
|
|
70
255
|
};
|
|
71
256
|
|
|
72
|
-
const targetOptions = useMemo(() => {
|
|
73
|
-
return Object.entries(GOTO_TARGETS).map(([key, data]) => ({
|
|
74
|
-
value: key,
|
|
75
|
-
label: data.name,
|
|
76
|
-
}));
|
|
77
|
-
}, []);
|
|
78
|
-
|
|
79
|
-
const subcommandOptions = useMemo(() => {
|
|
80
|
-
if (!selectedTarget || !GOTO_TARGETS[selectedTarget]?.subcommands) {
|
|
81
|
-
return [];
|
|
82
|
-
}
|
|
83
|
-
return GOTO_TARGETS[selectedTarget].subcommands!.map((cmd) => ({
|
|
84
|
-
value: cmd,
|
|
85
|
-
label: cmd,
|
|
86
|
-
}));
|
|
87
|
-
}, [selectedTarget]);
|
|
88
|
-
|
|
89
257
|
const handleTargetChange = (newTarget: string) => {
|
|
90
258
|
setSelectedTarget(newTarget);
|
|
91
259
|
setSelectedSubcommand('');
|
|
92
260
|
setParam1('');
|
|
93
261
|
setParam2('');
|
|
94
|
-
//setParam3('');
|
|
95
262
|
updateValue(newTarget);
|
|
96
263
|
};
|
|
97
264
|
|
|
@@ -100,14 +267,62 @@ export default function ActionBuilderField({
|
|
|
100
267
|
updateValue(selectedTarget, newSubcommand);
|
|
101
268
|
};
|
|
102
269
|
|
|
270
|
+
const handleClearParam = (param: 'param1' | 'param2') => {
|
|
271
|
+
if (param === 'param1') {
|
|
272
|
+
setParam1('');
|
|
273
|
+
setParam2(''); // Cascade unset
|
|
274
|
+
updateValue(selectedTarget, selectedSubcommand, '', '');
|
|
275
|
+
} else {
|
|
276
|
+
setParam2('');
|
|
277
|
+
updateValue(selectedTarget, selectedSubcommand, param1, '');
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
const targetOptions = useMemo(
|
|
282
|
+
() =>
|
|
283
|
+
Object.entries(GOTO_TARGETS).map(([key, data]) => ({
|
|
284
|
+
value: key,
|
|
285
|
+
label: data.name,
|
|
286
|
+
})),
|
|
287
|
+
[]
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
const subcommandOptions = useMemo(() => {
|
|
291
|
+
if (!selectedTarget || !GOTO_TARGETS[selectedTarget]?.subcommands) {
|
|
292
|
+
return [];
|
|
293
|
+
}
|
|
294
|
+
return GOTO_TARGETS[selectedTarget].subcommands!.map((cmd) => ({
|
|
295
|
+
value: cmd,
|
|
296
|
+
label: cmd,
|
|
297
|
+
}));
|
|
298
|
+
}, [selectedTarget]);
|
|
299
|
+
|
|
103
300
|
const renderParamInput = (paramType: 'param1' | 'param2') => {
|
|
104
301
|
const isParam1 = paramType === 'param1';
|
|
105
302
|
const currentValue = isParam1 ? param1 : param2;
|
|
303
|
+
const hasValue = currentValue && currentValue.trim() !== '';
|
|
304
|
+
|
|
305
|
+
const inputWithClear = (inputEl: JSX.Element) => (
|
|
306
|
+
<div className="flex items-center gap-2">
|
|
307
|
+
<div className="flex-grow">{inputEl}</div>
|
|
308
|
+
{hasValue && (
|
|
309
|
+
<button
|
|
310
|
+
type="button"
|
|
311
|
+
onClick={() => handleClearParam(paramType)}
|
|
312
|
+
className="flex-shrink-0 rounded p-1 text-gray-500 hover:bg-gray-100 hover:text-gray-800"
|
|
313
|
+
title="Clear selection"
|
|
314
|
+
>
|
|
315
|
+
<XMarkIcon className="h-4 w-4" />
|
|
316
|
+
</button>
|
|
317
|
+
)}
|
|
318
|
+
</div>
|
|
319
|
+
);
|
|
106
320
|
|
|
107
321
|
switch (selectedTarget) {
|
|
108
322
|
case 'context':
|
|
109
323
|
return (
|
|
110
|
-
isParam1 &&
|
|
324
|
+
isParam1 &&
|
|
325
|
+
inputWithClear(
|
|
111
326
|
<ActionBuilderSlugSelector
|
|
112
327
|
type="context"
|
|
113
328
|
value={currentValue}
|
|
@@ -123,7 +338,8 @@ export default function ActionBuilderField({
|
|
|
123
338
|
|
|
124
339
|
case 'storyFragment':
|
|
125
340
|
return (
|
|
126
|
-
isParam1 &&
|
|
341
|
+
isParam1 &&
|
|
342
|
+
inputWithClear(
|
|
127
343
|
<ActionBuilderSlugSelector
|
|
128
344
|
type="storyFragment"
|
|
129
345
|
value={currentValue}
|
|
@@ -136,16 +352,15 @@ export default function ActionBuilderField({
|
|
|
136
352
|
/>
|
|
137
353
|
)
|
|
138
354
|
);
|
|
139
|
-
|
|
140
355
|
case 'storyFragmentPane':
|
|
141
356
|
if (isParam1) {
|
|
142
|
-
return (
|
|
357
|
+
return inputWithClear(
|
|
143
358
|
<ActionBuilderSlugSelector
|
|
144
359
|
type="storyFragment"
|
|
145
360
|
value={currentValue}
|
|
146
361
|
onSelect={(newValue) => {
|
|
147
362
|
setParam1(newValue);
|
|
148
|
-
setParam2('');
|
|
363
|
+
setParam2('');
|
|
149
364
|
updateValue(selectedTarget, '', newValue, '');
|
|
150
365
|
}}
|
|
151
366
|
label="Select Story Fragment"
|
|
@@ -155,7 +370,8 @@ export default function ActionBuilderField({
|
|
|
155
370
|
}
|
|
156
371
|
return (
|
|
157
372
|
!isParam1 &&
|
|
158
|
-
param1 &&
|
|
373
|
+
param1 &&
|
|
374
|
+
inputWithClear(
|
|
159
375
|
<ActionBuilderSlugSelector
|
|
160
376
|
type="pane"
|
|
161
377
|
value={currentValue}
|
|
@@ -169,17 +385,16 @@ export default function ActionBuilderField({
|
|
|
169
385
|
/>
|
|
170
386
|
)
|
|
171
387
|
);
|
|
172
|
-
|
|
173
388
|
case 'bunny':
|
|
174
389
|
if (isParam1 && slug) {
|
|
175
390
|
if (!currentValue) setParam1(slug);
|
|
176
|
-
return (
|
|
391
|
+
return inputWithClear(
|
|
177
392
|
<ActionBuilderSlugSelector
|
|
178
393
|
type="storyFragment"
|
|
179
394
|
value={currentValue || slug}
|
|
180
395
|
onSelect={(newValue) => {
|
|
181
396
|
setParam1(newValue);
|
|
182
|
-
setParam2('');
|
|
397
|
+
setParam2('');
|
|
183
398
|
updateValue(selectedTarget, '', newValue, '');
|
|
184
399
|
}}
|
|
185
400
|
label="Select Story Fragment"
|
|
@@ -188,47 +403,57 @@ export default function ActionBuilderField({
|
|
|
188
403
|
);
|
|
189
404
|
}
|
|
190
405
|
return null;
|
|
191
|
-
|
|
192
406
|
case 'url':
|
|
193
407
|
return (
|
|
194
408
|
isParam1 && (
|
|
195
|
-
<div className="
|
|
196
|
-
<
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
409
|
+
<div className="flex items-center gap-2">
|
|
410
|
+
<div className="flex-grow space-y-2">
|
|
411
|
+
<label className="block text-sm font-bold text-gray-700">
|
|
412
|
+
External URL
|
|
413
|
+
</label>
|
|
414
|
+
<input
|
|
415
|
+
type="text"
|
|
416
|
+
value={currentValue}
|
|
417
|
+
onChange={(e) => {
|
|
418
|
+
const newValue = e.target.value;
|
|
419
|
+
setParam1(newValue);
|
|
420
|
+
}}
|
|
421
|
+
onBlur={(e) => {
|
|
422
|
+
updateValue(selectedTarget, '', e.target.value);
|
|
423
|
+
}}
|
|
424
|
+
placeholder="https://..."
|
|
425
|
+
className="w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-cyan-700 focus:ring-cyan-700"
|
|
426
|
+
/>
|
|
427
|
+
</div>
|
|
428
|
+
{hasValue && (
|
|
429
|
+
<button
|
|
430
|
+
type="button"
|
|
431
|
+
onClick={() => handleClearParam('param1')}
|
|
432
|
+
className="mt-6 flex-shrink-0 rounded p-1 text-gray-500 hover:bg-gray-100 hover:text-gray-800"
|
|
433
|
+
title="Clear URL"
|
|
434
|
+
>
|
|
435
|
+
<XMarkIcon className="h-4 w-4" />
|
|
436
|
+
</button>
|
|
437
|
+
)}
|
|
210
438
|
</div>
|
|
211
439
|
)
|
|
212
440
|
);
|
|
213
|
-
|
|
214
441
|
default:
|
|
215
442
|
return null;
|
|
216
443
|
}
|
|
217
444
|
};
|
|
218
445
|
|
|
446
|
+
const commonSelectClass =
|
|
447
|
+
'w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-cyan-700 focus:ring-cyan-700';
|
|
448
|
+
|
|
219
449
|
return (
|
|
220
450
|
<div className="space-y-4">
|
|
221
|
-
{label && (
|
|
222
|
-
<label className="block text-sm font-bold text-gray-700">{label}</label>
|
|
223
|
-
)}
|
|
224
|
-
|
|
225
|
-
{/* Target Selection */}
|
|
226
451
|
<div className="space-y-2">
|
|
227
452
|
<label className="block text-sm text-gray-700">Navigation Target</label>
|
|
228
453
|
<select
|
|
229
454
|
value={selectedTarget}
|
|
230
455
|
onChange={(e) => handleTargetChange(e.target.value)}
|
|
231
|
-
className=
|
|
456
|
+
className={commonSelectClass}
|
|
232
457
|
>
|
|
233
458
|
<option value="">Select a target...</option>
|
|
234
459
|
{targetOptions.map((option) => (
|
|
@@ -237,7 +462,6 @@ export default function ActionBuilderField({
|
|
|
237
462
|
</option>
|
|
238
463
|
))}
|
|
239
464
|
</select>
|
|
240
|
-
|
|
241
465
|
{GOTO_TARGETS[selectedTarget] && (
|
|
242
466
|
<p className="mt-1 text-sm text-gray-500">
|
|
243
467
|
{GOTO_TARGETS[selectedTarget].description}
|
|
@@ -245,14 +469,13 @@ export default function ActionBuilderField({
|
|
|
245
469
|
)}
|
|
246
470
|
</div>
|
|
247
471
|
|
|
248
|
-
{/* Subcommand Selection */}
|
|
249
472
|
{selectedTarget && GOTO_TARGETS[selectedTarget]?.subcommands && (
|
|
250
473
|
<div className="space-y-2">
|
|
251
474
|
<label className="block text-sm text-gray-700">Section</label>
|
|
252
475
|
<select
|
|
253
476
|
value={selectedSubcommand}
|
|
254
477
|
onChange={(e) => handleSubcommandChange(e.target.value)}
|
|
255
|
-
className=
|
|
478
|
+
className={commonSelectClass}
|
|
256
479
|
>
|
|
257
480
|
<option value="">Select a section...</option>
|
|
258
481
|
{subcommandOptions.map((option) => (
|
|
@@ -264,19 +487,15 @@ export default function ActionBuilderField({
|
|
|
264
487
|
</div>
|
|
265
488
|
)}
|
|
266
489
|
|
|
267
|
-
{/* Parameter 1 */}
|
|
268
490
|
{selectedTarget &&
|
|
269
491
|
GOTO_TARGETS[selectedTarget]?.requiresParam &&
|
|
270
492
|
!GOTO_TARGETS[selectedTarget].subcommands &&
|
|
271
493
|
renderParamInput('param1')}
|
|
272
494
|
|
|
273
|
-
{/* Parameter 2 */}
|
|
274
495
|
{selectedTarget &&
|
|
275
496
|
GOTO_TARGETS[selectedTarget]?.requiresSecondParam &&
|
|
276
497
|
param1 &&
|
|
277
498
|
renderParamInput('param2')}
|
|
278
|
-
|
|
279
|
-
{error && <p className="text-sm text-red-600">{error}</p>}
|
|
280
499
|
</div>
|
|
281
500
|
);
|
|
282
501
|
}
|