@shopbb/helium 0.5.10 → 0.6.1
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/cache/withCache.d.ts +49 -0
- package/dist/cache/withCache.d.ts.map +1 -0
- package/dist/cache/withCache.js +117 -0
- package/dist/cache/withCache.js.map +1 -0
- package/dist/components/AddToCartButton.d.ts +28 -22
- package/dist/components/AddToCartButton.d.ts.map +1 -1
- package/dist/components/AddToCartButton.js +36 -47
- package/dist/components/AddToCartButton.js.map +1 -1
- package/dist/components/BuyNowButton.d.ts +45 -0
- package/dist/components/BuyNowButton.d.ts.map +1 -0
- package/dist/components/BuyNowButton.js +49 -0
- package/dist/components/BuyNowButton.js.map +1 -0
- package/dist/components/CartCheckoutButton.d.ts +39 -0
- package/dist/components/CartCheckoutButton.d.ts.map +1 -0
- package/dist/components/CartCheckoutButton.js +32 -0
- package/dist/components/CartCheckoutButton.js.map +1 -0
- package/dist/components/CartCost.d.ts +43 -0
- package/dist/components/CartCost.d.ts.map +1 -0
- package/dist/components/CartCost.js +34 -0
- package/dist/components/CartCost.js.map +1 -0
- package/dist/components/CartForm.d.ts +201 -0
- package/dist/components/CartForm.d.ts.map +1 -0
- package/dist/components/CartForm.js +213 -0
- package/dist/components/CartForm.js.map +1 -0
- package/dist/components/CartLineProvider.d.ts +78 -0
- package/dist/components/CartLineProvider.d.ts.map +1 -0
- package/dist/components/CartLineProvider.js +46 -0
- package/dist/components/CartLineProvider.js.map +1 -0
- package/dist/components/CartLineQuantity.d.ts +24 -0
- package/dist/components/CartLineQuantity.d.ts.map +1 -0
- package/dist/components/CartLineQuantity.js +9 -0
- package/dist/components/CartLineQuantity.js.map +1 -0
- package/dist/components/DiscountSelector.d.ts.map +1 -1
- package/dist/components/DiscountSelector.js +8 -19
- package/dist/components/DiscountSelector.js.map +1 -1
- package/dist/components/Image.d.ts +18 -0
- package/dist/components/Image.d.ts.map +1 -1
- package/dist/components/Image.js +26 -0
- package/dist/components/Image.js.map +1 -1
- package/dist/components/Pagination.d.ts +82 -0
- package/dist/components/Pagination.d.ts.map +1 -0
- package/dist/components/Pagination.js +84 -0
- package/dist/components/Pagination.js.map +1 -0
- package/dist/components/RichText.d.ts +78 -0
- package/dist/components/RichText.d.ts.map +1 -0
- package/dist/components/RichText.js +93 -0
- package/dist/components/RichText.js.map +1 -0
- package/dist/components/Seo.d.ts +25 -0
- package/dist/components/Seo.d.ts.map +1 -0
- package/dist/components/Seo.js +54 -0
- package/dist/components/Seo.js.map +1 -0
- package/dist/components/hooks/useMoney.d.ts +40 -0
- package/dist/components/hooks/useMoney.d.ts.map +1 -0
- package/dist/components/hooks/useMoney.js +60 -0
- package/dist/components/hooks/useMoney.js.map +1 -0
- package/dist/components/hooks/useOptimisticCart.d.ts +50 -0
- package/dist/components/hooks/useOptimisticCart.d.ts.map +1 -0
- package/dist/components/hooks/useOptimisticCart.js +146 -0
- package/dist/components/hooks/useOptimisticCart.js.map +1 -0
- package/dist/components/index.d.ts +28 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +21 -0
- package/dist/components/index.js.map +1 -1
- package/dist/createCartHandler.d.ts.map +1 -1
- package/dist/createCartHandler.js +57 -0
- package/dist/createCartHandler.js.map +1 -1
- package/dist/csp/csp.d.ts +57 -0
- package/dist/csp/csp.d.ts.map +1 -0
- package/dist/csp/csp.js +73 -0
- package/dist/csp/csp.js.map +1 -0
- package/dist/customer/createCustomerAccountClient.d.ts +43 -0
- package/dist/customer/createCustomerAccountClient.d.ts.map +1 -0
- package/dist/customer/createCustomerAccountClient.js +68 -0
- package/dist/customer/createCustomerAccountClient.js.map +1 -0
- package/dist/handleCartFormAction.d.ts +39 -0
- package/dist/handleCartFormAction.d.ts.map +1 -0
- package/dist/handleCartFormAction.js +103 -0
- package/dist/handleCartFormAction.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -1
- package/dist/routing/storefrontRedirect.d.ts +37 -0
- package/dist/routing/storefrontRedirect.d.ts.map +1 -0
- package/dist/routing/storefrontRedirect.js +64 -0
- package/dist/routing/storefrontRedirect.js.map +1 -0
- package/dist/seo/getSeoMeta.d.ts +68 -0
- package/dist/seo/getSeoMeta.d.ts.map +1 -0
- package/dist/seo/getSeoMeta.js +89 -0
- package/dist/seo/getSeoMeta.js.map +1 -0
- package/dist/sitemap/sitemap.d.ts +55 -0
- package/dist/sitemap/sitemap.d.ts.map +1 -0
- package/dist/sitemap/sitemap.js +93 -0
- package/dist/sitemap/sitemap.js.map +1 -0
- package/dist/types.d.ts +12 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/flattenConnection.d.ts +25 -0
- package/dist/utils/flattenConnection.d.ts.map +1 -0
- package/dist/utils/flattenConnection.js +25 -0
- package/dist/utils/flattenConnection.js.map +1 -0
- package/dist/utils/parseGid.d.ts +17 -0
- package/dist/utils/parseGid.d.ts.map +1 -0
- package/dist/utils/parseGid.js +19 -0
- package/dist/utils/parseGid.js.map +1 -0
- package/package.json +1 -1
- package/src/cache/withCache.ts +144 -0
- package/src/components/AddToCartButton.tsx +94 -56
- package/src/components/BuyNowButton.tsx +135 -0
- package/src/components/CartCheckoutButton.tsx +97 -0
- package/src/components/CartCost.tsx +65 -0
- package/src/components/CartForm.tsx +311 -0
- package/src/components/CartLineProvider.tsx +77 -0
- package/src/components/CartLineQuantity.tsx +37 -0
- package/src/components/DiscountSelector.tsx +34 -45
- package/src/components/Image.tsx +27 -0
- package/src/components/Pagination.tsx +139 -0
- package/src/components/RichText.tsx +122 -0
- package/src/components/Seo.tsx +61 -0
- package/src/components/hooks/useMoney.ts +87 -0
- package/src/components/hooks/useOptimisticCart.ts +183 -0
- package/src/components/index.ts +44 -0
- package/src/createCartHandler.ts +71 -0
- package/src/csp/csp.tsx +119 -0
- package/src/customer/createCustomerAccountClient.ts +89 -0
- package/src/handleCartFormAction.ts +129 -0
- package/src/index.ts +24 -0
- package/src/routing/storefrontRedirect.ts +86 -0
- package/src/seo/getSeoMeta.ts +125 -0
- package/src/sitemap/sitemap.ts +121 -0
- package/src/types.ts +12 -1
- package/src/utils/flattenConnection.ts +33 -0
- package/src/utils/parseGid.ts +25 -0
|
@@ -18,18 +18,15 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
|
|
|
18
18
|
*/
|
|
19
19
|
import * as React from 'react';
|
|
20
20
|
import { useDiscounts } from './DiscountProvider';
|
|
21
|
-
import { useAnalytics } from './AnalyticsProvider';
|
|
22
21
|
import { Money } from './Money';
|
|
23
22
|
import { useMounted } from './hooks/useMounted';
|
|
23
|
+
import { CartForm } from './CartForm';
|
|
24
24
|
export function DiscountSelector(props) {
|
|
25
25
|
const { className, unauthenticatedFallback, renderApplied } = props;
|
|
26
26
|
const mounted = useMounted();
|
|
27
|
-
const { myDiscounts, myDiscountsStatus, cartAllocations, appliedClaim,
|
|
28
|
-
const analytics = useAnalytics();
|
|
27
|
+
const { myDiscounts, myDiscountsStatus, cartAllocations, appliedClaim, } = useDiscounts();
|
|
29
28
|
const [open, setOpen] = React.useState(false);
|
|
30
|
-
const [pending, setPending] = React.useState(false);
|
|
31
29
|
// SSR + client 首次 render:返回固定占位,保证 hydration 不 mismatch。
|
|
32
|
-
// mounted 后才进入真正的渲染分支。
|
|
33
30
|
if (!mounted) {
|
|
34
31
|
return _jsx("div", { "data-discount-selector": true, "data-ssr-placeholder": true, className: className });
|
|
35
32
|
}
|
|
@@ -38,17 +35,6 @@ export function DiscountSelector(props) {
|
|
|
38
35
|
const appliedClaimId = appliedClaim?.claimId ?? null;
|
|
39
36
|
const appliedTitle = appliedClaim?.title ?? appliedAlloc?.title;
|
|
40
37
|
const appliedCode = appliedClaim?.code ?? appliedAlloc?.code;
|
|
41
|
-
const handleSelect = async (claimId) => {
|
|
42
|
-
setPending(true);
|
|
43
|
-
try {
|
|
44
|
-
await selectCartDiscount(claimId);
|
|
45
|
-
analytics.emit('discount_select', { claimId });
|
|
46
|
-
}
|
|
47
|
-
finally {
|
|
48
|
-
setPending(false);
|
|
49
|
-
setOpen(false);
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
38
|
// 未登录
|
|
53
39
|
if (myDiscountsStatus === 'unauthenticated') {
|
|
54
40
|
return unauthenticatedFallback !== undefined ? _jsx(_Fragment, { children: unauthenticatedFallback }) : (_jsxs("div", { "data-discount-selector": true, "data-unauth": true, className: className, children: [_jsx("span", { children: "\u767B\u5F55\u540E\u53EF\u4F7F\u7528\u4F18\u60E0\u5238" }), _jsx("a", { href: "/login?next=/cart", children: "\u53BB\u767B\u5F55" })] }));
|
|
@@ -65,17 +51,20 @@ export function DiscountSelector(props) {
|
|
|
65
51
|
title: appliedTitle ?? '',
|
|
66
52
|
amount: appliedAlloc?.discountedAmount ?? { amount: '0', currencyCode: 'CNY' },
|
|
67
53
|
onChange: () => setOpen(true),
|
|
68
|
-
}) : (_jsxs("div", { "data-applied": true, children: [_jsxs("div", { "data-info": true, children: [_jsx("span", { "data-label": true, children: "\u5DF2\u4F7F\u7528" }), _jsx("strong", { "data-title": true, children: appliedTitle }), appliedAlloc && (_jsxs("span", { "data-amount": true, children: ["\u2212 ", _jsx(Money, { data: { amount: appliedAlloc.discountedAmount.amount, currencyCode: appliedAlloc.discountedAmount.currencyCode } })] }))] }), _jsx("div", { "data-actions": true, children: _jsx("button", { type: "button", onClick: () => setOpen(true),
|
|
54
|
+
}) : (_jsxs("div", { "data-applied": true, children: [_jsxs("div", { "data-info": true, children: [_jsx("span", { "data-label": true, children: "\u5DF2\u4F7F\u7528" }), _jsx("strong", { "data-title": true, children: appliedTitle }), appliedAlloc && (_jsxs("span", { "data-amount": true, children: ["\u2212 ", _jsx(Money, { data: { amount: appliedAlloc.discountedAmount.amount, currencyCode: appliedAlloc.discountedAmount.currencyCode } })] }))] }), _jsx("div", { "data-actions": true, children: _jsx("button", { type: "button", onClick: () => setOpen(true), children: "\u5207\u6362" }) })] }))) : (_jsxs("div", { "data-empty-applied": true, children: [_jsx("div", { "data-info": true, children: _jsxs("span", { "data-label": true, children: ["\u6709 ", availableClaims.length, " \u5F20\u53EF\u7528\u4F18\u60E0\u5238"] }) }), _jsx("button", { type: "button", onClick: () => setOpen(true), children: "\u9009\u62E9" })] })) }), open && (_jsx(DiscountPickerSheet, { claims: availableClaims, currentClaimId: appliedClaimId, onClose: () => setOpen(false) }))] }));
|
|
69
55
|
}
|
|
70
56
|
/**
|
|
71
57
|
* 抽屉式选券面板。
|
|
72
58
|
* Demo 实现为屏底固定面板;商家可以替换为 modal / 抽屉等。
|
|
73
59
|
*/
|
|
74
|
-
function DiscountPickerSheet({ claims, currentClaimId,
|
|
60
|
+
function DiscountPickerSheet({ claims, currentClaimId, onClose, }) {
|
|
75
61
|
return (_jsx("div", { "data-discount-picker-overlay": true, onClick: onClose, children: _jsxs("div", { "data-discount-picker-sheet": true, onClick: (e) => e.stopPropagation(), children: [_jsxs("div", { "data-sheet-header": true, children: [_jsx("h3", { children: "\u9009\u62E9\u4F18\u60E0\u5238" }), _jsx("button", { type: "button", onClick: onClose, "data-sheet-close": true, children: "\u00D7" })] }), _jsx("div", { "data-sheet-list": true, children: claims.map((c) => {
|
|
62
|
+
// claim_id 既可能是 gid 也可能是 raw —— oxygen mutation 接受两种
|
|
76
63
|
const id = c.id.replace(/^gid:\/\/shopbb\/DiscountClaim\//, '');
|
|
77
64
|
const isCurrent = currentClaimId === id;
|
|
78
|
-
return (
|
|
65
|
+
return (
|
|
66
|
+
// 每张券一个 <CartForm>,submit 走 /cart action → cart.selectDiscount
|
|
67
|
+
_jsx(CartForm, { action: "CustomDiscountSelect", inputs: { claimId: id }, children: (fetcher) => (_jsxs("button", { type: "submit", "data-claim-card": true, "data-current": isCurrent ? '' : undefined, disabled: fetcher.state !== 'idle', children: [_jsxs("div", { "data-claim-left": true, children: [_jsx("div", { "data-claim-value": true, children: formatValue(c) }), c.discount.minSubtotal && (_jsxs("div", { "data-claim-min": true, children: ["\u6EE1 ", _jsx(Money, { data: { amount: c.discount.minSubtotal.amount, currencyCode: c.discount.minSubtotal.currencyCode } }), " \u53EF\u7528"] }))] }), _jsxs("div", { "data-claim-right": true, children: [_jsx("div", { "data-claim-title": true, children: c.discount.title }), c.discount.code && _jsx("div", { "data-claim-code": true, children: c.discount.code }), c.expiresAt && (_jsxs("div", { "data-claim-expiry": true, children: [new Date(c.expiresAt).toLocaleDateString('zh-CN'), " \u8FC7\u671F"] }))] }), isCurrent && _jsx("span", { "data-claim-current-tag": true, children: "\u5F53\u524D\u4F7F\u7528" })] })) }, c.id));
|
|
79
68
|
}) })] }) }));
|
|
80
69
|
}
|
|
81
70
|
function formatValue(c) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DiscountSelector.js","sourceRoot":"","sources":["../../src/components/DiscountSelector.tsx"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,YAAY,EAAsB,MAAM,oBAAoB,CAAC;AACtE,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"DiscountSelector.js","sourceRoot":"","sources":["../../src/components/DiscountSelector.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IMAGE_FRAGMENT — 对齐 Hydrogen React 同名常量
|
|
3
|
+
*
|
|
4
|
+
* 商家在 GraphQL 查询里直接用:
|
|
5
|
+
*
|
|
6
|
+
* import { IMAGE_FRAGMENT } from '@shopbb/helium';
|
|
7
|
+
*
|
|
8
|
+
* const Q = `
|
|
9
|
+
* ${IMAGE_FRAGMENT}
|
|
10
|
+
* query {
|
|
11
|
+
* product(handle: "x") {
|
|
12
|
+
* featuredImage { ...Image }
|
|
13
|
+
* images(first: 5) { nodes { ...Image } }
|
|
14
|
+
* }
|
|
15
|
+
* }
|
|
16
|
+
* `;
|
|
17
|
+
*/
|
|
18
|
+
export declare const IMAGE_FRAGMENT = "\n fragment Image on Image {\n id\n url\n altText\n width\n height\n }\n";
|
|
1
19
|
/**
|
|
2
20
|
* <Image> — 响应式商品图片
|
|
3
21
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Image.d.ts","sourceRoot":"","sources":["../../src/components/Image.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED,MAAM,WAAW,UAAW,SAAQ,IAAI,CAAC,KAAK,CAAC,iBAAiB,CAAC,gBAAgB,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,SAAS,CAAC;IAC/G,cAAc;IACd,IAAI,EAAE,SAAS,CAAC;IAChB,mDAAmD;IACnD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kCAAkC;IAClC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6BAA6B;IAC7B,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3B,oBAAoB;IACpB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAkBD,wBAAgB,KAAK,CAAC,KAAK,EAAE,UAAU,2CAmCtC"}
|
|
1
|
+
{"version":3,"file":"Image.d.ts","sourceRoot":"","sources":["../../src/components/Image.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,cAAc,8FAQ1B,CAAC;AAEF;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED,MAAM,WAAW,UAAW,SAAQ,IAAI,CAAC,KAAK,CAAC,iBAAiB,CAAC,gBAAgB,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,SAAS,CAAC;IAC/G,cAAc;IACd,IAAI,EAAE,SAAS,CAAC;IAChB,mDAAmD;IACnD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kCAAkC;IAClC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6BAA6B;IAC7B,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3B,oBAAoB;IACpB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAkBD,wBAAgB,KAAK,CAAC,KAAK,EAAE,UAAU,2CAmCtC"}
|
package/dist/components/Image.js
CHANGED
|
@@ -1,4 +1,30 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* IMAGE_FRAGMENT — 对齐 Hydrogen React 同名常量
|
|
4
|
+
*
|
|
5
|
+
* 商家在 GraphQL 查询里直接用:
|
|
6
|
+
*
|
|
7
|
+
* import { IMAGE_FRAGMENT } from '@shopbb/helium';
|
|
8
|
+
*
|
|
9
|
+
* const Q = `
|
|
10
|
+
* ${IMAGE_FRAGMENT}
|
|
11
|
+
* query {
|
|
12
|
+
* product(handle: "x") {
|
|
13
|
+
* featuredImage { ...Image }
|
|
14
|
+
* images(first: 5) { nodes { ...Image } }
|
|
15
|
+
* }
|
|
16
|
+
* }
|
|
17
|
+
* `;
|
|
18
|
+
*/
|
|
19
|
+
export const IMAGE_FRAGMENT = /* GraphQL */ `
|
|
20
|
+
fragment Image on Image {
|
|
21
|
+
id
|
|
22
|
+
url
|
|
23
|
+
altText
|
|
24
|
+
width
|
|
25
|
+
height
|
|
26
|
+
}
|
|
27
|
+
`;
|
|
2
28
|
// 默认 srcset 档位
|
|
3
29
|
const DEFAULT_WIDTHS = [375, 750, 1024, 1536, 1920];
|
|
4
30
|
/** 给 url 加 ?width=N query */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Image.js","sourceRoot":"","sources":["../../src/components/Image.tsx"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"Image.js","sourceRoot":"","sources":["../../src/components/Image.tsx"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,aAAa,CAAC;;;;;;;;CAQ3C,CAAC;AA2CF,eAAe;AACf,MAAM,cAAc,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;AAEpD,6BAA6B;AAC7B,SAAS,SAAS,CAAC,GAAW,EAAE,KAAa;IAC3C,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QACvB,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QAC3C,OAAO,CAAC,CAAC,QAAQ,EAAE,CAAC;IACtB,CAAC;IAAC,MAAM,CAAC;QACP,mBAAmB;QACnB,MAAM,GAAG,GAAG,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;QAC1C,OAAO,GAAG,GAAG,GAAG,GAAG,SAAS,KAAK,EAAE,CAAC;IACtC,CAAC;AACH,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,KAAiB;IACrC,MAAM,EACJ,IAAI,EACJ,KAAK,EACL,WAAW,EACX,OAAO,GAAG,MAAM,EAChB,MAAM,GAAG,cAAc,EACvB,KAAK,EACL,GAAG,EAAE,OAAO,EACZ,GAAG,IAAI,EACR,GAAG,KAAK,CAAC;IAEV,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC/E,YAAY;IACZ,MAAM,GAAG,GAAG,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAEvE,MAAM,UAAU,GAAwB;QACtC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,SAAS,EAAE,OAAgB,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;QACtE,GAAG,KAAK;KACT,CAAC;IAEF,OAAO,CACL,iBACM,IAAI,EACR,GAAG,EAAE,GAAG,EACR,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,OAAO,IAAI,IAAI,CAAC,OAAO,IAAI,EAAE,EAClC,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAC,OAAO,EAChB,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,SAAS,EAC9B,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,SAAS,EAChC,KAAK,EAAE,UAAU,GACjB,CACH,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <Pagination> + getPaginationVariables() — 对齐 Hydrogen Pagination
|
|
3
|
+
*
|
|
4
|
+
* GraphQL cursor-based 分页(forward + backward)。SSR 友好:URL ?cursor= / ?direction= 控制。
|
|
5
|
+
*
|
|
6
|
+
* 用法(服务端 loader):
|
|
7
|
+
*
|
|
8
|
+
* const variables = getPaginationVariables(request, { pageBy: 20 });
|
|
9
|
+
* // → { first: 20, last: undefined, startCursor: undefined, endCursor: undefined }
|
|
10
|
+
* const data = await storefront.query(PRODUCTS_QUERY, { variables });
|
|
11
|
+
*
|
|
12
|
+
* 用法(组件):
|
|
13
|
+
*
|
|
14
|
+
* <Pagination connection={data.products}>
|
|
15
|
+
* {({ nodes, hasNextPage, NextLink, hasPreviousPage, PreviousLink, state }) => (
|
|
16
|
+
* <>
|
|
17
|
+
* {hasPreviousPage && <PreviousLink>上一页</PreviousLink>}
|
|
18
|
+
* <ul>{nodes.map((n) => <li key={n.id}>{n.title}</li>)}</ul>
|
|
19
|
+
* {hasNextPage && <NextLink>下一页</NextLink>}
|
|
20
|
+
* </>
|
|
21
|
+
* )}
|
|
22
|
+
* </Pagination>
|
|
23
|
+
*/
|
|
24
|
+
import * as React from 'react';
|
|
25
|
+
export interface PaginationConnection<T = any> {
|
|
26
|
+
pageInfo: {
|
|
27
|
+
hasNextPage: boolean;
|
|
28
|
+
hasPreviousPage: boolean;
|
|
29
|
+
startCursor?: string | null;
|
|
30
|
+
endCursor?: string | null;
|
|
31
|
+
};
|
|
32
|
+
nodes?: T[];
|
|
33
|
+
edges?: Array<{
|
|
34
|
+
node: T;
|
|
35
|
+
cursor?: string;
|
|
36
|
+
}>;
|
|
37
|
+
}
|
|
38
|
+
export interface PaginationVariables {
|
|
39
|
+
first?: number;
|
|
40
|
+
last?: number;
|
|
41
|
+
startCursor?: string | null;
|
|
42
|
+
endCursor?: string | null;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* 从 request URL 解析分页 query params。
|
|
46
|
+
*
|
|
47
|
+
* ?direction=next&cursor=xxx → { first: 20, endCursor: 'xxx' }
|
|
48
|
+
* ?direction=previous&cursor=xxx → { last: 20, startCursor: 'xxx' }
|
|
49
|
+
* (没 param) → { first: 20 }
|
|
50
|
+
*/
|
|
51
|
+
export declare function getPaginationVariables(request: Request, options?: {
|
|
52
|
+
pageBy?: number;
|
|
53
|
+
}): PaginationVariables;
|
|
54
|
+
export interface PaginationRenderProps<T> {
|
|
55
|
+
/** 拍平后的当前页 nodes */
|
|
56
|
+
nodes: T[];
|
|
57
|
+
/** Raw pageInfo(hasNextPage / hasPreviousPage 等) */
|
|
58
|
+
pageInfo: PaginationConnection['pageInfo'];
|
|
59
|
+
/** 当前 URL state */
|
|
60
|
+
state: {
|
|
61
|
+
direction: 'next' | 'previous' | null;
|
|
62
|
+
cursor: string | null;
|
|
63
|
+
};
|
|
64
|
+
hasNextPage: boolean;
|
|
65
|
+
hasPreviousPage: boolean;
|
|
66
|
+
/** "下一页" 链接组件(自动加 ?direction=next&cursor=...) */
|
|
67
|
+
NextLink: React.FC<{
|
|
68
|
+
children: React.ReactNode;
|
|
69
|
+
className?: string;
|
|
70
|
+
}>;
|
|
71
|
+
/** "上一页" 链接组件 */
|
|
72
|
+
PreviousLink: React.FC<{
|
|
73
|
+
children: React.ReactNode;
|
|
74
|
+
className?: string;
|
|
75
|
+
}>;
|
|
76
|
+
}
|
|
77
|
+
export interface PaginationProps<T> {
|
|
78
|
+
connection: PaginationConnection<T>;
|
|
79
|
+
children: (props: PaginationRenderProps<T>) => React.ReactNode;
|
|
80
|
+
}
|
|
81
|
+
export declare function Pagination<T>(props: PaginationProps<T>): import("react/jsx-runtime").JSX.Element;
|
|
82
|
+
//# sourceMappingURL=Pagination.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Pagination.d.ts","sourceRoot":"","sources":["../../src/components/Pagination.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAK/B,MAAM,WAAW,oBAAoB,CAAC,CAAC,GAAG,GAAG;IAC3C,QAAQ,EAAE;QACR,WAAW,EAAE,OAAO,CAAC;QACrB,eAAe,EAAE,OAAO,CAAC;QACzB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC5B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KAC3B,CAAC;IACF,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC;IACZ,KAAK,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,CAAC,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC7C;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,OAAO,EAChB,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GAChC,mBAAmB,CAYrB;AAED,MAAM,WAAW,qBAAqB,CAAC,CAAC;IACtC,oBAAoB;IACpB,KAAK,EAAE,CAAC,EAAE,CAAC;IACX,oDAAoD;IACpD,QAAQ,EAAE,oBAAoB,CAAC,UAAU,CAAC,CAAC;IAC3C,mBAAmB;IACnB,KAAK,EAAE;QAAE,SAAS,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAAC;QAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IAExE,WAAW,EAAE,OAAO,CAAC;IACrB,eAAe,EAAE,OAAO,CAAC;IACzB,iDAAiD;IACjD,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC;QAAE,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACtE,iBAAiB;IACjB,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC;QAAE,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC3E;AAED,MAAM,WAAW,eAAe,CAAC,CAAC;IAChC,UAAU,EAAE,oBAAoB,CAAC,CAAC,CAAC,CAAC;IACpC,QAAQ,EAAE,CAAC,KAAK,EAAE,qBAAqB,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,SAAS,CAAC;CAChE;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,KAAK,EAAE,eAAe,CAAC,CAAC,CAAC,2CA8CtD"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* <Pagination> + getPaginationVariables() — 对齐 Hydrogen Pagination
|
|
4
|
+
*
|
|
5
|
+
* GraphQL cursor-based 分页(forward + backward)。SSR 友好:URL ?cursor= / ?direction= 控制。
|
|
6
|
+
*
|
|
7
|
+
* 用法(服务端 loader):
|
|
8
|
+
*
|
|
9
|
+
* const variables = getPaginationVariables(request, { pageBy: 20 });
|
|
10
|
+
* // → { first: 20, last: undefined, startCursor: undefined, endCursor: undefined }
|
|
11
|
+
* const data = await storefront.query(PRODUCTS_QUERY, { variables });
|
|
12
|
+
*
|
|
13
|
+
* 用法(组件):
|
|
14
|
+
*
|
|
15
|
+
* <Pagination connection={data.products}>
|
|
16
|
+
* {({ nodes, hasNextPage, NextLink, hasPreviousPage, PreviousLink, state }) => (
|
|
17
|
+
* <>
|
|
18
|
+
* {hasPreviousPage && <PreviousLink>上一页</PreviousLink>}
|
|
19
|
+
* <ul>{nodes.map((n) => <li key={n.id}>{n.title}</li>)}</ul>
|
|
20
|
+
* {hasNextPage && <NextLink>下一页</NextLink>}
|
|
21
|
+
* </>
|
|
22
|
+
* )}
|
|
23
|
+
* </Pagination>
|
|
24
|
+
*/
|
|
25
|
+
import * as React from 'react';
|
|
26
|
+
import { flattenConnection } from '../utils/flattenConnection';
|
|
27
|
+
const DEFAULT_PAGE_BY = 20;
|
|
28
|
+
/**
|
|
29
|
+
* 从 request URL 解析分页 query params。
|
|
30
|
+
*
|
|
31
|
+
* ?direction=next&cursor=xxx → { first: 20, endCursor: 'xxx' }
|
|
32
|
+
* ?direction=previous&cursor=xxx → { last: 20, startCursor: 'xxx' }
|
|
33
|
+
* (没 param) → { first: 20 }
|
|
34
|
+
*/
|
|
35
|
+
export function getPaginationVariables(request, options = {}) {
|
|
36
|
+
const { pageBy = DEFAULT_PAGE_BY } = options;
|
|
37
|
+
const url = new URL(request.url);
|
|
38
|
+
const direction = url.searchParams.get('direction');
|
|
39
|
+
const cursor = url.searchParams.get('cursor');
|
|
40
|
+
if (direction === 'previous' && cursor) {
|
|
41
|
+
return { last: pageBy, startCursor: cursor };
|
|
42
|
+
}
|
|
43
|
+
if (direction === 'next' && cursor) {
|
|
44
|
+
return { first: pageBy, endCursor: cursor };
|
|
45
|
+
}
|
|
46
|
+
return { first: pageBy };
|
|
47
|
+
}
|
|
48
|
+
export function Pagination(props) {
|
|
49
|
+
const { connection, children } = props;
|
|
50
|
+
const pageInfo = connection.pageInfo;
|
|
51
|
+
const nodes = flattenConnection(connection);
|
|
52
|
+
// 当前 URL state(hydrate 后才能访问 window.location)
|
|
53
|
+
const [urlState, setUrlState] = React.useState({
|
|
54
|
+
direction: null, cursor: null,
|
|
55
|
+
});
|
|
56
|
+
React.useEffect(() => {
|
|
57
|
+
if (typeof window === 'undefined')
|
|
58
|
+
return;
|
|
59
|
+
const params = new URLSearchParams(window.location.search);
|
|
60
|
+
const d = params.get('direction');
|
|
61
|
+
setUrlState({
|
|
62
|
+
direction: d === 'next' || d === 'previous' ? d : null,
|
|
63
|
+
cursor: params.get('cursor'),
|
|
64
|
+
});
|
|
65
|
+
}, []);
|
|
66
|
+
const nextHref = pageInfo.endCursor
|
|
67
|
+
? `?direction=next&cursor=${encodeURIComponent(pageInfo.endCursor)}`
|
|
68
|
+
: '#';
|
|
69
|
+
const prevHref = pageInfo.startCursor
|
|
70
|
+
? `?direction=previous&cursor=${encodeURIComponent(pageInfo.startCursor)}`
|
|
71
|
+
: '#';
|
|
72
|
+
const NextLink = ({ children, className }) => (_jsx("a", { href: nextHref, className: className, "data-pagination-next": true, children: children }));
|
|
73
|
+
const PreviousLink = ({ children, className }) => (_jsx("a", { href: prevHref, className: className, "data-pagination-prev": true, children: children }));
|
|
74
|
+
return (_jsx(_Fragment, { children: children({
|
|
75
|
+
nodes,
|
|
76
|
+
pageInfo,
|
|
77
|
+
state: urlState,
|
|
78
|
+
hasNextPage: pageInfo.hasNextPage,
|
|
79
|
+
hasPreviousPage: pageInfo.hasPreviousPage,
|
|
80
|
+
NextLink,
|
|
81
|
+
PreviousLink,
|
|
82
|
+
}) }));
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=Pagination.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Pagination.js","sourceRoot":"","sources":["../../src/components/Pagination.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <RichText> — 对齐 Hydrogen React
|
|
3
|
+
*
|
|
4
|
+
* 渲染 Shopify rich-text metafield 的 JSON 结构。Shopify rich-text 标准格式:
|
|
5
|
+
*
|
|
6
|
+
* {
|
|
7
|
+
* type: "root",
|
|
8
|
+
* children: [
|
|
9
|
+
* { type: "heading", level: 1, children: [{type: "text", value: "Hello"}] },
|
|
10
|
+
* { type: "paragraph", children: [
|
|
11
|
+
* {type: "text", value: "Hello "},
|
|
12
|
+
* {type: "text", value: "world", bold: true},
|
|
13
|
+
* {type: "link", url: "...", children: [{type: "text", value: "click"}]},
|
|
14
|
+
* ]},
|
|
15
|
+
* ],
|
|
16
|
+
* }
|
|
17
|
+
*
|
|
18
|
+
* 用法:
|
|
19
|
+
* const rich = JSON.parse(product.descriptionRichText);
|
|
20
|
+
* <RichText data={rich} />
|
|
21
|
+
*
|
|
22
|
+
* // 自定义节点渲染(覆盖默认)
|
|
23
|
+
* <RichText data={rich} components={{ heading: ({level, children}) =>
|
|
24
|
+
* React.createElement(`h${level}`, {className: 'my-h'}, children) }} />
|
|
25
|
+
*/
|
|
26
|
+
import * as React from 'react';
|
|
27
|
+
export interface RichTextNode {
|
|
28
|
+
type: string;
|
|
29
|
+
value?: string;
|
|
30
|
+
level?: number;
|
|
31
|
+
url?: string;
|
|
32
|
+
title?: string;
|
|
33
|
+
target?: string;
|
|
34
|
+
bold?: boolean;
|
|
35
|
+
italic?: boolean;
|
|
36
|
+
listType?: 'ordered' | 'unordered';
|
|
37
|
+
children?: RichTextNode[];
|
|
38
|
+
}
|
|
39
|
+
export interface RichTextComponents {
|
|
40
|
+
root?: (props: {
|
|
41
|
+
children: React.ReactNode;
|
|
42
|
+
}) => React.ReactElement;
|
|
43
|
+
paragraph?: (props: {
|
|
44
|
+
children: React.ReactNode;
|
|
45
|
+
}) => React.ReactElement;
|
|
46
|
+
heading?: (props: {
|
|
47
|
+
level: number;
|
|
48
|
+
children: React.ReactNode;
|
|
49
|
+
}) => React.ReactElement;
|
|
50
|
+
list?: (props: {
|
|
51
|
+
listType: 'ordered' | 'unordered';
|
|
52
|
+
children: React.ReactNode;
|
|
53
|
+
}) => React.ReactElement;
|
|
54
|
+
listItem?: (props: {
|
|
55
|
+
children: React.ReactNode;
|
|
56
|
+
}) => React.ReactElement;
|
|
57
|
+
link?: (props: {
|
|
58
|
+
url: string;
|
|
59
|
+
title?: string;
|
|
60
|
+
target?: string;
|
|
61
|
+
children: React.ReactNode;
|
|
62
|
+
}) => React.ReactElement;
|
|
63
|
+
text?: (props: {
|
|
64
|
+
value: string;
|
|
65
|
+
bold?: boolean;
|
|
66
|
+
italic?: boolean;
|
|
67
|
+
}) => React.ReactElement;
|
|
68
|
+
}
|
|
69
|
+
export interface RichTextProps {
|
|
70
|
+
/** Rich-text JSON object(已 parse 过的)或者 JSON string */
|
|
71
|
+
data: RichTextNode | string | null | undefined;
|
|
72
|
+
/** 自定义渲染器 */
|
|
73
|
+
components?: RichTextComponents;
|
|
74
|
+
/** 包裹容器 className */
|
|
75
|
+
className?: string;
|
|
76
|
+
}
|
|
77
|
+
export declare function RichText({ data, components, className }: RichTextProps): import("react/jsx-runtime").JSX.Element | null;
|
|
78
|
+
//# sourceMappingURL=RichText.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RichText.d.ts","sourceRoot":"","sources":["../../src/components/RichText.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,SAAS,GAAG,WAAW,CAAC;IACnC,QAAQ,CAAC,EAAE,YAAY,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;KAAE,KAAK,KAAK,CAAC,YAAY,CAAC;IACpE,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;KAAE,KAAK,KAAK,CAAC,YAAY,CAAC;IACzE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;KAAE,KAAK,KAAK,CAAC,YAAY,CAAC;IACtF,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,QAAQ,EAAE,SAAS,GAAG,WAAW,CAAC;QAAC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;KAAE,KAAK,KAAK,CAAC,YAAY,CAAC;IACvG,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;KAAE,KAAK,KAAK,CAAC,YAAY,CAAC;IACxE,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;KAAE,KAAK,KAAK,CAAC,YAAY,CAAC;IAClH,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,KAAK,CAAC,YAAY,CAAC;CAC3F;AAED,MAAM,WAAW,aAAa;IAC5B,sDAAsD;IACtD,IAAI,EAAE,YAAY,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAC/C,aAAa;IACb,UAAU,CAAC,EAAE,kBAAkB,CAAC;IAChC,qBAAqB;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA6CD,wBAAgB,QAAQ,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,EAAE,aAAa,kDAkBtE"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* <RichText> — 对齐 Hydrogen React
|
|
4
|
+
*
|
|
5
|
+
* 渲染 Shopify rich-text metafield 的 JSON 结构。Shopify rich-text 标准格式:
|
|
6
|
+
*
|
|
7
|
+
* {
|
|
8
|
+
* type: "root",
|
|
9
|
+
* children: [
|
|
10
|
+
* { type: "heading", level: 1, children: [{type: "text", value: "Hello"}] },
|
|
11
|
+
* { type: "paragraph", children: [
|
|
12
|
+
* {type: "text", value: "Hello "},
|
|
13
|
+
* {type: "text", value: "world", bold: true},
|
|
14
|
+
* {type: "link", url: "...", children: [{type: "text", value: "click"}]},
|
|
15
|
+
* ]},
|
|
16
|
+
* ],
|
|
17
|
+
* }
|
|
18
|
+
*
|
|
19
|
+
* 用法:
|
|
20
|
+
* const rich = JSON.parse(product.descriptionRichText);
|
|
21
|
+
* <RichText data={rich} />
|
|
22
|
+
*
|
|
23
|
+
* // 自定义节点渲染(覆盖默认)
|
|
24
|
+
* <RichText data={rich} components={{ heading: ({level, children}) =>
|
|
25
|
+
* React.createElement(`h${level}`, {className: 'my-h'}, children) }} />
|
|
26
|
+
*/
|
|
27
|
+
import * as React from 'react';
|
|
28
|
+
const DEFAULTS = {
|
|
29
|
+
root: ({ children }) => _jsx("div", { "data-rich-text": true, children: children }),
|
|
30
|
+
paragraph: ({ children }) => _jsx("p", { children: children }),
|
|
31
|
+
heading: ({ level, children }) => React.createElement(`h${Math.min(Math.max(level, 1), 6)}`, null, children),
|
|
32
|
+
list: ({ listType, children }) => (listType === 'ordered' ? _jsx("ol", { children: children }) : _jsx("ul", { children: children })),
|
|
33
|
+
listItem: ({ children }) => _jsx("li", { children: children }),
|
|
34
|
+
link: ({ url, title, target, children }) => (_jsx("a", { href: url, title: title, target: target, rel: target === '_blank' ? 'noopener noreferrer' : undefined, children: children })),
|
|
35
|
+
text: ({ value, bold, italic }) => {
|
|
36
|
+
let out = value;
|
|
37
|
+
if (italic)
|
|
38
|
+
out = _jsx("em", { children: out });
|
|
39
|
+
if (bold)
|
|
40
|
+
out = _jsx("strong", { children: out });
|
|
41
|
+
return _jsx(_Fragment, { children: out });
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
function renderNode(node, comps, key) {
|
|
45
|
+
if (!node)
|
|
46
|
+
return null;
|
|
47
|
+
const childrenNodes = (node.children || []).map((c, i) => renderNode(c, comps, i));
|
|
48
|
+
switch (node.type) {
|
|
49
|
+
case 'root':
|
|
50
|
+
return _jsx(React.Fragment, { children: comps.root({ children: childrenNodes }) }, key);
|
|
51
|
+
case 'paragraph':
|
|
52
|
+
return _jsx(React.Fragment, { children: comps.paragraph({ children: childrenNodes }) }, key);
|
|
53
|
+
case 'heading':
|
|
54
|
+
return _jsx(React.Fragment, { children: comps.heading({ level: node.level || 2, children: childrenNodes }) }, key);
|
|
55
|
+
case 'list':
|
|
56
|
+
return _jsx(React.Fragment, { children: comps.list({ listType: node.listType || 'unordered', children: childrenNodes }) }, key);
|
|
57
|
+
case 'list-item':
|
|
58
|
+
return _jsx(React.Fragment, { children: comps.listItem({ children: childrenNodes }) }, key);
|
|
59
|
+
case 'link':
|
|
60
|
+
return _jsx(React.Fragment, { children: comps.link({ url: node.url || '#', title: node.title, target: node.target, children: childrenNodes }) }, key);
|
|
61
|
+
case 'text':
|
|
62
|
+
return _jsx(React.Fragment, { children: comps.text({ value: node.value || '', bold: node.bold, italic: node.italic }) }, key);
|
|
63
|
+
default:
|
|
64
|
+
// unknown type — 递归 children 不出错
|
|
65
|
+
return _jsx(React.Fragment, { children: childrenNodes }, key);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
export function RichText({ data, components, className }) {
|
|
69
|
+
let parsed = null;
|
|
70
|
+
if (!data)
|
|
71
|
+
return null;
|
|
72
|
+
if (typeof data === 'string') {
|
|
73
|
+
try {
|
|
74
|
+
parsed = JSON.parse(data);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return _jsx("div", { className: className, children: data });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
parsed = data;
|
|
82
|
+
}
|
|
83
|
+
if (!parsed)
|
|
84
|
+
return null;
|
|
85
|
+
const comps = { ...DEFAULTS, ...(components || {}) };
|
|
86
|
+
const rendered = renderNode(parsed, comps, 0);
|
|
87
|
+
if (className && parsed.type === 'root') {
|
|
88
|
+
// 简单包一层,给 root 加 className
|
|
89
|
+
return _jsx("div", { className: className, "data-rich-text": true, children: rendered });
|
|
90
|
+
}
|
|
91
|
+
return _jsx(_Fragment, { children: rendered });
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=RichText.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RichText.js","sourceRoot":"","sources":["../../src/components/RichText.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <Seo data={SeoConfig} /> — 对齐 Hydrogen React
|
|
3
|
+
*
|
|
4
|
+
* Hydrogen 在 Remix 里用 `meta` export 注入 head;我们在客户端用 `<Seo>`
|
|
5
|
+
* 做 SPA 路由切换时的 document.title / meta 动态更新。
|
|
6
|
+
*
|
|
7
|
+
* SSR 阶段 Seo 组件渲染为 null(不输出 DOM);服务端商家应该用 getSeoMeta
|
|
8
|
+
* 生成 head HTML 直接拼到 <head>。
|
|
9
|
+
*
|
|
10
|
+
* 用法(客户端 SPA navigate 时):
|
|
11
|
+
*
|
|
12
|
+
* <Seo data={{
|
|
13
|
+
* title: product.title,
|
|
14
|
+
* description: product.description,
|
|
15
|
+
* image: product.featuredImage?.url,
|
|
16
|
+
* url: location.href,
|
|
17
|
+
* type: 'product',
|
|
18
|
+
* }} />
|
|
19
|
+
*/
|
|
20
|
+
import { type SeoConfig } from '../seo/getSeoMeta';
|
|
21
|
+
export interface SeoProps {
|
|
22
|
+
data: SeoConfig;
|
|
23
|
+
}
|
|
24
|
+
export declare function Seo({ data }: SeoProps): null;
|
|
25
|
+
//# sourceMappingURL=Seo.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Seo.d.ts","sourceRoot":"","sources":["../../src/components/Seo.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,EAAc,KAAK,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAE/D,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,SAAS,CAAC;CACjB;AAED,wBAAgB,GAAG,CAAC,EAAE,IAAI,EAAE,EAAE,QAAQ,QAiCrC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <Seo data={SeoConfig} /> — 对齐 Hydrogen React
|
|
3
|
+
*
|
|
4
|
+
* Hydrogen 在 Remix 里用 `meta` export 注入 head;我们在客户端用 `<Seo>`
|
|
5
|
+
* 做 SPA 路由切换时的 document.title / meta 动态更新。
|
|
6
|
+
*
|
|
7
|
+
* SSR 阶段 Seo 组件渲染为 null(不输出 DOM);服务端商家应该用 getSeoMeta
|
|
8
|
+
* 生成 head HTML 直接拼到 <head>。
|
|
9
|
+
*
|
|
10
|
+
* 用法(客户端 SPA navigate 时):
|
|
11
|
+
*
|
|
12
|
+
* <Seo data={{
|
|
13
|
+
* title: product.title,
|
|
14
|
+
* description: product.description,
|
|
15
|
+
* image: product.featuredImage?.url,
|
|
16
|
+
* url: location.href,
|
|
17
|
+
* type: 'product',
|
|
18
|
+
* }} />
|
|
19
|
+
*/
|
|
20
|
+
import * as React from 'react';
|
|
21
|
+
import { getSeoMeta } from '../seo/getSeoMeta';
|
|
22
|
+
export function Seo({ data }) {
|
|
23
|
+
React.useEffect(() => {
|
|
24
|
+
if (typeof document === 'undefined')
|
|
25
|
+
return;
|
|
26
|
+
const seo = getSeoMeta(data);
|
|
27
|
+
if (seo.fullTitle)
|
|
28
|
+
document.title = seo.fullTitle;
|
|
29
|
+
// 同步关键 meta:description / og:* / twitter:*
|
|
30
|
+
for (const m of seo.meta) {
|
|
31
|
+
const attr = m.name ? 'name' : 'property';
|
|
32
|
+
const key = m.name || m.property;
|
|
33
|
+
let el = document.querySelector(`meta[${attr}="${key}"]`);
|
|
34
|
+
if (!el) {
|
|
35
|
+
el = document.createElement('meta');
|
|
36
|
+
el.setAttribute(attr, key);
|
|
37
|
+
document.head.appendChild(el);
|
|
38
|
+
}
|
|
39
|
+
el.setAttribute('content', m.content);
|
|
40
|
+
}
|
|
41
|
+
// canonical
|
|
42
|
+
if (data.url) {
|
|
43
|
+
let canon = document.querySelector('link[rel="canonical"]');
|
|
44
|
+
if (!canon) {
|
|
45
|
+
canon = document.createElement('link');
|
|
46
|
+
canon.setAttribute('rel', 'canonical');
|
|
47
|
+
document.head.appendChild(canon);
|
|
48
|
+
}
|
|
49
|
+
canon.setAttribute('href', data.url);
|
|
50
|
+
}
|
|
51
|
+
}, [data]);
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=Seo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Seo.js","sourceRoot":"","sources":["../../src/components/Seo.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,UAAU,EAAkB,MAAM,mBAAmB,CAAC;AAM/D,MAAM,UAAU,GAAG,CAAC,EAAE,IAAI,EAAY;IACpC,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,IAAI,OAAO,QAAQ,KAAK,WAAW;YAAE,OAAO;QAC5C,MAAM,GAAG,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;QAE7B,IAAI,GAAG,CAAC,SAAS;YAAE,QAAQ,CAAC,KAAK,GAAG,GAAG,CAAC,SAAS,CAAC;QAElD,2CAA2C;QAC3C,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC;YACzB,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC;YAC1C,MAAM,GAAG,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,QAAS,CAAC;YAClC,IAAI,EAAE,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,IAAI,KAAK,GAAG,IAAI,CAA2B,CAAC;YACpF,IAAI,CAAC,EAAE,EAAE,CAAC;gBACR,EAAE,GAAG,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;gBACpC,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;gBAC3B,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;YAChC,CAAC;YACD,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC;QACxC,CAAC;QAED,YAAY;QACZ,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;YACb,IAAI,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,uBAAuB,CAA2B,CAAC;YACtF,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;gBACvC,KAAK,CAAC,YAAY,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;gBACvC,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;YACnC,CAAC;YACD,KAAK,CAAC,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;QACvC,CAAC;IACH,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IAEX,OAAO,IAAI,CAAC;AACd,CAAC"}
|