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.
- package/LICENSE +110 -0
- package/README.md +56 -0
- package/astro.d.ts +64 -0
- package/bin/create-tractstack.js +483 -0
- package/dist/config.js +80 -0
- package/dist/index.js +2129 -0
- package/package.json +89 -0
- package/templates/artpacks/kCz/captainBreakfast_1080px.webp +0 -0
- package/templates/artpacks/kCz/captainBreakfast_1920px.webp +0 -0
- package/templates/artpacks/kCz/captainBreakfast_600px.webp +0 -0
- package/templates/artpacks/kCz/cleanDrips_1080px.webp +0 -0
- package/templates/artpacks/kCz/cleanDrips_1920px.webp +0 -0
- package/templates/artpacks/kCz/cleanDrips_600px.webp +0 -0
- package/templates/artpacks/kCz/crispwaves_1080px.webp +0 -0
- package/templates/artpacks/kCz/crispwaves_1920px.webp +0 -0
- package/templates/artpacks/kCz/crispwaves_600px.webp +0 -0
- package/templates/artpacks/kCz/dragonSkin_1080px.webp +0 -0
- package/templates/artpacks/kCz/dragonSkin_1920px.webp +0 -0
- package/templates/artpacks/kCz/dragonSkin_600px.webp +0 -0
- package/templates/artpacks/kCz/dragon_1080px.webp +0 -0
- package/templates/artpacks/kCz/dragon_1920px.webp +0 -0
- package/templates/artpacks/kCz/dragon_600px.webp +0 -0
- package/templates/artpacks/kCz/nightcity_1080px.webp +0 -0
- package/templates/artpacks/kCz/nightcity_1920px.webp +0 -0
- package/templates/artpacks/kCz/nightcity_600px.webp +0 -0
- package/templates/artpacks/kCz/pattern1_1080px.webp +0 -0
- package/templates/artpacks/kCz/pattern1_1920px.webp +0 -0
- package/templates/artpacks/kCz/pattern1_600px.webp +0 -0
- package/templates/artpacks/kCz/pattern2_1080px.webp +0 -0
- package/templates/artpacks/kCz/pattern2_1920px.webp +0 -0
- package/templates/artpacks/kCz/pattern2_600px.webp +0 -0
- package/templates/artpacks/kCz/skindrips_1080px.webp +0 -0
- package/templates/artpacks/kCz/skindrips_1920px.webp +0 -0
- package/templates/artpacks/kCz/skindrips_600px.webp +0 -0
- package/templates/artpacks/kCz/slimetime_1080px.webp +0 -0
- package/templates/artpacks/kCz/slimetime_1920px.webp +0 -0
- package/templates/artpacks/kCz/slimetime_600px.webp +0 -0
- package/templates/artpacks/kCz/snake_1080px.webp +0 -0
- package/templates/artpacks/kCz/snake_1920px.webp +0 -0
- package/templates/artpacks/kCz/snake_600px.webp +0 -0
- package/templates/artpacks/kCz/toxicshock_1080px.webp +0 -0
- package/templates/artpacks/kCz/toxicshock_1920px.webp +0 -0
- package/templates/artpacks/kCz/toxicshock_600px.webp +0 -0
- package/templates/artpacks/kCz/tractstack_1080px.webp +0 -0
- package/templates/artpacks/kCz/tractstack_1920px.webp +0 -0
- package/templates/artpacks/kCz/tractstack_600px.webp +0 -0
- package/templates/artpacks/kCz/tripdrips_1080px.webp +0 -0
- package/templates/artpacks/kCz/tripdrips_1920px.webp +0 -0
- package/templates/artpacks/kCz/tripdrips_600px.webp +0 -0
- package/templates/artpacks/kCz/wavedrips_1080px.webp +0 -0
- package/templates/artpacks/kCz/wavedrips_1920px.webp +0 -0
- package/templates/artpacks/kCz/wavedrips_600px.webp +0 -0
- package/templates/artpacks/t8k/beach_1080px.webp +0 -0
- package/templates/artpacks/t8k/beach_1920px.webp +0 -0
- package/templates/artpacks/t8k/beach_600px.webp +0 -0
- package/templates/artpacks/t8k/blast_1080px.webp +0 -0
- package/templates/artpacks/t8k/blast_1920px.webp +0 -0
- package/templates/artpacks/t8k/blast_600px.webp +0 -0
- package/templates/artpacks/t8k/bokeh_1080px.webp +0 -0
- package/templates/artpacks/t8k/bokeh_1920px.webp +0 -0
- package/templates/artpacks/t8k/bokeh_600px.webp +0 -0
- package/templates/artpacks/t8k/cartoon_1080px.webp +0 -0
- package/templates/artpacks/t8k/cartoon_1920px.webp +0 -0
- package/templates/artpacks/t8k/cartoon_600px.webp +0 -0
- package/templates/artpacks/t8k/darkeggshell_1080px.webp +0 -0
- package/templates/artpacks/t8k/darkeggshell_1920px.webp +0 -0
- package/templates/artpacks/t8k/darkeggshell_600px.webp +0 -0
- package/templates/artpacks/t8k/explosion_1080px.webp +0 -0
- package/templates/artpacks/t8k/explosion_1920px.webp +0 -0
- package/templates/artpacks/t8k/explosion_600px.webp +0 -0
- package/templates/artpacks/t8k/floral_1080px.webp +0 -0
- package/templates/artpacks/t8k/floral_1920px.webp +0 -0
- package/templates/artpacks/t8k/floral_600px.webp +0 -0
- package/templates/artpacks/t8k/flower_1080px.webp +0 -0
- package/templates/artpacks/t8k/flower_1920px.webp +0 -0
- package/templates/artpacks/t8k/flower_600px.webp +0 -0
- package/templates/artpacks/t8k/foliage_1080px.webp +0 -0
- package/templates/artpacks/t8k/foliage_1920px.webp +0 -0
- package/templates/artpacks/t8k/foliage_600px.webp +0 -0
- package/templates/artpacks/t8k/mist_1080px.webp +0 -0
- package/templates/artpacks/t8k/mist_1920px.webp +0 -0
- package/templates/artpacks/t8k/mist_600px.webp +0 -0
- package/templates/artpacks/t8k/portal_1080px.webp +0 -0
- package/templates/artpacks/t8k/portal_1920px.webp +0 -0
- package/templates/artpacks/t8k/portal_600px.webp +0 -0
- package/templates/artpacks/t8k/storytime_1080px.webp +0 -0
- package/templates/artpacks/t8k/storytime_1920px.webp +0 -0
- package/templates/artpacks/t8k/storytime_600px.webp +0 -0
- package/templates/artpacks/t8k/tacky_1080px.webp +0 -0
- package/templates/artpacks/t8k/tacky_1920px.webp +0 -0
- package/templates/artpacks/t8k/tacky_600px.webp +0 -0
- package/templates/artpacks/t8k/wallpaper_1080px.webp +0 -0
- package/templates/artpacks/t8k/wallpaper_1920px.webp +0 -0
- package/templates/artpacks/t8k/wallpaper_600px.webp +0 -0
- package/templates/brand/favicon.ico +0 -0
- package/templates/brand/logo.svg +19 -0
- package/templates/brand/static.jpg +0 -0
- package/templates/brand/wordmark.svg +4 -0
- package/templates/css/custom.css +51 -0
- package/templates/css/frontend.css +3519 -0
- package/templates/css/storykeep.css +92872 -0
- package/templates/custom/minimal/CodeHook.astro +53 -0
- package/templates/custom/minimal/CustomRoutes.astro +46 -0
- package/templates/custom/with-examples/CodeHook.astro +49 -0
- package/templates/custom/with-examples/CustomHero.astro +13 -0
- package/templates/custom/with-examples/CustomRoutes.astro +39 -0
- package/templates/custom/with-examples/pages/Collections.astro +110 -0
- package/templates/env.example +8 -0
- package/templates/fonts/Inter-Black.woff2 +0 -0
- package/templates/fonts/Inter-Bold.woff2 +0 -0
- package/templates/fonts/Inter-Regular.woff2 +0 -0
- package/templates/icons/h2.svg +1 -0
- package/templates/icons/h3.svg +1 -0
- package/templates/icons/h4.svg +1 -0
- package/templates/icons/h5.svg +1 -0
- package/templates/icons/image.svg +7 -0
- package/templates/icons/text.svg +6 -0
- package/templates/socials/codepen.svg +1 -0
- package/templates/socials/discord.svg +1 -0
- package/templates/socials/facebook.svg +1 -0
- package/templates/socials/github.svg +1 -0
- package/templates/socials/instagram.svg +1 -0
- package/templates/socials/linkedin.svg +1 -0
- package/templates/socials/mail.svg +1 -0
- package/templates/socials/rumble.svg +1 -0
- package/templates/socials/tiktok.svg +1 -0
- package/templates/socials/twitch.svg +1 -0
- package/templates/socials/twitter.svg +1 -0
- package/templates/socials/x.svg +1 -0
- package/templates/socials/youtube.svg +1 -0
- package/templates/src/client/analytics-events.ts +213 -0
- package/templates/src/client/belief-events.ts +205 -0
- package/templates/src/client/sse.ts +667 -0
- package/templates/src/components/Footer.astro +246 -0
- package/templates/src/components/Fragment.astro +70 -0
- package/templates/src/components/Header.astro +458 -0
- package/templates/src/components/Menu.tsx +196 -0
- package/templates/src/components/codehooks/BunnyVideoSetup.tsx +692 -0
- package/templates/src/components/codehooks/BunnyVideoWrapper.astro +78 -0
- package/templates/src/components/codehooks/EpinetDurationSelector.tsx +1020 -0
- package/templates/src/components/codehooks/EpinetTableView.tsx +594 -0
- package/templates/src/components/codehooks/EpinetWrapper.tsx +424 -0
- package/templates/src/components/codehooks/FeaturedContent.astro +273 -0
- package/templates/src/components/codehooks/FeaturedContentSetup.tsx +738 -0
- package/templates/src/components/codehooks/ListContent.astro +460 -0
- package/templates/src/components/codehooks/ListContentSetup.tsx +649 -0
- package/templates/src/components/codehooks/SankeyDiagram.tsx +359 -0
- package/templates/src/components/compositor/Compositor.tsx +144 -0
- package/templates/src/components/compositor/Node.tsx +415 -0
- package/templates/src/components/compositor/NodeWithGuid.tsx +25 -0
- package/templates/src/components/compositor/PanelVisibilityWrapper.tsx +87 -0
- package/templates/src/components/compositor/elements/Belief.tsx +148 -0
- package/templates/src/components/compositor/elements/BgImage.tsx +118 -0
- package/templates/src/components/compositor/elements/BgVisualBreak.tsx +102 -0
- package/templates/src/components/compositor/elements/BunnyVideo.tsx +63 -0
- package/templates/src/components/compositor/elements/IdentifyAs.tsx +66 -0
- package/templates/src/components/compositor/elements/PlayButton.tsx +19 -0
- package/templates/src/components/compositor/elements/SignUp.tsx +179 -0
- package/templates/src/components/compositor/elements/Svg.tsx +33 -0
- package/templates/src/components/compositor/elements/ToggleBelief.tsx +36 -0
- package/templates/src/components/compositor/elements/YouTubeWrapper.tsx +33 -0
- package/templates/src/components/compositor/nodes/BgPaneWrapper.tsx +35 -0
- package/templates/src/components/compositor/nodes/GhostInsertBlock.tsx +189 -0
- package/templates/src/components/compositor/nodes/Markdown.tsx +179 -0
- package/templates/src/components/compositor/nodes/Pane.tsx +277 -0
- package/templates/src/components/compositor/nodes/Pane_eraser.tsx +69 -0
- package/templates/src/components/compositor/nodes/Pane_layout.tsx +77 -0
- package/templates/src/components/compositor/nodes/RenderChildren.tsx +19 -0
- package/templates/src/components/compositor/nodes/StoryFragment.tsx +35 -0
- package/templates/src/components/compositor/nodes/TagElement.tsx +14 -0
- package/templates/src/components/compositor/nodes/Widget.tsx +115 -0
- package/templates/src/components/compositor/nodes/tagElements/NodeA.tsx +4 -0
- package/templates/src/components/compositor/nodes/tagElements/NodeA_eraser.tsx +26 -0
- package/templates/src/components/compositor/nodes/tagElements/NodeAnchorComponent.tsx +248 -0
- package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag.tsx +684 -0
- package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag_eraser.tsx +62 -0
- package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag_insert.tsx +120 -0
- package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag_settings.tsx +62 -0
- package/templates/src/components/compositor/nodes/tagElements/NodeButton.tsx +5 -0
- package/templates/src/components/compositor/nodes/tagElements/NodeButton_eraser.tsx +26 -0
- package/templates/src/components/compositor/nodes/tagElements/NodeImg.tsx +28 -0
- package/templates/src/components/compositor/nodes/tagElements/NodeText.tsx +18 -0
- package/templates/src/components/compositor/nodes/tagElements/TabIndicator.tsx +51 -0
- package/templates/src/components/compositor/preview/FeaturedContentPreview.tsx +128 -0
- package/templates/src/components/compositor/preview/ListContentPreview.tsx +213 -0
- package/templates/src/components/compositor/preview/OgImagePreview.tsx +223 -0
- package/templates/src/components/compositor/preview/PaneSnapshotGenerator.tsx +199 -0
- package/templates/src/components/compositor/preview/PanesPreviewGenerator.tsx +123 -0
- package/templates/src/components/compositor/preview/VisualBreakPreview.tsx +154 -0
- package/templates/src/components/edit/Header.tsx +181 -0
- package/templates/src/components/edit/PanelSwitch.tsx +446 -0
- package/templates/src/components/edit/SettingsPanel.tsx +70 -0
- package/templates/src/components/edit/ToolBar.tsx +101 -0
- package/templates/src/components/edit/ToolMode.tsx +121 -0
- package/templates/src/components/edit/context/ContextPaneConfig.tsx +91 -0
- package/templates/src/components/edit/context/ContextPaneConfig_slug.tsx +174 -0
- package/templates/src/components/edit/context/ContextPaneConfig_title.tsx +186 -0
- package/templates/src/components/edit/pane/AddPanePanel.tsx +136 -0
- package/templates/src/components/edit/pane/AddPanePanel_break.tsx +470 -0
- package/templates/src/components/edit/pane/AddPanePanel_codehook.tsx +264 -0
- package/templates/src/components/edit/pane/AddPanePanel_new.tsx +623 -0
- package/templates/src/components/edit/pane/AddPanePanel_newAICopy.tsx +107 -0
- package/templates/src/components/edit/pane/AddPanePanel_newAICopy_modal.tsx +217 -0
- package/templates/src/components/edit/pane/AddPanePanel_newCopyMode.tsx +109 -0
- package/templates/src/components/edit/pane/AddPanePanel_newCustomCopy.tsx +39 -0
- package/templates/src/components/edit/pane/AddPanePanel_reuse.tsx +445 -0
- package/templates/src/components/edit/pane/ConfigPanePanel.tsx +245 -0
- package/templates/src/components/edit/pane/PageGen.tsx +485 -0
- package/templates/src/components/edit/pane/PageGenSelector.tsx +238 -0
- package/templates/src/components/edit/pane/PageGenSpecial.tsx +362 -0
- package/templates/src/components/edit/pane/PageGen_preview.tsx +495 -0
- package/templates/src/components/edit/pane/PanePanel_impression.tsx +258 -0
- package/templates/src/components/edit/pane/PanePanel_path.tsx +268 -0
- package/templates/src/components/edit/pane/PanePanel_slug.tsx +219 -0
- package/templates/src/components/edit/pane/PanePanel_title.tsx +142 -0
- package/templates/src/components/edit/panels/StyleBreakPanel.tsx +182 -0
- package/templates/src/components/edit/panels/StyleCodeHookPanel.tsx +439 -0
- package/templates/src/components/edit/panels/StyleElementPanel.tsx +177 -0
- package/templates/src/components/edit/panels/StyleElementPanel_add.tsx +349 -0
- package/templates/src/components/edit/panels/StyleElementPanel_remove.tsx +159 -0
- package/templates/src/components/edit/panels/StyleElementPanel_update.tsx +320 -0
- package/templates/src/components/edit/panels/StyleImagePanel.tsx +460 -0
- package/templates/src/components/edit/panels/StyleImagePanel_add.tsx +296 -0
- package/templates/src/components/edit/panels/StyleImagePanel_remove.tsx +153 -0
- package/templates/src/components/edit/panels/StyleImagePanel_update.tsx +312 -0
- package/templates/src/components/edit/panels/StyleLiElementPanel.tsx +273 -0
- package/templates/src/components/edit/panels/StyleLiElementPanel_add.tsx +301 -0
- package/templates/src/components/edit/panels/StyleLiElementPanel_remove.tsx +132 -0
- package/templates/src/components/edit/panels/StyleLiElementPanel_update.tsx +313 -0
- package/templates/src/components/edit/panels/StyleLinkPanel.tsx +346 -0
- package/templates/src/components/edit/panels/StyleLinkPanel_add.tsx +265 -0
- package/templates/src/components/edit/panels/StyleLinkPanel_config.tsx +240 -0
- package/templates/src/components/edit/panels/StyleLinkPanel_remove.tsx +94 -0
- package/templates/src/components/edit/panels/StyleLinkPanel_update.tsx +110 -0
- package/templates/src/components/edit/panels/StyleParentPanel.tsx +263 -0
- package/templates/src/components/edit/panels/StyleParentPanel_add.tsx +275 -0
- package/templates/src/components/edit/panels/StyleParentPanel_deleteLayer.tsx +112 -0
- package/templates/src/components/edit/panels/StyleParentPanel_remove.tsx +87 -0
- package/templates/src/components/edit/panels/StyleParentPanel_update.tsx +141 -0
- package/templates/src/components/edit/panels/StyleWidgetPanel.tsx +428 -0
- package/templates/src/components/edit/panels/StyleWidgetPanel_add.tsx +292 -0
- package/templates/src/components/edit/panels/StyleWidgetPanel_config.tsx +190 -0
- package/templates/src/components/edit/panels/StyleWidgetPanel_remove.tsx +152 -0
- package/templates/src/components/edit/panels/StyleWidgetPanel_update.tsx +308 -0
- package/templates/src/components/edit/state/SaveModal.tsx +811 -0
- package/templates/src/components/edit/state/StylesMemory.tsx +310 -0
- package/templates/src/components/edit/storyfragment/StoryFragmentConfigPanel.tsx +289 -0
- package/templates/src/components/edit/storyfragment/StoryFragmentPanel_menu.tsx +320 -0
- package/templates/src/components/edit/storyfragment/StoryFragmentPanel_og.tsx +888 -0
- package/templates/src/components/edit/storyfragment/StoryFragmentPanel_slug.tsx +269 -0
- package/templates/src/components/edit/storyfragment/StoryFragmentPanel_title.tsx +190 -0
- package/templates/src/components/edit/widgets/BeliefWidget.tsx +183 -0
- package/templates/src/components/edit/widgets/BunnyWidget.tsx +134 -0
- package/templates/src/components/edit/widgets/IdentifyAsWidget.tsx +193 -0
- package/templates/src/components/edit/widgets/SignupWidget.tsx +177 -0
- package/templates/src/components/edit/widgets/ToggleWidget.tsx +152 -0
- package/templates/src/components/edit/widgets/YouTubeWidget.tsx +65 -0
- package/templates/src/components/fields/ActionBuilderTimeSelector.tsx +353 -0
- package/templates/src/components/fields/ArtpackImage.tsx +480 -0
- package/templates/src/components/fields/BackgroundImage.tsx +530 -0
- package/templates/src/components/fields/BackgroundImageWrapper.tsx +192 -0
- package/templates/src/components/fields/BooleanParam.tsx +67 -0
- package/templates/src/components/fields/BunnyMomentSelector.tsx +56 -0
- package/templates/src/components/fields/ColorPickerCombo.tsx +284 -0
- package/templates/src/components/fields/ImageUpload.tsx +405 -0
- package/templates/src/components/fields/MultiParam.tsx +75 -0
- package/templates/src/components/fields/PaneBreakCollectionSelector.tsx +97 -0
- package/templates/src/components/fields/PaneBreakShapeSelector.tsx +134 -0
- package/templates/src/components/fields/SelectedTailwindClass.tsx +44 -0
- package/templates/src/components/fields/SingleParam.tsx +73 -0
- package/templates/src/components/fields/ViewportComboBox.tsx +252 -0
- package/templates/src/components/form/ActionBuilderField.tsx +282 -0
- package/templates/src/components/form/ActionBuilderSlugSelector.tsx +182 -0
- package/templates/src/components/form/BooleanToggle.tsx +94 -0
- package/templates/src/components/form/ColorPicker.tsx +153 -0
- package/templates/src/components/form/DateTimeInput.tsx +638 -0
- package/templates/src/components/form/EnumSelect.tsx +88 -0
- package/templates/src/components/form/FileUpload.tsx +465 -0
- package/templates/src/components/form/MagicPathBuilder.tsx +546 -0
- package/templates/src/components/form/NumberInput.tsx +101 -0
- package/templates/src/components/form/ParagraphArrayInput.tsx +207 -0
- package/templates/src/components/form/StringArrayInput.tsx +163 -0
- package/templates/src/components/form/StringInput.tsx +88 -0
- package/templates/src/components/form/UnsavedChangesBar.tsx +295 -0
- package/templates/src/components/form/advanced/APIConfigSection.tsx +69 -0
- package/templates/src/components/form/advanced/AuthConfigSection.tsx +97 -0
- package/templates/src/components/form/brand/BrandAssetsSection.tsx +93 -0
- package/templates/src/components/form/brand/BrandColorsSection.tsx +201 -0
- package/templates/src/components/form/brand/SEOSection.tsx +101 -0
- package/templates/src/components/form/brand/SiteConfigSection.tsx +61 -0
- package/templates/src/components/form/brand/SocialLinksSection.tsx +393 -0
- package/templates/src/components/profile/ProfileConsent.tsx +65 -0
- package/templates/src/components/profile/ProfileCreate.tsx +462 -0
- package/templates/src/components/profile/ProfileEdit.tsx +409 -0
- package/templates/src/components/profile/ProfileSwitch.tsx +255 -0
- package/templates/src/components/profile/ProfileUnlock.tsx +221 -0
- package/templates/src/components/storykeep/Dashboard.tsx +160 -0
- package/templates/src/components/storykeep/Dashboard_Activity.tsx +56 -0
- package/templates/src/components/storykeep/Dashboard_Advanced.tsx +165 -0
- package/templates/src/components/storykeep/Dashboard_Analytics.tsx +451 -0
- package/templates/src/components/storykeep/Dashboard_Branding.tsx +95 -0
- package/templates/src/components/storykeep/Dashboard_Content.tsx +191 -0
- package/templates/src/components/storykeep/controls/UsageCell.tsx +71 -0
- package/templates/src/components/storykeep/controls/content/BeliefForm.tsx +378 -0
- package/templates/src/components/storykeep/controls/content/BeliefTable.tsx +329 -0
- package/templates/src/components/storykeep/controls/content/ContentBrowser.tsx +385 -0
- package/templates/src/components/storykeep/controls/content/ContentSummary.tsx +149 -0
- package/templates/src/components/storykeep/controls/content/KnownResourceForm.tsx +397 -0
- package/templates/src/components/storykeep/controls/content/KnownResourceTable.tsx +260 -0
- package/templates/src/components/storykeep/controls/content/ManageContent.tsx +439 -0
- package/templates/src/components/storykeep/controls/content/MenuForm.tsx +239 -0
- package/templates/src/components/storykeep/controls/content/MenuTable.tsx +332 -0
- package/templates/src/components/storykeep/controls/content/ResourceBulkIngest.tsx +724 -0
- package/templates/src/components/storykeep/controls/content/ResourceForm.tsx +355 -0
- package/templates/src/components/storykeep/controls/content/ResourceTable.tsx +222 -0
- package/templates/src/components/storykeep/controls/content/StoryFragmentTable.tsx +482 -0
- package/templates/src/components/storykeep/state/BrandingWrapper.tsx +42 -0
- package/templates/src/components/storykeep/state/FetchAnalytics.tsx +350 -0
- package/templates/src/components/storykeep/widgets/ResponsiveLine.tsx +319 -0
- package/templates/src/components/storykeep/widgets/Wizard.tsx +278 -0
- package/templates/src/components/tenant/RegistrationForm.tsx +447 -0
- package/templates/src/components/widgets/BunnyVideoHero.astro +775 -0
- package/templates/src/components/widgets/Impression.tsx +102 -0
- package/templates/src/components/widgets/ImpressionWrapper.tsx +214 -0
- package/templates/src/constants/beliefs.ts +61 -0
- package/templates/src/constants/brandThemes.ts +133 -0
- package/templates/src/constants/prompts.json +55 -0
- package/templates/src/constants/shapes.ts +556 -0
- package/templates/src/constants/stopWords.ts +116 -0
- package/templates/src/constants/tailwindColors.json +344 -0
- package/templates/src/constants.ts +274 -0
- package/templates/src/hooks/useFormState.ts +203 -0
- package/templates/src/layouts/Layout.astro +290 -0
- package/templates/src/lib/session.ts +126 -0
- package/templates/src/lib/storyData.ts +56 -0
- package/templates/src/middleware.ts +52 -0
- package/templates/src/pages/404.astro +54 -0
- package/templates/src/pages/[...slug]/edit.astro +216 -0
- package/templates/src/pages/[...slug].astro +148 -0
- package/templates/src/pages/api/auth/decode.ts +101 -0
- package/templates/src/pages/api/auth/login.ts +122 -0
- package/templates/src/pages/api/auth/logout.ts +37 -0
- package/templates/src/pages/api/auth/profile.ts +76 -0
- package/templates/src/pages/api/orphan-analysis.ts +106 -0
- package/templates/src/pages/api/tailwind.ts +116 -0
- package/templates/src/pages/collections/[param1].astro +65 -0
- package/templates/src/pages/context/[...contextSlug]/edit.astro +207 -0
- package/templates/src/pages/context/[...contextSlug].astro +161 -0
- package/templates/src/pages/llms.txt.ts +122 -0
- package/templates/src/pages/maint.astro +183 -0
- package/templates/src/pages/media/[...slug].astro +67 -0
- package/templates/src/pages/robots.txt.ts +36 -0
- package/templates/src/pages/sandbox/activate.astro +258 -0
- package/templates/src/pages/sandbox/register.astro +44 -0
- package/templates/src/pages/sandbox/success.astro +179 -0
- package/templates/src/pages/sitemap.xml.ts +119 -0
- package/templates/src/pages/storykeep/advanced.astro +69 -0
- package/templates/src/pages/storykeep/branding.astro +57 -0
- package/templates/src/pages/storykeep/content.astro +71 -0
- package/templates/src/pages/storykeep/init.astro +36 -0
- package/templates/src/pages/storykeep/login.astro +266 -0
- package/templates/src/pages/storykeep/logout.astro +84 -0
- package/templates/src/pages/storykeep/profile.astro +98 -0
- package/templates/src/pages/storykeep.astro +81 -0
- package/templates/src/stores/analytics.ts +171 -0
- package/templates/src/stores/backend.ts +16 -0
- package/templates/src/stores/navigation.ts +149 -0
- package/templates/src/stores/nodes.ts +2390 -0
- package/templates/src/stores/nodesHistory.ts +85 -0
- package/templates/src/stores/notificationSystem.ts +41 -0
- package/templates/src/stores/orphanAnalysis.ts +409 -0
- package/templates/src/stores/storykeep.ts +247 -0
- package/templates/src/types/astro.ts +86 -0
- package/templates/src/types/compositorTypes.ts +456 -0
- package/templates/src/types/formTypes.ts +281 -0
- package/templates/src/types/multiTenant.ts +77 -0
- package/templates/src/types/nodeProps.ts +66 -0
- package/templates/src/types/tractstack.ts +445 -0
- package/templates/src/utils/aai/getTitleSlug.ts +72 -0
- package/templates/src/utils/actions/actionButton.ts +101 -0
- package/templates/src/utils/actions/lispLexer.ts +57 -0
- package/templates/src/utils/actions/preParse_Action.ts +85 -0
- package/templates/src/utils/actions/preParse_Bunny.ts +50 -0
- package/templates/src/utils/actions/preParse_Clicked.ts +87 -0
- package/templates/src/utils/actions/preParse_Impression.ts +71 -0
- package/templates/src/utils/api/advancedConfig.ts +66 -0
- package/templates/src/utils/api/advancedHelpers.ts +134 -0
- package/templates/src/utils/api/beliefConfig.ts +87 -0
- package/templates/src/utils/api/beliefHelpers.ts +196 -0
- package/templates/src/utils/api/brandConfig.ts +126 -0
- package/templates/src/utils/api/brandHelpers.ts +155 -0
- package/templates/src/utils/api/fileHelpers.ts +306 -0
- package/templates/src/utils/api/menuConfig.ts +57 -0
- package/templates/src/utils/api/menuHelpers.ts +156 -0
- package/templates/src/utils/api/resourceConfig.ts +158 -0
- package/templates/src/utils/api/resourceHelpers.ts +72 -0
- package/templates/src/utils/api/tenantConfig.ts +97 -0
- package/templates/src/utils/api/tenantHelpers.ts +172 -0
- package/templates/src/utils/api.ts +183 -0
- package/templates/src/utils/auth.ts +150 -0
- package/templates/src/utils/backend.ts +243 -0
- package/templates/src/utils/compositor/TemplateMarkdowns.ts +118 -0
- package/templates/src/utils/compositor/TemplateNodes.ts +138 -0
- package/templates/src/utils/compositor/TemplatePanes.ts +100 -0
- package/templates/src/utils/compositor/allowInsert.ts +100 -0
- package/templates/src/utils/compositor/domHelpers.ts +37 -0
- package/templates/src/utils/compositor/handleClickEvent.ts +131 -0
- package/templates/src/utils/compositor/nodesHelper.ts +491 -0
- package/templates/src/utils/compositor/nodesMarkdownGenerator.ts +292 -0
- package/templates/src/utils/compositor/processMarkdown.ts +431 -0
- package/templates/src/utils/compositor/reduceNodesClassNames.ts +192 -0
- package/templates/src/utils/compositor/tailwindClasses.ts +1795 -0
- package/templates/src/utils/compositor/tailwindColors.ts +227 -0
- package/templates/src/utils/compositor/templateMarkdownStyles.ts +1265 -0
- package/templates/src/utils/compositor/typeGuards.ts +193 -0
- package/templates/src/utils/etl/extractor.ts +119 -0
- package/templates/src/utils/etl/index.ts +88 -0
- package/templates/src/utils/etl/loader.ts +36 -0
- package/templates/src/utils/etl/transformer.ts +286 -0
- package/templates/src/utils/helpers.ts +435 -0
- package/templates/src/utils/layout.ts +209 -0
- package/templates/src/utils/profileStorage.ts +306 -0
- package/templates/src/utils/useInterval.ts +27 -0
- package/templates/tailwind.config.cjs +169 -0
- package/utils/create-resolver.ts +10 -0
- package/utils/inject-files.ts +2140 -0
- package/utils/validate-config.ts +43 -0
|
@@ -0,0 +1,811 @@
|
|
|
1
|
+
import { useState, useEffect, useRef } from 'react';
|
|
2
|
+
import { Dialog } from '@ark-ui/react/dialog';
|
|
3
|
+
import { Portal } from '@ark-ui/react/portal';
|
|
4
|
+
import { navigate } from 'astro:transitions/client';
|
|
5
|
+
import { getCtx } from '@/stores/nodes';
|
|
6
|
+
import {
|
|
7
|
+
transformLivePaneForSave,
|
|
8
|
+
transformStoryFragmentForSave,
|
|
9
|
+
} from '@/utils/etl/index';
|
|
10
|
+
import {
|
|
11
|
+
fullContentMapStore,
|
|
12
|
+
getPendingImageOperation,
|
|
13
|
+
clearPendingImageOperation,
|
|
14
|
+
} from '@/stores/storykeep';
|
|
15
|
+
import { startLoadingAnimation } from '@/utils/helpers';
|
|
16
|
+
import type {
|
|
17
|
+
BaseNode,
|
|
18
|
+
PaneNode,
|
|
19
|
+
StoryFragmentNode,
|
|
20
|
+
} from '@/types/compositorTypes';
|
|
21
|
+
|
|
22
|
+
type SaveStage =
|
|
23
|
+
| 'PREPARING'
|
|
24
|
+
| 'SAVING_PENDING_FILES'
|
|
25
|
+
| 'PROCESSING_OG_IMAGES'
|
|
26
|
+
| 'SAVING_PANES'
|
|
27
|
+
| 'SAVING_STORY_FRAGMENTS'
|
|
28
|
+
| 'LINKING_FILES'
|
|
29
|
+
| 'PROCESSING_STYLES'
|
|
30
|
+
| 'COMPLETED'
|
|
31
|
+
| 'ERROR';
|
|
32
|
+
|
|
33
|
+
interface SaveStageProgress {
|
|
34
|
+
currentStep: number;
|
|
35
|
+
totalSteps: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface SaveModalProps {
|
|
39
|
+
show: boolean;
|
|
40
|
+
slug: string;
|
|
41
|
+
isContext: boolean;
|
|
42
|
+
onClose: () => void;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export default function SaveModal({
|
|
46
|
+
show,
|
|
47
|
+
slug,
|
|
48
|
+
isContext,
|
|
49
|
+
onClose,
|
|
50
|
+
}: SaveModalProps) {
|
|
51
|
+
const [stage, setStage] = useState<SaveStage>('PREPARING');
|
|
52
|
+
const [progress, setProgress] = useState(0);
|
|
53
|
+
const [stageProgress, setStageProgress] = useState<SaveStageProgress>({
|
|
54
|
+
currentStep: 0,
|
|
55
|
+
totalSteps: 0,
|
|
56
|
+
});
|
|
57
|
+
const [error, setError] = useState<string | null>(null);
|
|
58
|
+
const [showDebug, setShowDebug] = useState(false);
|
|
59
|
+
const [debugMessages, setDebugMessages] = useState<string[]>([]);
|
|
60
|
+
const isSaving = useRef(false);
|
|
61
|
+
const [isNavigating, setIsNavigating] = useState(false);
|
|
62
|
+
|
|
63
|
+
// Determine if we're in create mode
|
|
64
|
+
const isCreateMode = slug === 'create';
|
|
65
|
+
|
|
66
|
+
const contentMap = fullContentMapStore.get();
|
|
67
|
+
|
|
68
|
+
// Get backend URL
|
|
69
|
+
const goBackend =
|
|
70
|
+
import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080';
|
|
71
|
+
const tenantId = import.meta.env.PUBLIC_TENANTID || 'default';
|
|
72
|
+
|
|
73
|
+
const addDebugMessage = (message: string) => {
|
|
74
|
+
const timestamp = new Date().toLocaleTimeString();
|
|
75
|
+
setDebugMessages((prev) => [...prev, `${timestamp}: ${message}`]);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// Main save process
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
// Reset state when modal is hidden or if save is already running
|
|
81
|
+
if (!show) {
|
|
82
|
+
setStage('PREPARING');
|
|
83
|
+
setProgress(0);
|
|
84
|
+
setDebugMessages([]);
|
|
85
|
+
setShowDebug(false);
|
|
86
|
+
isSaving.current = false;
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (isSaving.current) return;
|
|
91
|
+
|
|
92
|
+
const runSaveProcess = async () => {
|
|
93
|
+
isSaving.current = true;
|
|
94
|
+
const ctx = getCtx();
|
|
95
|
+
const allDirtyNodes = ctx.getDirtyNodes();
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
setStage('PREPARING');
|
|
99
|
+
setProgress(5);
|
|
100
|
+
addDebugMessage(
|
|
101
|
+
`Starting save process... (${isContext ? 'Context' : 'StoryFragment'} mode, ${isCreateMode ? 'CREATE' : 'UPDATE'})`
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
// Filter nodes based on context mode
|
|
105
|
+
let dirtyPanes = allDirtyNodes.filter(
|
|
106
|
+
(node) => node.nodeType === 'Pane'
|
|
107
|
+
);
|
|
108
|
+
let dirtyStoryFragments = allDirtyNodes.filter(
|
|
109
|
+
(node) => node.nodeType === 'StoryFragment'
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
// In context mode, we only care about panes, not story fragments
|
|
113
|
+
if (isContext) {
|
|
114
|
+
dirtyStoryFragments = [];
|
|
115
|
+
addDebugMessage('Context mode: Ignoring StoryFragment nodes');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const nodesWithPendingFiles = allDirtyNodes.filter(
|
|
119
|
+
(node): node is BaseNode & { base64Data?: string } =>
|
|
120
|
+
'base64Data' in node && !!node.base64Data
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
// Check for story fragments with pending OG image operations
|
|
124
|
+
const storyFragmentsWithPendingImages = dirtyStoryFragments.filter(
|
|
125
|
+
(fragment) => {
|
|
126
|
+
const pendingOp = getPendingImageOperation(fragment.id);
|
|
127
|
+
return pendingOp && pendingOp.type === 'upload';
|
|
128
|
+
}
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
const relevantNodeCount =
|
|
132
|
+
dirtyPanes.length + dirtyStoryFragments.length;
|
|
133
|
+
addDebugMessage(
|
|
134
|
+
`Found ${relevantNodeCount} relevant dirty nodes to save (${dirtyPanes.length} Panes, ${dirtyStoryFragments.length} StoryFragments)`
|
|
135
|
+
);
|
|
136
|
+
addDebugMessage(
|
|
137
|
+
`Found ${storyFragmentsWithPendingImages.length} story fragments with pending OG image operations`
|
|
138
|
+
);
|
|
139
|
+
addDebugMessage(
|
|
140
|
+
`Found ${nodesWithPendingFiles.length} nodes with pending file uploads`
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
if (
|
|
144
|
+
relevantNodeCount === 0 &&
|
|
145
|
+
nodesWithPendingFiles.length === 0 &&
|
|
146
|
+
storyFragmentsWithPendingImages.length === 0
|
|
147
|
+
) {
|
|
148
|
+
addDebugMessage('No changes to save');
|
|
149
|
+
setStage('COMPLETED');
|
|
150
|
+
setProgress(100);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const totalSteps =
|
|
155
|
+
nodesWithPendingFiles.length +
|
|
156
|
+
storyFragmentsWithPendingImages.length +
|
|
157
|
+
dirtyPanes.length +
|
|
158
|
+
dirtyStoryFragments.length +
|
|
159
|
+
2; // +1 for file linking, +1 for styles
|
|
160
|
+
|
|
161
|
+
addDebugMessage(
|
|
162
|
+
`Save plan: ${nodesWithPendingFiles.length} files, ${storyFragmentsWithPendingImages.length} og images, ${dirtyPanes.length} panes, ${dirtyStoryFragments.length} story fragments, 1 file linking, 1 styles = ${totalSteps} total steps`
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
let completedSteps = 1;
|
|
166
|
+
|
|
167
|
+
// PHASE 1: Upload all pending files and OG images first
|
|
168
|
+
const uploadedOGPaths: Record<string, string> = {};
|
|
169
|
+
|
|
170
|
+
// Handle pending files
|
|
171
|
+
if (nodesWithPendingFiles.length > 0) {
|
|
172
|
+
setStage('SAVING_PENDING_FILES');
|
|
173
|
+
setStageProgress({
|
|
174
|
+
currentStep: 0,
|
|
175
|
+
totalSteps: nodesWithPendingFiles.length,
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
for (let i = 0; i < nodesWithPendingFiles.length; i++) {
|
|
179
|
+
const fileNode = nodesWithPendingFiles[i];
|
|
180
|
+
const endpoint = `${goBackend}/api/v1/nodes/files/create`;
|
|
181
|
+
addDebugMessage(
|
|
182
|
+
`Processing file ${i + 1}/${nodesWithPendingFiles.length}: ${fileNode.id} -> POST ${endpoint}`
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
try {
|
|
186
|
+
const response = await fetch(endpoint, {
|
|
187
|
+
method: 'POST',
|
|
188
|
+
headers: {
|
|
189
|
+
'Content-Type': 'application/json',
|
|
190
|
+
'X-Tenant-ID': tenantId,
|
|
191
|
+
},
|
|
192
|
+
credentials: 'include',
|
|
193
|
+
body: JSON.stringify({ base64Data: fileNode.base64Data }), // FIXED: only send base64Data
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
if (!response.ok) {
|
|
197
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const result = await response.json();
|
|
201
|
+
|
|
202
|
+
// Update tree with response data - handle different node types properly
|
|
203
|
+
const updatedNode = { ...fileNode, isChanged: true };
|
|
204
|
+
|
|
205
|
+
// Remove base64Data and add file properties
|
|
206
|
+
if ('base64Data' in updatedNode) {
|
|
207
|
+
delete updatedNode.base64Data;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Add file properties - these properties already exist in FlatNode and BgImageNode types
|
|
211
|
+
if ('fileId' in updatedNode) {
|
|
212
|
+
updatedNode.fileId = result.fileId;
|
|
213
|
+
}
|
|
214
|
+
if ('src' in updatedNode) {
|
|
215
|
+
updatedNode.src = result.src;
|
|
216
|
+
}
|
|
217
|
+
if ('srcSet' in updatedNode && result.srcSet) {
|
|
218
|
+
updatedNode.srcSet = result.srcSet;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
ctx.modifyNodes([updatedNode]);
|
|
222
|
+
|
|
223
|
+
addDebugMessage(
|
|
224
|
+
`File ${fileNode.id} uploaded successfully - got fileId: ${result.fileId}`
|
|
225
|
+
);
|
|
226
|
+
} catch (error) {
|
|
227
|
+
const errorMsg =
|
|
228
|
+
error instanceof Error ? error.message : 'Unknown error';
|
|
229
|
+
addDebugMessage(`File ${fileNode.id} upload failed: ${errorMsg}`);
|
|
230
|
+
throw new Error(
|
|
231
|
+
`Failed to upload file ${fileNode.id}: ${errorMsg}`
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
setStageProgress((prev) => ({ ...prev, currentStep: i + 1 }));
|
|
236
|
+
completedSteps++;
|
|
237
|
+
setProgress((completedSteps / totalSteps) * 80);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Handle OG image uploads
|
|
242
|
+
if (storyFragmentsWithPendingImages.length > 0) {
|
|
243
|
+
setStage('PROCESSING_OG_IMAGES');
|
|
244
|
+
setStageProgress({
|
|
245
|
+
currentStep: 0,
|
|
246
|
+
totalSteps: storyFragmentsWithPendingImages.length,
|
|
247
|
+
});
|
|
248
|
+
for (let i = 0; i < storyFragmentsWithPendingImages.length; i++) {
|
|
249
|
+
const fragment = storyFragmentsWithPendingImages[i];
|
|
250
|
+
const pendingOp = getPendingImageOperation(fragment.id);
|
|
251
|
+
|
|
252
|
+
if (pendingOp && pendingOp.type === 'upload' && pendingOp.data) {
|
|
253
|
+
const ogUploadEndpoint = `${goBackend}/api/v1/nodes/images/og`;
|
|
254
|
+
addDebugMessage(
|
|
255
|
+
`Processing OG image ${i + 1}/${storyFragmentsWithPendingImages.length}: ${fragment.id} -> POST ${ogUploadEndpoint}`
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
const uploadPayload = {
|
|
259
|
+
data: pendingOp.data,
|
|
260
|
+
filename:
|
|
261
|
+
pendingOp.filename || `${fragment.id}-${Date.now()}.png`,
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
try {
|
|
265
|
+
const response = await fetch(ogUploadEndpoint, {
|
|
266
|
+
method: 'POST',
|
|
267
|
+
headers: {
|
|
268
|
+
'Content-Type': 'application/json',
|
|
269
|
+
'X-Tenant-ID': tenantId,
|
|
270
|
+
},
|
|
271
|
+
credentials: 'include',
|
|
272
|
+
body: JSON.stringify(uploadPayload),
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
if (!response.ok) {
|
|
276
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const result = await response.json();
|
|
280
|
+
|
|
281
|
+
uploadedOGPaths[fragment.id] = result.path;
|
|
282
|
+
addDebugMessage(
|
|
283
|
+
`OG image uploaded successfully: ${result.path}`
|
|
284
|
+
);
|
|
285
|
+
} catch (error) {
|
|
286
|
+
const errorMsg =
|
|
287
|
+
error instanceof Error ? error.message : 'Unknown error';
|
|
288
|
+
addDebugMessage(
|
|
289
|
+
`OG image upload failed for ${fragment.id}: ${errorMsg}`
|
|
290
|
+
);
|
|
291
|
+
throw new Error(
|
|
292
|
+
`Failed to upload OG image for ${fragment.id}: ${errorMsg}`
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
setStageProgress((prev) => ({ ...prev, currentStep: i + 1 }));
|
|
298
|
+
completedSteps++;
|
|
299
|
+
setProgress((completedSteps / totalSteps) * 80);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Handle panes
|
|
304
|
+
if (dirtyPanes.length > 0) {
|
|
305
|
+
setStage('SAVING_PANES');
|
|
306
|
+
setStageProgress({
|
|
307
|
+
currentStep: 0,
|
|
308
|
+
totalSteps: dirtyPanes.length,
|
|
309
|
+
});
|
|
310
|
+
for (let i = 0; i < dirtyPanes.length; i++) {
|
|
311
|
+
const paneNode = dirtyPanes[i];
|
|
312
|
+
|
|
313
|
+
try {
|
|
314
|
+
const payload = transformLivePaneForSave(
|
|
315
|
+
ctx,
|
|
316
|
+
paneNode.id,
|
|
317
|
+
isContext
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
// Check if this pane exists or is new
|
|
321
|
+
const paneExistsInBackend = contentMap.some(
|
|
322
|
+
(item) => item.type === 'Pane' && item.id === paneNode.id
|
|
323
|
+
);
|
|
324
|
+
const isCreatePaneMode = !paneExistsInBackend;
|
|
325
|
+
const endpoint = isCreatePaneMode
|
|
326
|
+
? `${goBackend}/api/v1/nodes/panes/create`
|
|
327
|
+
: `${goBackend}/api/v1/nodes/panes/${payload.id}`;
|
|
328
|
+
const method = isCreatePaneMode ? 'POST' : 'PUT';
|
|
329
|
+
|
|
330
|
+
addDebugMessage(
|
|
331
|
+
`Processing pane ${i + 1}/${dirtyPanes.length}: ${paneNode.id} -> ${method} ${endpoint}`
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
const response = await fetch(endpoint, {
|
|
335
|
+
method,
|
|
336
|
+
headers: {
|
|
337
|
+
'Content-Type': 'application/json',
|
|
338
|
+
'X-Tenant-ID': tenantId,
|
|
339
|
+
},
|
|
340
|
+
credentials: 'include',
|
|
341
|
+
body: JSON.stringify(payload),
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
if (!response.ok) {
|
|
345
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
//const result =
|
|
349
|
+
await response.json();
|
|
350
|
+
addDebugMessage(`Pane ${paneNode.id} saved successfully`);
|
|
351
|
+
} catch (etlError) {
|
|
352
|
+
const errorMsg =
|
|
353
|
+
etlError instanceof Error ? etlError.message : 'Unknown error';
|
|
354
|
+
addDebugMessage(`Pane ${paneNode.id} ETL failed: ${errorMsg}`);
|
|
355
|
+
throw new Error(
|
|
356
|
+
`Failed to save pane ${paneNode.id}: ${errorMsg}`
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
setStageProgress((prev) => ({ ...prev, currentStep: i + 1 }));
|
|
361
|
+
completedSteps++;
|
|
362
|
+
setProgress((completedSteps / totalSteps) * 80);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Handle story fragments
|
|
367
|
+
if (!isContext && dirtyStoryFragments.length > 0) {
|
|
368
|
+
setStage('SAVING_STORY_FRAGMENTS');
|
|
369
|
+
setStageProgress({
|
|
370
|
+
currentStep: 0,
|
|
371
|
+
totalSteps: dirtyStoryFragments.length,
|
|
372
|
+
});
|
|
373
|
+
for (let i = 0; i < dirtyStoryFragments.length; i++) {
|
|
374
|
+
const fragment = dirtyStoryFragments[i];
|
|
375
|
+
|
|
376
|
+
try {
|
|
377
|
+
const payload = await transformStoryFragmentForSave(
|
|
378
|
+
ctx,
|
|
379
|
+
fragment.id,
|
|
380
|
+
window.TRACTSTACK_CONFIG?.tenantId || 'default'
|
|
381
|
+
);
|
|
382
|
+
|
|
383
|
+
// If we uploaded an OG image for this fragment, use that path
|
|
384
|
+
if (uploadedOGPaths[fragment.id]) {
|
|
385
|
+
payload.socialImagePath = uploadedOGPaths[fragment.id];
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const endpoint = isCreateMode
|
|
389
|
+
? `${goBackend}/api/v1/nodes/storyfragments/create`
|
|
390
|
+
: `${goBackend}/api/v1/nodes/storyfragments/${payload.id}/complete`;
|
|
391
|
+
const method = isCreateMode ? 'POST' : 'PUT';
|
|
392
|
+
|
|
393
|
+
addDebugMessage(
|
|
394
|
+
`Processing story fragment ${i + 1}/${dirtyStoryFragments.length}: ${fragment.id} -> ${method} ${endpoint}`
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
const response = await fetch(endpoint, {
|
|
398
|
+
method,
|
|
399
|
+
headers: {
|
|
400
|
+
'Content-Type': 'application/json',
|
|
401
|
+
'X-Tenant-ID': tenantId,
|
|
402
|
+
},
|
|
403
|
+
credentials: 'include',
|
|
404
|
+
body: JSON.stringify(payload),
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
if (!response.ok) {
|
|
408
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
//const result =
|
|
412
|
+
await response.json();
|
|
413
|
+
addDebugMessage(
|
|
414
|
+
`StoryFragment ${fragment.id} saved successfully`
|
|
415
|
+
);
|
|
416
|
+
|
|
417
|
+
// Clear pending image operation after successful save
|
|
418
|
+
if (uploadedOGPaths[fragment.id]) {
|
|
419
|
+
clearPendingImageOperation(fragment.id);
|
|
420
|
+
addDebugMessage(
|
|
421
|
+
`Cleared pending image operation for ${fragment.id}`
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
} catch (etlError) {
|
|
425
|
+
const errorMsg =
|
|
426
|
+
etlError instanceof Error ? etlError.message : 'Unknown error';
|
|
427
|
+
addDebugMessage(
|
|
428
|
+
`StoryFragment ${fragment.id} ETL failed: ${errorMsg}`
|
|
429
|
+
);
|
|
430
|
+
throw new Error(
|
|
431
|
+
`Failed to save story fragment ${fragment.id}: ${errorMsg}`
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
setStageProgress((prev) => ({ ...prev, currentStep: i + 1 }));
|
|
436
|
+
completedSteps++;
|
|
437
|
+
setProgress((completedSteps / totalSteps) * 80);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// PHASE 3: Link file-pane relationships
|
|
442
|
+
if (dirtyPanes.length > 0) {
|
|
443
|
+
setStage('LINKING_FILES');
|
|
444
|
+
addDebugMessage('Starting file-pane relationship linking...');
|
|
445
|
+
|
|
446
|
+
// Extract pane<>file relationships from saved panes
|
|
447
|
+
const relationships = [];
|
|
448
|
+
for (const paneNode of dirtyPanes) {
|
|
449
|
+
const fileIds = ctx.getPaneImageFileIds(paneNode.id);
|
|
450
|
+
relationships.push({
|
|
451
|
+
paneId: paneNode.id,
|
|
452
|
+
fileIds: fileIds,
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
if (relationships.some((rel) => rel.fileIds.length > 0)) {
|
|
457
|
+
try {
|
|
458
|
+
const bulkEndpoint = `${goBackend}/api/v1/nodes/panes/files/bulk`;
|
|
459
|
+
addDebugMessage(
|
|
460
|
+
`Linking relationships: ${JSON.stringify(relationships)}`
|
|
461
|
+
);
|
|
462
|
+
|
|
463
|
+
const response = await fetch(bulkEndpoint, {
|
|
464
|
+
method: 'POST',
|
|
465
|
+
headers: {
|
|
466
|
+
'Content-Type': 'application/json',
|
|
467
|
+
'X-Tenant-ID': tenantId,
|
|
468
|
+
},
|
|
469
|
+
credentials: 'include',
|
|
470
|
+
body: JSON.stringify({ relationships }),
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
if (!response.ok) {
|
|
474
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const result = await response.json();
|
|
478
|
+
addDebugMessage(
|
|
479
|
+
`File-pane relationships linked successfully: ${result.message}`
|
|
480
|
+
);
|
|
481
|
+
} catch (error) {
|
|
482
|
+
const errorMsg =
|
|
483
|
+
error instanceof Error ? error.message : 'Unknown error';
|
|
484
|
+
addDebugMessage(
|
|
485
|
+
`Failed to link file-pane relationships: ${errorMsg}`
|
|
486
|
+
);
|
|
487
|
+
throw new Error(
|
|
488
|
+
`Failed to link file-pane relationships: ${errorMsg}`
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
} else {
|
|
492
|
+
addDebugMessage('No file relationships to link');
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
completedSteps++;
|
|
496
|
+
setProgress((completedSteps / totalSteps) * 90);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// PHASE 4: Styles processing (2-step process)
|
|
500
|
+
setStage('PROCESSING_STYLES');
|
|
501
|
+
setProgress(95);
|
|
502
|
+
addDebugMessage(`Processing styles...`);
|
|
503
|
+
|
|
504
|
+
try {
|
|
505
|
+
const { dirtyPaneIds, classes: dirtyClasses } =
|
|
506
|
+
ctx.getDirtyNodesClassData();
|
|
507
|
+
|
|
508
|
+
if (dirtyClasses.length === 0) {
|
|
509
|
+
addDebugMessage(
|
|
510
|
+
'No dirty classes to process, skipping Tailwind update'
|
|
511
|
+
);
|
|
512
|
+
} else {
|
|
513
|
+
// STEP 1: Generate CSS using Astro API
|
|
514
|
+
const astroEndpoint = `/api/tailwind`;
|
|
515
|
+
const astroPayload = { dirtyPaneIds, dirtyClasses };
|
|
516
|
+
const astroResponse = await fetch(astroEndpoint, {
|
|
517
|
+
method: 'POST',
|
|
518
|
+
headers: {
|
|
519
|
+
'Content-Type': 'application/json',
|
|
520
|
+
'X-Tenant-ID': tenantId,
|
|
521
|
+
},
|
|
522
|
+
credentials: 'include',
|
|
523
|
+
body: JSON.stringify(astroPayload),
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
if (!astroResponse.ok) {
|
|
527
|
+
throw new Error(
|
|
528
|
+
`CSS generation failed! status: ${astroResponse.status}`
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
const astroResult = await astroResponse.json();
|
|
533
|
+
|
|
534
|
+
if (!astroResult.success || !astroResult.generatedCss) {
|
|
535
|
+
throw new Error('CSS generation failed: no CSS returned');
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
addDebugMessage(
|
|
539
|
+
`CSS generated: ${astroResult.generatedCss.length} bytes for ${dirtyClasses.length} classes`
|
|
540
|
+
);
|
|
541
|
+
|
|
542
|
+
// STEP 2: Save CSS to Go backend
|
|
543
|
+
const goEndpoint = `${goBackend}/api/v1/tailwind/update`;
|
|
544
|
+
const goPayload = { frontendCss: astroResult.generatedCss };
|
|
545
|
+
const goResponse = await fetch(goEndpoint, {
|
|
546
|
+
method: 'POST',
|
|
547
|
+
headers: {
|
|
548
|
+
'Content-Type': 'application/json',
|
|
549
|
+
'X-Tenant-ID': tenantId,
|
|
550
|
+
},
|
|
551
|
+
credentials: 'include',
|
|
552
|
+
body: JSON.stringify(goPayload),
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
if (!goResponse.ok) {
|
|
556
|
+
throw new Error(`CSS save failed! status: ${goResponse.status}`);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
const goResult = await goResponse.json();
|
|
560
|
+
addDebugMessage(
|
|
561
|
+
`CSS saved successfully: stylesVer ${goResult.stylesVer}`
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
} catch (error) {
|
|
565
|
+
const errorMsg =
|
|
566
|
+
error instanceof Error ? error.message : 'Unknown error';
|
|
567
|
+
addDebugMessage(`Styles processing failed: ${errorMsg}`);
|
|
568
|
+
throw new Error(`Failed to process styles: ${errorMsg}`);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// Success!
|
|
572
|
+
setStage('COMPLETED');
|
|
573
|
+
setProgress(100);
|
|
574
|
+
addDebugMessage('Save process completed successfully!');
|
|
575
|
+
} catch (err) {
|
|
576
|
+
setStage('ERROR');
|
|
577
|
+
const errorMessage =
|
|
578
|
+
err instanceof Error && err.message
|
|
579
|
+
? err.message
|
|
580
|
+
: 'Unknown error occurred';
|
|
581
|
+
setError(errorMessage);
|
|
582
|
+
addDebugMessage(`Save process failed: ${errorMessage}`);
|
|
583
|
+
} finally {
|
|
584
|
+
isSaving.current = false;
|
|
585
|
+
}
|
|
586
|
+
};
|
|
587
|
+
|
|
588
|
+
runSaveProcess();
|
|
589
|
+
}, [show, slug, isContext, isCreateMode, goBackend, tenantId]);
|
|
590
|
+
|
|
591
|
+
const getStageDescription = () => {
|
|
592
|
+
const getProgressText = () =>
|
|
593
|
+
stageProgress.totalSteps > 0
|
|
594
|
+
? ` (${stageProgress.currentStep}/${stageProgress.totalSteps})`
|
|
595
|
+
: '';
|
|
596
|
+
|
|
597
|
+
const modeText = isContext ? 'Context Pane' : 'Story Fragment';
|
|
598
|
+
const actionText = isCreateMode ? 'Creating' : 'Updating';
|
|
599
|
+
|
|
600
|
+
switch (stage) {
|
|
601
|
+
case 'PREPARING':
|
|
602
|
+
return `Preparing ${actionText.toLowerCase()} ${modeText.toLowerCase()}...`;
|
|
603
|
+
case 'SAVING_PENDING_FILES':
|
|
604
|
+
return `Uploading files...${getProgressText()}`;
|
|
605
|
+
case 'PROCESSING_OG_IMAGES':
|
|
606
|
+
return `Processing OG images...${getProgressText()}`;
|
|
607
|
+
case 'SAVING_PANES':
|
|
608
|
+
return `${actionText} pane content...${getProgressText()}`;
|
|
609
|
+
case 'SAVING_STORY_FRAGMENTS':
|
|
610
|
+
return `${actionText} story fragments...${getProgressText()}`;
|
|
611
|
+
case 'LINKING_FILES':
|
|
612
|
+
return 'Linking file relationships...';
|
|
613
|
+
case 'PROCESSING_STYLES':
|
|
614
|
+
return 'Processing styles...';
|
|
615
|
+
case 'COMPLETED':
|
|
616
|
+
return `${actionText} ${modeText.toLowerCase()} completed successfully!`;
|
|
617
|
+
case 'ERROR':
|
|
618
|
+
return `Error: ${error}`;
|
|
619
|
+
default:
|
|
620
|
+
return '';
|
|
621
|
+
}
|
|
622
|
+
};
|
|
623
|
+
|
|
624
|
+
const handleOpenChange = (details: { open: boolean }) => {
|
|
625
|
+
if (!details.open && (stage === 'COMPLETED' || stage === 'ERROR')) {
|
|
626
|
+
onClose();
|
|
627
|
+
}
|
|
628
|
+
};
|
|
629
|
+
|
|
630
|
+
const handleSuccessClose = async () => {
|
|
631
|
+
if (stage === 'COMPLETED') {
|
|
632
|
+
startLoadingAnimation();
|
|
633
|
+
setIsNavigating(true);
|
|
634
|
+
|
|
635
|
+
if (isCreateMode) {
|
|
636
|
+
let actualSlug: string;
|
|
637
|
+
|
|
638
|
+
if (isContext) {
|
|
639
|
+
// For context mode, get slug from the saved pane
|
|
640
|
+
const ctx = getCtx();
|
|
641
|
+
const allDirtyNodes = ctx.getDirtyNodes();
|
|
642
|
+
const dirtyPanes = allDirtyNodes.filter(
|
|
643
|
+
(node): node is PaneNode => node.nodeType === 'Pane'
|
|
644
|
+
);
|
|
645
|
+
actualSlug = dirtyPanes[0].slug;
|
|
646
|
+
} else {
|
|
647
|
+
// For storyfragment mode, get slug from the saved storyfragment
|
|
648
|
+
const ctx = getCtx();
|
|
649
|
+
const allDirtyNodes = ctx.getDirtyNodes();
|
|
650
|
+
const dirtyStoryFragments = allDirtyNodes.filter(
|
|
651
|
+
(node): node is StoryFragmentNode =>
|
|
652
|
+
node.nodeType === 'StoryFragment'
|
|
653
|
+
);
|
|
654
|
+
actualSlug = dirtyStoryFragments[0].slug;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
const editUrl = isContext
|
|
658
|
+
? `/context/${actualSlug}/edit`
|
|
659
|
+
: `/${actualSlug}/edit`;
|
|
660
|
+
await navigate(editUrl);
|
|
661
|
+
} else {
|
|
662
|
+
const currentUrl = isContext
|
|
663
|
+
? `/context/${slug}/edit`
|
|
664
|
+
: `/${slug}/edit`;
|
|
665
|
+
await navigate(currentUrl);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
};
|
|
669
|
+
|
|
670
|
+
const visitPageUrl = (() => {
|
|
671
|
+
startLoadingAnimation();
|
|
672
|
+
const ctx = getCtx();
|
|
673
|
+
const allDirtyNodes = ctx.getDirtyNodes();
|
|
674
|
+
|
|
675
|
+
if (isContext) {
|
|
676
|
+
const dirtyPanes = allDirtyNodes.filter(
|
|
677
|
+
(node): node is PaneNode => node.nodeType === 'Pane'
|
|
678
|
+
);
|
|
679
|
+
const currentSlug = dirtyPanes[0]?.slug || slug;
|
|
680
|
+
return `/context/${currentSlug}`;
|
|
681
|
+
} else {
|
|
682
|
+
const dirtyStoryFragments = allDirtyNodes.filter(
|
|
683
|
+
(node): node is StoryFragmentNode => node.nodeType === 'StoryFragment'
|
|
684
|
+
);
|
|
685
|
+
const currentSlug = dirtyStoryFragments[0]?.slug || slug;
|
|
686
|
+
return `/${currentSlug}`;
|
|
687
|
+
}
|
|
688
|
+
})();
|
|
689
|
+
|
|
690
|
+
return (
|
|
691
|
+
<Dialog.Root
|
|
692
|
+
open={show}
|
|
693
|
+
onOpenChange={handleOpenChange}
|
|
694
|
+
modal={true}
|
|
695
|
+
preventScroll={true}
|
|
696
|
+
>
|
|
697
|
+
<Portal>
|
|
698
|
+
<Dialog.Backdrop
|
|
699
|
+
className="fixed inset-0 bg-black bg-opacity-75"
|
|
700
|
+
style={{ zIndex: 9005 }}
|
|
701
|
+
/>
|
|
702
|
+
<Dialog.Positioner
|
|
703
|
+
className="fixed inset-0 flex items-center justify-center p-4"
|
|
704
|
+
style={{ zIndex: 9005 }}
|
|
705
|
+
>
|
|
706
|
+
<Dialog.Content
|
|
707
|
+
className="w-full max-w-2xl overflow-hidden rounded-lg bg-white shadow-xl"
|
|
708
|
+
style={{ maxHeight: '90vh' }}
|
|
709
|
+
>
|
|
710
|
+
<div className="border-b border-gray-200 px-6 py-4">
|
|
711
|
+
<div className="flex items-center justify-between">
|
|
712
|
+
<Dialog.Title className="text-xl font-bold text-gray-900">
|
|
713
|
+
{isCreateMode ? 'Creating' : 'Saving'}{' '}
|
|
714
|
+
{isContext ? 'Context Pane' : 'Story Fragment'}
|
|
715
|
+
</Dialog.Title>
|
|
716
|
+
<button
|
|
717
|
+
onClick={() => setShowDebug(!showDebug)}
|
|
718
|
+
className="text-sm text-gray-500 hover:text-gray-700"
|
|
719
|
+
>
|
|
720
|
+
{showDebug ? 'Hide Debug' : 'Show Debug'}
|
|
721
|
+
</button>
|
|
722
|
+
</div>
|
|
723
|
+
</div>
|
|
724
|
+
|
|
725
|
+
<div className="p-6">
|
|
726
|
+
<div className="mb-4">
|
|
727
|
+
<div className="mb-2 flex items-center justify-between">
|
|
728
|
+
<span className="text-sm text-gray-700">
|
|
729
|
+
{getStageDescription()}
|
|
730
|
+
</span>
|
|
731
|
+
{stage !== 'ERROR' && (
|
|
732
|
+
<span className="text-sm text-gray-500">
|
|
733
|
+
{Math.round(progress)}%
|
|
734
|
+
</span>
|
|
735
|
+
)}
|
|
736
|
+
</div>
|
|
737
|
+
<div className="h-2 w-full rounded-full bg-gray-200">
|
|
738
|
+
<div
|
|
739
|
+
className={`h-2 rounded-full transition-all duration-300 ${
|
|
740
|
+
stage === 'ERROR' ? 'bg-red-500' : 'bg-green-500'
|
|
741
|
+
}`}
|
|
742
|
+
style={{ width: `${progress}%` }}
|
|
743
|
+
/>
|
|
744
|
+
</div>
|
|
745
|
+
</div>
|
|
746
|
+
|
|
747
|
+
{showDebug && (
|
|
748
|
+
<div className="mb-4 max-h-40 overflow-y-auto rounded border bg-gray-50 p-3">
|
|
749
|
+
<div className="text-xs text-gray-600">
|
|
750
|
+
{debugMessages.map((message, index) => (
|
|
751
|
+
<div key={index} className="mb-1">
|
|
752
|
+
{message}
|
|
753
|
+
</div>
|
|
754
|
+
))}
|
|
755
|
+
</div>
|
|
756
|
+
</div>
|
|
757
|
+
)}
|
|
758
|
+
|
|
759
|
+
{stage === 'COMPLETED' && (
|
|
760
|
+
<div className="mb-4 rounded bg-green-50 p-3 text-green-800">
|
|
761
|
+
Save completed successfully!
|
|
762
|
+
</div>
|
|
763
|
+
)}
|
|
764
|
+
|
|
765
|
+
{stage === 'ERROR' && (
|
|
766
|
+
<div className="mb-4 rounded bg-red-50 p-3 text-red-800">
|
|
767
|
+
<div className="font-medium">Save failed</div>
|
|
768
|
+
<div className="mt-1 text-sm">{error}</div>
|
|
769
|
+
</div>
|
|
770
|
+
)}
|
|
771
|
+
|
|
772
|
+
{(stage === 'COMPLETED' || stage === 'ERROR') && (
|
|
773
|
+
<div className="flex justify-end gap-2">
|
|
774
|
+
{stage === 'COMPLETED' && (
|
|
775
|
+
<>
|
|
776
|
+
<a
|
|
777
|
+
href={visitPageUrl}
|
|
778
|
+
className={`rounded bg-cyan-600 px-4 py-2 text-white transition-colors hover:bg-cyan-700`}
|
|
779
|
+
>
|
|
780
|
+
Visit Page
|
|
781
|
+
</a>
|
|
782
|
+
<button
|
|
783
|
+
onClick={handleSuccessClose}
|
|
784
|
+
disabled={isNavigating}
|
|
785
|
+
className={`rounded px-4 py-2 text-white transition-colors ${
|
|
786
|
+
isNavigating
|
|
787
|
+
? 'cursor-not-allowed bg-gray-400'
|
|
788
|
+
: 'bg-gray-600 hover:bg-gray-700'
|
|
789
|
+
}`}
|
|
790
|
+
>
|
|
791
|
+
Keep Editing
|
|
792
|
+
</button>
|
|
793
|
+
</>
|
|
794
|
+
)}
|
|
795
|
+
{stage === 'ERROR' && (
|
|
796
|
+
<button
|
|
797
|
+
onClick={onClose}
|
|
798
|
+
className="rounded bg-gray-600 px-4 py-2 text-white transition-colors hover:bg-gray-700"
|
|
799
|
+
>
|
|
800
|
+
Close
|
|
801
|
+
</button>
|
|
802
|
+
)}
|
|
803
|
+
</div>
|
|
804
|
+
)}
|
|
805
|
+
</div>
|
|
806
|
+
</Dialog.Content>
|
|
807
|
+
</Dialog.Positioner>
|
|
808
|
+
</Portal>
|
|
809
|
+
</Dialog.Root>
|
|
810
|
+
);
|
|
811
|
+
}
|