webcake-storefront-mcp 1.20.0 → 1.22.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/builder/attributes.js +7 -7
- package/dist/builder/bindings.js +54 -4
- package/dist/builder/events.js +30 -8
- package/dist/builder/factory.js +14 -12
- package/dist/builder/guide.js +31 -7
- package/dist/changelog.json +14 -14
- package/package.json +1 -1
|
@@ -205,19 +205,19 @@ export const ELEMENT_ATTRS = {
|
|
|
205
205
|
switch: { events: "none", notes: "Toggle; style width/height." },
|
|
206
206
|
// ── commerce ──
|
|
207
207
|
"grid-product": {
|
|
208
|
-
specials: { displayStyle: "default | only_image | border.", original_price_position: "left | right.", products_per_load: "Products fetched per page/scroll (real
|
|
209
|
-
config: { columns: "Products per row.", responsive: "autofit | fixed | custom.", item_show: "How many products to show.", image_ratio: "
|
|
208
|
+
specials: { displayStyle: "default | only_image | border.", original_price_position: "left | right.", products_per_load: "Products fetched per page/scroll (real range 8–36).", category_id: "Source products from a specific category id (else current category).", show_original_price: "Show the compare-at price (boolean).", show_discount_on_price: "Show a discount badge on the price (boolean).", show_quick_view: "Show a quick-view button on hover (boolean).", show_product_name: "Show the product name (boolean).", show_product_price: "Show the price (boolean).", show_rating: "Show the star rating (boolean).", load_rating: "Fetch ratings for the cards (boolean).", show_ribbon: "Show a ribbon/badge (boolean).", load_brand: "Fetch + show the brand (boolean).", load_category: "Fetch + show the category (boolean).", show_categories: "Show category labels (boolean).", on_hover: "Card hover effect: zoom | swap (swap to 2nd image).", randomDisplay: "Randomise product order (boolean).", typeLinkCategory: "Related-products source: similar_product | products_viewed.", plugins: "Extra card plugins [{name,type,icon}] (e.g. attribute templates, extra info)." },
|
|
209
|
+
config: { columns: "Products per row (real range 1–6).", responsive: "autofit | fixed | custom (fixed keeps the column count; custom collapses on mobile).", item_show: "How many products to show.", image_ratio: "INDUSTRY-DEPENDENT: 4/5 fashion · 2/3 kids · 3/4 food · 1/1 square (cosmetics/electronics). Also 16/9|3/2|4/3|9/16.", img_object_fit: "cover|contain|fill|stretch.", gap_column: "Column gap (real range 8–40).", gap_row: "Row gap (real range 0–40).", product_info_padding_y: "Vertical padding of the name/price block (px).", price_padding_y: "Vertical padding around the price (px).", product_info_alignment: "left|center|right.", productNameFont: "Product-name font family.", productNameFontSize: "Product-name font size (px, real 12–18).", productNameColor: "Product-name colour.", productNameHover: "Product-name hover colour.", productNamePaddingY: "Vertical padding of the name (px).", productPriceFont: "Price font family.", productPriceFontSize: "Price font size (px).", productPriceFontWeight: "Price font weight — bold across ALL surveyed templates.", productPriceColor: "Price colour (often a theme accent / var).", productOriginalPriceFontSize: "Compare-at price font size (px).", productOriginalPriceColor: "Compare-at price colour (muted grey).", sizeThumbnail: "{ width, height } of the card image." },
|
|
210
210
|
events: "none",
|
|
211
211
|
bindings: ["product::product_image", "product::product_name", "product::product_price"],
|
|
212
|
-
notes: "Lists store products (self-renders image+name+price card). Requires a 'store' page / use_store.
|
|
212
|
+
notes: "Lists store products (self-renders image+name+price card). Requires a 'store' page / use_store. Config is INDUSTRY-DEPENDENT (surveyed fashion/kids/cosmetics/food/electronics): only bold price + responsive collapse + show original/discount are near-universal; pick image_ratio/columns/gaps/fonts/colours to match the brand.",
|
|
213
213
|
},
|
|
214
214
|
"slider-product": { specials: { displayStyle: "default|only_image|border.", original_price_position: "left|right." }, config: { slideItems: "Visible products.", slideWidth: "Card width.", image_ratio: "Aspect ratio." }, events: "none", bindings: ["product::product_image", "product::product_name", "product::product_price"] },
|
|
215
|
-
"product-gallery": { events: "none", notes: "Product detail image gallery (product page)." },
|
|
215
|
+
"product-gallery": { specials: { showNavigation: "Show the thumbnail strip / nav (boolean — real templates often false).", on_hover: "Hover effect on the main image (e.g. zoom)." }, config: { sizeThumbnail: "{ width, height } of the main image (real 500–1044).", image_ratio: "Main image ratio (often 'auto')." }, events: "none", notes: "Product detail image gallery (product page). Mostly just sizeThumbnail + showNavigation." },
|
|
216
216
|
"product-image-carousel": { events: "none", notes: "Product image carousel (product page)." },
|
|
217
217
|
"cart-items": { events: "none", bindings: ["cart_item::cart_item_image", "cart_item::cart_item_name", "cart_item::cart_item_price", "cart_item::cart_item_total_price", "cart_item::cart_item_prod_attr"], notes: "Cart line items (cart page). A repeater whose config.columns defines the per-row column template — real templates use [70px image, 1fr name, ~240px price, ~116px qty, ~240px total, 34px remove]." },
|
|
218
218
|
"cart-icon": { specials: { svg: "Icon SVG (defaults to a bag icon; fill='currentColor' so it inherits style.color).", itemCount: "Badge count (0 placeholder)." }, config: { itemCountFontFamily: "Badge font family.", itemCountFontSize: "Badge font size (e.g. 11px).", itemCountColor: "Badge text colour.", itemCountBorderRadius: "Badge corner radius (50% = circle)." }, events: "click — opens cart. Real templates wrap it in a container with open_popup(mouseenter)/close_popup(mouseleave) for a mini-cart, plus a container with open_page→cart page." },
|
|
219
|
-
"quantity-input": { events: "none", notes: "Quantity stepper (product/cart)." },
|
|
220
|
-
attr: { events: "none", notes: "Product attribute selector (
|
|
219
|
+
"quantity-input": { specials: { spinner: "Native spinner style: hide-spin (product/cart, the default) | circle (checkout).", showRightBtn: "Show the +/- buttons (boolean)." }, config: { textColor: "Quantity text colour." }, events: "none", notes: "Quantity stepper (product/cart). Defaults to spinner:'hide-spin'." },
|
|
220
|
+
attr: { specials: { attrName: "Attribute to show, or 'auto'.", autoAttrName: "Resolved attribute name when attrName='auto' (e.g. Màu sắc, Kích thước).", autoNamePriority: "Order when auto (1,2,3…).", attrPrimary: "This is the primary variation (boolean).", layout: "Swatch layout variant (1,4…).", defaultSelected: "Pre-select the first option (boolean)." }, config: { borderColorHover: "Swatch hover border colour." }, events: "none", notes: "Product attribute/variation selector (product page). Add one attr per variation axis (colour, size); attrName:'auto' lets it resolve from the product." },
|
|
221
221
|
payment: { events: "none", notes: "Payment methods block (checkout)." },
|
|
222
222
|
"delivery-method": { specials: { options: "[{type: delivery|store_pickup, name, active}]." }, events: "none" },
|
|
223
223
|
"order-items": { events: "none", bindings: ["order_item::product_image", "order_item::product_name", "order_item::product_quantity", "order_item::product_attrs", "order_item::items_sum_up_price"], notes: "Order summary lines (checkout/thank-you). Repeater whose config.columns is the per-row template — real templates use [75px image, 1fr name, 95px price]." },
|
|
@@ -253,7 +253,7 @@ export const ELEMENT_ATTRS = {
|
|
|
253
253
|
"favorite-icon": { events: "click — toggle favourite." },
|
|
254
254
|
"bonus-items": { specials: { typeBonus: "combo_product | other." }, events: "none" },
|
|
255
255
|
// ── navigation ──
|
|
256
|
-
menu: { specials: { type: "horizontal | vertical | hamburger | anchor.", sync: "Auto-sync items with site pages (boolean).", fullField: "Stretch items to fill the menu width (boolean)." }, config: { showArrow: "Show the dropdown caret on items with a submenu (boolean).", textColor: "Item text colour (often a theme var like var(--color_20)).", colorHover: "Item hover colour.", "--padX": "Horizontal padding inside each item (CSS var, e.g. 15px)." }, events: "none", notes: "Holds menu-item children. Real
|
|
256
|
+
menu: { specials: { type: "horizontal | vertical | hamburger | collapse | grid | anchor.", sync: "Auto-sync items with site pages (boolean).", fullField: "Stretch items to fill the menu width (boolean)." }, config: { showArrow: "Show the dropdown caret on items with a submenu (boolean).", textColor: "Item text colour (often a theme var like var(--color_20)).", colorHover: "Item hover colour.", "--padX": "Horizontal padding inside each item (CSS var, e.g. 15px)." }, events: "none", notes: "Holds menu-item children. Real headers often have TWO menus (a horizontal desktop menu + a hamburger/collapse mobile menu). Colour items via theme vars (var(--color_20))." },
|
|
257
257
|
"menu-item": { specials: { name: "Link label.", isCustom: "Custom link (boolean).", linkType: "page | category | blog_category | brand | product_tag | custom.", linkPage: "Target page id (linkType=page).", pageId: "Target page id (mirror of linkPage, set both).", linkCategory: "Linked category id.", categoryId: "Category id.", syncSubmenu: "Auto-build submenu (boolean)." }, events: "navigation lives in SPECIALS here, NOT events — set linkType + the matching id.", notes: "For a page link set linkType='page' + linkPage + pageId + name. (Generic text/container/button elements navigate via events:[{action:'open_page',open_page_id}] instead.)" },
|
|
258
258
|
"menu-anchor-item": { specials: { name: "Label.", isCustom: "Custom.", linkType: "Link kind.", categoryId: "Category id." }, events: "click — scrolls to an anchor." },
|
|
259
259
|
submenu: { events: "none", notes: "Dropdown under a menu-item." },
|
package/dist/builder/bindings.js
CHANGED
|
@@ -26,7 +26,12 @@ export const BINDING_DATASETS = {
|
|
|
26
26
|
"product::product_categories", "product::brand_name", "product::product_tag",
|
|
27
27
|
"product::product_remain_quantity", "product::total_sold_web", "product::rating_point",
|
|
28
28
|
"product::rating_count", "product::variation_sku", "product::product_wholesale_price",
|
|
29
|
-
"product::discount_price_from_original_price", "product::
|
|
29
|
+
"product::discount_price_from_original_price", "product::discount_price_to_original_price",
|
|
30
|
+
"product::preorder_text", "product::product_note",
|
|
31
|
+
// course / LMS products (use_store + course app):
|
|
32
|
+
"product::total_course", "product::total_course_learning", "product::course_member_quantity",
|
|
33
|
+
"product::course_total_time", "product::course_video_quantity", "product::lesson_name",
|
|
34
|
+
"product::lesson_description", "product::video_lesson",
|
|
30
35
|
],
|
|
31
36
|
},
|
|
32
37
|
"product-overlay": {
|
|
@@ -46,7 +51,9 @@ export const BINDING_DATASETS = {
|
|
|
46
51
|
"cart_item::cart_item_original_price", "cart_item::cart_item_subtotal", "cart_item::cart_item_total_price",
|
|
47
52
|
"cart_item::cart_item_total_quantity", "cart_item::cart_item_prod_attr", "cart_item::sku",
|
|
48
53
|
"cart_item::cart_total_price", "cart_item::cart_total_tax", "cart_item::cart_shipping_fee",
|
|
49
|
-
"cart_item::
|
|
54
|
+
"cart_item::cart_surcharge_fee", "cart_item::cart_promotion_discount", "cart_item::discount_percent",
|
|
55
|
+
"cart_item::discount_reward_point", "cart_item::total_price_all_promotions",
|
|
56
|
+
"cart_item::subtotal_before_discount", "cart_item::product_note",
|
|
50
57
|
],
|
|
51
58
|
},
|
|
52
59
|
order: {
|
|
@@ -58,6 +65,7 @@ export const BINDING_DATASETS = {
|
|
|
58
65
|
"order::order_payment_method", "order::shipping_fee", "order::subtotal", "order::total_price",
|
|
59
66
|
"order::total_tax", "order::code_order", "order::order_status", "order::payment_status",
|
|
60
67
|
"order::order_quantity", "order::order_date", "order::order_coupon", "order::order_promotion_discount",
|
|
68
|
+
"order::order_promotion_coupon",
|
|
61
69
|
],
|
|
62
70
|
},
|
|
63
71
|
order_item: {
|
|
@@ -97,6 +105,7 @@ export const BINDING_DATASETS = {
|
|
|
97
105
|
"customer::profile_avatar", "customer::profile_name", "customer::phone_number", "customer::email",
|
|
98
106
|
"customer::gender", "customer::birthday", "customer::order_count", "customer::purchased_amount",
|
|
99
107
|
"customer::pos_reward_point", "customer::pos_reward_point_level", "customer::pos_level_discount",
|
|
108
|
+
"customer::succeed_order_count",
|
|
100
109
|
],
|
|
101
110
|
},
|
|
102
111
|
customer_address: {
|
|
@@ -146,6 +155,46 @@ export const BINDING_DATASETS = {
|
|
|
146
155
|
summary: "Page / site info.",
|
|
147
156
|
fields: ["general_info::page_name", "general_info::site_name", "general_info::menu_title"],
|
|
148
157
|
},
|
|
158
|
+
agency: {
|
|
159
|
+
name: "agency",
|
|
160
|
+
page_type: "any",
|
|
161
|
+
repeater_parents: ["agency"],
|
|
162
|
+
summary: "A store / branch in a store-locator (agency) list.",
|
|
163
|
+
fields: ["agency::name", "agency::phone_number", "agency::address", "agency::time"],
|
|
164
|
+
},
|
|
165
|
+
blog: {
|
|
166
|
+
name: "blog",
|
|
167
|
+
page_type: "blog",
|
|
168
|
+
summary: "The current blog CATEGORY (heading on a blog list page) — distinct from `post`.",
|
|
169
|
+
fields: ["blog::category_name", "blog::category_description"],
|
|
170
|
+
},
|
|
171
|
+
search_result: {
|
|
172
|
+
name: "search_result",
|
|
173
|
+
page_type: "store",
|
|
174
|
+
summary: "Search-results context (the query + matched products) on a search page.",
|
|
175
|
+
fields: ["search_result::search_name", "search_result::products"],
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
// Binding META keys beyond {id,name,target} — these are HOW a source field combines into the
|
|
179
|
+
// element (mined from 34 real templates). makeBinding preserves any of them verbatim, e.g.
|
|
180
|
+
// new_element('text-dataset',{ bindings:[{ target:'product::product_name', name_style:'sku',
|
|
181
|
+
// number_of_line_title:1 }] }). Listed so the AI knows the real combine-keys (no invented keys).
|
|
182
|
+
export const BINDING_META_KEYS = {
|
|
183
|
+
name_style: "Render style of a name/value: 'sku' | 'variation-sku' (show the SKU/variation form).",
|
|
184
|
+
attr_id: "Bind a SPECIFIC product attribute by id (ATTR-xxxx) — used with attr::attr_name / attr::attr_value.",
|
|
185
|
+
target_attr: "For cart_item::cart_item_prod_attr — pick one attribute by name (e.g. 'Màu sắc').",
|
|
186
|
+
value_attr: "Companion to target_attr — which part to show ('value').",
|
|
187
|
+
prefix_content: "Text prepended before the value (e.g. '-' or a label).",
|
|
188
|
+
separator: "Separator string when the field is a list.",
|
|
189
|
+
value: "A SECOND source for a COMPUTED display, e.g. cart_item::cart_total_price with value:'form::coupon_price' subtracts the coupon. Combine two datasets in one node.",
|
|
190
|
+
order: "Index into a multi-value field (pick the Nth).",
|
|
191
|
+
order_value: "Which sub-value of an ordered field to show (e.g. 'description').",
|
|
192
|
+
number_of_line_title: "Clamp the text to N lines.",
|
|
193
|
+
showText: "Show the field label/text alongside the value (boolean).",
|
|
194
|
+
max_item_show: "Max items to render for a list-valued field.",
|
|
195
|
+
bind_image_by_category_id: "For images: resolve from a category instead of the product.",
|
|
196
|
+
auto_hide_without_star: "Hide the node when there's no rating (boolean).",
|
|
197
|
+
show_video_demo: "Show the product video demo (boolean).",
|
|
149
198
|
};
|
|
150
199
|
// Build fast lookups.
|
|
151
200
|
const ALL_TARGETS = new Set();
|
|
@@ -241,8 +290,9 @@ export function validateBindings(node) {
|
|
|
241
290
|
/** Catalog for the list_bindings discovery tool. */
|
|
242
291
|
export function describeBindingsCatalog() {
|
|
243
292
|
return {
|
|
244
|
-
note: "Dataset elements (text-dataset/image-dataset/…) and children of a repeater (grid-product, cart-items, post-list…) carry bindings:[{ id, name, target }]. Build via new_element opts.bindings (ids auto-minted) — e.g. new_element('text-dataset',{ bindings:[{ target:'product::product_price', show_tax:true }] }). A target only resolves on a page whose type enables its dataset (store→use_store, member→use_member, blog→use_blog) — build_page handles the flag.",
|
|
245
|
-
|
|
293
|
+
note: "Dataset elements (text-dataset/image-dataset/…) and children of a repeater (grid-product, cart-items, post-list…) carry bindings:[{ id, name, target, ...meta }]. Build via new_element opts.bindings (ids auto-minted) — e.g. new_element('text-dataset',{ bindings:[{ target:'product::product_price', show_tax:true }] }). A target only resolves on a page whose type enables its dataset (store→use_store, member→use_member, blog→use_blog) — build_page handles the flag. The optional meta keys (name_style, prefix_content, value, attr_id, …) control HOW the value renders/combines — see meta_keys.",
|
|
294
|
+
meta_keys: BINDING_META_KEYS,
|
|
295
|
+
page_type_required: { store: ["product", "product-overlay", "cart_item", "order", "order_item", "category", "bonus_item", "promotion_item", "attr", "search_result"], member: ["customer", "customer_address"], blog: ["post", "blog"], any: ["form", "general_info", "agency"] },
|
|
246
296
|
repeater_context: REPEATER_CONTEXT,
|
|
247
297
|
datasets: Object.values(BINDING_DATASETS).map((ds) => ({
|
|
248
298
|
name: ds.name,
|
package/dist/builder/events.js
CHANGED
|
@@ -26,22 +26,22 @@ export const EVENT_TRIGGERS = [
|
|
|
26
26
|
const TRIGGER_NAMES = new Set(EVENT_TRIGGERS.map((t) => t.name));
|
|
27
27
|
export const EVENT_ACTIONS = [
|
|
28
28
|
// navigation
|
|
29
|
-
{ action: "open_page", trigger: "click", category: "navigation", summary: "Go to a page in this site.", required: ["open_page_id"], optional: ["scrollTarget", "scroll_to_id", "scroll_to_element_id", "scrollMore", "no_follow", "active_color"] },
|
|
29
|
+
{ action: "open_page", trigger: "click", category: "navigation", summary: "Go to a page in this site.", required: ["open_page_id"], optional: ["scrollTarget", "scroll_to_id", "scroll_to_element_id", "scrollMore", "no_follow", "active_color", "popup_id", "popup_overlay", "close_all_other_popup", "open_category_id", "open_blog_category_id", "open_menu_id", "activeNotify"] },
|
|
30
30
|
{ action: "open_link", trigger: "click", category: "navigation", summary: "Open an external URL.", required: ["link_target"], optional: ["link_target_url", "no_follow"] },
|
|
31
31
|
{ action: "open_category", trigger: "click", category: "navigation", summary: "Open a product category page.", required: ["open_category_id"], optional: ["no_follow"] },
|
|
32
32
|
{ action: "open_blog_category", trigger: "click", category: "navigation", summary: "Open a blog category page.", required: ["open_blog_category_id"], optional: ["no_follow"] },
|
|
33
33
|
{ action: "scroll_to", trigger: "click", category: "navigation", summary: "Smooth-scroll to a section/element on this page.", required: ["scroll_to_id"], optional: ["scroll_to_element_id", "scrollMore", "scrollTarget"], target_in_page: ["scroll_to_id", "scroll_to_element_id"] },
|
|
34
34
|
{ action: "direction_login", trigger: "click", category: "navigation", summary: "Redirect by login state: open_page_id if logged in, target_direction_id if not.", required: ["open_page_id"], optional: ["target_direction_id"] },
|
|
35
35
|
// ui control
|
|
36
|
-
{ action: "toggle", trigger: "click", category: "ui", summary: "Show/hide another element on this page.", required: ["toggle_id"], optional: ["toggle_status", "only_mode"], target_in_page: ["toggle_id"] },
|
|
37
|
-
{ action: "open_popup", trigger: "click", category: "ui", summary: "Open a popup.", required: ["popup_id"], optional: ["popup_overlay", "close_all_other_popup"] },
|
|
36
|
+
{ action: "toggle", trigger: "click", category: "ui", summary: "Show/hide another element on this page (the most common interaction — accordions, drawers, filters).", required: ["toggle_id"], optional: ["toggle_status", "only_mode"], target_in_page: ["toggle_id"] },
|
|
37
|
+
{ action: "open_popup", trigger: "click", category: "ui", summary: "Open a popup (use mouseenter for a hover mini-cart/submenu).", required: ["popup_id"], optional: ["popup_overlay", "close_all_other_popup", "open_page_id", "only_mode", "toggle_id", "open_category_id", "scroll_to_id"] },
|
|
38
38
|
{ action: "close_popup", trigger: "click", category: "ui", summary: "Close a popup.", required: ["popup_id"], optional: ["popup_overlay", "close_all_other_popup"] },
|
|
39
39
|
{ action: "open_menu", trigger: "click", category: "ui", summary: "Open a dropdown/submenu.", required: ["open_menu_id"], target_in_page: ["open_menu_id"] },
|
|
40
40
|
{ action: "close_menu", trigger: "click", category: "ui", summary: "Close a menu.", optional: ["close_menu_id"] },
|
|
41
41
|
{ action: "change_tab", trigger: "click", category: "ui", summary: "Switch a swiper/tabs to a given tab.", required: ["change_tab_id"], optional: ["move_to", "tab_index", "tab_color"], target_in_page: ["change_tab_id"] },
|
|
42
42
|
{ action: "load_more", trigger: "click", category: "ui", summary: "Load more items in a list.", optional: ["load_more_id"], target_in_page: ["load_more_id"] },
|
|
43
43
|
// cart / commerce
|
|
44
|
-
{ action: "add_to_cart", trigger: "click", category: "cart", summary: "Add the current product to the cart.", optional: ["open_page", "auto_add_bonus", "activeNotify", "productNoteElId"] },
|
|
44
|
+
{ action: "add_to_cart", trigger: "click", category: "cart", summary: "Add the current product to the cart.", optional: ["open_page", "open_page_id", "open_popup_id", "open_target", "open_category_id", "auto_add_bonus", "activeNotify", "productNoteElId"] },
|
|
45
45
|
{ action: "buy_now", trigger: "click", category: "cart", summary: "Add to cart and go straight to checkout.", optional: ["open_page", "auto_add_bonus", "activeNotify"] },
|
|
46
46
|
{ action: "add_to_cart_form", trigger: "success", category: "cart", summary: "Add to cart from a form submit (product form).", optional: ["open_page"] },
|
|
47
47
|
{ action: "open_cart", trigger: "click", category: "cart", summary: "Open the cart sidebar/drawer." },
|
|
@@ -66,10 +66,32 @@ export const EVENT_ACTIONS = [
|
|
|
66
66
|
{ action: "send_whatsapp_mess", trigger: "click", category: "contact", summary: "Open WhatsApp chat.", optional: ["whatsapp_phone_number"] },
|
|
67
67
|
{ action: "sharing", trigger: "click", category: "contact", summary: "Share to a social network.", optional: ["shareTarget", "shareLink", "shareLinkCustom"] },
|
|
68
68
|
// hover styles (eventName must be "hover")
|
|
69
|
-
{ action: "scale", trigger: "hover", category: "hover", summary: "Scale the element on hover.", optional: ["el_target_id"] },
|
|
70
|
-
{ action: "change_background", trigger: "hover", category: "hover", summary: "Change background colour on hover." },
|
|
71
|
-
{ action: "change_text_color", trigger: "hover", category: "hover", summary: "Change text colour on hover." },
|
|
72
|
-
{ action: "change_border_color", trigger: "hover", category: "hover", summary: "Change border colour on hover." },
|
|
69
|
+
{ action: "scale", trigger: "hover", category: "hover", summary: "Scale the element on hover.", optional: ["el_target_id", "scale_value"] },
|
|
70
|
+
{ action: "change_background", trigger: "hover", category: "hover", summary: "Change background colour on hover.", optional: ["change_background", "apply_element", "cursor", "el_target_id"] },
|
|
71
|
+
{ action: "change_text_color", trigger: "hover", category: "hover", summary: "Change text colour on hover.", optional: ["change_text_color", "apply_element", "cursor", "el_target_id"] },
|
|
72
|
+
{ action: "change_border_color", trigger: "hover", category: "hover", summary: "Change border colour on hover.", optional: ["change_border_color", "apply_element", "cursor", "el_target_id"] },
|
|
73
|
+
{ action: "transform_y", trigger: "hover", category: "hover", summary: "Shift the element vertically on hover (lift effect).", optional: ["transform_y", "apply_element"] },
|
|
74
|
+
{ action: "transform_x", trigger: "hover", category: "hover", summary: "Shift the element horizontally on hover.", optional: ["transform_x", "apply_element"] },
|
|
75
|
+
{ action: "cursor", trigger: "hover", category: "hover", summary: "Change the cursor on hover.", optional: ["cursor"] },
|
|
76
|
+
// commerce / cart interactions (mined from 34 real templates)
|
|
77
|
+
{ action: "open_quick_view", trigger: "click", category: "cart", summary: "Open a product quick-view popup from a product card.", optional: ["popup_id", "activeNotify", "active_color"] },
|
|
78
|
+
{ action: "open_product_page", trigger: "click", category: "navigation", summary: "Open the product detail page for this card's product." },
|
|
79
|
+
{ action: "open_preview_product_gallery", trigger: "click", category: "media", summary: "Open the product image in a gallery preview.", optional: ["open_preview_product_gallery_id", "target"] },
|
|
80
|
+
{ action: "close_cart_item", trigger: "click", category: "cart", summary: "Remove this line item from the cart." },
|
|
81
|
+
{ action: "edit_cart_item", trigger: "click", category: "cart", summary: "Edit this cart line item (qty / variation)." },
|
|
82
|
+
{ action: "change_column_grid_prd", trigger: "click", category: "ui", summary: "Switch the product grid's column count.", optional: ["columnNumber", "target_id"], target_in_page: ["target_id"] },
|
|
83
|
+
{ action: "add_wishlist_product", trigger: "click", category: "cart", summary: "Add the product to the wishlist.", optional: ["activeNotify"] },
|
|
84
|
+
{ action: "wishlist_remove_item", trigger: "click", category: "cart", summary: "Remove an item from the wishlist.", optional: ["target_custom_links"] },
|
|
85
|
+
{ action: "use_reward_point", trigger: "click", category: "cart", summary: "Apply loyalty reward points to the order.", optional: ["reward_point", "target"] },
|
|
86
|
+
{ action: "copy_promotion", trigger: "click", category: "cart", summary: "Copy a promotion/coupon code to the clipboard.", optional: ["has_text", "is_hidden", "text_change"] },
|
|
87
|
+
// search + menu + account interactions
|
|
88
|
+
{ action: "search", trigger: "click", category: "navigation", summary: "Run a storefront product search.", optional: ["open_page_id", "open_category_id"] },
|
|
89
|
+
{ action: "close_search", trigger: "click", category: "ui", summary: "Close the search box/droppable.", optional: ["close_search_id"], target_in_page: ["close_search_id"] },
|
|
90
|
+
{ action: "toggle_group", trigger: "click", category: "ui", summary: "Toggle a group of elements together (e.g. a mobile menu / filter group).", optional: ["actions", "data", "data_more", "show"] },
|
|
91
|
+
{ action: "back_to_menu_parent", trigger: "click", category: "ui", summary: "Go back up one level in a multi-level mobile menu." },
|
|
92
|
+
{ action: "lightbox", trigger: "click", category: "media", summary: "Open an image in a lightbox.", optional: ["lightboxLink"] },
|
|
93
|
+
{ action: "set_address_default", trigger: "click", category: "account", summary: "Mark a saved address as the default." },
|
|
94
|
+
{ action: "change_avatar", trigger: "click", category: "account", summary: "Change the member avatar." },
|
|
73
95
|
];
|
|
74
96
|
const ACTION_BY_NAME = {};
|
|
75
97
|
for (const a of EVENT_ACTIONS)
|
package/dist/builder/factory.js
CHANGED
|
@@ -363,9 +363,13 @@ export const buildElementWithBreakpoint = (el, breakpoint) => {
|
|
|
363
363
|
el.specials = Object.assign({}, el.specials || {}, customSpecials);
|
|
364
364
|
delete el.runtime;
|
|
365
365
|
};
|
|
366
|
-
//
|
|
367
|
-
//
|
|
368
|
-
//
|
|
366
|
+
// grid-product card self-renders image + name + price. The defaults below are the keys that
|
|
367
|
+
// are near-UNIVERSAL across real designer templates (surveyed 5 industries — fashion/kids/
|
|
368
|
+
// cosmetics/food/electronics): bold price, responsive collapse, show original/discount price,
|
|
369
|
+
// a neutral square image + medium gaps. INDUSTRY-DEPENDENT keys (image_ratio 4/5|2/3|3/4|1/1,
|
|
370
|
+
// font family/size, column count, exact gaps, productNameColor/productPriceColor) are
|
|
371
|
+
// intentionally NOT forced here — the caller / template sets them. Override via opts.config /
|
|
372
|
+
// opts.specials.
|
|
369
373
|
export const createGridProduct = (opts = {}) => {
|
|
370
374
|
const product = cloneDeep(SKELETON);
|
|
371
375
|
product.id = 'GRID-PRODUCT-' + randomString(8);
|
|
@@ -377,23 +381,20 @@ export const createGridProduct = (opts = {}) => {
|
|
|
377
381
|
};
|
|
378
382
|
product.runtime.config = {
|
|
379
383
|
columns: 4,
|
|
380
|
-
image_ratio: '
|
|
384
|
+
image_ratio: '1/1',
|
|
381
385
|
img_object_fit: 'cover',
|
|
382
|
-
gap_column:
|
|
383
|
-
gap_row:
|
|
384
|
-
product_info_padding_y:
|
|
386
|
+
gap_column: 24,
|
|
387
|
+
gap_row: 24,
|
|
388
|
+
product_info_padding_y: 12,
|
|
385
389
|
price_padding_y: 5,
|
|
386
|
-
productNameFontSize: 18,
|
|
387
|
-
productPriceFontSize: 18,
|
|
388
390
|
productPriceFontWeight: 'bold',
|
|
389
|
-
productOriginalPriceFontSize: 18,
|
|
390
391
|
responsive: 'custom',
|
|
391
392
|
heightUnit: 'auto',
|
|
392
393
|
...(opts.config || {})
|
|
393
394
|
};
|
|
394
395
|
product.specials = {
|
|
395
396
|
element_async: true,
|
|
396
|
-
products_per_load:
|
|
397
|
+
products_per_load: 12,
|
|
397
398
|
show_original_price: true,
|
|
398
399
|
show_discount_on_price: true,
|
|
399
400
|
show_quick_view: false,
|
|
@@ -751,7 +752,8 @@ export const createQuantityInput = (opts = {}) => {
|
|
|
751
752
|
...opts.style
|
|
752
753
|
};
|
|
753
754
|
input.runtime.config = opts.config || {};
|
|
754
|
-
|
|
755
|
+
// Every surveyed template hides the native number spinner on product/cart steppers.
|
|
756
|
+
input.specials = { spinner: 'hide-spin', ...(opts.specials || {}) };
|
|
755
757
|
if (opts.breakpoint) {
|
|
756
758
|
buildElementWithBreakpoint(input, opts.breakpoint);
|
|
757
759
|
}
|
package/dist/builder/guide.js
CHANGED
|
@@ -87,13 +87,15 @@ A bare stack of default elements looks unfinished. Apply real styling:
|
|
|
87
87
|
- HERO: prefer a section with a background image + an overlay heading/sub/button on top
|
|
88
88
|
(set section_opts.style.background or a full-width image, then text centered), rather than a
|
|
89
89
|
small image stacked above text.
|
|
90
|
-
- PRODUCT GRID: grid-product SELF-RENDERS the image+name+price card
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
\`opts.
|
|
95
|
-
|
|
96
|
-
|
|
90
|
+
- PRODUCT GRID: grid-product SELF-RENDERS the image+name+price card. Its look is
|
|
91
|
+
INDUSTRY-DEPENDENT (surveyed fashion/kids/cosmetics/food/electronics) — only \`bold price\`,
|
|
92
|
+
\`responsive\` collapse and \`show_original_price/show_discount_on_price\` are near-universal;
|
|
93
|
+
the factory ships those + neutral defaults (columns 4, image_ratio "1/1", gap 24). Tune per
|
|
94
|
+
brand via \`opts.config\`: { columns:3-6, image_ratio:"4/5"|"2/3"|"3/4"|"1/1", img_object_fit:"cover",
|
|
95
|
+
gap_column:8-40, gap_row, product_info_alignment:"center"?, productNameColor, productPriceColor }
|
|
96
|
+
and \`opts.specials\`: { products_per_load:8-36, on_hover:"zoom"|"swap", show_rating, show_ribbon }.
|
|
97
|
+
(There are NO cardBorderRadius/cardBoxShadow keys.) For variations use \`attr\` elements
|
|
98
|
+
(attrName:"auto"); steppers are \`quantity-input\` (defaults spinner:"hide-spin").
|
|
97
99
|
- BRAND COLOURS: reuse var(--color_00) (text), and an accent (var(--color_02)/var(--color_03))
|
|
98
100
|
for buttons, prices, highlights — consistent accent = looks intentional.
|
|
99
101
|
- IMAGES: must be WebCake-CDN urls (search_images cdn_url / upload_images), else they won't show.
|
|
@@ -129,6 +131,28 @@ key — only bp1..bp4.)
|
|
|
129
131
|
\`text-dataset\` with \`product::product_name\` resolves to that cell's product (no extra
|
|
130
132
|
wiring). Same for cart-items→cart_item, order-items→order_item, post-list→post,
|
|
131
133
|
grid-category→category. A target only resolves on a page of the matching \`type\` (below).
|
|
134
|
+
- BINDING COMBINE-KEYS (how a field renders/combines — mined from real templates; pass them
|
|
135
|
+
alongside \`target\`): \`name_style:"sku"|"variation-sku"\`, \`prefix_content:"-"\` (prepend),
|
|
136
|
+
\`number_of_line_title:1\` (clamp lines), \`attr_id:"ATTR-…"\` (bind ONE attribute), and
|
|
137
|
+
\`value:"form::coupon_price"\` — a SECOND source so one node shows a COMPUTED value (e.g.
|
|
138
|
+
\`cart_item::cart_total_price\` with \`value:"form::coupon_price"\` = total minus coupon). Call
|
|
139
|
+
\`list_bindings\` → meta_keys for the full list.
|
|
140
|
+
|
|
141
|
+
## Composition recipes (how real templates assemble components)
|
|
142
|
+
Repeaters fall into two kinds (surveyed across 34 templates):
|
|
143
|
+
- SELF-RENDERING (just add badges): \`grid-product\` / \`slider-product\` / \`post-list\` render the
|
|
144
|
+
card themselves — their children are only \`product-overlay\` (sale/discount badge) or, for a
|
|
145
|
+
fully custom card, a \`custom-layout\` wrapping dataset cells. \`post-list\`→\`post-overlay\` children.
|
|
146
|
+
- HAND-COMPOSED from per-item dataset cells (build the row yourself):
|
|
147
|
+
- \`cart-items\` row = \`image-dataset[cart_item::cart_item_image]\`, \`text-dataset[cart_item::cart_item_name]\`,
|
|
148
|
+
\`text-dataset[cart_item::cart_item_price]\`, \`quantity-input\`, \`text-dataset[cart_item::cart_item_total_price]\`,
|
|
149
|
+
a \`container\` for name+\`cart_item::cart_item_prod_attr\`, \`line\` (config.columns sets the row template).
|
|
150
|
+
- \`order-items\` row = \`image-dataset[order_item::product_image]\` + \`text-dataset\` for
|
|
151
|
+
\`order_item::product_name\` / \`product_attrs\` / \`product_quantity\` / \`items_sum_up_price\`.
|
|
152
|
+
- \`layout-dataset\` / \`customer-address\` = a generic repeater: \`text-dataset[customer_address::full_name|phone_number|address|pdc|is_default]\` + a "Set default" button.
|
|
153
|
+
- \`promotions\` = \`text-dataset[promotion_item::code|description|end_date]\` + a copy button (\`copy_promotion\`).
|
|
154
|
+
The bare \`cart-items\`/\`order-items\` (no children) self-render a DEFAULT row — fine for a quick build; compose cells only when you want a custom layout.
|
|
155
|
+
HEADER patterns: real headers carry TWO menus (a horizontal desktop \`menu\` + a hamburger/collapse mobile \`menu\`), the logo (image/text), \`cart-icon\`, often \`input-search\`/\`search-droppable\`, \`member-bar\`, and \`language-menu\`. BREADCRUMB is common — keep it on category/product pages (often its own slim section, or next to a \`text-dataset\` page title).
|
|
132
156
|
|
|
133
157
|
## Events (clicks, navigation, cart, popups)
|
|
134
158
|
Interactive nodes (button, text, image, container, rectangle, icons) carry an \`events\`
|
package/dist/changelog.json
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"v": "1.22.0",
|
|
4
|
+
"d": "25/06/2026",
|
|
5
|
+
"type": "Added",
|
|
6
|
+
"en": "list_bindings now returns a meta_keys map documenting binding combine-keys mined from 34 production templates — name_style, attr_id, prefix_content,…",
|
|
7
|
+
"vi": "list_bindings nay trả về map meta_keys tài liệu hóa các combine-key liên kết dữ liệu được khai thác từ 34 template production — name_style, attr_id,…"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"v": "1.21.0",
|
|
11
|
+
"d": "25/06/2026",
|
|
12
|
+
"type": "Changed",
|
|
13
|
+
"en": "The grid-product factory now ships cross-industry neutral defaults (image_ratio:\"1/1\", gap_column/gap_row 24, products_per_load 12) instead of the…",
|
|
14
|
+
"vi": "Factory grid-product nay sử dụng các giá trị mặc định trung lập đa ngành (image_ratio:\"1/1\", gap_column/gap_row 24, products_per_load 12) thay vì…"
|
|
15
|
+
},
|
|
2
16
|
{
|
|
3
17
|
"v": "1.20.0",
|
|
4
18
|
"d": "25/06/2026",
|
|
@@ -26,19 +40,5 @@
|
|
|
26
40
|
"type": "Changed",
|
|
27
41
|
"en": "get_build_guide now includes three new sections: \"Build the WHOLE storefront\" with per-page recipes for enriching category, product detail, cart,…",
|
|
28
42
|
"vi": "get_build_guide nay bổ sung ba mục mới: \"Build the WHOLE storefront\" — công thức từng trang để nâng cấp danh mục, trang chi tiết sản phẩm, giỏ hàng,…"
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
"v": "1.18.0",
|
|
32
|
-
"d": "25/06/2026",
|
|
33
|
-
"type": "Added",
|
|
34
|
-
"en": "New get_intake_guide tool returns a step-by-step questionnaire and recommended build flow (create_site → products → build_page → global sections →…",
|
|
35
|
-
"vi": "Tool mới get_intake_guide trả về bảng câu hỏi từng bước và quy trình build được khuyến nghị (create_site → sản phẩm → build_page → global sections →…"
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
"v": "1.17.1",
|
|
39
|
-
"d": "25/06/2026",
|
|
40
|
-
"type": "Changed",
|
|
41
|
-
"en": "get_http_function and get_site_custom_code now include a \"File / media uploads\" section in the embedded HTTP function guide, documenting three ways…",
|
|
42
|
-
"vi": "get_http_function và get_site_custom_code nay bổ sung mục \"File / media uploads\" vào hướng dẫn HTTP function nhúng sẵn, tài liệu hóa ba cách tải…"
|
|
43
43
|
}
|
|
44
44
|
]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "webcake-storefront-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.22.0",
|
|
4
4
|
"description": "MCP server for the WebCake/StoreCake storefront builder — page CRUD, page authoring, products, orders, and more",
|
|
5
5
|
"mcpName": "io.github.vuluu2k/webcake-storefront-mcp",
|
|
6
6
|
"license": "MIT",
|