@wix/agent-skills 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +111 -0
- package/package.json +29 -0
- package/skills/wix-app/SKILL.md +344 -0
- package/skills/wix-app/references/APP_IDENTIFIERS.md +35 -0
- package/skills/wix-app/references/APP_MARKET_REVIEW.md +311 -0
- package/skills/wix-app/references/APP_VALIDATION.md +171 -0
- package/skills/wix-app/references/BACKEND_API.md +233 -0
- package/skills/wix-app/references/BACKEND_EVENT.md +116 -0
- package/skills/wix-app/references/CODE_QUALITY.md +33 -0
- package/skills/wix-app/references/CUSTOM_ELEMENT_WIDGET.md +587 -0
- package/skills/wix-app/references/DASHBOARD_MENU_PLUGIN.md +157 -0
- package/skills/wix-app/references/DASHBOARD_MODAL.md +283 -0
- package/skills/wix-app/references/DASHBOARD_PAGE.md +403 -0
- package/skills/wix-app/references/DASHBOARD_PLUGIN.md +205 -0
- package/skills/wix-app/references/DATA_COLLECTION.md +451 -0
- package/skills/wix-app/references/DOCUMENTATION.md +42 -0
- package/skills/wix-app/references/EDITOR_REACT_COMPONENT.md +65 -0
- package/skills/wix-app/references/EMBEDDED_SCRIPT.md +468 -0
- package/skills/wix-app/references/EXTENSION_REGISTRATION.md +96 -0
- package/skills/wix-app/references/SERVICE_PLUGIN.md +235 -0
- package/skills/wix-app/references/SITE_PLUGIN.md +436 -0
- package/skills/wix-app/references/STORES_VERSIONING.md +334 -0
- package/skills/wix-app/references/backend-event/COMMON-EVENTS.md +84 -0
- package/skills/wix-app/references/custom-element-widget/SETTINGS_PANEL.md +345 -0
- package/skills/wix-app/references/dashboard-menu-plugin/blog-slots.md +71 -0
- package/skills/wix-app/references/dashboard-menu-plugin/bookings-slots.md +58 -0
- package/skills/wix-app/references/dashboard-menu-plugin/crm-slots.md +8 -0
- package/skills/wix-app/references/dashboard-menu-plugin/ecommerce-slots.md +17 -0
- package/skills/wix-app/references/dashboard-menu-plugin/events-slots.md +29 -0
- package/skills/wix-app/references/dashboard-menu-plugin/restaurants-slots.md +27 -0
- package/skills/wix-app/references/dashboard-menu-plugin/stores-slots.md +35 -0
- package/skills/wix-app/references/dashboard-page/API_SPEC.md +138 -0
- package/skills/wix-app/references/dashboard-page/DASHBOARD_API.md +573 -0
- package/skills/wix-app/references/dashboard-page/DYNAMIC_PARAMETERS.md +269 -0
- package/skills/wix-app/references/dashboard-page/ECOM_NAVIGATION.md +164 -0
- package/skills/wix-app/references/dashboard-plugin/SLOTS.md +294 -0
- package/skills/wix-app/references/data-collection/WIX_DATA.md +270 -0
- package/skills/wix-app/references/data-collection/extension-template.ts +107 -0
- package/skills/wix-app/references/editor-react-component/ACCESSIBILITY.md +138 -0
- package/skills/wix-app/references/editor-react-component/COMPONENT-API.md +454 -0
- package/skills/wix-app/references/editor-react-component/COMPONENT-CONFIGURATION.md +165 -0
- package/skills/wix-app/references/editor-react-component/CSS-GUIDELINES.md +576 -0
- package/skills/wix-app/references/editor-react-component/DIRECTIONALITY.md +170 -0
- package/skills/wix-app/references/editor-react-component/EDIT-FLOW.md +6 -0
- package/skills/wix-app/references/editor-react-component/PARTS.md +25 -0
- package/skills/wix-app/references/editor-react-component/PROPS-VS-CSS.md +203 -0
- package/skills/wix-app/references/editor-react-component/REACT-GUIDELINES.md +167 -0
- package/skills/wix-app/references/editor-react-component/REACT-PATTERNS.md +137 -0
- package/skills/wix-app/references/service-plugin/ADDITIONAL-FEES.md +112 -0
- package/skills/wix-app/references/service-plugin/BOOKINGS-STAFF-SORTING.md +118 -0
- package/skills/wix-app/references/service-plugin/DISCOUNT-TRIGGERS.md +87 -0
- package/skills/wix-app/references/service-plugin/GIFT-CARDS.md +86 -0
- package/skills/wix-app/references/service-plugin/PAYMENT-SETTINGS.md +135 -0
- package/skills/wix-app/references/service-plugin/SHIPPING-RATES.md +79 -0
- package/skills/wix-app/references/service-plugin/VALIDATIONS.md +68 -0
- package/skills/wix-app/references/site-plugin/EXAMPLES.md +311 -0
- package/skills/wix-app/references/site-plugin/SLOTS.md +263 -0
- package/skills/wix-design-system/SKILL.md +129 -0
- package/skills/wix-design-system/references/file-structure.md +56 -0
- package/skills/wix-design-system/scripts/wds.cjs +531 -0
- package/skills/wix-headless/SKILL.md +418 -0
- package/skills/wix-headless/references/DISCOVERY.md +332 -0
- package/skills/wix-headless/references/ORCHESTRATION.md +269 -0
- package/skills/wix-headless/references/SETUP.md +154 -0
- package/skills/wix-headless/references/blog/BLOG_CONTENT.md +152 -0
- package/skills/wix-headless/references/blog/BLOG_PAGES.md +299 -0
- package/skills/wix-headless/references/blog/BLOG_SETUP.md +218 -0
- package/skills/wix-headless/references/blog/INSTRUCTIONS.md +32 -0
- package/skills/wix-headless/references/cms/CMS_FOUNDATIONS.md +373 -0
- package/skills/wix-headless/references/cms/FAQ_KNOWLEDGE_BASE.md +229 -0
- package/skills/wix-headless/references/cms/INSTRUCTIONS.md +40 -0
- package/skills/wix-headless/references/cms/PORTFOLIO.md +454 -0
- package/skills/wix-headless/references/cms/RESOURCE_LIBRARY.md +356 -0
- package/skills/wix-headless/references/cms/TEAM_DIRECTORY.md +299 -0
- package/skills/wix-headless/references/commands/install-app.md +42 -0
- package/skills/wix-headless/references/commands/known-apps.json +5 -0
- package/skills/wix-headless/references/commands/mcp-bootstrap.md +28 -0
- package/skills/wix-headless/references/designer/INSTRUCTIONS.md +828 -0
- package/skills/wix-headless/references/ecom/CART_PAGES.md +120 -0
- package/skills/wix-headless/references/ecom/CART_WIRING.md +103 -0
- package/skills/wix-headless/references/ecom/COMPONENTS_CSS.md +132 -0
- package/skills/wix-headless/references/ecom/INSTRUCTIONS.md +59 -0
- package/skills/wix-headless/references/forms/CONTACT_FORM.md +477 -0
- package/skills/wix-headless/references/forms/INSTRUCTIONS.md +43 -0
- package/skills/wix-headless/references/gift-cards/COMPONENTS.md +119 -0
- package/skills/wix-headless/references/gift-cards/INSTRUCTIONS.md +51 -0
- package/skills/wix-headless/references/gift-cards/PAGES.md +129 -0
- package/skills/wix-headless/references/images/INSTRUCTIONS.md +359 -0
- package/skills/wix-headless/references/shared/BUILD_NOISE.md +8 -0
- package/skills/wix-headless/references/shared/IMAGE_GENERATION.md +185 -0
- package/skills/wix-headless/references/shared/IMPLEMENTER.md +155 -0
- package/skills/wix-headless/references/shared/MCP_PREFIX.md +105 -0
- package/skills/wix-headless/references/shared/PRODUCTION_SHARP_EDGES.md +97 -0
- package/skills/wix-headless/references/shared/RETURN_CONTRACT.md +423 -0
- package/skills/wix-headless/references/shared/STYLING.md +117 -0
- package/skills/wix-headless/references/stores/BACK_IN_STOCK.md +178 -0
- package/skills/wix-headless/references/stores/CATEGORY_PAGES.md +86 -0
- package/skills/wix-headless/references/stores/COMPONENTS_CSS.md +129 -0
- package/skills/wix-headless/references/stores/HOME_AND_NAV.md +227 -0
- package/skills/wix-headless/references/stores/INSTRUCTIONS.md +96 -0
- package/skills/wix-headless/references/stores/PRODUCT_CATALOG_DATA.md +249 -0
- package/skills/wix-headless/references/stores/PRODUCT_PAGES.md +213 -0
- package/skills/wix-headless/references/stores/SHARED_WIRING.md +164 -0
- package/skills/wix-headless/references/verticals/_schema.json +160 -0
- package/skills/wix-headless/references/verticals/_schema.md +185 -0
- package/skills/wix-headless/references/verticals/blog.md +125 -0
- package/skills/wix-headless/references/verticals/cms.md +121 -0
- package/skills/wix-headless/references/verticals/ecom.md +96 -0
- package/skills/wix-headless/references/verticals/forms.md +106 -0
- package/skills/wix-headless/references/verticals/gift-cards.md +121 -0
- package/skills/wix-headless/references/verticals/stores.md +167 -0
- package/skills/wix-headless/scripts/check-manifest.mjs +198 -0
- package/skills/wix-headless/scripts/copy-utility-templates.mjs +153 -0
- package/skills/wix-headless/scripts/emit-design-tokens.mjs +77 -0
- package/skills/wix-headless/scripts/finalize-run-json.mjs +141 -0
- package/skills/wix-headless/scripts/init-site-json.mjs +63 -0
- package/skills/wix-headless/scripts/patch-decorative-slots.mjs +124 -0
- package/skills/wix-headless/scripts/plan-entity-image-waves.mjs +190 -0
- package/skills/wix-headless/scripts/preview.sh +45 -0
- package/skills/wix-headless/scripts/release.sh +73 -0
- package/skills/wix-headless/scripts/scaffold.sh +53 -0
- package/skills/wix-headless/scripts/seed-utilities.sh +55 -0
- package/skills/wix-headless/shared-utilities/analytics.ts +40 -0
- package/skills/wix-headless/shared-utilities/ricos.ts +179 -0
- package/skills/wix-headless/shared-utilities/wix-image.ts +72 -0
- package/skills/wix-headless/templates/ecom/CartBadge.tsx +84 -0
- package/skills/wix-headless/templates/ecom/CartView.tsx +543 -0
- package/skills/wix-headless/templates/ecom/cart.astro +13 -0
- package/skills/wix-headless/templates/ecom/components-ecom.css +215 -0
- package/skills/wix-headless/templates/ecom/discounts.ts +71 -0
- package/skills/wix-headless/templates/ecom/thank-you.astro +24 -0
- package/skills/wix-headless/templates/gift-cards/GiftCardPurchase.tsx +172 -0
- package/skills/wix-headless/templates/gift-cards/_home-teaser-snippet.astro +42 -0
- package/skills/wix-headless/templates/gift-cards/_nav-snippet.astro +11 -0
- package/skills/wix-headless/templates/gift-cards/components-gift-cards.css +219 -0
- package/skills/wix-headless/templates/gift-cards/gift-cards.astro +51 -0
- package/skills/wix-headless/templates/gift-cards/gift-cards.ts +75 -0
- package/skills/wix-headless/templates/stores/AddToCartButton.tsx +77 -0
- package/skills/wix-headless/templates/stores/BackInStockForm.tsx +176 -0
- package/skills/wix-headless/templates/stores/CategoryRail.astro +69 -0
- package/skills/wix-headless/templates/stores/ProductCard.astro +79 -0
- package/skills/wix-headless/templates/stores/ProductPurchase.tsx +397 -0
- package/skills/wix-headless/templates/stores/SeoTags.astro +21 -0
- package/skills/wix-headless/templates/stores/back-in-stock.ts +67 -0
- package/skills/wix-headless/templates/stores/categories.ts +155 -0
- package/skills/wix-headless/templates/stores/category/[slug].astro +130 -0
- package/skills/wix-headless/templates/stores/components-stores.css +295 -0
- package/skills/wix-headless/templates/stores/products/[slug].astro +128 -0
- package/skills/wix-headless/templates/stores/products/index.astro +101 -0
- package/skills/wix-manage/SKILL.md +280 -0
- package/skills/wix-manage/references/app-installation/install-wix-apps.md +155 -0
- package/skills/wix-manage/references/app-installation/list-installed-apps.md +124 -0
- package/skills/wix-manage/references/blog/how-to-create-blog-posts.md +226 -0
- package/skills/wix-manage/references/bookings/booking-service-policy-setup.md +95 -0
- package/skills/wix-manage/references/bookings/booking-system-integration-gaps.md +416 -0
- package/skills/wix-manage/references/bookings/bookings-staff-setup.md +255 -0
- package/skills/wix-manage/references/bookings/create-and-update-booking-services.md +462 -0
- package/skills/wix-manage/references/bookings/end-to-end-booking-flow.md +278 -0
- package/skills/wix-manage/references/bookings/external-calendar-integration.md +258 -0
- package/skills/wix-manage/references/bookings/multi-resource-service-creation.md +231 -0
- package/skills/wix-manage/references/calendar/configure-default-business-hours.md +226 -0
- package/skills/wix-manage/references/cms/cms-data-items-crud.md +422 -0
- package/skills/wix-manage/references/cms/cms-data-operations-extended.md +263 -0
- package/skills/wix-manage/references/cms/cms-ecommerce-catalog-integration.md +136 -0
- package/skills/wix-manage/references/cms/cms-references-and-relationships.md +160 -0
- package/skills/wix-manage/references/cms/cms-schema-management.md +143 -0
- package/skills/wix-manage/references/contacts/bulk-delete-contacts.md +73 -0
- package/skills/wix-manage/references/contacts/bulk-label-and-unlabel-contacts.md +67 -0
- package/skills/wix-manage/references/domains/domain-search-and-purchase.md +434 -0
- package/skills/wix-manage/references/ecommerce/api-recommendation-tracking.md +420 -0
- package/skills/wix-manage/references/ecommerce/flow-add-free-shipping.md +267 -0
- package/skills/wix-manage/references/ecommerce/flow-bundle-and-save.md +290 -0
- package/skills/wix-manage/references/ecommerce/flow-fix-coverage-gaps.md +300 -0
- package/skills/wix-manage/references/ecommerce/flow-optimize-shipping-rates.md +364 -0
- package/skills/wix-manage/references/ecommerce/flow-seasonal-promotion.md +315 -0
- package/skills/wix-manage/references/ecommerce/flow-stock-mover.md +308 -0
- package/skills/wix-manage/references/ecommerce/flow-upsell-boost.md +271 -0
- package/skills/wix-manage/references/ecommerce/goal-clear-inventory.md +118 -0
- package/skills/wix-manage/references/ecommerce/goal-drive-cross-sells.md +122 -0
- package/skills/wix-manage/references/ecommerce/goal-increase-aov.md +132 -0
- package/skills/wix-manage/references/ecommerce/goal-reduce-cart-abandonment.md +148 -0
- package/skills/wix-manage/references/ecommerce/goal-seasonal-revenue.md +146 -0
- package/skills/wix-manage/references/ecommerce/guardrail-discount-conflicts.md +118 -0
- package/skills/wix-manage/references/ecommerce/guardrail-margin-protection.md +88 -0
- package/skills/wix-manage/references/ecommerce/guardrail-rate-pricing-sanity.md +108 -0
- package/skills/wix-manage/references/ecommerce/guardrail-shipping-health.md +124 -0
- package/skills/wix-manage/references/ecommerce/recipe-apply-shipping-recommendations.md +299 -0
- package/skills/wix-manage/references/ecommerce/recommend-ecommerce-strategy.md +582 -0
- package/skills/wix-manage/references/ecommerce/setup-coupons.md +338 -0
- package/skills/wix-manage/references/ecommerce/setup-discount-rules.md +444 -0
- package/skills/wix-manage/references/ecommerce/setup-shipping-rates.md +86 -0
- package/skills/wix-manage/references/ecommerce/setup-shipping-regions.md +56 -0
- package/skills/wix-manage/references/ecommerce/setup-store-pickup-location.md +209 -0
- package/skills/wix-manage/references/ecommerce/skill-graph.md +139 -0
- package/skills/wix-manage/references/ecommerce/troubleshoot-checkout-delivery-dropoff.md +124 -0
- package/skills/wix-manage/references/ecommerce/troubleshoot-discount-not-applying.md +95 -0
- package/skills/wix-manage/references/events/list-events.md +203 -0
- package/skills/wix-manage/references/forms/create-form.md +260 -0
- package/skills/wix-manage/references/get-paid/create-payment-links.md +212 -0
- package/skills/wix-manage/references/get-paid/how-to-setup-wix-payments.md +57 -0
- package/skills/wix-manage/references/get-paid/payment-links-for-bookings.md +101 -0
- package/skills/wix-manage/references/media/upload-media-to-wix.md +216 -0
- package/skills/wix-manage/references/pricing-plans/create-and-update-pricing-plans.md +77 -0
- package/skills/wix-manage/references/pricing-plans/pricing-plans-bookings-integration.md +166 -0
- package/skills/wix-manage/references/restaurants/wix-restaurants-setup.md +336 -0
- package/skills/wix-manage/references/rich-content/ricos-converter-service.md +295 -0
- package/skills/wix-manage/references/site-properties/change-payment-currency-site-properties.md +59 -0
- package/skills/wix-manage/references/sites/create-site-from-template.md +183 -0
- package/skills/wix-manage/references/sites/query-sites.md +125 -0
- package/skills/wix-manage/references/stores/add-store-pages-to-site.md +57 -0
- package/skills/wix-manage/references/stores/bulk-create-products-with-options.md +1041 -0
- package/skills/wix-manage/references/stores/create-product-catalog-v1.md +125 -0
- package/skills/wix-manage/references/stores/create-product-from-image.md +824 -0
- package/skills/wix-manage/references/stores/create-product-with-options-catalog-v3.md +566 -0
- package/skills/wix-manage/references/stores/find-products-query-and-search-catalog-v3.md +194 -0
- package/skills/wix-manage/references/stores/query-products-catalog-v1.md +80 -0
- package/skills/wix-manage/references/stores/setup-online-store-catalog-v3.md +99 -0
- package/skills/wix-manage/references/stores/update-product-pre-order.md +213 -0
- package/skills/wix-manage/references/stores/update-product-with-options.md +318 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSR-friendly Ricos JSON to HTML renderer.
|
|
3
|
+
*
|
|
4
|
+
* Ricos is Wix's structured rich-text format. CMS rich-text fields (about-content
|
|
5
|
+
* body, FAQ answer, etc.) are stored as a node tree, not HTML strings. The Wix
|
|
6
|
+
* dashboard's rich-text editor reads and writes this format.
|
|
7
|
+
*
|
|
8
|
+
* Use this util on Astro SSR pages that need to render Ricos content as HTML
|
|
9
|
+
* without pulling the full @wix/ricos React package (~80kb) into a static page.
|
|
10
|
+
* The blog vertical uses @wix/ricos's RicosViewer React component because blog
|
|
11
|
+
* posts can carry the full Ricos feature set (galleries, polls, embedded media).
|
|
12
|
+
* CMS pages render a much narrower subset, so this walker covers about 80% of
|
|
13
|
+
* what the dashboard editor produces:
|
|
14
|
+
*
|
|
15
|
+
* - PARAGRAPH, HEADING (levels 1–6), BULLETED_LIST, ORDERED_LIST, LIST_ITEM
|
|
16
|
+
* - BLOCKQUOTE, DIVIDER
|
|
17
|
+
* - TEXT decorations: BOLD, ITALIC, UNDERLINE, LINK
|
|
18
|
+
*
|
|
19
|
+
* Anything outside this set is rendered as a defensive `<p>` with the raw
|
|
20
|
+
* `textData.text` (or empty string), so unknown nodes never throw — they
|
|
21
|
+
* just render plainly. Add cases as the CMS surface grows.
|
|
22
|
+
*
|
|
23
|
+
* Usage:
|
|
24
|
+
* import { renderRicos } from "../utils/ricos";
|
|
25
|
+
* ---
|
|
26
|
+
* const html = renderRicos(item.body);
|
|
27
|
+
* ---
|
|
28
|
+
* <div class="prose" set:html={html} />
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
type RicosTextDecoration =
|
|
32
|
+
| { type: "BOLD" }
|
|
33
|
+
| { type: "ITALIC" }
|
|
34
|
+
| { type: "UNDERLINE" }
|
|
35
|
+
| { type: "LINK"; linkData?: { link?: { url?: string; target?: string; rel?: string } } }
|
|
36
|
+
| { type: string; [key: string]: unknown };
|
|
37
|
+
|
|
38
|
+
interface RicosTextData {
|
|
39
|
+
text?: string;
|
|
40
|
+
decorations?: RicosTextDecoration[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface RicosNode {
|
|
44
|
+
type?: string;
|
|
45
|
+
nodes?: RicosNode[];
|
|
46
|
+
textData?: RicosTextData;
|
|
47
|
+
headingData?: { level?: number };
|
|
48
|
+
[key: string]: unknown;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface RicosDocument {
|
|
52
|
+
nodes?: RicosNode[];
|
|
53
|
+
[key: string]: unknown;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Render a Ricos document (or a JSON-encoded Ricos string) as HTML.
|
|
58
|
+
* Returns an empty string on null/undefined input — pages can `set:html=""`
|
|
59
|
+
* without throwing.
|
|
60
|
+
*/
|
|
61
|
+
export function renderRicos(
|
|
62
|
+
content: RicosDocument | string | null | undefined,
|
|
63
|
+
): string {
|
|
64
|
+
if (!content) return "";
|
|
65
|
+
|
|
66
|
+
const doc = typeof content === "string" ? safeParse(content) : content;
|
|
67
|
+
if (!doc || !Array.isArray(doc.nodes)) return "";
|
|
68
|
+
|
|
69
|
+
return doc.nodes.map(renderBlockNode).join("");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function safeParse(raw: string): RicosDocument | null {
|
|
73
|
+
try {
|
|
74
|
+
const parsed = JSON.parse(raw);
|
|
75
|
+
if (parsed && typeof parsed === "object") return parsed as RicosDocument;
|
|
76
|
+
return null;
|
|
77
|
+
} catch {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function renderBlockNode(node: RicosNode): string {
|
|
83
|
+
switch (node.type) {
|
|
84
|
+
case "PARAGRAPH":
|
|
85
|
+
return `<p>${renderInlineChildren(node)}</p>`;
|
|
86
|
+
|
|
87
|
+
case "HEADING": {
|
|
88
|
+
const level = clampHeadingLevel(node.headingData?.level);
|
|
89
|
+
return `<h${level}>${renderInlineChildren(node)}</h${level}>`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
case "BULLETED_LIST":
|
|
93
|
+
return `<ul>${(node.nodes ?? []).map(renderBlockNode).join("")}</ul>`;
|
|
94
|
+
|
|
95
|
+
case "ORDERED_LIST":
|
|
96
|
+
return `<ol>${(node.nodes ?? []).map(renderBlockNode).join("")}</ol>`;
|
|
97
|
+
|
|
98
|
+
case "LIST_ITEM":
|
|
99
|
+
return `<li>${(node.nodes ?? []).map(renderBlockNode).join("")}</li>`;
|
|
100
|
+
|
|
101
|
+
case "BLOCKQUOTE":
|
|
102
|
+
return `<blockquote>${(node.nodes ?? []).map(renderBlockNode).join("")}</blockquote>`;
|
|
103
|
+
|
|
104
|
+
case "DIVIDER":
|
|
105
|
+
return "<hr />";
|
|
106
|
+
|
|
107
|
+
default:
|
|
108
|
+
return `<p>${renderInlineChildren(node)}</p>`;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function renderInlineChildren(node: RicosNode): string {
|
|
113
|
+
if (!Array.isArray(node.nodes)) return "";
|
|
114
|
+
return node.nodes.map(renderInlineNode).join("");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function renderInlineNode(node: RicosNode): string {
|
|
118
|
+
if (node.type !== "TEXT") {
|
|
119
|
+
if (Array.isArray(node.nodes)) return renderInlineChildren(node);
|
|
120
|
+
return "";
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const text = escapeHtml(node.textData?.text ?? "");
|
|
124
|
+
const decorations = node.textData?.decorations ?? [];
|
|
125
|
+
|
|
126
|
+
let html = text;
|
|
127
|
+
let linkOpen = "";
|
|
128
|
+
let linkClose = "";
|
|
129
|
+
|
|
130
|
+
for (const decoration of decorations) {
|
|
131
|
+
switch (decoration.type) {
|
|
132
|
+
case "BOLD":
|
|
133
|
+
html = `<strong>${html}</strong>`;
|
|
134
|
+
break;
|
|
135
|
+
case "ITALIC":
|
|
136
|
+
html = `<em>${html}</em>`;
|
|
137
|
+
break;
|
|
138
|
+
case "UNDERLINE":
|
|
139
|
+
html = `<u>${html}</u>`;
|
|
140
|
+
break;
|
|
141
|
+
case "LINK": {
|
|
142
|
+
const url = decoration.linkData?.link?.url;
|
|
143
|
+
if (url) {
|
|
144
|
+
const target = decoration.linkData?.link?.target;
|
|
145
|
+
const rel = decoration.linkData?.link?.rel ?? (target === "_blank" ? "noopener noreferrer" : undefined);
|
|
146
|
+
linkOpen = `<a href="${escapeAttr(url)}"${target ? ` target="${escapeAttr(target)}"` : ""}${rel ? ` rel="${escapeAttr(rel)}"` : ""}>`;
|
|
147
|
+
linkClose = "</a>";
|
|
148
|
+
}
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
default:
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return `${linkOpen}${html}${linkClose}`;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function clampHeadingLevel(raw: number | undefined): 1 | 2 | 3 | 4 | 5 | 6 {
|
|
160
|
+
if (typeof raw !== "number" || !Number.isFinite(raw)) return 2;
|
|
161
|
+
const n = Math.floor(raw);
|
|
162
|
+
if (n < 1) return 1;
|
|
163
|
+
if (n > 6) return 6;
|
|
164
|
+
return n as 1 | 2 | 3 | 4 | 5 | 6;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function escapeHtml(s: string): string {
|
|
168
|
+
return s
|
|
169
|
+
.replace(/&/g, "&")
|
|
170
|
+
.replace(/</g, "<")
|
|
171
|
+
.replace(/>/g, ">");
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function escapeAttr(s: string): string {
|
|
175
|
+
return s
|
|
176
|
+
.replace(/&/g, "&")
|
|
177
|
+
.replace(/"/g, """)
|
|
178
|
+
.replace(/</g, "<");
|
|
179
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { media } from "@wix/sdk";
|
|
2
|
+
|
|
3
|
+
export type WixImage = {
|
|
4
|
+
url: string;
|
|
5
|
+
altText?: string;
|
|
6
|
+
width?: number;
|
|
7
|
+
height?: number;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function resolveWixImage(
|
|
11
|
+
image: string | { id?: string; url?: string } | undefined
|
|
12
|
+
): WixImage | null {
|
|
13
|
+
if (!image) return null;
|
|
14
|
+
|
|
15
|
+
if (typeof image === "string") {
|
|
16
|
+
if (image.startsWith("wix:image://")) {
|
|
17
|
+
const resolved = media.getImageUrl(image);
|
|
18
|
+
return { url: resolved.url, width: resolved.width, height: resolved.height };
|
|
19
|
+
}
|
|
20
|
+
return { url: image };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (image.url) {
|
|
24
|
+
return { url: image.url };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (image.id) {
|
|
28
|
+
const resolved = media.getImageUrl(image.id);
|
|
29
|
+
return { url: resolved.url, width: resolved.width, height: resolved.height };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function scaledSrc(img: WixImage, width: number): string {
|
|
36
|
+
const url = new URL(img.url);
|
|
37
|
+
url.searchParams.set("w", String(width));
|
|
38
|
+
return url.toString();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function srcSet(img: WixImage, widths: number[]): string {
|
|
42
|
+
return widths.map((w) => `${scaledSrc(img, w)} ${w}w`).join(", ");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Consumed by every vertical that renders a Wix image at a specific size
|
|
46
|
+
// (stores ProductCard + detail page, blog post hero, cms list thumbnails).
|
|
47
|
+
// Kept alongside resolveWixImage so verticals do not ship their own copy.
|
|
48
|
+
export function resolveWixImageUrl(
|
|
49
|
+
image: string | { id?: string; url?: string } | undefined,
|
|
50
|
+
width?: number,
|
|
51
|
+
height?: number,
|
|
52
|
+
): string | null {
|
|
53
|
+
if (!image) return null;
|
|
54
|
+
|
|
55
|
+
if (
|
|
56
|
+
typeof image === "string" &&
|
|
57
|
+
image.startsWith("wix:image://") &&
|
|
58
|
+
width != null &&
|
|
59
|
+
height != null
|
|
60
|
+
) {
|
|
61
|
+
return media.getScaledToFillImageUrl(image, width, height, {});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const resolved = resolveWixImage(image);
|
|
65
|
+
if (!resolved) return null;
|
|
66
|
+
if (width == null && height == null) return resolved.url;
|
|
67
|
+
|
|
68
|
+
const url = new URL(resolved.url);
|
|
69
|
+
if (width != null) url.searchParams.set("w", String(width));
|
|
70
|
+
if (height != null) url.searchParams.set("h", String(height));
|
|
71
|
+
return url.toString();
|
|
72
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { useState, useEffect } from "react";
|
|
2
|
+
import { currentCart } from "@wix/ecom";
|
|
3
|
+
|
|
4
|
+
// CartView writes { lineItems, summary } to sessionStorage on every successful
|
|
5
|
+
// fetch/update. Read the snapshot here so the badge count renders instantly
|
|
6
|
+
// on every navigation instead of flashing empty while the fresh fetch runs.
|
|
7
|
+
// Same key as CartView; CartBadge is a read-only consumer.
|
|
8
|
+
const CART_CACHE_KEY = "cart:last-snapshot";
|
|
9
|
+
|
|
10
|
+
function readCachedCount(): number {
|
|
11
|
+
if (typeof window === "undefined") return 0;
|
|
12
|
+
try {
|
|
13
|
+
const raw = window.sessionStorage.getItem(CART_CACHE_KEY);
|
|
14
|
+
if (!raw) return 0;
|
|
15
|
+
const parsed = JSON.parse(raw);
|
|
16
|
+
const items = Array.isArray(parsed?.lineItems) ? parsed.lineItems : [];
|
|
17
|
+
return items.reduce(
|
|
18
|
+
(sum: number, item: any) => sum + (item?.quantity ?? 0),
|
|
19
|
+
0,
|
|
20
|
+
);
|
|
21
|
+
} catch {
|
|
22
|
+
return 0;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default function CartBadge() {
|
|
27
|
+
const [count, setCount] = useState(() => readCachedCount());
|
|
28
|
+
|
|
29
|
+
const fetchCount = async () => {
|
|
30
|
+
try {
|
|
31
|
+
const cart = await currentCart.getCurrentCart();
|
|
32
|
+
const total = (cart.lineItems ?? []).reduce(
|
|
33
|
+
(sum, item) => sum + (item.quantity ?? 0),
|
|
34
|
+
0
|
|
35
|
+
);
|
|
36
|
+
setCount(total);
|
|
37
|
+
} catch {
|
|
38
|
+
setCount(0);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
// Always fetch client-side — cart count is per-visitor and must not
|
|
44
|
+
// be server-rendered (would break SSR caching)
|
|
45
|
+
fetchCount();
|
|
46
|
+
|
|
47
|
+
const handleCartUpdate = (e: Event) => {
|
|
48
|
+
const detail = (e as CustomEvent).detail;
|
|
49
|
+
if (detail?.cart?.lineItems) {
|
|
50
|
+
const total = (detail.cart.lineItems as any[]).reduce(
|
|
51
|
+
(sum: number, item: any) => sum + (item.quantity ?? 0),
|
|
52
|
+
0
|
|
53
|
+
);
|
|
54
|
+
setCount(total);
|
|
55
|
+
} else if (detail?.delta) {
|
|
56
|
+
setCount((prev) => prev + detail.delta);
|
|
57
|
+
} else {
|
|
58
|
+
fetchCount();
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
window.addEventListener("cart-updated", handleCartUpdate);
|
|
62
|
+
return () => window.removeEventListener("cart-updated", handleCartUpdate);
|
|
63
|
+
}, []);
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<a href="/cart" className="cart-badge" aria-label={count > 0 ? `Cart, ${count} items` : "Cart"}>
|
|
67
|
+
<svg
|
|
68
|
+
className="cart-badge-icon"
|
|
69
|
+
viewBox="0 0 24 24"
|
|
70
|
+
fill="none"
|
|
71
|
+
stroke="currentColor"
|
|
72
|
+
strokeWidth="1.6"
|
|
73
|
+
strokeLinecap="round"
|
|
74
|
+
strokeLinejoin="round"
|
|
75
|
+
aria-hidden="true"
|
|
76
|
+
focusable="false"
|
|
77
|
+
>
|
|
78
|
+
<path d="M5 8h14l-1.2 10.2a2 2 0 0 1-2 1.8H8.2a2 2 0 0 1-2-1.8L5 8Z" />
|
|
79
|
+
<path d="M9 8V6a3 3 0 0 1 6 0v2" />
|
|
80
|
+
</svg>
|
|
81
|
+
{count > 0 && <span className="cart-badge-count">{count}</span>}
|
|
82
|
+
</a>
|
|
83
|
+
);
|
|
84
|
+
}
|