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
|
@@ -2,7 +2,8 @@ import { Menu } from '@ark-ui/react';
|
|
|
2
2
|
import { Portal } from '@ark-ui/react/portal';
|
|
3
3
|
import ChevronDownIcon from '@heroicons/react/20/solid/ChevronDownIcon';
|
|
4
4
|
import { lispLexer } from '@/utils/actions/lispLexer';
|
|
5
|
-
import { preParseAction } from '@/utils/actions
|
|
5
|
+
import { preParseAction } from '@/utils/actions/preParse_Action';
|
|
6
|
+
import type { LispToken } from '@/types/compositorTypes';
|
|
6
7
|
|
|
7
8
|
// CSS to style the menu items with hover and selection states
|
|
8
9
|
const menuStyles = `
|
|
@@ -46,9 +47,10 @@ interface MenuDatum {
|
|
|
46
47
|
optionsPayload: MenuLink[];
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
interface
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
interface ProcessedMenuLinkDatum extends MenuLink {
|
|
51
|
+
renderAs: 'a' | 'button' | 'span';
|
|
52
|
+
href?: string;
|
|
53
|
+
htmxVals?: string;
|
|
52
54
|
}
|
|
53
55
|
|
|
54
56
|
interface MenuProps {
|
|
@@ -62,7 +64,75 @@ const MenuComponent = (props: MenuProps) => {
|
|
|
62
64
|
const { payload, slug, isContext, brandConfig } = props;
|
|
63
65
|
const thisPayload = payload.optionsPayload;
|
|
64
66
|
|
|
65
|
-
|
|
67
|
+
function processMenuLink(e: MenuLink): ProcessedMenuLinkDatum {
|
|
68
|
+
const item = { ...e } as ProcessedMenuLinkDatum;
|
|
69
|
+
const actionLisp = item.actionLisp?.trim();
|
|
70
|
+
|
|
71
|
+
if (!actionLisp) {
|
|
72
|
+
item.renderAs = 'span';
|
|
73
|
+
return item;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
if (actionLisp.startsWith('(goto')) {
|
|
78
|
+
const tokens = lispLexer(actionLisp);
|
|
79
|
+
const to = preParseAction(tokens, slug, isContext, brandConfig);
|
|
80
|
+
item.renderAs = 'a';
|
|
81
|
+
item.href = to || '#';
|
|
82
|
+
return item;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const [lispTokens] = lispLexer(actionLisp);
|
|
86
|
+
|
|
87
|
+
if (lispTokens && lispTokens.length > 0) {
|
|
88
|
+
// Deconstruct the nested structure: e.g., ['declare', ['HotLead', 'BELIEVES_YES']]
|
|
89
|
+
const tokens = lispTokens[0] as LispToken[];
|
|
90
|
+
|
|
91
|
+
if (
|
|
92
|
+
(tokens[0] === 'declare' || tokens[0] === 'identifyAs') &&
|
|
93
|
+
Array.isArray(tokens[1]) &&
|
|
94
|
+
tokens[1].length >= 2
|
|
95
|
+
) {
|
|
96
|
+
const command = tokens[0] as string;
|
|
97
|
+
const params = tokens[1] as (string | number)[];
|
|
98
|
+
const beliefId = params[0] as string;
|
|
99
|
+
const value = params[1] as string;
|
|
100
|
+
|
|
101
|
+
let hxValsMap: { [key: string]: string } = {};
|
|
102
|
+
|
|
103
|
+
if (command === 'declare') {
|
|
104
|
+
hxValsMap = {
|
|
105
|
+
beliefId: beliefId,
|
|
106
|
+
beliefType: 'Belief',
|
|
107
|
+
beliefValue: value,
|
|
108
|
+
};
|
|
109
|
+
} else if (command === 'identifyAs') {
|
|
110
|
+
hxValsMap = {
|
|
111
|
+
beliefId: beliefId,
|
|
112
|
+
beliefType: 'Belief',
|
|
113
|
+
beliefVerb: 'IDENTIFY_AS',
|
|
114
|
+
beliefObject: value,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (Object.keys(hxValsMap).length > 0) {
|
|
119
|
+
item.renderAs = 'button';
|
|
120
|
+
item.htmxVals = JSON.stringify(hxValsMap);
|
|
121
|
+
return item;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
} catch (error) {
|
|
126
|
+
console.error(
|
|
127
|
+
`Failed to process menu item for action: ${actionLisp}`,
|
|
128
|
+
error
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
item.renderAs = 'span';
|
|
133
|
+
return item;
|
|
134
|
+
}
|
|
135
|
+
|
|
66
136
|
const featuredLinks = thisPayload
|
|
67
137
|
.filter((e: MenuLink) => e.featured)
|
|
68
138
|
.map(processMenuLink);
|
|
@@ -70,19 +140,46 @@ const MenuComponent = (props: MenuProps) => {
|
|
|
70
140
|
.filter((e: MenuLink) => !e.featured)
|
|
71
141
|
.map(processMenuLink);
|
|
72
142
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
143
|
+
const InteractiveMenuItem = ({ item }: { item: ProcessedMenuLinkDatum }) => {
|
|
144
|
+
if (item.renderAs === 'button') {
|
|
145
|
+
return (
|
|
146
|
+
<button
|
|
147
|
+
type="button"
|
|
148
|
+
className="text-mydarkgrey focus:ring-myblue block text-2xl font-bold leading-6 hover:text-black hover:underline hover:decoration-dashed hover:decoration-4 hover:underline-offset-4 focus:text-black focus:outline-none focus:ring-2"
|
|
149
|
+
title={item.description}
|
|
150
|
+
aria-label={`${item.name} - ${item.description}`}
|
|
151
|
+
hx-post="/api/v1/state"
|
|
152
|
+
hx-swap="none"
|
|
153
|
+
hx-vals={item.htmxVals}
|
|
154
|
+
>
|
|
155
|
+
{item.name}
|
|
156
|
+
</button>
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (item.renderAs === 'a') {
|
|
161
|
+
return (
|
|
162
|
+
<a
|
|
163
|
+
href={item.href}
|
|
164
|
+
className="text-mydarkgrey focus:ring-myblue block text-2xl font-bold leading-6 hover:text-black hover:underline hover:decoration-dashed hover:decoration-4 hover:underline-offset-4 focus:text-black focus:outline-none focus:ring-2"
|
|
165
|
+
title={item.description}
|
|
166
|
+
aria-label={`${item.name} - ${item.description}`}
|
|
167
|
+
>
|
|
168
|
+
{item.name}
|
|
169
|
+
</a>
|
|
170
|
+
);
|
|
83
171
|
}
|
|
84
|
-
|
|
85
|
-
|
|
172
|
+
|
|
173
|
+
return (
|
|
174
|
+
<span
|
|
175
|
+
className="text-mydarkgrey block text-2xl font-bold leading-6 opacity-50"
|
|
176
|
+
title={item.description}
|
|
177
|
+
aria-label={`${item.name} - ${item.description}`}
|
|
178
|
+
>
|
|
179
|
+
{item.name}
|
|
180
|
+
</span>
|
|
181
|
+
);
|
|
182
|
+
};
|
|
86
183
|
|
|
87
184
|
return (
|
|
88
185
|
<>
|
|
@@ -90,16 +187,9 @@ const MenuComponent = (props: MenuProps) => {
|
|
|
90
187
|
|
|
91
188
|
{/* Desktop Navigation */}
|
|
92
189
|
<nav className="font-action ml-6 hidden flex-wrap items-center justify-end space-x-3 md:flex md:space-x-6">
|
|
93
|
-
{featuredLinks.map((item:
|
|
190
|
+
{featuredLinks.map((item: ProcessedMenuLinkDatum) => (
|
|
94
191
|
<div key={item.name} className="relative py-1.5">
|
|
95
|
-
<
|
|
96
|
-
href={item.to}
|
|
97
|
-
className="text-mydarkgrey focus:ring-myblue block text-2xl font-bold leading-6 hover:text-black hover:underline hover:decoration-dashed hover:decoration-4 hover:underline-offset-4 focus:text-black focus:outline-none focus:ring-2"
|
|
98
|
-
title={item.description}
|
|
99
|
-
aria-label={`${item.name} - ${item.description}`}
|
|
100
|
-
>
|
|
101
|
-
{item.name}
|
|
102
|
-
</a>
|
|
192
|
+
<InteractiveMenuItem item={item} />
|
|
103
193
|
</div>
|
|
104
194
|
))}
|
|
105
195
|
</nav>
|
|
@@ -122,21 +212,42 @@ const MenuComponent = (props: MenuProps) => {
|
|
|
122
212
|
<div className="text-md ring-mydarkgrey/5 flex-auto overflow-hidden rounded-3xl bg-white p-4 leading-6 shadow-lg ring-1">
|
|
123
213
|
{/* Featured Links Section */}
|
|
124
214
|
<div className="px-8">
|
|
125
|
-
{featuredLinks.map((item:
|
|
215
|
+
{featuredLinks.map((item: ProcessedMenuLinkDatum) => (
|
|
126
216
|
<Menu.Item
|
|
127
217
|
key={item.name}
|
|
128
218
|
value={item.name}
|
|
129
219
|
className="menu-item hover:bg-mygreen/20 group relative flex gap-x-6 rounded-lg p-4"
|
|
130
220
|
>
|
|
131
221
|
<div>
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
222
|
+
{item.renderAs === 'button' ? (
|
|
223
|
+
<button
|
|
224
|
+
type="button"
|
|
225
|
+
className="font-action text-myblack text-xl hover:text-black focus:text-black focus:outline-none"
|
|
226
|
+
aria-label={`${item.name} - ${item.description}`}
|
|
227
|
+
hx-post="/api/v1/state"
|
|
228
|
+
hx-swap="none"
|
|
229
|
+
hx-vals={item.htmxVals}
|
|
230
|
+
>
|
|
231
|
+
{item.name}
|
|
232
|
+
<span className="absolute inset-0" />
|
|
233
|
+
</button>
|
|
234
|
+
) : item.renderAs === 'a' ? (
|
|
235
|
+
<a
|
|
236
|
+
href={item.href}
|
|
237
|
+
className="font-action text-myblack text-xl hover:text-black focus:text-black focus:outline-none"
|
|
238
|
+
aria-label={`${item.name} - ${item.description}`}
|
|
239
|
+
>
|
|
240
|
+
{item.name}
|
|
241
|
+
<span className="absolute inset-0" />
|
|
242
|
+
</a>
|
|
243
|
+
) : (
|
|
244
|
+
<span
|
|
245
|
+
className="font-action text-myblack text-xl opacity-50"
|
|
246
|
+
aria-label={`${item.name} - ${item.description}`}
|
|
247
|
+
>
|
|
248
|
+
{item.name}
|
|
249
|
+
</span>
|
|
250
|
+
)}
|
|
140
251
|
<p className="text-mydarkgrey mt-1">
|
|
141
252
|
{item.description}
|
|
142
253
|
</p>
|
|
@@ -161,24 +272,49 @@ const MenuComponent = (props: MenuProps) => {
|
|
|
161
272
|
className="mt-6 space-y-6"
|
|
162
273
|
aria-labelledby="additional-links-heading"
|
|
163
274
|
>
|
|
164
|
-
{additionalLinks.map(
|
|
165
|
-
|
|
166
|
-
<
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
<a
|
|
171
|
-
href={item.to}
|
|
172
|
-
className="text-mydarkgrey block truncate rounded p-2 text-sm font-bold leading-6 hover:text-black focus:text-black focus:underline focus:outline-none"
|
|
173
|
-
title={item.description}
|
|
174
|
-
aria-label={`${item.name} - ${item.description}`}
|
|
275
|
+
{additionalLinks.map(
|
|
276
|
+
(item: ProcessedMenuLinkDatum) => (
|
|
277
|
+
<li key={item.name} className="relative">
|
|
278
|
+
<Menu.Item
|
|
279
|
+
value={item.name}
|
|
280
|
+
className="menu-item block w-full text-left"
|
|
175
281
|
>
|
|
176
|
-
{item.
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
282
|
+
{item.renderAs === 'button' ? (
|
|
283
|
+
<button
|
|
284
|
+
type="button"
|
|
285
|
+
className="text-mydarkgrey block truncate rounded p-2 text-sm font-bold leading-6 hover:text-black focus:text-black focus:underline focus:outline-none"
|
|
286
|
+
title={item.description}
|
|
287
|
+
aria-label={`${item.name} - ${item.description}`}
|
|
288
|
+
hx-post="/api/v1/state"
|
|
289
|
+
hx-swap="none"
|
|
290
|
+
hx-vals={item.htmxVals}
|
|
291
|
+
>
|
|
292
|
+
{item.name}
|
|
293
|
+
<span className="absolute inset-0" />
|
|
294
|
+
</button>
|
|
295
|
+
) : item.renderAs === 'a' ? (
|
|
296
|
+
<a
|
|
297
|
+
href={item.href}
|
|
298
|
+
className="text-mydarkgrey block truncate rounded p-2 text-sm font-bold leading-6 hover:text-black focus:text-black focus:underline focus:outline-none"
|
|
299
|
+
title={item.description}
|
|
300
|
+
aria-label={`${item.name} - ${item.description}`}
|
|
301
|
+
>
|
|
302
|
+
{item.name}
|
|
303
|
+
<span className="absolute inset-0" />
|
|
304
|
+
</a>
|
|
305
|
+
) : (
|
|
306
|
+
<span
|
|
307
|
+
className="text-mydarkgrey block truncate rounded p-2 text-sm font-bold leading-6 opacity-50"
|
|
308
|
+
title={item.description}
|
|
309
|
+
aria-label={`${item.name} - ${item.description}`}
|
|
310
|
+
>
|
|
311
|
+
{item.name}
|
|
312
|
+
</span>
|
|
313
|
+
)}
|
|
314
|
+
</Menu.Item>
|
|
315
|
+
</li>
|
|
316
|
+
)
|
|
317
|
+
)}
|
|
182
318
|
</ul>
|
|
183
319
|
</div>
|
|
184
320
|
)}
|
|
@@ -45,11 +45,13 @@ interface ContentMapItem {
|
|
|
45
45
|
interface EpinetDurationSelectorProps {
|
|
46
46
|
fullContentMap?: ContentMapItem[];
|
|
47
47
|
isLoading?: boolean;
|
|
48
|
+
hourlyNodeActivity?: any;
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
const EpinetDurationSelector = ({
|
|
51
52
|
fullContentMap,
|
|
52
53
|
isLoading,
|
|
54
|
+
hourlyNodeActivity,
|
|
53
55
|
}: EpinetDurationSelectorProps = {}) => {
|
|
54
56
|
const [startDate, setStartDate] = useState<Date | null>(null);
|
|
55
57
|
const [endDate, setEndDate] = useState<Date | null>(null);
|
|
@@ -185,22 +187,12 @@ const EpinetDurationSelector = ({
|
|
|
185
187
|
const startUTCTime = createUTCDateTime(startDate, localFilters.startHour);
|
|
186
188
|
const endUTCTime = createUTCDateTime(endDate, localFilters.endHour);
|
|
187
189
|
|
|
188
|
-
if (endUTCTime
|
|
190
|
+
if (endUTCTime < startUTCTime) {
|
|
189
191
|
setErrorMessage('End time must be after start time.');
|
|
190
192
|
return;
|
|
191
193
|
}
|
|
192
194
|
|
|
193
195
|
const nowUTC = new Date();
|
|
194
|
-
const maxPastTime = new Date(
|
|
195
|
-
nowUTC.getTime() - MAX_ANALYTICS_HOURS * 60 * 60 * 1000
|
|
196
|
-
);
|
|
197
|
-
|
|
198
|
-
if (startUTCTime < maxPastTime) {
|
|
199
|
-
setErrorMessage(
|
|
200
|
-
`Start time cannot be more than ${MAX_ANALYTICS_HOURS} hours in the past.`
|
|
201
|
-
);
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
196
|
|
|
205
197
|
if (endUTCTime > nowUTC) {
|
|
206
198
|
setErrorMessage('End time cannot be in the future.');
|
|
@@ -549,7 +541,7 @@ const EpinetDurationSelector = ({
|
|
|
549
541
|
|
|
550
542
|
return (
|
|
551
543
|
<>
|
|
552
|
-
<div className="space-y-4">
|
|
544
|
+
<div className="space-y-4 overflow-visible">
|
|
553
545
|
{$epinetCustomFilters.enabled && (
|
|
554
546
|
<div
|
|
555
547
|
className={`space-y-4 rounded-lg border-2 border-dashed border-gray-200 bg-gray-50 p-4`}
|
|
@@ -865,7 +857,10 @@ const EpinetDurationSelector = ({
|
|
|
865
857
|
</Select.Control>
|
|
866
858
|
<Portal>
|
|
867
859
|
<Select.Positioner>
|
|
868
|
-
<Select.Content
|
|
860
|
+
<Select.Content
|
|
861
|
+
className="z-10 mt-2 max-h-96 overflow-auto rounded-md bg-white text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"
|
|
862
|
+
style={{ width: 'var(--trigger-width)' }}
|
|
863
|
+
>
|
|
869
864
|
{paginatedUserCounts.length > 0 ? (
|
|
870
865
|
[
|
|
871
866
|
<Select.Item
|
|
@@ -930,6 +925,7 @@ const EpinetDurationSelector = ({
|
|
|
930
925
|
<EpinetTableView
|
|
931
926
|
fullContentMap={fullContentMap || []}
|
|
932
927
|
isLoading={isLoading}
|
|
928
|
+
hourlyNodeActivity={hourlyNodeActivity}
|
|
933
929
|
/>
|
|
934
930
|
)}
|
|
935
931
|
</div>
|
|
@@ -53,13 +53,17 @@ interface ContentMapItem {
|
|
|
53
53
|
type: string;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
interface Props {
|
|
57
|
+
fullContentMap: ContentMapItem[];
|
|
58
|
+
isLoading?: boolean;
|
|
59
|
+
hourlyNodeActivity?: any;
|
|
60
|
+
}
|
|
61
|
+
|
|
56
62
|
const EpinetTableView = ({
|
|
57
63
|
fullContentMap,
|
|
58
64
|
isLoading = false,
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
isLoading?: boolean;
|
|
62
|
-
}) => {
|
|
65
|
+
hourlyNodeActivity,
|
|
66
|
+
}: Props) => {
|
|
63
67
|
const $epinetCustomFilters = useStore(epinetCustomFilters);
|
|
64
68
|
const [currentDay, setCurrentDay] = useState<string | null>(null);
|
|
65
69
|
const [availableDays, setAvailableDays] = useState<string[]>([]);
|
|
@@ -174,7 +178,7 @@ const EpinetTableView = ({
|
|
|
174
178
|
};
|
|
175
179
|
|
|
176
180
|
useEffect(() => {
|
|
177
|
-
const hourlyActivity =
|
|
181
|
+
const hourlyActivity = hourlyNodeActivity || {};
|
|
178
182
|
const hourKeys = Object.keys(hourlyActivity);
|
|
179
183
|
|
|
180
184
|
if (hourKeys.length === 0) {
|
|
@@ -192,7 +196,7 @@ const EpinetTableView = ({
|
|
|
192
196
|
setAvailableDays(days);
|
|
193
197
|
setCurrentDay(days[0] || null);
|
|
194
198
|
setCurrentDayIndex(0);
|
|
195
|
-
}, [
|
|
199
|
+
}, [hourlyNodeActivity]);
|
|
196
200
|
|
|
197
201
|
const navigateDay = (direction: 'prev' | 'next') => {
|
|
198
202
|
const newIndex =
|
|
@@ -213,7 +217,7 @@ const EpinetTableView = ({
|
|
|
213
217
|
if (!currentDay)
|
|
214
218
|
return { data: [], dailyTotal: 0, dailyVisitors: 0, maxHourlyTotal: 0 };
|
|
215
219
|
|
|
216
|
-
const hourlyActivity =
|
|
220
|
+
const hourlyActivity = hourlyNodeActivity || {};
|
|
217
221
|
const result: HourData[] = [];
|
|
218
222
|
let emptyRangeStart: number | null = null;
|
|
219
223
|
let dailyTotal = 0;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { FullContentMapItem } from '@/types/tractstack';
|
|
3
|
+
|
|
4
|
+
export interface Props {
|
|
5
|
+
options?: {
|
|
6
|
+
params?: {
|
|
7
|
+
options?: string;
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
contentMap: FullContentMapItem[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const { options, contentMap } = Astro.props;
|
|
14
|
+
|
|
15
|
+
let parsedOptions;
|
|
16
|
+
try {
|
|
17
|
+
parsedOptions = JSON.parse(options?.params?.options || '{}');
|
|
18
|
+
} catch (e) {
|
|
19
|
+
console.error('Invalid options for FeaturedArticle', e);
|
|
20
|
+
parsedOptions = { slug: '' };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const slug = parsedOptions.slug || '';
|
|
24
|
+
|
|
25
|
+
const featuredStory = contentMap.find(
|
|
26
|
+
(item: FullContentMapItem) =>
|
|
27
|
+
item.slug === slug &&
|
|
28
|
+
item.type === 'StoryFragment' &&
|
|
29
|
+
item.description &&
|
|
30
|
+
item.panes &&
|
|
31
|
+
item.panes.length > 0 &&
|
|
32
|
+
item.thumbSrc
|
|
33
|
+
);
|
|
34
|
+
const bgColor = parsedOptions.bgColor || '';
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
{
|
|
38
|
+
featuredStory ? (
|
|
39
|
+
<div
|
|
40
|
+
class="mx-auto w-full max-w-7xl px-8 py-12"
|
|
41
|
+
style={bgColor ? `background-color: ${bgColor}` : ''}
|
|
42
|
+
>
|
|
43
|
+
<div class="grid grid-cols-1 items-center md:grid-cols-2 md:gap-y-12">
|
|
44
|
+
<div class="w-full">
|
|
45
|
+
<a href={`/${featuredStory.slug}`} class="block">
|
|
46
|
+
<div class="max-w-lg pr-12">
|
|
47
|
+
<p class="font-action mb-4 text-lg font-bold uppercase text-gray-500">
|
|
48
|
+
Featured Article
|
|
49
|
+
</p>
|
|
50
|
+
<div class="space-y-6">
|
|
51
|
+
<h2 class="py-2 text-3xl font-bold leading-snug text-black transition-colors md:text-4xl xl:text-5xl">
|
|
52
|
+
{featuredStory.title}
|
|
53
|
+
</h2>
|
|
54
|
+
{featuredStory.description && (
|
|
55
|
+
<p class="text-sm leading-relaxed text-gray-700 md:text-lg xl:text-xl">
|
|
56
|
+
{featuredStory.description}
|
|
57
|
+
</p>
|
|
58
|
+
)}
|
|
59
|
+
{featuredStory.topics && featuredStory.topics.length > 0 && (
|
|
60
|
+
<div class="flex flex-wrap gap-2 pb-6 pt-2">
|
|
61
|
+
{featuredStory.topics.map((topic: string) => (
|
|
62
|
+
<span class="inline-flex items-center rounded-full bg-cyan-100 px-3 py-1 text-sm font-bold text-cyan-800">
|
|
63
|
+
{topic}
|
|
64
|
+
</span>
|
|
65
|
+
))}
|
|
66
|
+
</div>
|
|
67
|
+
)}
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
</a>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
<div class="w-full py-6">
|
|
74
|
+
<div class="mx-auto max-w-2xl">
|
|
75
|
+
<a href={`/${featuredStory.slug}`}>
|
|
76
|
+
<img
|
|
77
|
+
src={featuredStory.thumbSrc}
|
|
78
|
+
srcset={featuredStory.thumbSrcSet}
|
|
79
|
+
sizes="(min-width: 768px) 50vw, 100vw"
|
|
80
|
+
alt={`Preview of ${featuredStory.title}`}
|
|
81
|
+
class="w-full rounded-lg shadow-lg"
|
|
82
|
+
style="aspect-ratio: 1200 / 630;"
|
|
83
|
+
/>
|
|
84
|
+
</a>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
<div class="md:py-6">
|
|
88
|
+
<a
|
|
89
|
+
href={`/${featuredStory.slug}`}
|
|
90
|
+
class="inline-block w-fit rounded-md bg-gray-100 px-5 py-3 text-sm font-bold text-gray-900 transition-colors hover:bg-gray-200"
|
|
91
|
+
>
|
|
92
|
+
Read More
|
|
93
|
+
</a>
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
) : (
|
|
98
|
+
<div class="mx-auto max-w-7xl px-4 py-16">
|
|
99
|
+
<p class="italic text-cyan-600">
|
|
100
|
+
Featured article not found or is missing required content (description,
|
|
101
|
+
panes, thumbnail).
|
|
102
|
+
</p>
|
|
103
|
+
</div>
|
|
104
|
+
)
|
|
105
|
+
}
|