astro-tractstack 2.0.0-rc.8 → 2.0.0
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 +35 -11
- 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 +1 -0
- 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_Content.tsx +6 -0
- package/templates/src/components/storykeep/StoryKeepBackdrop.astro +87 -0
- package/templates/src/components/storykeep/controls/content/BeliefForm.tsx +37 -33
- package/templates/src/components/storykeep/controls/content/MenuForm.tsx +55 -7
- package/templates/src/components/storykeep/controls/content/ResourceForm.tsx +17 -2
- 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/tenant/RegistrationForm.tsx +1 -1
- 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
|
@@ -5,10 +5,12 @@ import {
|
|
|
5
5
|
type SetStateAction,
|
|
6
6
|
type ChangeEvent,
|
|
7
7
|
} from 'react';
|
|
8
|
+
import { useStore } from '@nanostores/react';
|
|
8
9
|
import ExclamationTriangleIcon from '@heroicons/react/24/outline/ExclamationTriangleIcon';
|
|
9
10
|
import CheckIcon from '@heroicons/react/24/outline/CheckIcon';
|
|
11
|
+
import { fullContentMapStore } from '@/stores/storykeep';
|
|
10
12
|
import { getCtx } from '@/stores/nodes';
|
|
11
|
-
import { cloneDeep } from '@/utils/helpers';
|
|
13
|
+
import { cloneDeep, findUniqueSlug, titleToSlug } from '@/utils/helpers';
|
|
12
14
|
import { PaneConfigMode, type PaneNode } from '@/types/compositorTypes';
|
|
13
15
|
|
|
14
16
|
interface PaneTitlePanelProps {
|
|
@@ -18,36 +20,141 @@ interface PaneTitlePanelProps {
|
|
|
18
20
|
|
|
19
21
|
const PaneTitlePanel = ({ nodeId, setMode }: PaneTitlePanelProps) => {
|
|
20
22
|
const [title, setTitle] = useState('');
|
|
21
|
-
const [
|
|
22
|
-
const [
|
|
23
|
-
const [
|
|
23
|
+
const [slug, setSlug] = useState('');
|
|
24
|
+
const [isValidTitle, setIsValidTitle] = useState(false);
|
|
25
|
+
const [isValidSlug, setIsValidSlug] = useState(false);
|
|
26
|
+
const [warningTitle, setWarningTitle] = useState(false);
|
|
27
|
+
const [warningSlug, setWarningSlug] = useState(false);
|
|
28
|
+
const [titleCharCount, setTitleCharCount] = useState(0);
|
|
29
|
+
const [slugCharCount, setSlugCharCount] = useState(0);
|
|
30
|
+
const [slugValidationError, setSlugValidationError] = useState<string | null>(
|
|
31
|
+
null
|
|
32
|
+
);
|
|
33
|
+
const [canSaveSlug, setCanSaveSlug] = useState(false);
|
|
24
34
|
|
|
35
|
+
const $contentMap = useStore(fullContentMapStore);
|
|
25
36
|
const ctx = getCtx();
|
|
26
37
|
const allNodes = ctx.allNodes.get();
|
|
27
38
|
const paneNode = allNodes.get(nodeId) as PaneNode;
|
|
28
39
|
if (!paneNode) return null;
|
|
29
40
|
|
|
41
|
+
const existingSlugs = $contentMap
|
|
42
|
+
.filter(
|
|
43
|
+
(item) =>
|
|
44
|
+
['Pane', 'StoryFragment'].includes(item.type) && item.id !== nodeId
|
|
45
|
+
)
|
|
46
|
+
.map((item) => item.slug);
|
|
47
|
+
|
|
30
48
|
useEffect(() => {
|
|
31
49
|
setTitle(paneNode.title);
|
|
32
|
-
|
|
33
|
-
|
|
50
|
+
setSlug(paneNode.slug);
|
|
51
|
+
setTitleCharCount(paneNode.title.length);
|
|
52
|
+
setSlugCharCount(paneNode.slug.length);
|
|
53
|
+
checkSlugLiveValidity(paneNode.slug);
|
|
54
|
+
}, [paneNode.title, paneNode.slug]);
|
|
34
55
|
|
|
35
56
|
const handleTitleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
|
36
57
|
const newTitle = e.target.value;
|
|
37
58
|
if (newTitle.length <= 50) {
|
|
38
|
-
// Prevent more than 70 chars
|
|
39
59
|
setTitle(newTitle);
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
60
|
+
setTitleCharCount(newTitle.length);
|
|
61
|
+
setIsValidTitle(newTitle.length >= 5 && newTitle.length <= 35);
|
|
62
|
+
setWarningTitle(newTitle.length > 35 && newTitle.length <= 50);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const handleSlugChange = (e: ChangeEvent<HTMLInputElement>) => {
|
|
67
|
+
const newSlug = e.target.value.toLowerCase().replace(/[^a-z0-9-]/g, '');
|
|
68
|
+
|
|
69
|
+
if (newSlug.length <= 50) {
|
|
70
|
+
setSlug(newSlug);
|
|
71
|
+
checkSlugLiveValidity(newSlug);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const checkSlugLiveValidity = (value: string) => {
|
|
76
|
+
const length = value.length;
|
|
77
|
+
setSlugCharCount(length);
|
|
78
|
+
|
|
79
|
+
// Basic format check for allowed characters
|
|
80
|
+
if (!/^[a-z0-9-]*$/.test(value)) {
|
|
81
|
+
setSlugValidationError(
|
|
82
|
+
'Only lowercase letters, numbers, and hyphens allowed'
|
|
83
|
+
);
|
|
84
|
+
setIsValidSlug(false);
|
|
85
|
+
setCanSaveSlug(false);
|
|
86
|
+
return false;
|
|
43
87
|
}
|
|
88
|
+
|
|
89
|
+
// Length checks
|
|
90
|
+
setIsValidSlug(length >= 3 && length <= 40);
|
|
91
|
+
setWarningSlug(length > 40 && length <= 50);
|
|
92
|
+
setSlugValidationError(null);
|
|
93
|
+
|
|
94
|
+
// Check if we can save
|
|
95
|
+
if (length >= 3) {
|
|
96
|
+
const saveValidation = checkSlugSaveValidity(value);
|
|
97
|
+
setCanSaveSlug(saveValidation.isValid);
|
|
98
|
+
if (!saveValidation.isValid) {
|
|
99
|
+
setSlugValidationError(saveValidation.error || null);
|
|
100
|
+
}
|
|
101
|
+
} else {
|
|
102
|
+
setCanSaveSlug(false);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return true;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const checkSlugSaveValidity = (
|
|
109
|
+
value: string
|
|
110
|
+
): { isValid: boolean; error?: string } => {
|
|
111
|
+
// Strict pattern that prevents leading/trailing hyphens and multiple consecutive hyphens
|
|
112
|
+
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(value)) {
|
|
113
|
+
return {
|
|
114
|
+
isValid: false,
|
|
115
|
+
error:
|
|
116
|
+
'Slug must start and end with letters or numbers, and no consecutive hyphens',
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Check duplicates
|
|
121
|
+
if (existingSlugs.includes(value)) {
|
|
122
|
+
return {
|
|
123
|
+
isValid: false,
|
|
124
|
+
error: 'This slug is already in use',
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return { isValid: true };
|
|
44
129
|
};
|
|
45
130
|
|
|
46
131
|
const handleTitleBlur = () => {
|
|
47
132
|
if (title.length >= 5) {
|
|
48
|
-
//
|
|
133
|
+
// Auto-generate slug if slug is empty or still system-generated
|
|
134
|
+
let updatedSlug = slug;
|
|
135
|
+
if (!slug || slug === paneNode.slug) {
|
|
136
|
+
const generatedSlug = titleToSlug(title);
|
|
137
|
+
const uniqueSlug = findUniqueSlug(generatedSlug, existingSlugs);
|
|
138
|
+
updatedSlug = uniqueSlug;
|
|
139
|
+
setSlug(uniqueSlug);
|
|
140
|
+
checkSlugLiveValidity(uniqueSlug);
|
|
141
|
+
}
|
|
142
|
+
|
|
49
143
|
const ctx = getCtx();
|
|
50
|
-
const updatedNode = {
|
|
144
|
+
const updatedNode = {
|
|
145
|
+
...cloneDeep(paneNode),
|
|
146
|
+
title,
|
|
147
|
+
slug: updatedSlug,
|
|
148
|
+
isChanged: true,
|
|
149
|
+
};
|
|
150
|
+
ctx.modifyNodes([updatedNode]);
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const handleSlugBlur = () => {
|
|
155
|
+
if (canSaveSlug) {
|
|
156
|
+
const ctx = getCtx();
|
|
157
|
+
const updatedNode = { ...cloneDeep(paneNode), slug, isChanged: true };
|
|
51
158
|
ctx.modifyNodes([updatedNode]);
|
|
52
159
|
}
|
|
53
160
|
};
|
|
@@ -56,7 +163,7 @@ const PaneTitlePanel = ({ nodeId, setMode }: PaneTitlePanelProps) => {
|
|
|
56
163
|
<div className="group mb-4 w-full rounded-b-md bg-white px-1.5 py-6 shadow-inner">
|
|
57
164
|
<div className="px-3.5">
|
|
58
165
|
<div className="mb-4 flex justify-between">
|
|
59
|
-
<h3 className="text-lg font-bold">Pane Title</h3>
|
|
166
|
+
<h3 className="text-lg font-bold">Pane Title & Slug</h3>
|
|
60
167
|
<button
|
|
61
168
|
onClick={() => setMode(PaneConfigMode.DEFAULT)}
|
|
62
169
|
className="text-myblue hover:text-black"
|
|
@@ -65,73 +172,133 @@ const PaneTitlePanel = ({ nodeId, setMode }: PaneTitlePanelProps) => {
|
|
|
65
172
|
</button>
|
|
66
173
|
</div>
|
|
67
174
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
<ExclamationTriangleIcon className="h-5 w-5 text-red-500" />
|
|
93
|
-
) : isValid ? (
|
|
94
|
-
<CheckIcon className="h-5 w-5 text-green-500" />
|
|
95
|
-
) : warning ? (
|
|
96
|
-
<ExclamationTriangleIcon className="h-5 w-5 text-yellow-500" />
|
|
97
|
-
) : null}
|
|
98
|
-
<span
|
|
99
|
-
className={`text-sm ${
|
|
100
|
-
charCount < 5
|
|
101
|
-
? 'text-red-500'
|
|
102
|
-
: isValid
|
|
103
|
-
? 'text-green-500'
|
|
104
|
-
: warning
|
|
105
|
-
? 'text-yellow-500'
|
|
106
|
-
: 'text-gray-500'
|
|
175
|
+
{/* Title Input */}
|
|
176
|
+
<div className="mb-6">
|
|
177
|
+
<label className="mb-2 block text-sm font-bold text-gray-700">
|
|
178
|
+
Title
|
|
179
|
+
</label>
|
|
180
|
+
<div className="relative">
|
|
181
|
+
<input
|
|
182
|
+
type="text"
|
|
183
|
+
value={title}
|
|
184
|
+
onChange={handleTitleChange}
|
|
185
|
+
onBlur={handleTitleBlur}
|
|
186
|
+
onKeyDown={(e) => {
|
|
187
|
+
if (e.key === 'Enter') {
|
|
188
|
+
e.currentTarget.blur();
|
|
189
|
+
}
|
|
190
|
+
}}
|
|
191
|
+
className={`w-full rounded-md border px-2 py-1 pr-16 ${
|
|
192
|
+
titleCharCount < 5
|
|
193
|
+
? 'border-red-500 bg-red-50'
|
|
194
|
+
: isValidTitle
|
|
195
|
+
? 'border-green-500 bg-green-50'
|
|
196
|
+
: warningTitle
|
|
197
|
+
? 'border-yellow-500 bg-yellow-50'
|
|
198
|
+
: 'border-gray-300'
|
|
107
199
|
}`}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
200
|
+
placeholder="Enter pane title (5-35 characters recommended)"
|
|
201
|
+
/>
|
|
202
|
+
<div className="absolute right-2 top-1/2 flex -translate-y-1/2 items-center gap-2">
|
|
203
|
+
{titleCharCount < 5 ? (
|
|
204
|
+
<ExclamationTriangleIcon className="h-5 w-5 text-red-500" />
|
|
205
|
+
) : isValidTitle ? (
|
|
206
|
+
<CheckIcon className="h-5 w-5 text-green-500" />
|
|
207
|
+
) : warningTitle ? (
|
|
208
|
+
<ExclamationTriangleIcon className="h-5 w-5 text-yellow-500" />
|
|
209
|
+
) : null}
|
|
210
|
+
<span
|
|
211
|
+
className={`text-sm ${
|
|
212
|
+
titleCharCount < 5
|
|
213
|
+
? 'text-red-500'
|
|
214
|
+
: isValidTitle
|
|
215
|
+
? 'text-green-500'
|
|
216
|
+
: warningTitle
|
|
217
|
+
? 'text-yellow-500'
|
|
218
|
+
: 'text-gray-500'
|
|
219
|
+
}`}
|
|
220
|
+
>
|
|
221
|
+
{titleCharCount}/50
|
|
222
|
+
</span>
|
|
223
|
+
</div>
|
|
111
224
|
</div>
|
|
112
225
|
</div>
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
226
|
+
|
|
227
|
+
{/* Slug Input */}
|
|
228
|
+
<div className="mb-4">
|
|
229
|
+
<label className="mb-2 block text-sm font-bold text-gray-700">
|
|
230
|
+
Slug (URL)
|
|
231
|
+
</label>
|
|
232
|
+
<div className="relative">
|
|
233
|
+
<input
|
|
234
|
+
type="text"
|
|
235
|
+
value={slug}
|
|
236
|
+
onChange={handleSlugChange}
|
|
237
|
+
onBlur={handleSlugBlur}
|
|
238
|
+
onKeyDown={(e) => {
|
|
239
|
+
if (e.key === 'Enter') {
|
|
240
|
+
e.currentTarget.blur();
|
|
241
|
+
}
|
|
242
|
+
}}
|
|
243
|
+
className={`w-full rounded-md border px-2 py-1 pr-16 ${
|
|
244
|
+
slugValidationError || slugCharCount < 3
|
|
245
|
+
? 'border-red-500 bg-red-50'
|
|
246
|
+
: isValidSlug && canSaveSlug
|
|
247
|
+
? 'border-green-500 bg-green-50'
|
|
248
|
+
: warningSlug
|
|
249
|
+
? 'border-yellow-500 bg-yellow-50'
|
|
250
|
+
: 'border-gray-300'
|
|
251
|
+
}`}
|
|
252
|
+
placeholder="Enter pane slug (3-40 characters recommended)"
|
|
253
|
+
/>
|
|
254
|
+
<div className="absolute right-2 top-1/2 flex -translate-y-1/2 items-center gap-2">
|
|
255
|
+
{slugValidationError || slugCharCount < 3 ? (
|
|
256
|
+
<ExclamationTriangleIcon className="h-5 w-5 text-red-500" />
|
|
257
|
+
) : isValidSlug && canSaveSlug ? (
|
|
258
|
+
<CheckIcon className="h-5 w-5 text-green-500" />
|
|
259
|
+
) : warningSlug ? (
|
|
260
|
+
<ExclamationTriangleIcon className="h-5 w-5 text-yellow-500" />
|
|
261
|
+
) : null}
|
|
262
|
+
<span
|
|
263
|
+
className={`text-sm ${
|
|
264
|
+
slugValidationError || slugCharCount < 3
|
|
265
|
+
? 'text-red-500'
|
|
266
|
+
: isValidSlug && canSaveSlug
|
|
267
|
+
? 'text-green-500'
|
|
268
|
+
: warningSlug
|
|
269
|
+
? 'text-yellow-500'
|
|
270
|
+
: 'text-gray-500'
|
|
271
|
+
}`}
|
|
272
|
+
>
|
|
273
|
+
{slugCharCount}/50
|
|
127
274
|
</span>
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
275
|
+
</div>
|
|
276
|
+
</div>
|
|
277
|
+
{slugValidationError && (
|
|
278
|
+
<div className="mt-2 text-sm text-red-600">
|
|
279
|
+
<ExclamationTriangleIcon className="mr-1 inline h-4 w-4" />
|
|
280
|
+
{slugValidationError}
|
|
281
|
+
</div>
|
|
282
|
+
)}
|
|
283
|
+
</div>
|
|
284
|
+
|
|
285
|
+
{/* Help Text */}
|
|
286
|
+
<div className="space-y-4 text-sm text-gray-600">
|
|
287
|
+
<div>
|
|
288
|
+
<h4 className="font-bold">Title Guidelines:</h4>
|
|
289
|
+
<ul className="ml-4 mt-1 list-disc">
|
|
290
|
+
<li>5-35 characters recommended for optimal display</li>
|
|
291
|
+
<li>Clear, descriptive title for the pane content</li>
|
|
292
|
+
</ul>
|
|
293
|
+
</div>
|
|
294
|
+
<div>
|
|
295
|
+
<h4 className="font-bold">Slug Guidelines:</h4>
|
|
296
|
+
<ul className="ml-4 mt-1 list-disc">
|
|
297
|
+
<li>Used for analytics tracking</li>
|
|
298
|
+
<li>Only lowercase letters, numbers, and hyphens</li>
|
|
299
|
+
<li>Must start and end with letter or number</li>
|
|
300
|
+
<li>No consecutive hyphens</li>
|
|
301
|
+
</ul>
|
|
135
302
|
</div>
|
|
136
303
|
</div>
|
|
137
304
|
</div>
|
|
@@ -155,25 +155,23 @@ const StyleBreakPanel = ({ node, parentNode, config }: BasePanelProps) => {
|
|
|
155
155
|
</div>
|
|
156
156
|
|
|
157
157
|
<div className="space-y-4">
|
|
158
|
-
<
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
/>
|
|
176
|
-
</div>
|
|
158
|
+
<ColorPickerCombo
|
|
159
|
+
title="Shape Color"
|
|
160
|
+
defaultColor={settings.svgFill}
|
|
161
|
+
onColorChange={(color: string) =>
|
|
162
|
+
setSettings((prev) => ({ ...prev, svgFill: color }))
|
|
163
|
+
}
|
|
164
|
+
config={config!}
|
|
165
|
+
/>
|
|
166
|
+
<ColorPickerCombo
|
|
167
|
+
title="Background Color"
|
|
168
|
+
defaultColor={settings.bgColor}
|
|
169
|
+
onColorChange={(color: string) =>
|
|
170
|
+
setSettings((prev) => ({ ...prev, bgColor: color }))
|
|
171
|
+
}
|
|
172
|
+
config={config!}
|
|
173
|
+
allowNull={true}
|
|
174
|
+
/>
|
|
177
175
|
</div>
|
|
178
176
|
</div>
|
|
179
177
|
);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useState, useCallback, useEffect, useMemo, useRef } from 'react';
|
|
2
|
-
import { Combobox, Switch } from '@ark-ui/react';
|
|
2
|
+
import { Combobox, Switch, Portal } from '@ark-ui/react';
|
|
3
3
|
import { createListCollection } from '@ark-ui/react/collection';
|
|
4
4
|
import XMarkIcon from '@heroicons/react/24/outline/XMarkIcon';
|
|
5
5
|
import PlusIcon from '@heroicons/react/24/outline/PlusIcon';
|
|
@@ -8,7 +8,7 @@ import ExclamationTriangleIcon from '@heroicons/react/24/outline/ExclamationTria
|
|
|
8
8
|
import CheckIcon from '@heroicons/react/24/outline/CheckIcon';
|
|
9
9
|
import { settingsPanelStore } from '@/stores/storykeep';
|
|
10
10
|
import { getCtx } from '@/stores/nodes';
|
|
11
|
-
import { cloneDeep } from '@/utils/helpers';
|
|
11
|
+
import { cloneDeep, useDropdownDirection } from '@/utils/helpers';
|
|
12
12
|
import { isPaneNode } from '@/utils/compositor/typeGuards';
|
|
13
13
|
import type { PaneNode, BasePanelProps } from '@/types/compositorTypes';
|
|
14
14
|
|
|
@@ -30,7 +30,8 @@ const StyleCodeHookPanel = ({
|
|
|
30
30
|
|
|
31
31
|
const [localTarget, setLocalTarget] = useState(node.codeHookTarget || '');
|
|
32
32
|
const [query, setQuery] = useState('');
|
|
33
|
-
const
|
|
33
|
+
const comboboxRef = useRef<HTMLDivElement>(null);
|
|
34
|
+
const { openAbove } = useDropdownDirection(comboboxRef);
|
|
34
35
|
|
|
35
36
|
// Parse the nested options from JSON string
|
|
36
37
|
const [localOptions, setLocalOptions] = useState<OptionState[]>(() => {
|
|
@@ -320,42 +321,52 @@ const StyleCodeHookPanel = ({
|
|
|
320
321
|
loopFocus={true}
|
|
321
322
|
openOnKeyPress={true}
|
|
322
323
|
composite={true}
|
|
324
|
+
positioning={{
|
|
325
|
+
placement: openAbove ? 'top' : 'bottom',
|
|
326
|
+
gutter: 4,
|
|
327
|
+
sameWidth: true,
|
|
328
|
+
}}
|
|
323
329
|
>
|
|
324
|
-
<
|
|
325
|
-
<
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
/>
|
|
331
|
-
<Combobox.Trigger className="absolute inset-y-0 right-0 flex items-center pr-2">
|
|
332
|
-
<ChevronUpDownIcon
|
|
333
|
-
className="text-mydarkgrey h-5 w-5"
|
|
334
|
-
aria-hidden="true"
|
|
330
|
+
<Combobox.Control ref={comboboxRef}>
|
|
331
|
+
<div className="relative">
|
|
332
|
+
<Combobox.Input
|
|
333
|
+
className={commonInputClass}
|
|
334
|
+
placeholder="Select a code hook..."
|
|
335
|
+
onBlur={handleBlur}
|
|
335
336
|
/>
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
className="
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
337
|
+
<Combobox.Trigger className="absolute inset-y-0 right-0 flex items-center pr-2">
|
|
338
|
+
<ChevronUpDownIcon
|
|
339
|
+
className="text-mydarkgrey h-5 w-5"
|
|
340
|
+
aria-hidden="true"
|
|
341
|
+
/>
|
|
342
|
+
</Combobox.Trigger>
|
|
343
|
+
</div>
|
|
344
|
+
</Combobox.Control>
|
|
345
|
+
|
|
346
|
+
<Portal>
|
|
347
|
+
<Combobox.Positioner style={{ zIndex: 1002 }}>
|
|
348
|
+
<Combobox.Content className="z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
|
|
349
|
+
{collection.items.length === 0 ? (
|
|
350
|
+
<div className="text-mydarkgrey relative cursor-default select-none px-4 py-2">
|
|
351
|
+
Nothing found.
|
|
352
|
+
</div>
|
|
353
|
+
) : (
|
|
354
|
+
collection.items.map((hook) => (
|
|
355
|
+
<Combobox.Item
|
|
356
|
+
key={hook}
|
|
357
|
+
item={hook}
|
|
358
|
+
className="codehook-item relative cursor-default select-none py-2 pl-10 pr-4"
|
|
359
|
+
>
|
|
360
|
+
<span className="block truncate">{hook}</span>
|
|
361
|
+
<span className="codehook-indicator absolute inset-y-0 left-0 flex items-center pl-3 text-cyan-600">
|
|
362
|
+
<CheckIcon className="h-5 w-5" aria-hidden="true" />
|
|
363
|
+
</span>
|
|
364
|
+
</Combobox.Item>
|
|
365
|
+
))
|
|
366
|
+
)}
|
|
367
|
+
</Combobox.Content>
|
|
368
|
+
</Combobox.Positioner>
|
|
369
|
+
</Portal>
|
|
359
370
|
</Combobox.Root>
|
|
360
371
|
</div>
|
|
361
372
|
{!isValidCodeHook && localTarget && (
|