@wix/himalaya-cli 0.1583.0 → 0.1585.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/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/dev-server/src/app-module.d.ts +7 -4
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/ts/src/bindings.d.ts +7 -6
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/blog-v3/src/extensions.d.ts +2 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/comments-v1/src/extensions.d.ts +2 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/events/src/index.d.ts +3 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/groups/src/extensions.d.ts +15 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/loyalty/src/extensions.d.ts +13 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/members-v1/src/index.d.ts +19 -4
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/core.mjs +2 -2
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/sdui.mjs +8 -2
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-comments.mjs +3 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-events.mjs +4 -2
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-groups.mjs +5 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-loyalty.mjs +4 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-members.mjs +10 -3
- package/dist/cli.mjs +2201 -1716
- package/dist/preview-assets.generated.bin +0 -0
- package/fixtures/wix-canned.json +25 -0
- package/guides/branded-lite-site-binding.md +7 -1
- package/guides/designing-himi-screens.md +47 -7
- package/guides/himi-skill.md +16 -0
- package/guides/index.json +1 -0
- package/guides/one-app-to-himi-app.md +52 -1
- package/guides/sdk-reference.json +37 -9
- package/guides/tier-5-add-a-screen.md +9 -0
- package/guides/wix-api-usage.md +9 -0
- package/package.json +2 -2
- package/templates/beauty-salon/config.ts +7 -0
- package/templates/beauty-salon/dev/index.ts +758 -352
- package/templates/beauty-salon/himalaya.content.json +3 -0
- package/templates/beauty-salon/tier5-src/_shared/branded-lite-hosted.ts +1 -1
- package/templates/beauty-salon/tier5-src/_shared/collection-contract.ts +19 -3
- package/templates/beauty-salon/tier5-src/_shared/himi-restaurants.ts +2 -1
- package/templates/beauty-salon/tier5-src/_shared/owner-editor-value.ts +164 -0
- package/templates/beauty-salon/tier5-src/_shared/store-product-info.ts +50 -0
- package/templates/beauty-salon/tier5-src/himi-blog-post/index.ts +21 -8
- package/templates/beauty-salon/tier5-src/himi-branded-lite-category/index.ts +3 -2
- package/templates/beauty-salon/tier5-src/himi-branded-lite-notifsettings/index.ts +173 -0
- package/templates/beauty-salon/tier5-src/himi-branded-lite-plans/index.ts +2 -1
- package/templates/beauty-salon/tier5-src/himi-store-product/index.ts +14 -3
- package/templates/beauty-salon/tier5-src/wix-spaces-plan-detail/index.ts +2 -1
- package/templates/beauty-salon/tier5-src/wix-spaces-whats-new/index.ts +2 -1
- package/templates/beauty-salon-owner/dev/index.ts +129 -66
- package/templates/beauty-salon-owner/tier5-src/_shared/collection-contract.ts +19 -3
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-post/index.ts +7 -0
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-product/index.ts +39 -6
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-product-form/index.ts +14 -8
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-products/index.ts +7 -4
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-service/index.ts +38 -7
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-service-form/index.ts +53 -7
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-services/index.ts +4 -2
- package/templates/branded-lite/config.ts +9 -0
- package/templates/branded-lite/dev/index.ts +4988 -3304
- package/templates/branded-lite/himalaya.content.json +3 -0
- package/templates/branded-lite/tier5-src/_shared/branded-lite-hosted.ts +1 -1
- package/templates/branded-lite/tier5-src/_shared/collection-contract.ts +19 -3
- package/templates/branded-lite/tier5-src/_shared/event-registration.ts +86 -0
- package/templates/branded-lite/tier5-src/_shared/himi-restaurants.ts +2 -1
- package/templates/branded-lite/tier5-src/_shared/owner-editor-value.ts +164 -0
- package/templates/branded-lite/tier5-src/_shared/store-product-info.ts +50 -0
- package/templates/branded-lite/tier5-src/himi-blog-post/index.ts +21 -8
- package/templates/branded-lite/tier5-src/himi-branded-lite-badges/index.ts +2 -1
- package/templates/branded-lite/tier5-src/himi-branded-lite-category/index.ts +3 -2
- package/templates/branded-lite/tier5-src/himi-branded-lite-feed/index.ts +4 -3
- package/templates/branded-lite/tier5-src/himi-branded-lite-group-about/index.ts +243 -0
- package/templates/branded-lite/tier5-src/himi-branded-lite-loyalty/index.ts +115 -49
- package/templates/branded-lite/tier5-src/himi-branded-lite-plans/index.ts +2 -1
- package/templates/branded-lite/tier5-src/himi-branded-lite-podcasts/index.ts +2 -1
- package/templates/branded-lite/tier5-src/himi-branded-lite-portfolio/index.ts +2 -1
- package/templates/branded-lite/tier5-src/himi-branded-lite-portfolio-project/index.ts +3 -2
- package/templates/branded-lite/tier5-src/himi-branded-lite-program/facts.ts +52 -0
- package/templates/branded-lite/tier5-src/himi-branded-lite-program/index.ts +21 -11
- package/templates/branded-lite/tier5-src/himi-branded-lite-video/index.ts +2 -1
- package/templates/branded-lite/tier5-src/himi-events-detail/index.ts +12 -86
- package/templates/branded-lite/tier5-src/himi-events-list/index.ts +47 -6
- package/templates/branded-lite/tier5-src/himi-store-product/index.ts +13 -3
- package/templates/branded-lite/tier5-src/wix-spaces-forum-post/index.ts +3 -2
- package/templates/branded-lite/tier5-src/wix-spaces-member-detail/index.ts +53 -37
- package/templates/branded-lite/tier5-src/wix-spaces-members/index.ts +100 -34
- package/templates/branded-lite/tier5-src/wix-spaces-plan-detail/index.ts +2 -1
- package/templates/branded-lite/tier5-src/wix-spaces-whats-new/index.ts +2 -1
- package/templates/community-club/config.ts +16 -0
- package/templates/community-club/dev/index.ts +1930 -401
- package/templates/community-club/himalaya.content.json +6 -0
- package/templates/community-club/tier5-src/_shared/branded-lite-hosted.ts +1 -1
- package/templates/community-club/tier5-src/_shared/collection-contract.ts +19 -3
- package/templates/community-club/tier5-src/_shared/event-registration.ts +86 -0
- package/templates/community-club/tier5-src/_shared/himi-restaurants.ts +2 -1
- package/templates/community-club/tier5-src/himi-blog-post/index.ts +21 -8
- package/templates/community-club/tier5-src/himi-branded-lite-feed/index.ts +4 -3
- package/templates/community-club/tier5-src/himi-branded-lite-group-about/index.ts +243 -0
- package/templates/community-club/tier5-src/himi-branded-lite-notifsettings/index.ts +173 -0
- package/templates/community-club/tier5-src/himi-events-detail/index.ts +12 -86
- package/templates/community-club/tier5-src/himi-events-list/index.ts +47 -6
- package/templates/community-club/tier5-src/wix-spaces-whats-new/index.ts +2 -1
- package/templates/community-club-owner/dev/index.ts +53 -1
- package/templates/community-club-owner/tier5-src/_shared/collection-contract.ts +19 -3
- package/templates/community-club-owner/tier5-src/himi-owner-lite-event/index.ts +16 -8
- package/templates/community-club-owner/tier5-src/himi-owner-lite-event-form/index.ts +16 -7
- package/templates/community-club-owner/tier5-src/himi-owner-lite-events/index.ts +4 -2
- package/templates/community-club-owner/tier5-src/himi-owner-lite-post/index.ts +7 -0
- package/templates/community-club-owner/tier5-src/himi-owner-lite-product/index.ts +39 -6
- package/templates/community-club-owner/tier5-src/himi-owner-lite-product-form/index.ts +14 -8
- package/templates/community-club-owner/tier5-src/himi-owner-lite-products/index.ts +7 -4
- package/templates/events-meetups/config.ts +16 -0
- package/templates/events-meetups/dev/index.ts +1930 -401
- package/templates/events-meetups/himalaya.content.json +6 -0
- package/templates/events-meetups/tier5-src/_shared/branded-lite-hosted.ts +1 -1
- package/templates/events-meetups/tier5-src/_shared/collection-contract.ts +19 -3
- package/templates/events-meetups/tier5-src/_shared/event-registration.ts +86 -0
- package/templates/events-meetups/tier5-src/_shared/himi-restaurants.ts +2 -1
- package/templates/events-meetups/tier5-src/himi-blog-post/index.ts +21 -8
- package/templates/events-meetups/tier5-src/himi-branded-lite-feed/index.ts +4 -3
- package/templates/events-meetups/tier5-src/himi-branded-lite-group-about/index.ts +243 -0
- package/templates/events-meetups/tier5-src/himi-branded-lite-notifsettings/index.ts +173 -0
- package/templates/events-meetups/tier5-src/himi-events-detail/index.ts +12 -86
- package/templates/events-meetups/tier5-src/himi-events-list/index.ts +47 -6
- package/templates/events-meetups/tier5-src/wix-spaces-whats-new/index.ts +2 -1
- package/templates/events-meetups-owner/dev/index.ts +6 -0
- package/templates/events-meetups-owner/tier5-src/_shared/collection-contract.ts +19 -3
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-event/index.ts +16 -8
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-event-form/index.ts +16 -7
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-events/index.ts +4 -2
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-post/index.ts +7 -0
- package/templates/gym-fitness/config.ts +7 -0
- package/templates/gym-fitness/dev/index.ts +1115 -278
- package/templates/gym-fitness/himalaya.content.json +3 -0
- package/templates/gym-fitness/tier5-src/_shared/branded-lite-hosted.ts +1 -1
- package/templates/gym-fitness/tier5-src/_shared/collection-contract.ts +19 -3
- package/templates/gym-fitness/tier5-src/_shared/event-registration.ts +86 -0
- package/templates/gym-fitness/tier5-src/_shared/himi-restaurants.ts +2 -1
- package/templates/gym-fitness/tier5-src/_shared/owner-editor-value.ts +164 -0
- package/templates/gym-fitness/tier5-src/_shared/store-product-info.ts +50 -0
- package/templates/gym-fitness/tier5-src/himi-blog-post/index.ts +21 -8
- package/templates/gym-fitness/tier5-src/himi-branded-lite-category/index.ts +3 -2
- package/templates/gym-fitness/tier5-src/himi-branded-lite-notifsettings/index.ts +173 -0
- package/templates/gym-fitness/tier5-src/himi-branded-lite-plans/index.ts +2 -1
- package/templates/gym-fitness/tier5-src/himi-events-detail/index.ts +12 -86
- package/templates/gym-fitness/tier5-src/himi-events-list/index.ts +47 -6
- package/templates/gym-fitness/tier5-src/himi-store-product/index.ts +14 -3
- package/templates/gym-fitness/tier5-src/wix-spaces-plan-detail/index.ts +2 -1
- package/templates/gym-fitness/tier5-src/wix-spaces-whats-new/index.ts +2 -1
- package/templates/gym-fitness-owner/dev/index.ts +132 -66
- package/templates/gym-fitness-owner/tier5-src/_shared/collection-contract.ts +19 -3
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-event/index.ts +16 -8
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-event-form/index.ts +16 -7
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-events/index.ts +4 -2
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-post/index.ts +7 -0
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-product/index.ts +39 -6
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-product-form/index.ts +14 -8
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-products/index.ts +7 -4
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-service/index.ts +38 -7
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-service-form/index.ts +53 -7
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-services/index.ts +4 -2
- package/templates/nonprofit-cause/config.ts +16 -0
- package/templates/nonprofit-cause/dev/index.ts +1925 -396
- package/templates/nonprofit-cause/himalaya.content.json +6 -0
- package/templates/nonprofit-cause/tier5-src/_shared/branded-lite-hosted.ts +1 -1
- package/templates/nonprofit-cause/tier5-src/_shared/collection-contract.ts +19 -3
- package/templates/nonprofit-cause/tier5-src/_shared/event-registration.ts +86 -0
- package/templates/nonprofit-cause/tier5-src/_shared/himi-restaurants.ts +2 -1
- package/templates/nonprofit-cause/tier5-src/himi-blog-post/index.ts +21 -8
- package/templates/nonprofit-cause/tier5-src/himi-branded-lite-feed/index.ts +4 -3
- package/templates/nonprofit-cause/tier5-src/himi-branded-lite-group-about/index.ts +243 -0
- package/templates/nonprofit-cause/tier5-src/himi-branded-lite-notifsettings/index.ts +173 -0
- package/templates/nonprofit-cause/tier5-src/himi-branded-lite-plans/index.ts +2 -1
- package/templates/nonprofit-cause/tier5-src/himi-events-detail/index.ts +12 -86
- package/templates/nonprofit-cause/tier5-src/himi-events-list/index.ts +47 -6
- package/templates/nonprofit-cause/tier5-src/wix-spaces-plan-detail/index.ts +2 -1
- package/templates/nonprofit-cause/tier5-src/wix-spaces-whats-new/index.ts +2 -1
- package/templates/nonprofit-cause-owner/dev/index.ts +85 -65
- package/templates/nonprofit-cause-owner/tier5-src/_shared/collection-contract.ts +19 -3
- package/templates/nonprofit-cause-owner/tier5-src/_shared/owner-rich-guard.ts +27 -0
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-event/index.ts +16 -8
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-event-form/index.ts +16 -7
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-events/index.ts +4 -2
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-post/index.ts +7 -0
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-service/index.ts +38 -7
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-service-form/index.ts +53 -7
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-services/index.ts +4 -2
- package/templates/owner-lite/dev/index.ts +132 -66
- package/templates/owner-lite/tier5-src/_shared/collection-contract.ts +19 -3
- package/templates/owner-lite/tier5-src/himi-owner-lite-event/index.ts +16 -8
- package/templates/owner-lite/tier5-src/himi-owner-lite-event-form/index.ts +16 -7
- package/templates/owner-lite/tier5-src/himi-owner-lite-events/index.ts +4 -2
- package/templates/owner-lite/tier5-src/himi-owner-lite-post/index.ts +7 -0
- package/templates/owner-lite/tier5-src/himi-owner-lite-product/index.ts +39 -6
- package/templates/owner-lite/tier5-src/himi-owner-lite-product-form/index.ts +14 -8
- package/templates/owner-lite/tier5-src/himi-owner-lite-products/index.ts +7 -4
- package/templates/owner-lite/tier5-src/himi-owner-lite-service/index.ts +38 -7
- package/templates/owner-lite/tier5-src/himi-owner-lite-service-form/index.ts +53 -7
- package/templates/owner-lite/tier5-src/himi-owner-lite-services/index.ts +4 -2
- package/templates/podcast-creator/config.ts +7 -0
- package/templates/podcast-creator/dev/index.ts +1051 -158
- package/templates/podcast-creator/himalaya.content.json +3 -0
- package/templates/podcast-creator/tier5-src/_shared/branded-lite-hosted.ts +1 -1
- package/templates/podcast-creator/tier5-src/_shared/collection-contract.ts +19 -3
- package/templates/podcast-creator/tier5-src/_shared/event-registration.ts +86 -0
- package/templates/podcast-creator/tier5-src/_shared/himi-restaurants.ts +2 -1
- package/templates/podcast-creator/tier5-src/himi-blog-post/index.ts +21 -8
- package/templates/podcast-creator/tier5-src/himi-branded-lite-feed/index.ts +4 -3
- package/templates/podcast-creator/tier5-src/himi-branded-lite-notifsettings/index.ts +173 -0
- package/templates/podcast-creator/tier5-src/himi-branded-lite-plans/index.ts +2 -1
- package/templates/podcast-creator/tier5-src/himi-branded-lite-podcasts/index.ts +2 -1
- package/templates/podcast-creator/tier5-src/himi-events-detail/index.ts +12 -86
- package/templates/podcast-creator/tier5-src/himi-events-list/index.ts +47 -6
- package/templates/podcast-creator/tier5-src/wix-spaces-plan-detail/index.ts +2 -1
- package/templates/podcast-creator/tier5-src/wix-spaces-whats-new/index.ts +2 -1
- package/templates/podcast-creator-owner/dev/index.ts +53 -1
- package/templates/podcast-creator-owner/tier5-src/_shared/collection-contract.ts +19 -3
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-event/index.ts +16 -8
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-event-form/index.ts +16 -7
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-events/index.ts +4 -2
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-post/index.ts +7 -0
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-product/index.ts +39 -6
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-product-form/index.ts +14 -8
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-products/index.ts +7 -4
- package/templates/portfolio-creative/config.ts +7 -0
- package/templates/portfolio-creative/dev/index.ts +1305 -468
- package/templates/portfolio-creative/himalaya.content.json +3 -0
- package/templates/portfolio-creative/tier5-src/_shared/branded-lite-hosted.ts +1 -1
- package/templates/portfolio-creative/tier5-src/_shared/collection-contract.ts +19 -3
- package/templates/portfolio-creative/tier5-src/_shared/event-registration.ts +86 -0
- package/templates/portfolio-creative/tier5-src/_shared/himi-restaurants.ts +2 -1
- package/templates/portfolio-creative/tier5-src/_shared/owner-editor-value.ts +164 -0
- package/templates/portfolio-creative/tier5-src/_shared/store-product-info.ts +50 -0
- package/templates/portfolio-creative/tier5-src/himi-blog-post/index.ts +21 -8
- package/templates/portfolio-creative/tier5-src/himi-branded-lite-category/index.ts +3 -2
- package/templates/portfolio-creative/tier5-src/himi-branded-lite-notifsettings/index.ts +173 -0
- package/templates/portfolio-creative/tier5-src/himi-branded-lite-portfolio/index.ts +2 -1
- package/templates/portfolio-creative/tier5-src/himi-branded-lite-portfolio-project/index.ts +3 -2
- package/templates/portfolio-creative/tier5-src/himi-events-detail/index.ts +12 -86
- package/templates/portfolio-creative/tier5-src/himi-events-list/index.ts +47 -6
- package/templates/portfolio-creative/tier5-src/himi-store-product/index.ts +14 -3
- package/templates/portfolio-creative/tier5-src/wix-spaces-whats-new/index.ts +2 -1
- package/templates/portfolio-creative-owner/dev/index.ts +129 -66
- package/templates/portfolio-creative-owner/tier5-src/_shared/collection-contract.ts +19 -3
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-post/index.ts +7 -0
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-product/index.ts +39 -6
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-product-form/index.ts +14 -8
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-products/index.ts +7 -4
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-service/index.ts +38 -7
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-service-form/index.ts +53 -7
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-services/index.ts +4 -2
- package/templates/restaurant/config.ts +7 -0
- package/templates/restaurant/dev/index.ts +1051 -158
- package/templates/restaurant/himalaya.content.json +3 -0
- package/templates/restaurant/tier5-src/_shared/branded-lite-hosted.ts +1 -1
- package/templates/restaurant/tier5-src/_shared/collection-contract.ts +19 -3
- package/templates/restaurant/tier5-src/_shared/event-registration.ts +86 -0
- package/templates/restaurant/tier5-src/_shared/himi-restaurants.ts +2 -1
- package/templates/restaurant/tier5-src/himi-blog-post/index.ts +21 -8
- package/templates/restaurant/tier5-src/himi-branded-lite-notifsettings/index.ts +173 -0
- package/templates/restaurant/tier5-src/himi-events-detail/index.ts +12 -86
- package/templates/restaurant/tier5-src/himi-events-list/index.ts +47 -6
- package/templates/restaurant/tier5-src/wix-spaces-whats-new/index.ts +2 -1
- package/templates/restaurant-owner/dev/index.ts +132 -66
- package/templates/restaurant-owner/tier5-src/_shared/collection-contract.ts +19 -3
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-event/index.ts +16 -8
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-event-form/index.ts +16 -7
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-events/index.ts +4 -2
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-post/index.ts +7 -0
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-product/index.ts +39 -6
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-product-form/index.ts +14 -8
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-products/index.ts +7 -4
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-service/index.ts +38 -7
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-service-form/index.ts +53 -7
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-services/index.ts +4 -2
- package/templates/retail-store/config.ts +7 -0
- package/templates/retail-store/dev/index.ts +597 -191
- package/templates/retail-store/himalaya.content.json +3 -0
- package/templates/retail-store/tier5-src/_shared/branded-lite-hosted.ts +1 -1
- package/templates/retail-store/tier5-src/_shared/collection-contract.ts +19 -3
- package/templates/retail-store/tier5-src/_shared/himi-restaurants.ts +2 -1
- package/templates/retail-store/tier5-src/_shared/owner-editor-value.ts +164 -0
- package/templates/retail-store/tier5-src/_shared/store-product-info.ts +50 -0
- package/templates/retail-store/tier5-src/himi-blog-post/index.ts +21 -8
- package/templates/retail-store/tier5-src/himi-branded-lite-category/index.ts +3 -2
- package/templates/retail-store/tier5-src/himi-branded-lite-notifsettings/index.ts +173 -0
- package/templates/retail-store/tier5-src/himi-store-product/index.ts +13 -3
- package/templates/retail-store/tier5-src/wix-spaces-whats-new/index.ts +2 -1
- package/templates/retail-store-owner/dev/index.ts +50 -1
- package/templates/retail-store-owner/tier5-src/_shared/collection-contract.ts +19 -3
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-post/index.ts +7 -0
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-product/index.ts +39 -6
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-product-form/index.ts +14 -8
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-products/index.ts +7 -4
- package/templates/yoga-studio/config.ts +7 -0
- package/templates/yoga-studio/dev/index.ts +1107 -110
- package/templates/yoga-studio/himalaya.content.json +3 -0
- package/templates/yoga-studio/tier5-src/_shared/branded-lite-hosted.ts +1 -1
- package/templates/yoga-studio/tier5-src/_shared/collection-contract.ts +19 -3
- package/templates/yoga-studio/tier5-src/_shared/event-registration.ts +86 -0
- package/templates/yoga-studio/tier5-src/_shared/himi-restaurants.ts +2 -1
- package/templates/yoga-studio/tier5-src/himi-blog-post/index.ts +21 -8
- package/templates/yoga-studio/tier5-src/himi-branded-lite-notifsettings/index.ts +173 -0
- package/templates/yoga-studio/tier5-src/himi-branded-lite-plans/index.ts +2 -1
- package/templates/yoga-studio/tier5-src/himi-branded-lite-program/facts.ts +52 -0
- package/templates/yoga-studio/tier5-src/himi-branded-lite-program/index.ts +21 -11
- package/templates/yoga-studio/tier5-src/himi-events-detail/index.ts +12 -86
- package/templates/yoga-studio/tier5-src/himi-events-list/index.ts +47 -6
- package/templates/yoga-studio/tier5-src/wix-spaces-plan-detail/index.ts +2 -1
- package/templates/yoga-studio/tier5-src/wix-spaces-whats-new/index.ts +2 -1
- package/templates/yoga-studio-owner/dev/index.ts +85 -65
- package/templates/yoga-studio-owner/tier5-src/_shared/collection-contract.ts +19 -3
- package/templates/yoga-studio-owner/tier5-src/_shared/owner-rich-guard.ts +27 -0
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-event/index.ts +16 -8
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-event-form/index.ts +16 -7
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-events/index.ts +4 -2
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-post/index.ts +7 -0
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-service/index.ts +38 -7
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-service-form/index.ts +53 -7
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-services/index.ts +4 -2
|
@@ -97,6 +97,7 @@ export const HOSTED_SCREEN_VERTICALS: Readonly<Record<string, readonly BrandedLi
|
|
|
97
97
|
himi_gift_card: ["giftCards"],
|
|
98
98
|
himi_gift_cards: ["giftCards"],
|
|
99
99
|
himi_groups: ["groups"],
|
|
100
|
+
himi_group_about: ["groups"],
|
|
100
101
|
himi_invoice: ["invoices"],
|
|
101
102
|
himi_invoices: ["invoices"],
|
|
102
103
|
himi_loyalty: ["loyalty"],
|
|
@@ -107,7 +108,6 @@ export const HOSTED_SCREEN_VERTICALS: Readonly<Record<string, readonly BrandedLi
|
|
|
107
108
|
himi_my_events: ["events"],
|
|
108
109
|
himi_my_subscriptions: ["plans"],
|
|
109
110
|
himi_new_message: ["inbox", "members"],
|
|
110
|
-
himi_notif_settings: ["notifsettings"],
|
|
111
111
|
himi_notifications: ["notifications"],
|
|
112
112
|
himi_orders: ["store"],
|
|
113
113
|
himi_photo_album: ["galleries"],
|
|
@@ -19,6 +19,8 @@ export interface CollectionConfig {
|
|
|
19
19
|
/** Numeric 0–5 row field rendered as a Rating (accessible "N out of 5") instead of
|
|
20
20
|
* folding a "★★★★☆" glyph string into the metadata line, so screen readers get a value. */
|
|
21
21
|
rating?: string;
|
|
22
|
+
/** Domain-provided calendar text and its unavailable flag, kept separate from media. */
|
|
23
|
+
dateBadge?: { text: string; hidden: string };
|
|
22
24
|
sectioned?: boolean;
|
|
23
25
|
view: 'list' | 'grid';
|
|
24
26
|
backendQuery?: boolean;
|
|
@@ -38,7 +40,13 @@ export interface CollectionConfig {
|
|
|
38
40
|
/** The card's primary verb, shown as a visible button beside the overflow.
|
|
39
41
|
* `screenId` opens that screen with the row id; without it the verb reuses the
|
|
40
42
|
* row's own tap action (the detail screen that owns the verb). */
|
|
41
|
-
primaryVerb?: { label: string; screenId?: string
|
|
43
|
+
primaryVerb?: { label: string; screenId?: string;
|
|
44
|
+
/** A row field holding THIS row's verb label (Register, RSVP, Buy Tickets…); `label` stays the fallback copy. */
|
|
45
|
+
labelPath?: string;
|
|
46
|
+
/** A row boolean that hides the verb on rows that offer no action (an event with no registration). */
|
|
47
|
+
hiddenPath?: string };
|
|
48
|
+
/** The worker action that shares a row (`tier5.<service>.<name>` with `{ id }`): adds Share to the card's overflow menu. */
|
|
49
|
+
shareAction?: string;
|
|
42
50
|
/** A TIME-BOUND entity (an event, a booking, a reservation): the boolean row field the
|
|
43
51
|
* DOMAIN sets once the record has finished — computed by the same rule as the row's own
|
|
44
52
|
* "Past" label, in the same place, so the two cannot disagree. A finished
|
|
@@ -73,7 +81,7 @@ export const COLLECTIONS: Record<string, CollectionConfig> = {
|
|
|
73
81
|
himiBookingBookings: { rows: 'rows', title: 'title', subtitle: 'subtitle', metadata: ['status'], view: 'list', backendQuery: true,
|
|
74
82
|
defaultFilter: 'value:Upcoming', searchPlaceholder: 'Search by booking ID', countNoun: ['booking', 'bookings'], filterValues: ['Upcoming', 'Past', 'Cancelled'],
|
|
75
83
|
sortLabels: { default: 'Soonest', az: 'Oldest', za: 'Latest' } },
|
|
76
|
-
himiEvents: { rows: 'results', title: 'title', subtitle: 'dateLine', image: 'image', view: 'grid', backendQuery: true, defaultFilter: 'value:Upcoming', searchPlaceholder: 'Search events', countNoun: ['event', 'events'], filterValues: ['Upcoming', 'Past'], sortLabels: { default: 'Date', az: 'Title A–Z', za: 'Title Z–A' }, primaryVerb: { label: 'Register' }, endedWhen: 'isPast' },
|
|
84
|
+
himiEvents: { rows: 'results', title: 'title', subtitle: 'dateLine', image: 'image', dateBadge: { text: 'dateBadge', hidden: 'noDateBadge' }, gridColumns: 1, view: 'grid', backendQuery: true, defaultFilter: 'value:Upcoming', searchPlaceholder: 'Search events', countNoun: ['event', 'events'], filterValues: ['Upcoming', 'Past'], sortLabels: { default: 'Date', az: 'Title A–Z', za: 'Title Z–A' }, primaryVerb: { label: 'Register', labelPath: 'actionLabel', hiddenPath: 'noAction' }, shareAction: 'share', endedWhen: 'isPast' },
|
|
77
85
|
himiBlogFeed: { rows: 'posts', title: 'title', subtitle: 'excerpt', image: 'image', filter: 'category', filterValuesFrom: 'categories', view: 'grid', narrowedBy: 'selectedCategoryId' },
|
|
78
86
|
himiOwnerLiteProducts: { rows: 'products', title: 'name', subtitle: 'price', image: 'image', view: 'grid', backendQuery: true, countNoun: ['product', 'products'] },
|
|
79
87
|
himiOwnerModules: { instanceInput: 'module', rows: 'rows', title: 'title', subtitle: 'subtitle', image: 'image', filter: 'statusLabel', view: 'list', paged: true, reviewAction: 'prepareRecord', reviewSelectedAction: 'prepareSelected' },
|
|
@@ -208,7 +216,15 @@ export function projectCollection(source: CollectionRow[], config: CollectionCon
|
|
|
208
216
|
...(config.endedWhen ? (() => {
|
|
209
217
|
const ended = row[config.endedWhen] === true || (config.endedWhen === 'isPast' && row.statusLabel === 'Past');
|
|
210
218
|
return { collectionEnded: ended, collectionNotEnded: !ended };
|
|
211
|
-
})() : {})
|
|
219
|
+
})() : {}),
|
|
220
|
+
// A row restored from state saved by an older build has no badge flag. An unset hidden flag
|
|
221
|
+
// fails open and would paint an empty chip, so it reads as "no badge" until a refresh.
|
|
222
|
+
...(config.dateBadge && typeof row[config.dateBadge.hidden] !== 'boolean'
|
|
223
|
+
? { [config.dateBadge.text]: '', [config.dateBadge.hidden]: true } : {}),
|
|
224
|
+
// Same for a per-row verb: an unset hidden flag fails open and would show a verb with an unresolved
|
|
225
|
+
// label, so a row without the flag offers no action until a refresh establishes one.
|
|
226
|
+
...(config.primaryVerb?.hiddenPath && typeof row[config.primaryVerb.hiddenPath] !== 'boolean'
|
|
227
|
+
? { ...(config.primaryVerb.labelPath ? { [config.primaryVerb.labelPath]: '' } : {}), [config.primaryVerb.hiddenPath]: true } : {}) })),
|
|
212
228
|
filters, loaded: rows.length,
|
|
213
229
|
};
|
|
214
230
|
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// Pure Events registration helpers shared by the event detail and the event list: which action an
|
|
2
|
+
// event offers, its label, and the URLs behind it. One copy, so a card and the detail screen never
|
|
3
|
+
// disagree about whether an event can be registered for.
|
|
4
|
+
import type { WixEvent } from "@himalaya/wix-events";
|
|
5
|
+
import { formatWeekdayDateTime } from "./engine-intl.js";
|
|
6
|
+
import { copyParam } from "./himi-copy.js";
|
|
7
|
+
|
|
8
|
+
function detailCopy(key: string, fallback: string): string { return copyParam(key, fallback); }
|
|
9
|
+
|
|
10
|
+
/** Registration CTA kind derived from the event's registration/eventType. */
|
|
11
|
+
export type RegistrationKind = "rsvp" | "tickets" | "external" | "none";
|
|
12
|
+
|
|
13
|
+
/** Map registration status and decoded eventType onto the CTA variant. The decoded
|
|
14
|
+
* `eventType` already folds registration.type / registration.initialType /
|
|
15
|
+
* the legacy flat `type` (decodeEvent, #478). Without a recognized type or
|
|
16
|
+
* status there is no registration action to offer. */
|
|
17
|
+
export function registrationKindOf(e: WixEvent): RegistrationKind {
|
|
18
|
+
const raw = e as unknown as { registration?: { type?: string; initialType?: string; status?: string } };
|
|
19
|
+
const value = (raw.registration?.type ?? e.eventType ?? raw.registration?.initialType ?? "").toUpperCase();
|
|
20
|
+
if (value === "NONE" || value.includes("NO_REGISTRATION")) return "none";
|
|
21
|
+
// Open statuses select the footer action, so they must also select the
|
|
22
|
+
// inventory read and action guards when type metadata is absent or stale.
|
|
23
|
+
switch (raw.registration?.status?.toUpperCase()) {
|
|
24
|
+
case "OPEN_TICKETS": return "tickets";
|
|
25
|
+
case "OPEN_EXTERNAL": return "external";
|
|
26
|
+
case "OPEN_RSVP":
|
|
27
|
+
case "OPEN_RSVP_WAITLIST_ONLY":
|
|
28
|
+
case "SCHEDULED_RSVP": return "rsvp";
|
|
29
|
+
}
|
|
30
|
+
if (value.includes("EXTERNAL")) return "external";
|
|
31
|
+
if (value.includes("TICKET")) return "tickets";
|
|
32
|
+
if (value.includes("RSVP")) return "rsvp";
|
|
33
|
+
return "none";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function eventStatus(e: WixEvent): string {
|
|
37
|
+
const raw = e as unknown as { status?: string };
|
|
38
|
+
return (raw.status ?? e.eventStatus ?? "").toUpperCase();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function registrationUi(e: WixEvent, now: number): { state: string; label: string; action: string; caption: string; notice: string } {
|
|
42
|
+
const raw = e as unknown as { registration?: { status?: string; registrationPaused?: boolean; registrationDisabled?: boolean; allowedGuestTypes?: string; rsvp?: { startDate?: string }; tickets?: { soldOut?: boolean }; external?: { url?: string } }; form?: { messages?: { registrationClosed?: { message?: string; exploreEventsActionLabel?: string }; ticketsUnavailable?: { message?: string; exploreEventsActionLabel?: string } } } };
|
|
43
|
+
const r = raw.registration ?? {};
|
|
44
|
+
const status = eventStatus(e);
|
|
45
|
+
const type = registrationKindOf(e);
|
|
46
|
+
if (status === "CANCELED") return { state: "notice", label: "", action: "", caption: "", notice: detailCopy("event.canceledNotice", "This event was canceled.") };
|
|
47
|
+
if (status === "ENDED") return { state: "notice", label: "", action: "", caption: "", notice: detailCopy("event.endedNotice", "This event has ended.") };
|
|
48
|
+
if (type === "none" || r.registrationDisabled) return { state: "none", label: "", action: "", caption: "", notice: "" };
|
|
49
|
+
const ownerClosed = raw.form?.messages?.registrationClosed?.message;
|
|
50
|
+
const ownerSold = raw.form?.messages?.ticketsUnavailable?.message;
|
|
51
|
+
if (["CLOSED_AUTOMATICALLY", "CLOSED_MANUALLY"].includes(r.status || "") || r.registrationPaused) return { state: "notice", label: "", action: "", caption: "", notice: ownerClosed || detailCopy("event.closedNotice", "Registration is closed.") };
|
|
52
|
+
if (type === "tickets" && r.tickets?.soldOut) return { state: "notice", label: "", action: "", caption: "", notice: ownerSold || detailCopy("event.soldOutNotice", "Tickets are sold out.") };
|
|
53
|
+
if (r.status === "SCHEDULED_RSVP") return { state: "scheduled", label: detailCopy("event.rsvpCta", "RSVP"), action: "", caption: detailCopy("event.opensCaption", "RSVP opens {date}.").replace("{date}", r.rsvp?.startDate ? formatWeekdayDateTime(new Date(r.rsvp.startDate)) : ""), notice: "" };
|
|
54
|
+
if (r.status === "OPEN_RSVP_WAITLIST_ONLY") return { state: "waitlist", label: detailCopy("event.waitlistCta", "Join Waitlist"), action: "himi.open", caption: detailCopy("event.fullCaption", "This event is full."), notice: "" };
|
|
55
|
+
if (r.status === "OPEN_TICKETS" || type === "tickets") return { state: "tickets", label: detailCopy("event.ticketsCta", "Buy Tickets"), action: "openTickets", caption: "", notice: "" };
|
|
56
|
+
if (r.status === "OPEN_EXTERNAL" || type === "external") return registrationUrlOf(e)
|
|
57
|
+
? { state: "external", label: detailCopy("event.externalCta", "Register"), action: "openRegistration", caption: "", notice: "" }
|
|
58
|
+
: { state: "none", label: "", action: "", caption: "", notice: "" };
|
|
59
|
+
if (type === "rsvp" && r.status === "OPEN_RSVP") return { state: "rsvp", label: detailCopy("event.rsvpCta", "RSVP"), action: "himi.open", caption: "", notice: "" };
|
|
60
|
+
if (r.status === "OPEN_RSVP" || (type === "rsvp" && !r.status)) return { state: "rsvp", label: detailCopy("event.rsvpCta", "RSVP"), action: "himi.open", caption: "", notice: "" };
|
|
61
|
+
void now;
|
|
62
|
+
return { state: "none", label: "", action: "", caption: "", notice: "" };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** External registration URL / event page URL from the raw payload ("" when absent). */
|
|
66
|
+
export function registrationUrlOf(e: WixEvent): string {
|
|
67
|
+
const raw = e as unknown as {
|
|
68
|
+
registration?: { external?: { registration?: string; url?: string } };
|
|
69
|
+
eventPageUrl?: { base?: string; path?: string } | string;
|
|
70
|
+
};
|
|
71
|
+
const external = raw.registration?.external?.registration ?? raw.registration?.external?.url;
|
|
72
|
+
if (typeof external === "string" && /^https?:\/\//i.test(external)) return external;
|
|
73
|
+
return eventPageUrlOf(e);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** The event's public page URL when the payload carries one ("" otherwise). */
|
|
77
|
+
export function eventPageUrlOf(e: WixEvent): string {
|
|
78
|
+
const raw = e as unknown as { eventPageUrl?: { base?: string; path?: string } | string };
|
|
79
|
+
const page = raw.eventPageUrl;
|
|
80
|
+
if (typeof page === "string" && /^https?:\/\//i.test(page)) return page;
|
|
81
|
+
if (page && typeof page === "object" && typeof page.base === "string" && page.base) {
|
|
82
|
+
const path = typeof page.path === "string" ? page.path : "";
|
|
83
|
+
return `${page.base.replace(/\/$/, "")}${path && !path.startsWith("/") ? "/" : ""}${path}`;
|
|
84
|
+
}
|
|
85
|
+
return "";
|
|
86
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { descriptionBody } from "./rich-body.js";
|
|
1
2
|
// Shared restaurants-menu shaping for himi workers (branded-lite `restaurants`
|
|
2
3
|
// vertical): dish cards + menu categories derived from the Wix Restaurants
|
|
3
4
|
// Menus hierarchy (menu → sections → items). Used by BOTH the menus worker
|
|
@@ -166,7 +167,7 @@ export function itemPriceVariants(item: WixItem): Array<{ id: string; variantId:
|
|
|
166
167
|
|
|
167
168
|
/** HTML-stripped, whitespace-collapsed description snippet (≤ 90 chars). */
|
|
168
169
|
export function descriptionSnippet(value: unknown, maxLength = 90): string {
|
|
169
|
-
const text = (
|
|
170
|
+
const text = descriptionBody(value).bodyText.replace(/\s+/g, " ").trim();
|
|
170
171
|
return text.length > maxLength ? `${text.slice(0, maxLength - 1).trimEnd()}…` : text;
|
|
171
172
|
}
|
|
172
173
|
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
// [rico] #3371 — how an owner form reads a RichContentEditor out of `submit(s, values)`.
|
|
2
|
+
//
|
|
3
|
+
// The editor writes its Ricos document into the Form's value bag as a JSON STRING under its `name`,
|
|
4
|
+
// on every change and again on blur. The value is ABSENT when the editor never became ready (the
|
|
5
|
+
// engine page is not bundled, failed, or timed out): the stored body must then stay exactly as it is
|
|
6
|
+
// (user decision, 2026-09-26 — save everything but the body). A document is written exactly as the
|
|
7
|
+
// editor produced it: never re-encoded (encodeRichContent is the VIEWER's; it rewrites media ids and
|
|
8
|
+
// drops nodes the owner never touched).
|
|
9
|
+
// Inside the platform source this import is a relative path, so node test runs resolve it; an app's
|
|
10
|
+
// copy imports "@himalaya/wix-rich-content".
|
|
11
|
+
import { parseRicosJson, isPlainRicos, richContentOverLimits, type RicosDocument } from "@himalaya/wix-rich-content";
|
|
12
|
+
|
|
13
|
+
export type EditorValue =
|
|
14
|
+
| { kind: "absent" } // leave the stored body untouched
|
|
15
|
+
| { kind: "unchanged" } // byte-identical to the seed: nothing to write
|
|
16
|
+
| { kind: "doc"; doc: RicosDocument; json: string } // write `doc`; `json` is the editor's own string
|
|
17
|
+
| { kind: "too_large" } // past the rich-content limits: save the rest, say why
|
|
18
|
+
| { kind: "invalid" }; // not a Ricos document: refuse, write nothing
|
|
19
|
+
|
|
20
|
+
const TEXT_ONLY = new Set(["PARAGRAPH", "TEXT", "HEADING", "BULLETED_LIST", "ORDERED_LIST", "LIST_ITEM", "BLOCKQUOTE", "CODE_BLOCK"]);
|
|
21
|
+
/** No visible text and no non-text node (an image, a divider, an embed…): an empty body. */
|
|
22
|
+
export function isEmptyDocument(doc: RicosDocument): boolean {
|
|
23
|
+
const empty = (nodes: RicosDocument["nodes"] | undefined): boolean => (nodes ?? []).every((n) =>
|
|
24
|
+
TEXT_ONLY.has(String(n?.type)) && !String(n.textData?.text ?? "").trim() && empty(n.nodes));
|
|
25
|
+
return empty(doc.nodes);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** A Ricos JSON string checked as every viewer would: the limits first, then the shape. */
|
|
29
|
+
function checkedDocument(raw: string): EditorValue {
|
|
30
|
+
// Every viewer renders a body past the limits as its fallback, so it is never written. One parse
|
|
31
|
+
// feeds both checks; a string past the byte limit is refused before it is parsed at all.
|
|
32
|
+
if (richContentOverLimits(raw, null) === "too_large") return { kind: "too_large" };
|
|
33
|
+
const doc = parseRicosJson(raw);
|
|
34
|
+
if (!doc) return { kind: "invalid" };
|
|
35
|
+
return richContentOverLimits(raw, doc) ? { kind: "too_large" } : { kind: "doc", doc, json: raw };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** `raw` is `values[name]`; `seededJson` is the string the form published as the editor's seed. */
|
|
39
|
+
export function editorValue(raw: unknown, seededJson: string): EditorValue {
|
|
40
|
+
if (typeof raw !== "string" || raw === "") return { kind: "absent" };
|
|
41
|
+
if (seededJson !== "" && raw === seededJson) return { kind: "unchanged" };
|
|
42
|
+
const checked = checkedDocument(raw);
|
|
43
|
+
// An empty editor over no body (a record that had none) writes nothing, rather than an empty document.
|
|
44
|
+
if (checked.kind === "doc" && seededJson === "" && isEmptyDocument(checked.doc)) return { kind: "unchanged" };
|
|
45
|
+
return checked;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ── The per-field editor machinery every owner form shares (post body, event / product description,
|
|
49
|
+
// FAQ answer). Each form keeps `<key>Json` (the seed), `<key>Pending` and `<key>Dirty` in its state.
|
|
50
|
+
|
|
51
|
+
/** `<key>Pending`: until this run's load lands, the descriptor hides the editor — it seeds ONCE, so a
|
|
52
|
+
* provisional editor the owner typed into would keep that text over the real document.
|
|
53
|
+
* `<key>Dirty`: the editor's on_change fired since the field was loaded or saved. Once ready the bag
|
|
54
|
+
* ALWAYS holds a document — the engine's own re-serialization of the seed, not byte-equal to it — so
|
|
55
|
+
* only this says the owner edited the field. Every arm fires a pending on_change before it releases a
|
|
56
|
+
* Form submit, so an edit made inside the debounce window is reported before the submit reads it. */
|
|
57
|
+
export interface EditorGate { pending: boolean; dirty: boolean }
|
|
58
|
+
|
|
59
|
+
/** What a live submit does with `values[name]`: only an edit the editor reported counts. */
|
|
60
|
+
export function editorSubmitValue(raw: unknown, seededJson: string, gate: EditorGate): EditorValue {
|
|
61
|
+
return gate.pending || !gate.dirty ? { kind: "absent" } : editorValue(raw, seededJson);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** A queued write's document. A replay runs in a FRESH worker with no editor, so the string is checked
|
|
65
|
+
* again (shape and limits); "absent" when the item carries none. Never "unchanged": it was an edit. */
|
|
66
|
+
export function queuedDocument(raw: unknown): EditorValue {
|
|
67
|
+
return typeof raw === "string" && raw !== "" ? checkedDocument(raw) : { kind: "absent" };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** The editor's on_change: the field is now edited, and a confirmation of an earlier save is stale.
|
|
71
|
+
* No timing window: an arm fires the pending on_change BEFORE it releases a submit, so any on_change
|
|
72
|
+
* that arrives after a save carries an edit that save did not write. */
|
|
73
|
+
export function editorChanged<S extends { status: string; savedMessage: string }>(s: S): Pick<S, "status" | "savedMessage"> {
|
|
74
|
+
return { status: s.status === "saved" ? "idle" : s.status, savedMessage: "" };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** The offline cache's verdict on the stored body (`<key>Protected`): rich unless KNOWN plain. From the
|
|
78
|
+
* document when the form knows it; otherwise plain only when the record was loaded as plain. A bundle
|
|
79
|
+
* rolled back past the editor reads this flag to decide whether its plain field may overwrite the body. */
|
|
80
|
+
export function cachedRichVerdict(doc: RicosDocument | null, knownPlain: boolean): "true" | "false" {
|
|
81
|
+
if (doc) return isPlainRicos(doc) ? "false" : "true";
|
|
82
|
+
return knownPlain ? "false" : "true";
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** The keys of one editor field that describe THIS run, never the last one: the seed (`<key>Json`),
|
|
86
|
+
* the load gate (`<key>Pending`), the edit flag (`<key>Dirty`) and its counter (`<key>Edits`). A form
|
|
87
|
+
* lists them in `defineService(name, { notHydrated })`, so a cold boot takes their DECLARED values: the
|
|
88
|
+
* editor seeds once, and a persisted seed would be painted as its content and saved back over a newer
|
|
89
|
+
* record. */
|
|
90
|
+
export function editorRunKeys<K extends string>(key: K): [`${K}Json`, `${K}Pending`, `${K}Dirty`, `${K}Edits`] {
|
|
91
|
+
return [`${key}Json`, `${key}Pending`, `${key}Dirty`, `${key}Edits`];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Does the form already hold `target` with its editor run in progress? A `load` for it then keeps the form as
|
|
95
|
+
* it is: on the way back to a warm worker (its refresh and onAppear) a re-read would race an edit in flight and
|
|
96
|
+
* reset the reported edit, and the editor, which seeds once, would show another document than the state holds.
|
|
97
|
+
* An edit form holds its record once the editor loaded; a create form holds a draft once the owner edited. */
|
|
98
|
+
export function holdsLoadedForm(state: object, target: { id: string; mode: string }, key: string): boolean {
|
|
99
|
+
const s = state as Record<string, unknown>;
|
|
100
|
+
if (s.status === 'error' || s.error || s.mode !== target.mode || (s.id ?? '') !== target.id) return false;
|
|
101
|
+
return target.mode === 'create' ? s[`${key}Dirty`] === true || Number(s[`${key}Edits`] ?? 0) > 0 : s[`${key}Pending`] === false;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** The same keys on a runtime that ignores `notHydrated` (a binary from before #3644, still served new
|
|
105
|
+
* bundles over the air): reset them in place, before any action runs. A no-op where the runtime
|
|
106
|
+
* honours the option (`himalaya.hydrationOptOut`). */
|
|
107
|
+
export function resetHydratedEditorOnOlderRuntime(svc: unknown, key: string, pendingAtBoot: boolean, extra: Record<string, unknown> = {}): void {
|
|
108
|
+
if ((globalThis as { himalaya?: { hydrationOptOut?: unknown } }).himalaya?.hydrationOptOut === true) return;
|
|
109
|
+
const booted = (svc as { getState?: () => Record<string, unknown> | undefined }).getState?.();
|
|
110
|
+
if (!booted || typeof booted !== "object") return;
|
|
111
|
+
if (booted[`${key}Json`]) booted[`${key}Json`] = "";
|
|
112
|
+
booted[`${key}Pending`] = pendingAtBoot;
|
|
113
|
+
booted[`${key}Dirty`] = false;
|
|
114
|
+
booted[`${key}Edits`] = 0;
|
|
115
|
+
Object.assign(booted, extra);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** What an awaited save leaves the field as, read from the LIVE state when it lands. An on_change that
|
|
119
|
+
* arrived while the save was in flight (`<key>Edits` moved) is an edit the save did not write: the
|
|
120
|
+
* field stays dirty and the save's confirmation is stale. Otherwise a save that wrote the field
|
|
121
|
+
* re-bases it as saved. `live` is the service's state now (none, or one that never moved: the start state). */
|
|
122
|
+
export function editorAfterSave(key: string, start: Record<string, unknown>, live: Record<string, unknown> | undefined, wrote: boolean):
|
|
123
|
+
{ dirty: boolean; edits: number; editedDuringSave: boolean } {
|
|
124
|
+
const atStart = Number(start[`${key}Edits`] ?? 0);
|
|
125
|
+
const now = Number(live?.[`${key}Edits`] ?? atStart);
|
|
126
|
+
// Only a counter that MOVED ON counts; one behind the start is a host that does not apply results.
|
|
127
|
+
const editedDuringSave = now > atStart;
|
|
128
|
+
return editedDuringSave
|
|
129
|
+
? { dirty: true, edits: now, editedDuringSave }
|
|
130
|
+
: { dirty: wrote ? false : Boolean(start[`${key}Dirty`]), edits: atStart, editedDuringSave };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** The live state of a service handle, when the runtime gives one. */
|
|
134
|
+
export function liveState(svc: unknown): Record<string, unknown> | undefined {
|
|
135
|
+
const st = (svc as { getState?: () => unknown } | undefined)?.getState?.();
|
|
136
|
+
return st && typeof st === "object" ? st as Record<string, unknown> : undefined;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** An awaited action's result, laid over the LIVE state: only the keys the action CHANGED (against the
|
|
140
|
+
* state it started from) are written, so anything the owner did while it awaited — an edit, a field in
|
|
141
|
+
* the details sheet, closing the sheet — survives a save that lands after it, success or failure. A
|
|
142
|
+
* key both changed goes to the action. Where the host gives no live state, the result as it is. */
|
|
143
|
+
/** An awaited load's result, unless the form moved to another record while it read. On a warm return the host's bare
|
|
144
|
+
* refresh and the screen's onAppear run together: after a save the refresh fetches the record just saved while the
|
|
145
|
+
* onAppear of a create screen opens a blank form, and the refresh landing last turned the new form into that record's
|
|
146
|
+
* edit. Nothing lands then (`undefined`, the runtime's no-op), nor from a load that changes nothing: started with the
|
|
147
|
+
* same state as the onAppear, it would put back the record that one replaced. A load for the record the form still
|
|
148
|
+
* holds lands as it is; so does any load where the host gives no live state. */
|
|
149
|
+
export function landsOnLive<S extends object>(svc: unknown, start: S, result: S): S | undefined {
|
|
150
|
+
if (result === start) return undefined;
|
|
151
|
+
const now = liveState(svc);
|
|
152
|
+
if (!now || now === start) return result;
|
|
153
|
+
const s0 = start as Record<string, unknown>;
|
|
154
|
+
return now.id !== s0.id || now.mode !== s0.mode ? undefined : result;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function settleOverLive<S extends object>(svc: unknown, start: S, result: S): S {
|
|
158
|
+
const now = liveState(svc) as S | undefined;
|
|
159
|
+
if (!now || now === start || now === result) return result;
|
|
160
|
+
const out = { ...now } as Record<string, unknown>;
|
|
161
|
+
const r = result as Record<string, unknown>, s0 = start as Record<string, unknown>;
|
|
162
|
+
for (const k of Object.keys(r)) if (!Object.is(r[k], s0[k])) out[k] = r[k];
|
|
163
|
+
return out as S;
|
|
164
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { StoreProduct } from "./himi-store.js";
|
|
2
|
+
import { cleanTitle } from "./himi-text.js";
|
|
3
|
+
import { descriptionBody } from "./rich-body.js";
|
|
4
|
+
import { isEmptyDocument } from "./owner-editor-value.js";
|
|
5
|
+
|
|
6
|
+
/** V3 returns only section ids and titles for `INFO_SECTION`; the rich and HTML bodies
|
|
7
|
+
* are separate fields. `DESCRIPTION` keeps the product's own Ricos description. A V1
|
|
8
|
+
* catalog ignores all of these. */
|
|
9
|
+
export const PRODUCT_DETAIL_FIELDS = ["DESCRIPTION", "INFO_SECTION", "INFO_SECTION_DESCRIPTION", "INFO_SECTION_PLAIN_DESCRIPTION"] as const;
|
|
10
|
+
|
|
11
|
+
export interface ProductInfoSection {
|
|
12
|
+
id: string;
|
|
13
|
+
title: string;
|
|
14
|
+
body: string;
|
|
15
|
+
bodyJson: string;
|
|
16
|
+
hasBodyJson: boolean;
|
|
17
|
+
noTitle: boolean;
|
|
18
|
+
expanded: boolean;
|
|
19
|
+
collapsed: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Keep authored order and bodies; retain expansion only where the source identity is unambiguous. */
|
|
23
|
+
export function productInfoSections(product: StoreProduct, previous: ProductInfoSection[]): ProductInfoSection[] {
|
|
24
|
+
const expanded = new Map(previous.map(section => [section.id, section.expanded]));
|
|
25
|
+
const occurrences = new Map<string, number>();
|
|
26
|
+
const sections = [...(product.infoSections ?? []), ...(product.additionalInfoSections ?? [])];
|
|
27
|
+
const titles = sections.map(section => cleanTitle(section.title));
|
|
28
|
+
const titleCounts = new Map<string, number>();
|
|
29
|
+
const previousTitleCounts = new Map<string, number>();
|
|
30
|
+
for (const title of titles) titleCounts.set(title, (titleCounts.get(title) ?? 0) + 1);
|
|
31
|
+
for (const section of previous) previousTitleCounts.set(section.title, (previousTitleCounts.get(section.title) ?? 0) + 1);
|
|
32
|
+
return sections.flatMap((section, index) => {
|
|
33
|
+
const title = titles[index];
|
|
34
|
+
const sourceId = "uniqueName" in section ? section.uniqueName?.trim() : "";
|
|
35
|
+
const key = sourceId || title;
|
|
36
|
+
const occurrence = occurrences.get(key) ?? 0;
|
|
37
|
+
occurrences.set(key, occurrence + 1);
|
|
38
|
+
const id = `info:${key}:${occurrence}`;
|
|
39
|
+
const fallback = descriptionBody("plainDescription" in section ? section.plainDescription : "").bodyText;
|
|
40
|
+
const rich = descriptionBody(section.description, fallback);
|
|
41
|
+
const body = rich.bodyText.trim() ? rich.bodyText : fallback;
|
|
42
|
+
// The bounded encoder can keep an empty paragraph. It must not hide real plain instructions.
|
|
43
|
+
const bodyJson = rich.bodyJson && !isEmptyDocument(JSON.parse(rich.bodyJson)) ? rich.bodyJson : "";
|
|
44
|
+
if (!body && !bodyJson) return [];
|
|
45
|
+
// V1 supplies no section id: duplicate titles cannot safely carry expansion through a refresh.
|
|
46
|
+
const mayKeepExpanded = !!sourceId || (titleCounts.get(title) === 1 && (previousTitleCounts.get(title) ?? 0) < 2);
|
|
47
|
+
const opened = !title || (mayKeepExpanded && (expanded.get(id) ?? false));
|
|
48
|
+
return [{ id, title, body, bodyJson, hasBodyJson: !!bodyJson, noTitle: !title, expanded: opened, collapsed: !opened }];
|
|
49
|
+
});
|
|
50
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Tier-5 bundle: Himi Blog post detail - reader content plus member actions.
|
|
2
2
|
import { defineService } from "@himalaya/state";
|
|
3
3
|
import { share as shareSheet } from "@himalaya/share";
|
|
4
|
-
import { getPost, likePost, unlikePost, type WixPost } from "@himalaya/wix-blog";
|
|
4
|
+
import { getPost, likePost, unlikePost, type WixPost, type WixPostWithContent } from "@himalaya/wix-blog";
|
|
5
5
|
import { comments, BLOG_COMMENTS_APP_ID, blogCommentRow, listBlogComments, type WixBlogCommentRow } from "@himalaya/wix-comments";
|
|
6
6
|
import { get, type DbEntity } from "@himalaya/local-db";
|
|
7
7
|
import { isSavedId, toggleSavedId } from "../_shared/himi-blog-saved";
|
|
@@ -38,6 +38,7 @@ interface S {
|
|
|
38
38
|
signInRequired: boolean; noSignIn: boolean; signInScreenId: string;
|
|
39
39
|
status: "loading" | "ready" | "error";
|
|
40
40
|
id: string;
|
|
41
|
+
commentResourceId: string;
|
|
41
42
|
title: string;
|
|
42
43
|
subtitle: string;
|
|
43
44
|
/** Author byline ("By <name>") — resolved from the post payload's writer-ish
|
|
@@ -80,7 +81,7 @@ interface S {
|
|
|
80
81
|
blogScreenId: string;
|
|
81
82
|
}
|
|
82
83
|
|
|
83
|
-
type PostWithExtras =
|
|
84
|
+
type PostWithExtras = WixPostWithContent & {
|
|
84
85
|
media?: { wixMedia?: { image?: { url?: string } }; embedMedia?: { thumbnail?: { url?: string } } };
|
|
85
86
|
coverImage?: { image?: { url?: string }; url?: string };
|
|
86
87
|
metrics?: { likeCount?: number; likes?: number; totalComments?: number; commentCount?: number; comments?: number };
|
|
@@ -219,6 +220,7 @@ const baseState: S = derive({
|
|
|
219
220
|
...NO_READ_SIGN_IN, signInScreenId: "himi_account",
|
|
220
221
|
status: "loading",
|
|
221
222
|
id: "",
|
|
223
|
+
commentResourceId: "",
|
|
222
224
|
title: "",
|
|
223
225
|
subtitle: "",
|
|
224
226
|
byline: "",
|
|
@@ -265,6 +267,7 @@ async function cachedPostState(s: S, id: string): Promise<S> {
|
|
|
265
267
|
...s,
|
|
266
268
|
status: "ready",
|
|
267
269
|
id,
|
|
270
|
+
commentResourceId: "",
|
|
268
271
|
title,
|
|
269
272
|
subtitle: String(cached.meta ?? ""),
|
|
270
273
|
excerpt: String(cached.excerpt ?? "").trim(),
|
|
@@ -294,19 +297,28 @@ async function loadPostState(s: S): Promise<S | undefined> {
|
|
|
294
297
|
}
|
|
295
298
|
try {
|
|
296
299
|
const access = await readAccess();
|
|
297
|
-
const
|
|
298
|
-
|
|
300
|
+
const response = await getPost(id, { fieldsets: ["RICH_CONTENT", "CONTENT_TEXT", "INTERNAL_ID"] }, { preserveAccessErrors: true });
|
|
301
|
+
await assertReadAccess(access);
|
|
302
|
+
if (version !== loadVersion) return undefined;
|
|
303
|
+
const post = response.post as PostWithExtras;
|
|
304
|
+
// Native Blog v3 Comments scopes by internalId, not the navigation UUID.
|
|
305
|
+
// A missing projection cannot establish that the post has no comments.
|
|
306
|
+
const commentResourceId = post.internalId?.trim() ?? "";
|
|
307
|
+
let commentsUnavailable = !commentResourceId;
|
|
308
|
+
let comments: CommentRow[] = [];
|
|
309
|
+
if (commentResourceId) {
|
|
310
|
+
try { comments = (await listBlogComments(commentResourceId)).map(toCommentRow); }
|
|
311
|
+
catch { commentsUnavailable = true; }
|
|
312
|
+
}
|
|
299
313
|
await assertReadAccess(access);
|
|
300
314
|
if (version !== loadVersion) return undefined;
|
|
301
|
-
const post = (((postResult.value as unknown as { post?: WixPost }).post) ?? postResult.value) as PostWithExtras;
|
|
302
|
-
const commentsUnavailable = commentsResult.status === "rejected";
|
|
303
|
-
const comments = commentsResult.status === "fulfilled" ? commentsResult.value.map(toCommentRow) : [];
|
|
304
315
|
const cover = imageUrl(post);
|
|
305
316
|
const title = post.title || "Untitled post";
|
|
306
317
|
return derive({
|
|
307
318
|
...clean,
|
|
308
319
|
status: "ready",
|
|
309
320
|
id,
|
|
321
|
+
commentResourceId,
|
|
310
322
|
title,
|
|
311
323
|
subtitle: subtitle(post),
|
|
312
324
|
byline: bylineOf(post),
|
|
@@ -398,9 +410,10 @@ const svc = defineService<S, {
|
|
|
398
410
|
submitComment: async (s) => {
|
|
399
411
|
const text = s.commentDraft.trim();
|
|
400
412
|
if (!text) return derive({ ...s, actionMessage: copyParam("blogPost.commentValidation", "Write a comment first.") });
|
|
413
|
+
if (!s.commentResourceId) return derive({ ...s, actionMessage: copyParam("blogPost.commentsUnavailable", "Comments are unavailable right now.") });
|
|
401
414
|
try {
|
|
402
415
|
const { comment } = await comments.createComment({
|
|
403
|
-
comment: { appId: BLOG_COMMENTS_APP_ID, contextId: s.
|
|
416
|
+
comment: { appId: BLOG_COMMENTS_APP_ID, contextId: s.commentResourceId, resourceId: s.commentResourceId, content: { richContent: commentRichContent(text) } },
|
|
404
417
|
});
|
|
405
418
|
const created = blogCommentRow(comment);
|
|
406
419
|
// Named to avoid shadowing the `comments` namespace import in this same block scope.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { descriptionBody } from "../_shared/rich-body.js";
|
|
1
2
|
// A category-scoped Shop gallery. Reuses Shop's card projection, filters, saved
|
|
2
3
|
// products, view preferences and App Store price presentation; the route remains
|
|
3
4
|
// himi_collection { id }. Never substitute the full catalog for a missing category.
|
|
@@ -117,12 +118,12 @@ const actions = {
|
|
|
117
118
|
const category = (await getStoreCategory(id)).category;
|
|
118
119
|
if (version !== loadVersion) return undefined;
|
|
119
120
|
if (!category?.id && !category?.name) return derive({ ...s, ...presence("missing"), loadId: version });
|
|
120
|
-
header = { name: category.name ?? "", subtitle: categoryCount(category), description: category.description
|
|
121
|
+
header = { name: category.name ?? "", subtitle: categoryCount(category), description: descriptionBody(category.description).bodyText };
|
|
121
122
|
} catch {
|
|
122
123
|
const category = (await getCollection(id)).collection;
|
|
123
124
|
if (version !== loadVersion) return undefined;
|
|
124
125
|
if (!category) return derive({ ...s, ...presence("missing"), loadId: version });
|
|
125
|
-
header = { name: category.name ?? "", subtitle: "", description: category.description
|
|
126
|
+
header = { name: category.name ?? "", subtitle: "", description: descriptionBody(category.description).bodyText };
|
|
126
127
|
}
|
|
127
128
|
} catch (error) {
|
|
128
129
|
if (version !== loadVersion) return undefined;
|