@wocart/storefront 1.21.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/CHANGELOG.md +20 -0
- package/USAGE.md +24 -2
- package/dist/basket.d.ts +14 -0
- package/dist/basket.js +20 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -2
- package/dist/seo.d.ts +21 -0
- package/dist/seo.js +17 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,26 @@ follows [semantic versioning](https://semver.org/): breaking changes ship only
|
|
|
5
5
|
in a new major version, and an export is marked `@deprecated` for at least one
|
|
6
6
|
minor release before a major removes it.
|
|
7
7
|
|
|
8
|
+
## 1.22.0 — 2026-10-24
|
|
9
|
+
|
|
10
|
+
What moving Theme-00 onto the SDK showed every theme needs: an add reported
|
|
11
|
+
as what it put in, one canonical address per page, and product descriptions
|
|
12
|
+
read the same for legacy rows.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- `addedLines(before, after)`: what an add put in the basket — each line that
|
|
17
|
+
grew, by what it grew — for an `AddToCart` event that reports the add, not
|
|
18
|
+
the whole basket; empty when the basket had no room.
|
|
19
|
+
- `canonicalUrl(origin, { pathname, locale, routing }, search)`: a page's
|
|
20
|
+
canonical address — its path in its language, keeping only the listing page
|
|
21
|
+
(`?page=2`), never an order, a search, a coupon or an ad's click id.
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- `productMeta` reads an empty `descriptionPlain` as no plain text: a legacy
|
|
26
|
+
text row is then described by its own text, as before plain text existed.
|
|
27
|
+
|
|
8
28
|
## 1.21.0 — 2026-10-24
|
|
9
29
|
|
|
10
30
|
### Added
|
package/USAGE.md
CHANGED
|
@@ -716,12 +716,23 @@ script:
|
|
|
716
716
|
|
|
717
717
|
~~~astro
|
|
718
718
|
---
|
|
719
|
-
import { jsonLd, productMeta, productStructuredData } from "@wocart/storefront";
|
|
719
|
+
import { canonicalUrl, jsonLd, productMeta, productStructuredData } from "@wocart/storefront";
|
|
720
720
|
const meta = productMeta(product);
|
|
721
|
+
const canonical = canonicalUrl(Astro.url.origin, store, Astro.url.search);
|
|
721
722
|
---
|
|
722
723
|
<title>{meta.title} | {store.config.name}</title>
|
|
723
724
|
{meta.description && <meta name="description" content={meta.description} />}
|
|
724
|
-
<script type="application/ld+json" set:html={jsonLd(productStructuredData(product, { url:
|
|
725
|
+
<script type="application/ld+json" set:html={jsonLd(productStructuredData(product, { url: canonical, storeName: store.config.name }))} />
|
|
726
|
+
~~~
|
|
727
|
+
|
|
728
|
+
Every page's `<link rel="canonical">` is `canonicalUrl(origin, { pathname,
|
|
729
|
+
locale, routing }, search)`: the page's path in its language, keeping only the
|
|
730
|
+
listing page it shows (`?page=2`). An order, a search, a coupon or an ad's
|
|
731
|
+
click id (`fbclid`, `utm_*`) in the address is the same page to a search
|
|
732
|
+
engine; a canonical that kept them would split one page into many.
|
|
733
|
+
|
|
734
|
+
~~~ts
|
|
735
|
+
const canonical = canonicalUrl(Astro.url.origin, store, Astro.url.search);
|
|
725
736
|
~~~
|
|
726
737
|
|
|
727
738
|
### Product lists
|
|
@@ -1190,6 +1201,17 @@ basket.addSelections(
|
|
|
1190
1201
|
);
|
|
1191
1202
|
~~~
|
|
1192
1203
|
|
|
1204
|
+
What an add put in the basket is `addedLines(before, after)`: each line that
|
|
1205
|
+
grew, by what it grew. Report that as the `AddToCart` event — never the whole
|
|
1206
|
+
basket — and read an empty answer as a basket with no room left.
|
|
1207
|
+
|
|
1208
|
+
~~~ts
|
|
1209
|
+
const before = basket.getLines();
|
|
1210
|
+
const added = addedLines(before, basket.addSelections(line, selections));
|
|
1211
|
+
if (added.length === 0) showBasketFull();
|
|
1212
|
+
else tracker.track("AddToCart", buildMetaAddToCartPayload(added));
|
|
1213
|
+
~~~
|
|
1214
|
+
|
|
1193
1215
|
### Single-product order versus basket order
|
|
1194
1216
|
|
|
1195
1217
|
An order is either:
|
package/dist/basket.d.ts
CHANGED
|
@@ -95,3 +95,17 @@ export declare function toOrderItems(lines: readonly BasketLine[]): BasketLineIn
|
|
|
95
95
|
* Create a deep Basket Store.
|
|
96
96
|
*/
|
|
97
97
|
export declare function createBasketStore(options?: BasketStoreOptions): BasketStore;
|
|
98
|
+
/**
|
|
99
|
+
* What an add put in the basket: each line of `after` whose quantity grew
|
|
100
|
+
* since `before`, with the quantity it grew by — what an `AddToCart` event
|
|
101
|
+
* reports, never the whole basket. Empty when nothing went in, as when the
|
|
102
|
+
* basket was full.
|
|
103
|
+
*
|
|
104
|
+
* ```ts
|
|
105
|
+
* const before = basket.getLines();
|
|
106
|
+
* const added = addedLines(before, basket.addSelections(line, selections));
|
|
107
|
+
* if (added.length === 0) showBasketFull();
|
|
108
|
+
* else tracker.track("AddToCart", buildMetaAddToCartPayload(added));
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
export declare function addedLines(before: readonly BasketLine[], after: readonly BasketLine[]): BasketLine[];
|
package/dist/basket.js
CHANGED
|
@@ -277,3 +277,23 @@ export function createBasketStore(options = {}) {
|
|
|
277
277
|
},
|
|
278
278
|
};
|
|
279
279
|
}
|
|
280
|
+
/**
|
|
281
|
+
* What an add put in the basket: each line of `after` whose quantity grew
|
|
282
|
+
* since `before`, with the quantity it grew by — what an `AddToCart` event
|
|
283
|
+
* reports, never the whole basket. Empty when nothing went in, as when the
|
|
284
|
+
* basket was full.
|
|
285
|
+
*
|
|
286
|
+
* ```ts
|
|
287
|
+
* const before = basket.getLines();
|
|
288
|
+
* const added = addedLines(before, basket.addSelections(line, selections));
|
|
289
|
+
* if (added.length === 0) showBasketFull();
|
|
290
|
+
* else tracker.track("AddToCart", buildMetaAddToCartPayload(added));
|
|
291
|
+
* ```
|
|
292
|
+
*/
|
|
293
|
+
export function addedLines(before, after) {
|
|
294
|
+
const had = new Map(before.map((line) => [basketLineKey(line.productId, line.variantId), line.quantity]));
|
|
295
|
+
return after.flatMap((line) => {
|
|
296
|
+
const grew = line.quantity - (had.get(basketLineKey(line.productId, line.variantId)) ?? 0);
|
|
297
|
+
return grew > 0 ? [{ ...line, quantity: grew }] : [];
|
|
298
|
+
});
|
|
299
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -22,7 +22,7 @@ export { servedLanguage } from "./locales.js";
|
|
|
22
22
|
export { alternateLinks, localeRouting, localizeHref, resolveLocalePath } from "./locale-routing.js";
|
|
23
23
|
export type { LocalePath, LocaleRouting } from "./locale-routing.js";
|
|
24
24
|
export type { StorefrontPageMatch, StorefrontPageType, StorefrontPathParams, StorefrontRoute } from "./routes.js";
|
|
25
|
-
export { createBasketStore, createMemoryStorageAdapter, createLocalStorageAdapter, DEFAULT_BASKET_STORAGE_KEY, toOrderItems, } from "./basket.js";
|
|
25
|
+
export { addedLines, createBasketStore, createMemoryStorageAdapter, createLocalStorageAdapter, DEFAULT_BASKET_STORAGE_KEY, toOrderItems, } from "./basket.js";
|
|
26
26
|
export { MAX_BASKET_LINES, MAX_BASKET_LINE_QUANTITY, basketLineKey } from "./basket-line.js";
|
|
27
27
|
export type { BasketStore, BasketStorageAdapter, BasketStoreOptions, BasketListener, BasketSelectionLine, } from "./basket.js";
|
|
28
28
|
export { createOtpMachine, createOtpTransport, mapOtpResponse, otpTransportFailure, } from "./otp.js";
|
|
@@ -35,8 +35,8 @@ export { createQuoter } from "./quote.js";
|
|
|
35
35
|
export type { Quoter, QuoterOptions, QuoteResult } from "./quote.js";
|
|
36
36
|
export { isOrderNumber, ORDER_NUMBER_PATTERN } from "./order-number.js";
|
|
37
37
|
export { DEFAULT_COLLECTION_HANDLES, PRODUCT_SORTS, SHOPPER_ORDER_JOURNEY, SHOPPER_ORDER_STAGES } from "./catalog.js";
|
|
38
|
-
export { SEO_TEXT_LIMITS, jsonLd, productMeta, productStructuredData } from "./seo.js";
|
|
39
|
-
export type { PageMeta, ProductStructuredDataOptions } from "./seo.js";
|
|
38
|
+
export { SEO_TEXT_LIMITS, canonicalUrl, jsonLd, productMeta, productStructuredData } from "./seo.js";
|
|
39
|
+
export type { CanonicalPage, PageMeta, ProductStructuredDataOptions } from "./seo.js";
|
|
40
40
|
export { REVIEW_REFUSAL_CODES, REVIEW_TEXT_LIMITS } from "./reviews.js";
|
|
41
41
|
export type { ReviewRefusalCode } from "./reviews.js";
|
|
42
42
|
export type { CommuneTuple } from "./data/communes.js";
|
package/dist/index.js
CHANGED
|
@@ -16,7 +16,7 @@ export { MAX_RESOLVED_PREFILL_LENGTH, PREFILL_TOKENS, stripTrackingParams, trunc
|
|
|
16
16
|
export { STOREFRONT_PAGE_PATHS, STOREFRONT_ROUTES, matchStorefrontPath, showsWidgetOn, storefrontPath } from "./routes.js";
|
|
17
17
|
export { servedLanguage } from "./locales.js";
|
|
18
18
|
export { alternateLinks, localeRouting, localizeHref, resolveLocalePath } from "./locale-routing.js";
|
|
19
|
-
export { createBasketStore, createMemoryStorageAdapter, createLocalStorageAdapter, DEFAULT_BASKET_STORAGE_KEY, toOrderItems, } from "./basket.js";
|
|
19
|
+
export { addedLines, createBasketStore, createMemoryStorageAdapter, createLocalStorageAdapter, DEFAULT_BASKET_STORAGE_KEY, toOrderItems, } from "./basket.js";
|
|
20
20
|
export { MAX_BASKET_LINES, MAX_BASKET_LINE_QUANTITY, basketLineKey } from "./basket-line.js";
|
|
21
21
|
export { createOtpMachine, createOtpTransport, mapOtpResponse, otpTransportFailure, } from "./otp.js";
|
|
22
22
|
export { WILAYAS } from "./data/wilayas.js";
|
|
@@ -26,7 +26,7 @@ export { readCartQuote, readCouponOutcome, readShippingRates } from "./answer-re
|
|
|
26
26
|
export { createQuoter } from "./quote.js";
|
|
27
27
|
export { isOrderNumber, ORDER_NUMBER_PATTERN } from "./order-number.js";
|
|
28
28
|
export { DEFAULT_COLLECTION_HANDLES, PRODUCT_SORTS, SHOPPER_ORDER_JOURNEY, SHOPPER_ORDER_STAGES } from "./catalog.js";
|
|
29
|
-
export { SEO_TEXT_LIMITS, jsonLd, productMeta, productStructuredData } from "./seo.js";
|
|
29
|
+
export { SEO_TEXT_LIMITS, canonicalUrl, jsonLd, productMeta, productStructuredData } from "./seo.js";
|
|
30
30
|
export { REVIEW_REFUSAL_CODES, REVIEW_TEXT_LIMITS } from "./reviews.js";
|
|
31
31
|
export { normalizeAlgerianPhone, toLocalAlgerianMobile } from "./phone.js";
|
|
32
32
|
export * from "./types.js";
|
package/dist/seo.d.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a product page tells search engines and shared links: its title and
|
|
3
|
+
* description, and its structured data — decided once, so every theme ranks
|
|
4
|
+
* the same products the same way.
|
|
5
|
+
*/
|
|
6
|
+
import { type LocaleRouting } from "./locale-routing.js";
|
|
1
7
|
import type { Product, ProductList } from "./types.js";
|
|
2
8
|
/** The lengths a search-engine title and description may have: about what a search result shows. */
|
|
3
9
|
export declare const SEO_TEXT_LIMITS: {
|
|
@@ -44,4 +50,19 @@ export declare function productStructuredData(product: Product, options: Product
|
|
|
44
50
|
* ```
|
|
45
51
|
*/
|
|
46
52
|
export declare function jsonLd(value: unknown): string;
|
|
53
|
+
/** Where a page is, as its canonical address needs it: the route it renders, in its language. */
|
|
54
|
+
export interface CanonicalPage {
|
|
55
|
+
/** The page's path without its language prefix (`/products/robe`). */
|
|
56
|
+
pathname: string;
|
|
57
|
+
locale: string;
|
|
58
|
+
routing: LocaleRouting;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* A page's canonical address: its path in its language on `origin`, keeping
|
|
62
|
+
* only the listing page it shows (`?page=2`). An order (`?sort=`), a search,
|
|
63
|
+
* an ad's click id (`fbclid`, `utm_*`) or a coupon in the address is the same
|
|
64
|
+
* page to a search engine, and a canonical that kept them would split one
|
|
65
|
+
* page into many.
|
|
66
|
+
*/
|
|
67
|
+
export declare function canonicalUrl(origin: string, page: CanonicalPage, search?: string | URLSearchParams): string;
|
|
47
68
|
export {};
|
package/dist/seo.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* description, and its structured data — decided once, so every theme ranks
|
|
4
4
|
* the same products the same way.
|
|
5
5
|
*/
|
|
6
|
+
import { localizeHref } from "./locale-routing.js";
|
|
6
7
|
import { getProductView } from "./product-view.js";
|
|
7
8
|
import { initialVariant } from "./variant-picker.js";
|
|
8
9
|
/** The lengths a search-engine title and description may have: about what a search result shows. */
|
|
@@ -27,7 +28,8 @@ export function productMeta(product) {
|
|
|
27
28
|
const written = collapse(product.metaDescription ?? "");
|
|
28
29
|
if (written)
|
|
29
30
|
return { title, description: written };
|
|
30
|
-
|
|
31
|
+
// An empty plain text is no plain text: a text row is then read as it is.
|
|
32
|
+
const plain = product.descriptionPlain || (product.descriptionFormat === "text" ? product.description : null);
|
|
31
33
|
const text = collapse(plain ?? "");
|
|
32
34
|
return { title, description: text ? shorten(text, SEO_TEXT_LIMITS.descriptionMax) : null };
|
|
33
35
|
}
|
|
@@ -85,3 +87,17 @@ export function jsonLd(value) {
|
|
|
85
87
|
.replace(/\u2028/g, "\\u2028")
|
|
86
88
|
.replace(/\u2029/g, "\\u2029");
|
|
87
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* A page's canonical address: its path in its language on `origin`, keeping
|
|
92
|
+
* only the listing page it shows (`?page=2`). An order (`?sort=`), a search,
|
|
93
|
+
* an ad's click id (`fbclid`, `utm_*`) or a coupon in the address is the same
|
|
94
|
+
* page to a search engine, and a canonical that kept them would split one
|
|
95
|
+
* page into many.
|
|
96
|
+
*/
|
|
97
|
+
export function canonicalUrl(origin, page, search = "") {
|
|
98
|
+
const url = new URL(localizeHref(page.pathname, page.locale, page.routing), origin);
|
|
99
|
+
const asked = Number(new URLSearchParams(search).get("page"));
|
|
100
|
+
if (Number.isSafeInteger(asked) && asked > 1)
|
|
101
|
+
url.searchParams.set("page", String(asked));
|
|
102
|
+
return url.href;
|
|
103
|
+
}
|
package/package.json
CHANGED