astro-tractstack 2.0.0-rc.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.
Files changed (427) hide show
  1. package/LICENSE +110 -0
  2. package/README.md +56 -0
  3. package/astro.d.ts +64 -0
  4. package/bin/create-tractstack.js +483 -0
  5. package/dist/config.js +80 -0
  6. package/dist/index.js +2129 -0
  7. package/package.json +89 -0
  8. package/templates/artpacks/kCz/captainBreakfast_1080px.webp +0 -0
  9. package/templates/artpacks/kCz/captainBreakfast_1920px.webp +0 -0
  10. package/templates/artpacks/kCz/captainBreakfast_600px.webp +0 -0
  11. package/templates/artpacks/kCz/cleanDrips_1080px.webp +0 -0
  12. package/templates/artpacks/kCz/cleanDrips_1920px.webp +0 -0
  13. package/templates/artpacks/kCz/cleanDrips_600px.webp +0 -0
  14. package/templates/artpacks/kCz/crispwaves_1080px.webp +0 -0
  15. package/templates/artpacks/kCz/crispwaves_1920px.webp +0 -0
  16. package/templates/artpacks/kCz/crispwaves_600px.webp +0 -0
  17. package/templates/artpacks/kCz/dragonSkin_1080px.webp +0 -0
  18. package/templates/artpacks/kCz/dragonSkin_1920px.webp +0 -0
  19. package/templates/artpacks/kCz/dragonSkin_600px.webp +0 -0
  20. package/templates/artpacks/kCz/dragon_1080px.webp +0 -0
  21. package/templates/artpacks/kCz/dragon_1920px.webp +0 -0
  22. package/templates/artpacks/kCz/dragon_600px.webp +0 -0
  23. package/templates/artpacks/kCz/nightcity_1080px.webp +0 -0
  24. package/templates/artpacks/kCz/nightcity_1920px.webp +0 -0
  25. package/templates/artpacks/kCz/nightcity_600px.webp +0 -0
  26. package/templates/artpacks/kCz/pattern1_1080px.webp +0 -0
  27. package/templates/artpacks/kCz/pattern1_1920px.webp +0 -0
  28. package/templates/artpacks/kCz/pattern1_600px.webp +0 -0
  29. package/templates/artpacks/kCz/pattern2_1080px.webp +0 -0
  30. package/templates/artpacks/kCz/pattern2_1920px.webp +0 -0
  31. package/templates/artpacks/kCz/pattern2_600px.webp +0 -0
  32. package/templates/artpacks/kCz/skindrips_1080px.webp +0 -0
  33. package/templates/artpacks/kCz/skindrips_1920px.webp +0 -0
  34. package/templates/artpacks/kCz/skindrips_600px.webp +0 -0
  35. package/templates/artpacks/kCz/slimetime_1080px.webp +0 -0
  36. package/templates/artpacks/kCz/slimetime_1920px.webp +0 -0
  37. package/templates/artpacks/kCz/slimetime_600px.webp +0 -0
  38. package/templates/artpacks/kCz/snake_1080px.webp +0 -0
  39. package/templates/artpacks/kCz/snake_1920px.webp +0 -0
  40. package/templates/artpacks/kCz/snake_600px.webp +0 -0
  41. package/templates/artpacks/kCz/toxicshock_1080px.webp +0 -0
  42. package/templates/artpacks/kCz/toxicshock_1920px.webp +0 -0
  43. package/templates/artpacks/kCz/toxicshock_600px.webp +0 -0
  44. package/templates/artpacks/kCz/tractstack_1080px.webp +0 -0
  45. package/templates/artpacks/kCz/tractstack_1920px.webp +0 -0
  46. package/templates/artpacks/kCz/tractstack_600px.webp +0 -0
  47. package/templates/artpacks/kCz/tripdrips_1080px.webp +0 -0
  48. package/templates/artpacks/kCz/tripdrips_1920px.webp +0 -0
  49. package/templates/artpacks/kCz/tripdrips_600px.webp +0 -0
  50. package/templates/artpacks/kCz/wavedrips_1080px.webp +0 -0
  51. package/templates/artpacks/kCz/wavedrips_1920px.webp +0 -0
  52. package/templates/artpacks/kCz/wavedrips_600px.webp +0 -0
  53. package/templates/artpacks/t8k/beach_1080px.webp +0 -0
  54. package/templates/artpacks/t8k/beach_1920px.webp +0 -0
  55. package/templates/artpacks/t8k/beach_600px.webp +0 -0
  56. package/templates/artpacks/t8k/blast_1080px.webp +0 -0
  57. package/templates/artpacks/t8k/blast_1920px.webp +0 -0
  58. package/templates/artpacks/t8k/blast_600px.webp +0 -0
  59. package/templates/artpacks/t8k/bokeh_1080px.webp +0 -0
  60. package/templates/artpacks/t8k/bokeh_1920px.webp +0 -0
  61. package/templates/artpacks/t8k/bokeh_600px.webp +0 -0
  62. package/templates/artpacks/t8k/cartoon_1080px.webp +0 -0
  63. package/templates/artpacks/t8k/cartoon_1920px.webp +0 -0
  64. package/templates/artpacks/t8k/cartoon_600px.webp +0 -0
  65. package/templates/artpacks/t8k/darkeggshell_1080px.webp +0 -0
  66. package/templates/artpacks/t8k/darkeggshell_1920px.webp +0 -0
  67. package/templates/artpacks/t8k/darkeggshell_600px.webp +0 -0
  68. package/templates/artpacks/t8k/explosion_1080px.webp +0 -0
  69. package/templates/artpacks/t8k/explosion_1920px.webp +0 -0
  70. package/templates/artpacks/t8k/explosion_600px.webp +0 -0
  71. package/templates/artpacks/t8k/floral_1080px.webp +0 -0
  72. package/templates/artpacks/t8k/floral_1920px.webp +0 -0
  73. package/templates/artpacks/t8k/floral_600px.webp +0 -0
  74. package/templates/artpacks/t8k/flower_1080px.webp +0 -0
  75. package/templates/artpacks/t8k/flower_1920px.webp +0 -0
  76. package/templates/artpacks/t8k/flower_600px.webp +0 -0
  77. package/templates/artpacks/t8k/foliage_1080px.webp +0 -0
  78. package/templates/artpacks/t8k/foliage_1920px.webp +0 -0
  79. package/templates/artpacks/t8k/foliage_600px.webp +0 -0
  80. package/templates/artpacks/t8k/mist_1080px.webp +0 -0
  81. package/templates/artpacks/t8k/mist_1920px.webp +0 -0
  82. package/templates/artpacks/t8k/mist_600px.webp +0 -0
  83. package/templates/artpacks/t8k/portal_1080px.webp +0 -0
  84. package/templates/artpacks/t8k/portal_1920px.webp +0 -0
  85. package/templates/artpacks/t8k/portal_600px.webp +0 -0
  86. package/templates/artpacks/t8k/storytime_1080px.webp +0 -0
  87. package/templates/artpacks/t8k/storytime_1920px.webp +0 -0
  88. package/templates/artpacks/t8k/storytime_600px.webp +0 -0
  89. package/templates/artpacks/t8k/tacky_1080px.webp +0 -0
  90. package/templates/artpacks/t8k/tacky_1920px.webp +0 -0
  91. package/templates/artpacks/t8k/tacky_600px.webp +0 -0
  92. package/templates/artpacks/t8k/wallpaper_1080px.webp +0 -0
  93. package/templates/artpacks/t8k/wallpaper_1920px.webp +0 -0
  94. package/templates/artpacks/t8k/wallpaper_600px.webp +0 -0
  95. package/templates/brand/favicon.ico +0 -0
  96. package/templates/brand/logo.svg +19 -0
  97. package/templates/brand/static.jpg +0 -0
  98. package/templates/brand/wordmark.svg +4 -0
  99. package/templates/css/custom.css +51 -0
  100. package/templates/css/frontend.css +3519 -0
  101. package/templates/css/storykeep.css +92872 -0
  102. package/templates/custom/minimal/CodeHook.astro +53 -0
  103. package/templates/custom/minimal/CustomRoutes.astro +46 -0
  104. package/templates/custom/with-examples/CodeHook.astro +49 -0
  105. package/templates/custom/with-examples/CustomHero.astro +13 -0
  106. package/templates/custom/with-examples/CustomRoutes.astro +39 -0
  107. package/templates/custom/with-examples/pages/Collections.astro +110 -0
  108. package/templates/env.example +8 -0
  109. package/templates/fonts/Inter-Black.woff2 +0 -0
  110. package/templates/fonts/Inter-Bold.woff2 +0 -0
  111. package/templates/fonts/Inter-Regular.woff2 +0 -0
  112. package/templates/icons/h2.svg +1 -0
  113. package/templates/icons/h3.svg +1 -0
  114. package/templates/icons/h4.svg +1 -0
  115. package/templates/icons/h5.svg +1 -0
  116. package/templates/icons/image.svg +7 -0
  117. package/templates/icons/text.svg +6 -0
  118. package/templates/socials/codepen.svg +1 -0
  119. package/templates/socials/discord.svg +1 -0
  120. package/templates/socials/facebook.svg +1 -0
  121. package/templates/socials/github.svg +1 -0
  122. package/templates/socials/instagram.svg +1 -0
  123. package/templates/socials/linkedin.svg +1 -0
  124. package/templates/socials/mail.svg +1 -0
  125. package/templates/socials/rumble.svg +1 -0
  126. package/templates/socials/tiktok.svg +1 -0
  127. package/templates/socials/twitch.svg +1 -0
  128. package/templates/socials/twitter.svg +1 -0
  129. package/templates/socials/x.svg +1 -0
  130. package/templates/socials/youtube.svg +1 -0
  131. package/templates/src/client/analytics-events.ts +213 -0
  132. package/templates/src/client/belief-events.ts +205 -0
  133. package/templates/src/client/sse.ts +667 -0
  134. package/templates/src/components/Footer.astro +246 -0
  135. package/templates/src/components/Fragment.astro +70 -0
  136. package/templates/src/components/Header.astro +458 -0
  137. package/templates/src/components/Menu.tsx +196 -0
  138. package/templates/src/components/codehooks/BunnyVideoSetup.tsx +692 -0
  139. package/templates/src/components/codehooks/BunnyVideoWrapper.astro +78 -0
  140. package/templates/src/components/codehooks/EpinetDurationSelector.tsx +1020 -0
  141. package/templates/src/components/codehooks/EpinetTableView.tsx +594 -0
  142. package/templates/src/components/codehooks/EpinetWrapper.tsx +424 -0
  143. package/templates/src/components/codehooks/FeaturedContent.astro +273 -0
  144. package/templates/src/components/codehooks/FeaturedContentSetup.tsx +738 -0
  145. package/templates/src/components/codehooks/ListContent.astro +460 -0
  146. package/templates/src/components/codehooks/ListContentSetup.tsx +649 -0
  147. package/templates/src/components/codehooks/SankeyDiagram.tsx +359 -0
  148. package/templates/src/components/compositor/Compositor.tsx +144 -0
  149. package/templates/src/components/compositor/Node.tsx +415 -0
  150. package/templates/src/components/compositor/NodeWithGuid.tsx +25 -0
  151. package/templates/src/components/compositor/PanelVisibilityWrapper.tsx +87 -0
  152. package/templates/src/components/compositor/elements/Belief.tsx +148 -0
  153. package/templates/src/components/compositor/elements/BgImage.tsx +118 -0
  154. package/templates/src/components/compositor/elements/BgVisualBreak.tsx +102 -0
  155. package/templates/src/components/compositor/elements/BunnyVideo.tsx +63 -0
  156. package/templates/src/components/compositor/elements/IdentifyAs.tsx +66 -0
  157. package/templates/src/components/compositor/elements/PlayButton.tsx +19 -0
  158. package/templates/src/components/compositor/elements/SignUp.tsx +179 -0
  159. package/templates/src/components/compositor/elements/Svg.tsx +33 -0
  160. package/templates/src/components/compositor/elements/ToggleBelief.tsx +36 -0
  161. package/templates/src/components/compositor/elements/YouTubeWrapper.tsx +33 -0
  162. package/templates/src/components/compositor/nodes/BgPaneWrapper.tsx +35 -0
  163. package/templates/src/components/compositor/nodes/GhostInsertBlock.tsx +189 -0
  164. package/templates/src/components/compositor/nodes/Markdown.tsx +179 -0
  165. package/templates/src/components/compositor/nodes/Pane.tsx +277 -0
  166. package/templates/src/components/compositor/nodes/Pane_eraser.tsx +69 -0
  167. package/templates/src/components/compositor/nodes/Pane_layout.tsx +77 -0
  168. package/templates/src/components/compositor/nodes/RenderChildren.tsx +19 -0
  169. package/templates/src/components/compositor/nodes/StoryFragment.tsx +35 -0
  170. package/templates/src/components/compositor/nodes/TagElement.tsx +14 -0
  171. package/templates/src/components/compositor/nodes/Widget.tsx +115 -0
  172. package/templates/src/components/compositor/nodes/tagElements/NodeA.tsx +4 -0
  173. package/templates/src/components/compositor/nodes/tagElements/NodeA_eraser.tsx +26 -0
  174. package/templates/src/components/compositor/nodes/tagElements/NodeAnchorComponent.tsx +248 -0
  175. package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag.tsx +684 -0
  176. package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag_eraser.tsx +62 -0
  177. package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag_insert.tsx +120 -0
  178. package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag_settings.tsx +62 -0
  179. package/templates/src/components/compositor/nodes/tagElements/NodeButton.tsx +5 -0
  180. package/templates/src/components/compositor/nodes/tagElements/NodeButton_eraser.tsx +26 -0
  181. package/templates/src/components/compositor/nodes/tagElements/NodeImg.tsx +28 -0
  182. package/templates/src/components/compositor/nodes/tagElements/NodeText.tsx +18 -0
  183. package/templates/src/components/compositor/nodes/tagElements/TabIndicator.tsx +51 -0
  184. package/templates/src/components/compositor/preview/FeaturedContentPreview.tsx +128 -0
  185. package/templates/src/components/compositor/preview/ListContentPreview.tsx +213 -0
  186. package/templates/src/components/compositor/preview/OgImagePreview.tsx +223 -0
  187. package/templates/src/components/compositor/preview/PaneSnapshotGenerator.tsx +199 -0
  188. package/templates/src/components/compositor/preview/PanesPreviewGenerator.tsx +123 -0
  189. package/templates/src/components/compositor/preview/VisualBreakPreview.tsx +154 -0
  190. package/templates/src/components/edit/Header.tsx +181 -0
  191. package/templates/src/components/edit/PanelSwitch.tsx +446 -0
  192. package/templates/src/components/edit/SettingsPanel.tsx +70 -0
  193. package/templates/src/components/edit/ToolBar.tsx +101 -0
  194. package/templates/src/components/edit/ToolMode.tsx +121 -0
  195. package/templates/src/components/edit/context/ContextPaneConfig.tsx +91 -0
  196. package/templates/src/components/edit/context/ContextPaneConfig_slug.tsx +174 -0
  197. package/templates/src/components/edit/context/ContextPaneConfig_title.tsx +186 -0
  198. package/templates/src/components/edit/pane/AddPanePanel.tsx +136 -0
  199. package/templates/src/components/edit/pane/AddPanePanel_break.tsx +470 -0
  200. package/templates/src/components/edit/pane/AddPanePanel_codehook.tsx +264 -0
  201. package/templates/src/components/edit/pane/AddPanePanel_new.tsx +623 -0
  202. package/templates/src/components/edit/pane/AddPanePanel_newAICopy.tsx +107 -0
  203. package/templates/src/components/edit/pane/AddPanePanel_newAICopy_modal.tsx +217 -0
  204. package/templates/src/components/edit/pane/AddPanePanel_newCopyMode.tsx +109 -0
  205. package/templates/src/components/edit/pane/AddPanePanel_newCustomCopy.tsx +39 -0
  206. package/templates/src/components/edit/pane/AddPanePanel_reuse.tsx +445 -0
  207. package/templates/src/components/edit/pane/ConfigPanePanel.tsx +245 -0
  208. package/templates/src/components/edit/pane/PageGen.tsx +485 -0
  209. package/templates/src/components/edit/pane/PageGenSelector.tsx +238 -0
  210. package/templates/src/components/edit/pane/PageGenSpecial.tsx +362 -0
  211. package/templates/src/components/edit/pane/PageGen_preview.tsx +495 -0
  212. package/templates/src/components/edit/pane/PanePanel_impression.tsx +258 -0
  213. package/templates/src/components/edit/pane/PanePanel_path.tsx +268 -0
  214. package/templates/src/components/edit/pane/PanePanel_slug.tsx +219 -0
  215. package/templates/src/components/edit/pane/PanePanel_title.tsx +142 -0
  216. package/templates/src/components/edit/panels/StyleBreakPanel.tsx +182 -0
  217. package/templates/src/components/edit/panels/StyleCodeHookPanel.tsx +439 -0
  218. package/templates/src/components/edit/panels/StyleElementPanel.tsx +177 -0
  219. package/templates/src/components/edit/panels/StyleElementPanel_add.tsx +349 -0
  220. package/templates/src/components/edit/panels/StyleElementPanel_remove.tsx +159 -0
  221. package/templates/src/components/edit/panels/StyleElementPanel_update.tsx +320 -0
  222. package/templates/src/components/edit/panels/StyleImagePanel.tsx +460 -0
  223. package/templates/src/components/edit/panels/StyleImagePanel_add.tsx +296 -0
  224. package/templates/src/components/edit/panels/StyleImagePanel_remove.tsx +153 -0
  225. package/templates/src/components/edit/panels/StyleImagePanel_update.tsx +312 -0
  226. package/templates/src/components/edit/panels/StyleLiElementPanel.tsx +273 -0
  227. package/templates/src/components/edit/panels/StyleLiElementPanel_add.tsx +301 -0
  228. package/templates/src/components/edit/panels/StyleLiElementPanel_remove.tsx +132 -0
  229. package/templates/src/components/edit/panels/StyleLiElementPanel_update.tsx +313 -0
  230. package/templates/src/components/edit/panels/StyleLinkPanel.tsx +346 -0
  231. package/templates/src/components/edit/panels/StyleLinkPanel_add.tsx +265 -0
  232. package/templates/src/components/edit/panels/StyleLinkPanel_config.tsx +240 -0
  233. package/templates/src/components/edit/panels/StyleLinkPanel_remove.tsx +94 -0
  234. package/templates/src/components/edit/panels/StyleLinkPanel_update.tsx +110 -0
  235. package/templates/src/components/edit/panels/StyleParentPanel.tsx +263 -0
  236. package/templates/src/components/edit/panels/StyleParentPanel_add.tsx +275 -0
  237. package/templates/src/components/edit/panels/StyleParentPanel_deleteLayer.tsx +112 -0
  238. package/templates/src/components/edit/panels/StyleParentPanel_remove.tsx +87 -0
  239. package/templates/src/components/edit/panels/StyleParentPanel_update.tsx +141 -0
  240. package/templates/src/components/edit/panels/StyleWidgetPanel.tsx +428 -0
  241. package/templates/src/components/edit/panels/StyleWidgetPanel_add.tsx +292 -0
  242. package/templates/src/components/edit/panels/StyleWidgetPanel_config.tsx +190 -0
  243. package/templates/src/components/edit/panels/StyleWidgetPanel_remove.tsx +152 -0
  244. package/templates/src/components/edit/panels/StyleWidgetPanel_update.tsx +308 -0
  245. package/templates/src/components/edit/state/SaveModal.tsx +811 -0
  246. package/templates/src/components/edit/state/StylesMemory.tsx +310 -0
  247. package/templates/src/components/edit/storyfragment/StoryFragmentConfigPanel.tsx +289 -0
  248. package/templates/src/components/edit/storyfragment/StoryFragmentPanel_menu.tsx +320 -0
  249. package/templates/src/components/edit/storyfragment/StoryFragmentPanel_og.tsx +888 -0
  250. package/templates/src/components/edit/storyfragment/StoryFragmentPanel_slug.tsx +269 -0
  251. package/templates/src/components/edit/storyfragment/StoryFragmentPanel_title.tsx +190 -0
  252. package/templates/src/components/edit/widgets/BeliefWidget.tsx +183 -0
  253. package/templates/src/components/edit/widgets/BunnyWidget.tsx +134 -0
  254. package/templates/src/components/edit/widgets/IdentifyAsWidget.tsx +193 -0
  255. package/templates/src/components/edit/widgets/SignupWidget.tsx +177 -0
  256. package/templates/src/components/edit/widgets/ToggleWidget.tsx +152 -0
  257. package/templates/src/components/edit/widgets/YouTubeWidget.tsx +65 -0
  258. package/templates/src/components/fields/ActionBuilderTimeSelector.tsx +353 -0
  259. package/templates/src/components/fields/ArtpackImage.tsx +480 -0
  260. package/templates/src/components/fields/BackgroundImage.tsx +530 -0
  261. package/templates/src/components/fields/BackgroundImageWrapper.tsx +192 -0
  262. package/templates/src/components/fields/BooleanParam.tsx +67 -0
  263. package/templates/src/components/fields/BunnyMomentSelector.tsx +56 -0
  264. package/templates/src/components/fields/ColorPickerCombo.tsx +284 -0
  265. package/templates/src/components/fields/ImageUpload.tsx +405 -0
  266. package/templates/src/components/fields/MultiParam.tsx +75 -0
  267. package/templates/src/components/fields/PaneBreakCollectionSelector.tsx +97 -0
  268. package/templates/src/components/fields/PaneBreakShapeSelector.tsx +134 -0
  269. package/templates/src/components/fields/SelectedTailwindClass.tsx +44 -0
  270. package/templates/src/components/fields/SingleParam.tsx +73 -0
  271. package/templates/src/components/fields/ViewportComboBox.tsx +252 -0
  272. package/templates/src/components/form/ActionBuilderField.tsx +282 -0
  273. package/templates/src/components/form/ActionBuilderSlugSelector.tsx +182 -0
  274. package/templates/src/components/form/BooleanToggle.tsx +94 -0
  275. package/templates/src/components/form/ColorPicker.tsx +153 -0
  276. package/templates/src/components/form/DateTimeInput.tsx +638 -0
  277. package/templates/src/components/form/EnumSelect.tsx +88 -0
  278. package/templates/src/components/form/FileUpload.tsx +465 -0
  279. package/templates/src/components/form/MagicPathBuilder.tsx +546 -0
  280. package/templates/src/components/form/NumberInput.tsx +101 -0
  281. package/templates/src/components/form/ParagraphArrayInput.tsx +207 -0
  282. package/templates/src/components/form/StringArrayInput.tsx +163 -0
  283. package/templates/src/components/form/StringInput.tsx +88 -0
  284. package/templates/src/components/form/UnsavedChangesBar.tsx +295 -0
  285. package/templates/src/components/form/advanced/APIConfigSection.tsx +69 -0
  286. package/templates/src/components/form/advanced/AuthConfigSection.tsx +97 -0
  287. package/templates/src/components/form/brand/BrandAssetsSection.tsx +93 -0
  288. package/templates/src/components/form/brand/BrandColorsSection.tsx +201 -0
  289. package/templates/src/components/form/brand/SEOSection.tsx +101 -0
  290. package/templates/src/components/form/brand/SiteConfigSection.tsx +61 -0
  291. package/templates/src/components/form/brand/SocialLinksSection.tsx +393 -0
  292. package/templates/src/components/profile/ProfileConsent.tsx +65 -0
  293. package/templates/src/components/profile/ProfileCreate.tsx +462 -0
  294. package/templates/src/components/profile/ProfileEdit.tsx +409 -0
  295. package/templates/src/components/profile/ProfileSwitch.tsx +255 -0
  296. package/templates/src/components/profile/ProfileUnlock.tsx +221 -0
  297. package/templates/src/components/storykeep/Dashboard.tsx +160 -0
  298. package/templates/src/components/storykeep/Dashboard_Activity.tsx +56 -0
  299. package/templates/src/components/storykeep/Dashboard_Advanced.tsx +165 -0
  300. package/templates/src/components/storykeep/Dashboard_Analytics.tsx +451 -0
  301. package/templates/src/components/storykeep/Dashboard_Branding.tsx +95 -0
  302. package/templates/src/components/storykeep/Dashboard_Content.tsx +191 -0
  303. package/templates/src/components/storykeep/controls/UsageCell.tsx +71 -0
  304. package/templates/src/components/storykeep/controls/content/BeliefForm.tsx +378 -0
  305. package/templates/src/components/storykeep/controls/content/BeliefTable.tsx +329 -0
  306. package/templates/src/components/storykeep/controls/content/ContentBrowser.tsx +385 -0
  307. package/templates/src/components/storykeep/controls/content/ContentSummary.tsx +149 -0
  308. package/templates/src/components/storykeep/controls/content/KnownResourceForm.tsx +397 -0
  309. package/templates/src/components/storykeep/controls/content/KnownResourceTable.tsx +260 -0
  310. package/templates/src/components/storykeep/controls/content/ManageContent.tsx +439 -0
  311. package/templates/src/components/storykeep/controls/content/MenuForm.tsx +239 -0
  312. package/templates/src/components/storykeep/controls/content/MenuTable.tsx +332 -0
  313. package/templates/src/components/storykeep/controls/content/ResourceBulkIngest.tsx +724 -0
  314. package/templates/src/components/storykeep/controls/content/ResourceForm.tsx +355 -0
  315. package/templates/src/components/storykeep/controls/content/ResourceTable.tsx +222 -0
  316. package/templates/src/components/storykeep/controls/content/StoryFragmentTable.tsx +482 -0
  317. package/templates/src/components/storykeep/state/BrandingWrapper.tsx +42 -0
  318. package/templates/src/components/storykeep/state/FetchAnalytics.tsx +350 -0
  319. package/templates/src/components/storykeep/widgets/ResponsiveLine.tsx +319 -0
  320. package/templates/src/components/storykeep/widgets/Wizard.tsx +278 -0
  321. package/templates/src/components/tenant/RegistrationForm.tsx +447 -0
  322. package/templates/src/components/widgets/BunnyVideoHero.astro +775 -0
  323. package/templates/src/components/widgets/Impression.tsx +102 -0
  324. package/templates/src/components/widgets/ImpressionWrapper.tsx +214 -0
  325. package/templates/src/constants/beliefs.ts +61 -0
  326. package/templates/src/constants/brandThemes.ts +133 -0
  327. package/templates/src/constants/prompts.json +55 -0
  328. package/templates/src/constants/shapes.ts +556 -0
  329. package/templates/src/constants/stopWords.ts +116 -0
  330. package/templates/src/constants/tailwindColors.json +344 -0
  331. package/templates/src/constants.ts +274 -0
  332. package/templates/src/hooks/useFormState.ts +203 -0
  333. package/templates/src/layouts/Layout.astro +290 -0
  334. package/templates/src/lib/session.ts +126 -0
  335. package/templates/src/lib/storyData.ts +56 -0
  336. package/templates/src/middleware.ts +52 -0
  337. package/templates/src/pages/404.astro +54 -0
  338. package/templates/src/pages/[...slug]/edit.astro +216 -0
  339. package/templates/src/pages/[...slug].astro +148 -0
  340. package/templates/src/pages/api/auth/decode.ts +101 -0
  341. package/templates/src/pages/api/auth/login.ts +122 -0
  342. package/templates/src/pages/api/auth/logout.ts +37 -0
  343. package/templates/src/pages/api/auth/profile.ts +76 -0
  344. package/templates/src/pages/api/orphan-analysis.ts +106 -0
  345. package/templates/src/pages/api/tailwind.ts +116 -0
  346. package/templates/src/pages/collections/[param1].astro +65 -0
  347. package/templates/src/pages/context/[...contextSlug]/edit.astro +207 -0
  348. package/templates/src/pages/context/[...contextSlug].astro +161 -0
  349. package/templates/src/pages/llms.txt.ts +122 -0
  350. package/templates/src/pages/maint.astro +183 -0
  351. package/templates/src/pages/media/[...slug].astro +67 -0
  352. package/templates/src/pages/robots.txt.ts +36 -0
  353. package/templates/src/pages/sandbox/activate.astro +258 -0
  354. package/templates/src/pages/sandbox/register.astro +44 -0
  355. package/templates/src/pages/sandbox/success.astro +179 -0
  356. package/templates/src/pages/sitemap.xml.ts +119 -0
  357. package/templates/src/pages/storykeep/advanced.astro +69 -0
  358. package/templates/src/pages/storykeep/branding.astro +57 -0
  359. package/templates/src/pages/storykeep/content.astro +71 -0
  360. package/templates/src/pages/storykeep/init.astro +36 -0
  361. package/templates/src/pages/storykeep/login.astro +266 -0
  362. package/templates/src/pages/storykeep/logout.astro +84 -0
  363. package/templates/src/pages/storykeep/profile.astro +98 -0
  364. package/templates/src/pages/storykeep.astro +81 -0
  365. package/templates/src/stores/analytics.ts +171 -0
  366. package/templates/src/stores/backend.ts +16 -0
  367. package/templates/src/stores/navigation.ts +149 -0
  368. package/templates/src/stores/nodes.ts +2390 -0
  369. package/templates/src/stores/nodesHistory.ts +85 -0
  370. package/templates/src/stores/notificationSystem.ts +41 -0
  371. package/templates/src/stores/orphanAnalysis.ts +409 -0
  372. package/templates/src/stores/storykeep.ts +247 -0
  373. package/templates/src/types/astro.ts +86 -0
  374. package/templates/src/types/compositorTypes.ts +456 -0
  375. package/templates/src/types/formTypes.ts +281 -0
  376. package/templates/src/types/multiTenant.ts +77 -0
  377. package/templates/src/types/nodeProps.ts +66 -0
  378. package/templates/src/types/tractstack.ts +445 -0
  379. package/templates/src/utils/aai/getTitleSlug.ts +72 -0
  380. package/templates/src/utils/actions/actionButton.ts +101 -0
  381. package/templates/src/utils/actions/lispLexer.ts +57 -0
  382. package/templates/src/utils/actions/preParse_Action.ts +85 -0
  383. package/templates/src/utils/actions/preParse_Bunny.ts +50 -0
  384. package/templates/src/utils/actions/preParse_Clicked.ts +87 -0
  385. package/templates/src/utils/actions/preParse_Impression.ts +71 -0
  386. package/templates/src/utils/api/advancedConfig.ts +66 -0
  387. package/templates/src/utils/api/advancedHelpers.ts +134 -0
  388. package/templates/src/utils/api/beliefConfig.ts +87 -0
  389. package/templates/src/utils/api/beliefHelpers.ts +196 -0
  390. package/templates/src/utils/api/brandConfig.ts +126 -0
  391. package/templates/src/utils/api/brandHelpers.ts +155 -0
  392. package/templates/src/utils/api/fileHelpers.ts +306 -0
  393. package/templates/src/utils/api/menuConfig.ts +57 -0
  394. package/templates/src/utils/api/menuHelpers.ts +156 -0
  395. package/templates/src/utils/api/resourceConfig.ts +158 -0
  396. package/templates/src/utils/api/resourceHelpers.ts +72 -0
  397. package/templates/src/utils/api/tenantConfig.ts +97 -0
  398. package/templates/src/utils/api/tenantHelpers.ts +172 -0
  399. package/templates/src/utils/api.ts +183 -0
  400. package/templates/src/utils/auth.ts +150 -0
  401. package/templates/src/utils/backend.ts +243 -0
  402. package/templates/src/utils/compositor/TemplateMarkdowns.ts +118 -0
  403. package/templates/src/utils/compositor/TemplateNodes.ts +138 -0
  404. package/templates/src/utils/compositor/TemplatePanes.ts +100 -0
  405. package/templates/src/utils/compositor/allowInsert.ts +100 -0
  406. package/templates/src/utils/compositor/domHelpers.ts +37 -0
  407. package/templates/src/utils/compositor/handleClickEvent.ts +131 -0
  408. package/templates/src/utils/compositor/nodesHelper.ts +491 -0
  409. package/templates/src/utils/compositor/nodesMarkdownGenerator.ts +292 -0
  410. package/templates/src/utils/compositor/processMarkdown.ts +431 -0
  411. package/templates/src/utils/compositor/reduceNodesClassNames.ts +192 -0
  412. package/templates/src/utils/compositor/tailwindClasses.ts +1795 -0
  413. package/templates/src/utils/compositor/tailwindColors.ts +227 -0
  414. package/templates/src/utils/compositor/templateMarkdownStyles.ts +1265 -0
  415. package/templates/src/utils/compositor/typeGuards.ts +193 -0
  416. package/templates/src/utils/etl/extractor.ts +119 -0
  417. package/templates/src/utils/etl/index.ts +88 -0
  418. package/templates/src/utils/etl/loader.ts +36 -0
  419. package/templates/src/utils/etl/transformer.ts +286 -0
  420. package/templates/src/utils/helpers.ts +435 -0
  421. package/templates/src/utils/layout.ts +209 -0
  422. package/templates/src/utils/profileStorage.ts +306 -0
  423. package/templates/src/utils/useInterval.ts +27 -0
  424. package/templates/tailwind.config.cjs +169 -0
  425. package/utils/create-resolver.ts +10 -0
  426. package/utils/inject-files.ts +2140 -0
  427. package/utils/validate-config.ts +43 -0
@@ -0,0 +1,286 @@
1
+ import { extractPaneSubtree, type PaneSubtree } from './extractor';
2
+ import { formatForSave, formatForPreview } from './loader';
3
+ import { storyFragmentTopicsStore } from '@/stores/storykeep';
4
+ import { getBrandConfig } from '@/utils/api/brandConfig';
5
+ import { fullContentMapStore } from '@/stores/storykeep';
6
+ import type { NodesContext } from '@/stores/nodes';
7
+ import type {
8
+ FlatNode,
9
+ MarkdownPaneFragmentNode,
10
+ VisualBreakNode,
11
+ ArtpackImageNode,
12
+ BgImageNode,
13
+ StoryFragmentNode,
14
+ } from '@/types/compositorTypes';
15
+ import type {
16
+ OptionsPayload,
17
+ BackendPreviewPayload,
18
+ BackendSavePayload,
19
+ } from './index';
20
+ import {
21
+ isBreakNode,
22
+ isArtpackImageNode,
23
+ isBgImageNode,
24
+ } from '@/utils/compositor/typeGuards';
25
+
26
+ const VERBOSE = false;
27
+
28
+ export function transformToOptionsPayload(
29
+ ctx: NodesContext,
30
+ subtree: PaneSubtree
31
+ ): OptionsPayload {
32
+ if (VERBOSE)
33
+ console.log('🔄 TRANSFORMER START - subtree:', {
34
+ paneNodeId: subtree.paneNode.id,
35
+ childNodeCount: subtree.allChildNodes.length,
36
+ allChildNodes: subtree.allChildNodes.map((n) => ({
37
+ id: n.id,
38
+ nodeType: n.nodeType,
39
+ parentId: n.parentId,
40
+ tagName: (n as any).tagName,
41
+ copy: (n as any).copy,
42
+ })),
43
+ });
44
+
45
+ // 1. Generate flattened nodes array with computed CSS
46
+ const flattenedNodes = subtree.allChildNodes
47
+ .map((node) => {
48
+ if (VERBOSE)
49
+ console.log('🔧 TRANSFORMER - Processing node:', {
50
+ id: node.id,
51
+ nodeType: node.nodeType,
52
+ parentId: node.parentId,
53
+ });
54
+
55
+ const baseNode = {
56
+ id: node.id,
57
+ nodeType: node.nodeType,
58
+ parentId: node.parentId,
59
+ };
60
+
61
+ // Add type-specific fields based on node type
62
+ if (node.nodeType === 'TagElement') {
63
+ const flatNode = node as FlatNode;
64
+
65
+ // Compute CSS using existing NodesContext methods
66
+ let computedCSS: string | undefined;
67
+ try {
68
+ computedCSS = ctx.getNodeClasses(node.id, 'auto', 0);
69
+ } catch (error) {
70
+ console.warn(`Failed to compute CSS for node ${node.id}:`, error);
71
+ }
72
+
73
+ const transformedNode = {
74
+ ...baseNode,
75
+ tagName: flatNode.tagName,
76
+ copy: flatNode.copy,
77
+ elementCss: computedCSS,
78
+ isPlaceholder: flatNode.isPlaceholder,
79
+ src: flatNode.src,
80
+ base64Data: flatNode.base64Data,
81
+ href: flatNode.href,
82
+ alt: flatNode.alt,
83
+ fileId: flatNode.fileId,
84
+ codeHookParams: flatNode.codeHookParams,
85
+ buttonPayload: flatNode.buttonPayload,
86
+ overrideClasses: flatNode.overrideClasses,
87
+ };
88
+
89
+ if (VERBOSE)
90
+ console.log('✅ TRANSFORMER - TagElement result:', transformedNode);
91
+ return transformedNode;
92
+ }
93
+
94
+ if (node.nodeType === 'Markdown') {
95
+ const markdownNode = node as MarkdownPaneFragmentNode;
96
+
97
+ // Compute parentCss if parentClasses exist
98
+ let parentCss: string[] | undefined;
99
+ if (markdownNode.parentClasses) {
100
+ try {
101
+ parentCss = markdownNode.parentClasses.map((_, index) =>
102
+ ctx.getNodeClasses(node.id, 'auto', index)
103
+ );
104
+ } catch (error) {
105
+ console.warn(
106
+ `Failed to compute parent CSS for markdown node ${node.id}:`,
107
+ error
108
+ );
109
+ }
110
+ }
111
+
112
+ const transformedNode = {
113
+ ...baseNode,
114
+ type: markdownNode.type,
115
+ markdownId: markdownNode.markdownId,
116
+ defaultClasses: markdownNode.defaultClasses,
117
+ parentClasses: markdownNode.parentClasses,
118
+ parentCss: parentCss,
119
+ hiddenViewportMobile: markdownNode.hiddenViewportMobile,
120
+ hiddenViewportTablet: markdownNode.hiddenViewportTablet,
121
+ hiddenViewportDesktop: markdownNode.hiddenViewportDesktop,
122
+ };
123
+
124
+ if (VERBOSE)
125
+ console.log('✅ TRANSFORMER - Markdown result:', transformedNode);
126
+ return transformedNode;
127
+ }
128
+
129
+ if (node.nodeType === 'BgPane') {
130
+ // Handle different BgPane types
131
+ if (isBreakNode(node as FlatNode)) {
132
+ const breakNode = node as VisualBreakNode;
133
+ const transformedNode = {
134
+ ...baseNode,
135
+ type: 'visual-break',
136
+ breakDesktop: breakNode.breakDesktop,
137
+ breakTablet: breakNode.breakTablet,
138
+ breakMobile: breakNode.breakMobile,
139
+ };
140
+ if (VERBOSE)
141
+ console.log(
142
+ '✅ TRANSFORMER - BgPane (visual-break) result:',
143
+ transformedNode
144
+ );
145
+ return transformedNode;
146
+ }
147
+
148
+ if (isArtpackImageNode(node)) {
149
+ const artpackNode = node as ArtpackImageNode;
150
+ const transformedNode = {
151
+ ...baseNode,
152
+ type: artpackNode.type,
153
+ collection: artpackNode.collection,
154
+ image: artpackNode.image,
155
+ src: artpackNode.src,
156
+ srcSet: artpackNode.srcSet,
157
+ alt: artpackNode.alt,
158
+ objectFit: artpackNode.objectFit,
159
+ position: artpackNode.position,
160
+ size: artpackNode.size,
161
+ };
162
+ if (VERBOSE)
163
+ console.log(
164
+ '✅ TRANSFORMER - BgPane (artpack-image) result:',
165
+ transformedNode
166
+ );
167
+ return transformedNode;
168
+ }
169
+
170
+ if (isBgImageNode(node)) {
171
+ const bgImageNode = node as BgImageNode;
172
+ const transformedNode = {
173
+ ...baseNode,
174
+ type: bgImageNode.type,
175
+ fileId: bgImageNode.fileId,
176
+ src: bgImageNode.src,
177
+ srcSet: bgImageNode.srcSet,
178
+ alt: bgImageNode.alt,
179
+ base64Data: bgImageNode.base64Data,
180
+ objectFit: bgImageNode.objectFit,
181
+ position: bgImageNode.position,
182
+ size: bgImageNode.size,
183
+ };
184
+ if (VERBOSE)
185
+ console.log(
186
+ '✅ TRANSFORMER - BgPane (background-image) result:',
187
+ transformedNode
188
+ );
189
+ return transformedNode;
190
+ }
191
+
192
+ // Fallback for unknown BgPane types
193
+ if (VERBOSE)
194
+ console.warn('⚠️ TRANSFORMER - Unknown BgPane type:', node);
195
+ return baseNode;
196
+ }
197
+
198
+ // Unknown node type - return base node
199
+ if (VERBOSE) console.warn('⚠️ TRANSFORMER - Unknown node type:', node);
200
+ return baseNode;
201
+ })
202
+ .filter((node) => node !== null);
203
+
204
+ // 2. Build final OptionsPayload
205
+ const optionsPayload: OptionsPayload = {
206
+ bgColour: subtree.paneNode.bgColour,
207
+ isDecorative: subtree.paneNode.isDecorative,
208
+ codeHookTarget: subtree.paneNode.codeHookTarget,
209
+ heldBeliefs: subtree.paneNode.heldBeliefs ?? {},
210
+ withheldBeliefs: subtree.paneNode.withheldBeliefs ?? {},
211
+ codeHookPayload: subtree.paneNode.codeHookPayload,
212
+ nodes: flattenedNodes,
213
+ };
214
+
215
+ if (VERBOSE)
216
+ console.log('✅ TRANSFORMER COMPLETE - Final payload:', {
217
+ nodeCount: optionsPayload.nodes.length,
218
+ bgColour: optionsPayload.bgColour,
219
+ isDecorative: optionsPayload.isDecorative,
220
+ });
221
+
222
+ return optionsPayload;
223
+ }
224
+
225
+ export async function transformStoryFragmentForSave(
226
+ ctx: NodesContext,
227
+ fragmentId: string,
228
+ tenantId: string
229
+ ): Promise<any> {
230
+ const node = ctx.allNodes.get().get(fragmentId) as StoryFragmentNode;
231
+ const seoData = storyFragmentTopicsStore.get()[fragmentId];
232
+
233
+ // Get brand config from store to find default tractstack
234
+ const brandConfig = await getBrandConfig(tenantId);
235
+ const defaultTractStackSlug =
236
+ brandConfig?.TRACTSTACK_HOME_SLUG || 'tractstack';
237
+ // Find the default tractstack ID from content map
238
+ const contentMap = fullContentMapStore.get();
239
+ const defaultTractStack = contentMap.find(
240
+ (item) => item.type === 'TractStack' && item.slug === defaultTractStackSlug
241
+ );
242
+ const finalTractStackId =
243
+ (node as any)?.tractStackId || defaultTractStack?.id || '';
244
+
245
+ const payload = {
246
+ ...node,
247
+ // Add deferred SEO data if available
248
+ ...(seoData && {
249
+ topics: seoData.topics?.map((t) => t.title) || [],
250
+ description: seoData.description || '',
251
+ }),
252
+ // Ensure tractStackId is set for new StoryFragments
253
+ tractStackId: finalTractStackId,
254
+ };
255
+
256
+ return payload;
257
+ }
258
+
259
+ export function transformLivePaneForSave(
260
+ ctx: NodesContext,
261
+ paneId: string,
262
+ isContext?: boolean
263
+ ): BackendSavePayload {
264
+ // 1. Extract distributed state
265
+ const subtree = extractPaneSubtree(ctx, paneId);
266
+
267
+ // 2. Transform to flattened OptionsPayload using existing NodesContext methods
268
+ const optionsPayload = transformToOptionsPayload(ctx, subtree);
269
+
270
+ // 3. Format for save endpoint
271
+ return formatForSave(subtree.paneNode, optionsPayload, isContext);
272
+ }
273
+
274
+ export function transformLivePaneForPreview(
275
+ ctx: NodesContext,
276
+ paneId: string
277
+ ): BackendPreviewPayload {
278
+ // 1. Extract distributed state
279
+ const subtree = extractPaneSubtree(ctx, paneId);
280
+
281
+ // 2. Transform to flattened OptionsPayload using existing NodesContext methods
282
+ const optionsPayload = transformToOptionsPayload(ctx, subtree);
283
+
284
+ // 3. Format for preview endpoint
285
+ return formatForPreview(subtree.paneNode, optionsPayload);
286
+ }
@@ -0,0 +1,435 @@
1
+ import { useState, useEffect, useCallback } from 'react';
2
+ import { stopWords } from '@/constants/stopWords';
3
+ import type { RefObject } from 'react';
4
+ import type { MenuNode } from '@/types/tractstack';
5
+
6
+ let progressInterval: NodeJS.Timeout | null = null;
7
+ let safetyTimeout: NodeJS.Timeout | null = null;
8
+
9
+ export function startLoadingAnimation() {
10
+ const loadingIndicator = document.getElementById(
11
+ 'loading-indicator'
12
+ ) as HTMLElement;
13
+
14
+ if (
15
+ window.matchMedia('(prefers-reduced-motion: no-preference)').matches &&
16
+ loadingIndicator
17
+ ) {
18
+ // Clear any existing safety timeout
19
+ if (safetyTimeout !== null) {
20
+ clearTimeout(safetyTimeout);
21
+ safetyTimeout = null;
22
+ }
23
+
24
+ loadingIndicator.style.transform = 'scaleX(0)';
25
+ loadingIndicator.style.display = 'block';
26
+
27
+ let progress = 0;
28
+ progressInterval = setInterval(() => {
29
+ progress += 2;
30
+ if (progress > 90) {
31
+ if (progressInterval !== null) {
32
+ clearInterval(progressInterval);
33
+ }
34
+ }
35
+ loadingIndicator.style.transform = `scaleX(${progress / 100})`;
36
+ }, 20);
37
+
38
+ // AUTOMATIC SHUTOFF: Always stop after 10 seconds
39
+ safetyTimeout = setTimeout(() => {
40
+ stopLoadingAnimation();
41
+ }, 10000);
42
+ }
43
+ }
44
+
45
+ export function stopLoadingAnimation() {
46
+ const loadingIndicator = document.getElementById(
47
+ 'loading-indicator'
48
+ ) as HTMLElement;
49
+ const content = document.getElementById('content') as HTMLElement;
50
+
51
+ // Clear safety timeout
52
+ if (safetyTimeout !== null) {
53
+ clearTimeout(safetyTimeout);
54
+ safetyTimeout = null;
55
+ }
56
+
57
+ if (
58
+ window.matchMedia('(prefers-reduced-motion: no-preference)').matches &&
59
+ loadingIndicator
60
+ ) {
61
+ if (progressInterval !== null) {
62
+ clearInterval(progressInterval);
63
+ progressInterval = null;
64
+ }
65
+ loadingIndicator.style.transform = 'scaleX(1)';
66
+ content.style.opacity = '1';
67
+
68
+ setTimeout(() => {
69
+ loadingIndicator.style.display = 'none';
70
+ loadingIndicator.style.transform = 'scaleX(0)';
71
+ }, 300);
72
+ }
73
+ }
74
+
75
+ // Emergency function for debugging stuck loading states
76
+ export function emergencyStopAllLoading() {
77
+ console.warn('EMERGENCY: Force stopping all loading animations');
78
+
79
+ // Clear timers
80
+ if (progressInterval !== null) {
81
+ clearInterval(progressInterval);
82
+ progressInterval = null;
83
+ }
84
+ if (safetyTimeout !== null) {
85
+ clearTimeout(safetyTimeout);
86
+ safetyTimeout = null;
87
+ }
88
+
89
+ // Force hide loading indicator
90
+ const loadingIndicator = document.getElementById('loading-indicator');
91
+ if (loadingIndicator) {
92
+ loadingIndicator.style.display = 'none';
93
+ loadingIndicator.style.transform = 'scaleX(0)';
94
+ }
95
+
96
+ // Reset content opacity
97
+ const content = document.getElementById('content');
98
+ if (content) {
99
+ content.style.opacity = '1';
100
+ }
101
+
102
+ // Hide any spinning animations
103
+ document.querySelectorAll('[class*="animate-spin"]').forEach((el) => {
104
+ (el as HTMLElement).style.display = 'none';
105
+ });
106
+ }
107
+
108
+ // Make emergency function available globally for debugging
109
+ if (typeof window !== 'undefined') {
110
+ (window as any).emergencyStopAllLoading = emergencyStopAllLoading;
111
+ }
112
+
113
+ export function classNames(...classes: string[]) {
114
+ return classes.filter(Boolean).join(` `);
115
+ }
116
+
117
+ export function debounce<T extends (...args: any[]) => void>(
118
+ func: T,
119
+ wait: number
120
+ ): (...args: Parameters<T>) => void {
121
+ let timeout: ReturnType<typeof setTimeout> | null = null;
122
+ return (...args: Parameters<T>) => {
123
+ const later = () => {
124
+ timeout = null;
125
+ func(...args);
126
+ };
127
+ if (timeout !== null) {
128
+ clearTimeout(timeout);
129
+ }
130
+ timeout = setTimeout(later, wait);
131
+ };
132
+ }
133
+
134
+ export function formatDateToYYYYMMDD(date: Date): string {
135
+ const year = date.getFullYear();
136
+ const month = (date.getMonth() + 1).toString().padStart(2, '0');
137
+ const day = date.getDate().toString().padStart(2, '0');
138
+ return `${year}-${month}-${day}`;
139
+ }
140
+
141
+ export function dateToUnixTimestamp(date: Date): number {
142
+ return Math.floor(date.getTime() / 1000);
143
+ }
144
+
145
+ export function cloneDeep<T>(obj: T): T {
146
+ return JSON.parse(JSON.stringify(obj));
147
+ }
148
+
149
+ // UTC date helpers for analytics
150
+ export function formatUTCHourKey(date: Date): string {
151
+ const year = date.getUTCFullYear();
152
+ const month = String(date.getUTCMonth() + 1).padStart(2, '0');
153
+ const day = String(date.getUTCDate()).padStart(2, '0');
154
+ const hour = String(date.getUTCHours()).padStart(2, '0');
155
+ return `${year}-${month}-${day}-${hour}`;
156
+ }
157
+
158
+ export function parseHourKeyToUTCDate(hourKey: string): Date {
159
+ const [year, month, day, hour] = hourKey.split('-').map(Number);
160
+ return new Date(Date.UTC(year, month - 1, day, hour));
161
+ }
162
+
163
+ export function getUTCHourKeysForTimeRange(hours: number): string[] {
164
+ const keys = [];
165
+ const now = new Date(
166
+ Date.UTC(
167
+ new Date().getUTCFullYear(),
168
+ new Date().getUTCMonth(),
169
+ new Date().getUTCDate(),
170
+ new Date().getUTCHours()
171
+ )
172
+ );
173
+ const MAX_ANALYTICS_HOURS = 672;
174
+ const hoursToGet = Math.min(hours, MAX_ANALYTICS_HOURS);
175
+ for (let i = 0; i < hoursToGet; i++) {
176
+ const hourDate = new Date(now.getTime() - i * 60 * 60 * 1000);
177
+ const key = formatUTCHourKey(hourDate);
178
+ keys.push(key);
179
+ }
180
+ return keys;
181
+ }
182
+
183
+ export function createStoryKeepMenu(params: {
184
+ isAuthenticated: boolean;
185
+ isAdmin: boolean;
186
+ }): MenuNode {
187
+ const { isAuthenticated } = params;
188
+
189
+ const links = [];
190
+
191
+ // Add Login link for unauthenticated users
192
+ if (!isAuthenticated) {
193
+ links.unshift({
194
+ name: 'Login',
195
+ description: 'Enter your Story Keep',
196
+ featured: true,
197
+ actionLisp: '(goto (storykeep login))',
198
+ });
199
+ } else {
200
+ // Add Logout link for authenticated users
201
+ links.unshift({
202
+ name: 'Logout',
203
+ description: 'Close this session',
204
+ featured: true,
205
+ actionLisp: '(goto (storykeep logout))',
206
+ });
207
+ }
208
+
209
+ return {
210
+ id: `storykeep`,
211
+ title: 'Story Keep Menu',
212
+ theme: 'default',
213
+ optionsPayload: links,
214
+ };
215
+ }
216
+
217
+ export function joinUrlPaths(base: string, path: string): string {
218
+ // Trim trailing slash from base
219
+ const trimmedBase = base.endsWith('/') ? base.slice(0, -1) : base;
220
+ // Trim leading slash from path
221
+ const trimmedPath = path.startsWith('/') ? path.slice(1) : path;
222
+ // Join with a single slash
223
+ return `${trimmedBase}/${trimmedPath}`;
224
+ }
225
+
226
+ export function isDeepEqual(obj1: any, obj2: any, excludeKeys: string[] = []) {
227
+ // Check if both are the same type and are objects
228
+ if (
229
+ typeof obj1 !== 'object' ||
230
+ typeof obj2 !== 'object' ||
231
+ obj1 === null ||
232
+ obj2 === null
233
+ ) {
234
+ return obj1 === obj2;
235
+ }
236
+ // Get the keys of both objects
237
+ const keys1 = Object.keys(obj1).filter((key) => !excludeKeys.includes(key));
238
+ const keys2 = Object.keys(obj2).filter((key) => !excludeKeys.includes(key));
239
+ // Check if the number of keys is the same
240
+ if (keys1.length !== keys2.length) {
241
+ return false;
242
+ }
243
+ // Check if all keys and their values are equal
244
+ for (const key of keys1) {
245
+ if (
246
+ !keys2.includes(key) ||
247
+ !isDeepEqual(obj1[key], obj2[key], excludeKeys)
248
+ ) {
249
+ return false;
250
+ }
251
+ }
252
+ return true;
253
+ }
254
+
255
+ export function cleanString(s: string): string {
256
+ if (!s) return s;
257
+ s = s.toLowerCase();
258
+ s = s.replace(/[^a-z0-9\s-_]/g, '');
259
+ s = s.replace(/\s+/g, '-');
260
+ const words = s.split(/[-_]/);
261
+ if (words.length > 1) {
262
+ s = words.filter((word) => !stopWords.has(word)).join('-');
263
+ }
264
+ s = s.replace(/^[^a-z]+/, '');
265
+ s = s.replace(/[-_]{2,}/g, '-');
266
+ s = s.replace(/^[-_]+|[-_]+$/g, '');
267
+ if (!s.match(/^[a-z][a-z0-9-_]*[a-z0-9]$/)) {
268
+ s = s.replace(/[^a-z0-9]/g, '');
269
+ }
270
+ return s;
271
+ }
272
+
273
+ export function titleToSlug(title: string, maxLength: number = 50): string {
274
+ const slug = cleanString(title);
275
+ if (slug.length <= maxLength) {
276
+ return slug;
277
+ }
278
+ const words = slug.split('-');
279
+ let result = '';
280
+ for (const word of words) {
281
+ if ((result + (result ? '-' : '') + word).length > maxLength) {
282
+ break;
283
+ }
284
+ result += (result ? '-' : '') + word;
285
+ }
286
+ if (!result) {
287
+ result = slug.slice(0, maxLength);
288
+ }
289
+ return result.replace(/-+$/, '');
290
+ }
291
+
292
+ export function findUniqueSlug(slug: string, existingSlugs: string[]): string {
293
+ if (!existingSlugs.includes(slug)) {
294
+ return slug;
295
+ }
296
+ let counter = 1;
297
+ let newSlug = `${slug}-${counter}`;
298
+ while (existingSlugs.includes(newSlug)) {
299
+ counter++;
300
+ newSlug = `${slug}-${counter}`;
301
+ }
302
+ return newSlug;
303
+ }
304
+
305
+ export const timestampNodeId = (id: string): string => `${id}-${Date.now()}`;
306
+
307
+ interface DropdownDirection {
308
+ openAbove: boolean;
309
+ maxHeight: number;
310
+ }
311
+
312
+ export function useDropdownDirection(
313
+ triggerRef: RefObject<HTMLElement | null>
314
+ ): DropdownDirection {
315
+ const [state, setState] = useState<DropdownDirection>({
316
+ openAbove: false,
317
+ maxHeight: 300,
318
+ });
319
+
320
+ const updateDirection = useCallback(() => {
321
+ if (triggerRef.current) {
322
+ const rect = triggerRef.current.getBoundingClientRect();
323
+ const viewportHeight = window.visualViewport
324
+ ? window.visualViewport.height
325
+ : window.innerHeight;
326
+ const spaceBelow = viewportHeight - rect.bottom;
327
+ const spaceAbove = rect.top;
328
+ const newOpenAbove = spaceBelow < 300 && spaceAbove > spaceBelow;
329
+ const newMaxHeight = newOpenAbove
330
+ ? Math.min(spaceAbove - 10, 300)
331
+ : Math.min(spaceBelow - 10, 300);
332
+ setState({ openAbove: newOpenAbove, maxHeight: newMaxHeight });
333
+ }
334
+ }, [triggerRef]);
335
+
336
+ useEffect(() => {
337
+ updateDirection();
338
+
339
+ const handleResize = () => {
340
+ // Only update if the width changes, to avoid keyboard triggers
341
+ if (window.innerWidth !== window.visualViewport?.width) {
342
+ updateDirection();
343
+ }
344
+ };
345
+
346
+ window.addEventListener('resize', handleResize);
347
+
348
+ // Use ResizeObserver to watch for changes in the trigger element's size or position
349
+ const resizeObserver = new ResizeObserver(updateDirection);
350
+ if (triggerRef.current) {
351
+ resizeObserver.observe(triggerRef.current);
352
+ }
353
+
354
+ return () => {
355
+ window.removeEventListener('resize', handleResize);
356
+ resizeObserver.disconnect();
357
+ };
358
+ }, [updateDirection, triggerRef]);
359
+
360
+ return state;
361
+ }
362
+
363
+ export function getSettingsPanelTitle(action: string): string {
364
+ switch (action) {
365
+ case 'style-break':
366
+ return 'Style Visual Break';
367
+
368
+ case 'style-parent':
369
+ case 'style-parent-add':
370
+ case 'style-parent-remove':
371
+ case 'style-parent-update':
372
+ case 'style-parent-delete-layer':
373
+ return 'Style Outer Container';
374
+
375
+ case 'style-link':
376
+ return 'Style/Configure Link';
377
+
378
+ case 'style-link-config':
379
+ return 'Configure Link';
380
+
381
+ case 'style-link-add':
382
+ case 'style-link-add-hover':
383
+ case 'style-link-update':
384
+ case 'style-link-update-hover':
385
+ case 'style-link-remove':
386
+ case 'style-link-remove-hover':
387
+ return 'Style Link';
388
+
389
+ case 'style-element':
390
+ case 'style-element-add':
391
+ case 'style-element-remove':
392
+ case 'style-element-update':
393
+ return ''; // "Style Element";
394
+
395
+ case 'style-image':
396
+ case 'style-img-add':
397
+ case 'style-img-container-add':
398
+ case 'style-img-outer-add':
399
+ case 'style-img-update':
400
+ case 'style-img-container-update':
401
+ case 'style-img-outer-update':
402
+ case 'style-img-remove':
403
+ case 'style-img-container-remove':
404
+ case 'style-img-outer-remove':
405
+ return 'Style Image';
406
+
407
+ case 'style-widget':
408
+ return 'Style/Configure Widget';
409
+
410
+ case 'style-code-config':
411
+ case 'setup-codehook':
412
+ return 'Configure Widget';
413
+
414
+ case 'style-code-add':
415
+ case 'style-code-container-add':
416
+ case 'style-code-outer-add':
417
+ case 'style-code-update':
418
+ case 'style-code-container-update':
419
+ case 'style-code-outer-update':
420
+ case 'style-code-remove':
421
+ case 'style-code-container-remove':
422
+ case 'style-code-outer-remove':
423
+ case 'style-li-element':
424
+ case 'style-li-element-add':
425
+ case 'style-li-container-add':
426
+ case 'style-li-element-update':
427
+ case 'style-li-container-update':
428
+ case 'style-li-element-remove':
429
+ case 'style-li-container-remove':
430
+ return 'Style Widget';
431
+
432
+ default:
433
+ return 'Settings';
434
+ }
435
+ }