platform-types 1.10.687 → 1.10.689
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/cjs/index.js +2 -0
- package/dist/cjs/storefrontAssets.js +26 -1
- package/dist/cjs/storefrontDesign.js +22 -10
- package/dist/cjs/storefrontSeo.js +64 -0
- package/dist/cjs/storefrontTheme.js +32 -8
- package/dist/cjs/storefrontWebLiteralKeys.js +1115 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/storefrontAssets.d.ts +23 -1
- package/dist/storefrontAssets.js +25 -0
- package/dist/storefrontDesign.d.ts +87 -11
- package/dist/storefrontDesign.js +20 -9
- package/dist/storefrontSeo.d.ts +77 -0
- package/dist/storefrontSeo.js +58 -0
- package/dist/storefrontTheme.d.ts +23 -7
- package/dist/storefrontTheme.js +30 -8
- package/dist/storefrontWebLiteralKeys.d.ts +23 -0
- package/dist/storefrontWebLiteralKeys.js +1111 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -79,6 +79,8 @@ export * from './storefrontTheme.ts';
|
|
|
79
79
|
export * from './storeNetwork.ts';
|
|
80
80
|
export * from './storefrontAssets.ts';
|
|
81
81
|
export * from './storefrontEvent.ts';
|
|
82
|
+
export * from './storefrontSeo.ts';
|
|
83
|
+
export * from './storefrontWebLiteralKeys.ts';
|
|
82
84
|
export * from './subscription.ts';
|
|
83
85
|
export * from './support.ts';
|
|
84
86
|
export * from './terminal.ts';
|
package/dist/index.js
CHANGED
|
@@ -87,6 +87,8 @@ export * from "./storefrontTheme.js";
|
|
|
87
87
|
export * from "./storeNetwork.js";
|
|
88
88
|
export * from "./storefrontAssets.js";
|
|
89
89
|
export * from "./storefrontEvent.js";
|
|
90
|
+
export * from "./storefrontSeo.js";
|
|
91
|
+
export * from "./storefrontWebLiteralKeys.js";
|
|
90
92
|
export * from "./subscription.js";
|
|
91
93
|
export * from "./support.js";
|
|
92
94
|
export * from "./terminal.js";
|
|
@@ -1,4 +1,26 @@
|
|
|
1
|
-
export declare const STOREFRONT_ASSET_VARIANTS: readonly ["original", "200x200", "400x400", "800x800", "1200x1200", "1600x1600"];
|
|
1
|
+
export declare const STOREFRONT_ASSET_VARIANTS: readonly ["original", "200x200", "400x400", "800x800", "1200x1200", "1600x1600", "1200x630", "192x192", "512x512"];
|
|
2
|
+
/**
|
|
3
|
+
* How each variant is rendered from the stored original. `original` is the
|
|
4
|
+
* stored image unchanged in size. `inside` is a thumbnail: it fits inside the
|
|
5
|
+
* box keeping its aspect and never enlarges, so a small original comes back at
|
|
6
|
+
* its own size. `1200x630` is `cover`: an EXACT-size crop for share images
|
|
7
|
+
* (`og:image`), enlarged when the original is smaller. `192x192` and `512x512`
|
|
8
|
+
* are `contain`: EXACT-size icons (favicon, PWA manifest), not thumbnails —
|
|
9
|
+
* the whole original is scaled to fit and the rest of the square is padded
|
|
10
|
+
* transparent, so nothing is cropped; they are always PNG, or WebP when the
|
|
11
|
+
* client accepts it, because a JPEG cannot carry the transparent padding.
|
|
12
|
+
*/
|
|
13
|
+
export declare const STOREFRONT_ASSET_VARIANT_FIT: {
|
|
14
|
+
readonly original: "original";
|
|
15
|
+
readonly '200x200': "inside";
|
|
16
|
+
readonly '400x400': "inside";
|
|
17
|
+
readonly '800x800': "inside";
|
|
18
|
+
readonly '1200x1200': "inside";
|
|
19
|
+
readonly '1600x1600': "inside";
|
|
20
|
+
readonly '1200x630': "cover";
|
|
21
|
+
readonly '192x192': "contain";
|
|
22
|
+
readonly '512x512': "contain";
|
|
23
|
+
};
|
|
2
24
|
/** The active-asset cap, the same value as `STOREFRONT_DESIGN_LIMITS.activeImages` by construction. */
|
|
3
25
|
export declare const MAX_STOREFRONT_ASSETS: 200;
|
|
4
26
|
declare global {
|
package/dist/storefrontAssets.js
CHANGED
|
@@ -6,6 +6,31 @@ export const STOREFRONT_ASSET_VARIANTS = [
|
|
|
6
6
|
'800x800',
|
|
7
7
|
'1200x1200',
|
|
8
8
|
'1600x1600',
|
|
9
|
+
'1200x630',
|
|
10
|
+
'192x192',
|
|
11
|
+
'512x512',
|
|
9
12
|
];
|
|
13
|
+
/**
|
|
14
|
+
* How each variant is rendered from the stored original. `original` is the
|
|
15
|
+
* stored image unchanged in size. `inside` is a thumbnail: it fits inside the
|
|
16
|
+
* box keeping its aspect and never enlarges, so a small original comes back at
|
|
17
|
+
* its own size. `1200x630` is `cover`: an EXACT-size crop for share images
|
|
18
|
+
* (`og:image`), enlarged when the original is smaller. `192x192` and `512x512`
|
|
19
|
+
* are `contain`: EXACT-size icons (favicon, PWA manifest), not thumbnails —
|
|
20
|
+
* the whole original is scaled to fit and the rest of the square is padded
|
|
21
|
+
* transparent, so nothing is cropped; they are always PNG, or WebP when the
|
|
22
|
+
* client accepts it, because a JPEG cannot carry the transparent padding.
|
|
23
|
+
*/
|
|
24
|
+
export const STOREFRONT_ASSET_VARIANT_FIT = {
|
|
25
|
+
original: 'original',
|
|
26
|
+
'200x200': 'inside',
|
|
27
|
+
'400x400': 'inside',
|
|
28
|
+
'800x800': 'inside',
|
|
29
|
+
'1200x1200': 'inside',
|
|
30
|
+
'1600x1600': 'inside',
|
|
31
|
+
'1200x630': 'cover',
|
|
32
|
+
'192x192': 'contain',
|
|
33
|
+
'512x512': 'contain',
|
|
34
|
+
};
|
|
10
35
|
/** The active-asset cap, the same value as `STOREFRONT_DESIGN_LIMITS.activeImages` by construction. */
|
|
11
36
|
export const MAX_STOREFRONT_ASSETS = STOREFRONT_DESIGN_LIMITS.activeImages;
|
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import type { StorefrontThemeWarning } from './storefrontTheme.ts';
|
|
2
|
+
/** The social kinds a contact may link, each to its own hosts; a contact links at most one per kind. */
|
|
3
|
+
export declare const STOREFRONT_SOCIAL_HOSTS: {
|
|
4
|
+
readonly instagram: readonly ["instagram.com", "www.instagram.com"];
|
|
5
|
+
readonly facebook: readonly ["facebook.com", "www.facebook.com"];
|
|
6
|
+
readonly youtube: readonly ["youtube.com", "www.youtube.com", "youtu.be", "www.youtu.be"];
|
|
7
|
+
readonly tiktok: readonly ["tiktok.com", "www.tiktok.com"];
|
|
8
|
+
readonly linkedin: readonly ["linkedin.com", "www.linkedin.com"];
|
|
9
|
+
readonly x: readonly ["x.com", "www.x.com"];
|
|
10
|
+
};
|
|
2
11
|
export declare const STOREFRONT_DESIGN_LIMITS: {
|
|
3
12
|
readonly customPages: 20;
|
|
13
|
+
readonly pageAliases: 20;
|
|
14
|
+
readonly pageAliasMetadataBytes: number;
|
|
4
15
|
readonly sectionsPerPage: 10;
|
|
5
16
|
readonly productCountMin: 4;
|
|
6
17
|
readonly productCountMax: 24;
|
|
@@ -35,9 +46,17 @@ export declare const STOREFRONT_DESIGN_LIMITS: {
|
|
|
35
46
|
readonly contactAddress: 500;
|
|
36
47
|
readonly contactNote: 200;
|
|
37
48
|
readonly contactHours: 14;
|
|
38
|
-
|
|
49
|
+
/** One link per social kind, so the cap is the number of kinds. */
|
|
50
|
+
readonly contactSocials: number;
|
|
39
51
|
readonly contactEmail: 254;
|
|
40
52
|
};
|
|
53
|
+
/**
|
|
54
|
+
* A custom page's slug: lowercase ASCII letters, digits and inner hyphens, 1 to
|
|
55
|
+
* `STOREFRONT_DESIGN_LIMITS.slug` characters, starting and ending alphanumeric. The one
|
|
56
|
+
* definition: the api's validators, its page renderer and the edge viewer request read it.
|
|
57
|
+
*/
|
|
58
|
+
export declare const STOREFRONT_PAGE_SLUG_RE: RegExp;
|
|
59
|
+
export declare const isStorefrontPageSlug: (value: unknown) => value is string;
|
|
41
60
|
export declare const STOREFRONT_SECTION_TYPES: readonly ["banner", "imageText", "products", "categories", "brands", "text", "faq", "benefits", "contact", "paymentDelivery"];
|
|
42
61
|
export declare const STOREFRONT_BUILTIN_DESTINATIONS: readonly ["home", "catalogue", "offers", "new", "search", "cart", "account", "privacy"];
|
|
43
62
|
/**
|
|
@@ -53,14 +72,6 @@ export declare const STOREFRONT_PROTECTED_BUILTIN_DESTINATIONS: readonly ["searc
|
|
|
53
72
|
export declare const STOREFRONT_CONTACT_FIELDS: readonly ["email", "phone", "whatsapp", "address", "hours", "socials"];
|
|
54
73
|
export declare const STOREFRONT_RESERVED_SLUGS: readonly ["home", "index", "api", "auth", "login", "logout", "account", "cart", "checkout", "orders", "search", "products", "product", "producto", "coleccion", "categories", "brands", "sale", "new", "privacy", "terms", "sitemap", "robots", "manifest", "assets", "preview", "admin", "storefront", "pagina", "basket", "profile", "favorites", "invoices", "payments", "payment-methods", "user", "mantenimiento"];
|
|
55
74
|
export declare const STOREFRONT_BENEFIT_ICONS: readonly ["truck", "shield", "credit-card", "store", "heart", "check"];
|
|
56
|
-
export declare const STOREFRONT_SOCIAL_HOSTS: {
|
|
57
|
-
readonly instagram: readonly ["instagram.com", "www.instagram.com"];
|
|
58
|
-
readonly facebook: readonly ["facebook.com", "www.facebook.com"];
|
|
59
|
-
readonly youtube: readonly ["youtube.com", "www.youtube.com", "youtu.be", "www.youtu.be"];
|
|
60
|
-
readonly tiktok: readonly ["tiktok.com", "www.tiktok.com"];
|
|
61
|
-
readonly linkedin: readonly ["linkedin.com", "www.linkedin.com"];
|
|
62
|
-
readonly x: readonly ["x.com", "www.x.com"];
|
|
63
|
-
};
|
|
64
75
|
export declare function createStorefrontDesignV1(): StorefrontDesignV1;
|
|
65
76
|
declare global {
|
|
66
77
|
type StorefrontDesignSectionType = (typeof STOREFRONT_SECTION_TYPES)[number];
|
|
@@ -180,7 +191,12 @@ declare global {
|
|
|
180
191
|
content: StorefrontDesignSectionContent[K];
|
|
181
192
|
};
|
|
182
193
|
}[StorefrontDesignSectionType];
|
|
194
|
+
interface StorefrontDesignPublicationMetadata {
|
|
195
|
+
pageAliases: Record<string, string[]>;
|
|
196
|
+
pageSlugs: Record<string, string>;
|
|
197
|
+
}
|
|
183
198
|
interface StorefrontDesignSeo {
|
|
199
|
+
noindex?: boolean;
|
|
184
200
|
title?: string;
|
|
185
201
|
description?: string;
|
|
186
202
|
image?: StorefrontDesignImagePlacement;
|
|
@@ -213,6 +229,7 @@ declare global {
|
|
|
213
229
|
links: StorefrontDesignMenuLink[];
|
|
214
230
|
}
|
|
215
231
|
interface StorefrontContactHours {
|
|
232
|
+
/** `days` are ISO 8601 weekdays, 1 = Monday … 7 = Sunday (unique per row); `opens` is before `closes`. */
|
|
216
233
|
rows: {
|
|
217
234
|
days: number[];
|
|
218
235
|
opens: string;
|
|
@@ -264,7 +281,7 @@ declare global {
|
|
|
264
281
|
};
|
|
265
282
|
/**
|
|
266
283
|
* Image placements gain `url`/`mobileUrl`: the variant-LESS base
|
|
267
|
-
* `/
|
|
284
|
+
* `/media/{stage}/{storeId}/{publicationId}/{assetId}`, a path on the images host. Append
|
|
268
285
|
* `/${StorefrontAssetVariant}` (one of `STOREFRONT_ASSET_VARIANTS`) to request it; the base
|
|
269
286
|
* alone is not servable. Never route it through `/fit-in/`: the media path is refused there.
|
|
270
287
|
* Private preview envelopes instead supply `/preview/read?operation=asset&assetId=...`; the
|
|
@@ -310,7 +327,8 @@ declare global {
|
|
|
310
327
|
seo: StorefrontDesignSeo;
|
|
311
328
|
literalOverrides: Record<string, string>;
|
|
312
329
|
}
|
|
313
|
-
|
|
330
|
+
/** `TOO_LARGE` at path `''` means the whole stored draft would exceed its row budget, not one field. */
|
|
331
|
+
type StorefrontDesignFieldErrorCode = 'INVALID' | 'REQUIRED' | 'UNKNOWN_KEY' | 'DUPLICATE' | 'RESERVED_SLUG' | 'ALIAS_CONFLICT' | 'ALIAS_LIMIT' | 'DANGLING_REFERENCE' | 'MAPPING_CONFLICT' | 'TOO_LARGE';
|
|
314
332
|
interface StorefrontDesignFieldError {
|
|
315
333
|
path: string;
|
|
316
334
|
code: StorefrontDesignFieldErrorCode;
|
|
@@ -359,6 +377,10 @@ declare global {
|
|
|
359
377
|
interface StorefrontDesignPreviewInput {
|
|
360
378
|
draftRevision: string;
|
|
361
379
|
}
|
|
380
|
+
/**
|
|
381
|
+
* GET draft answers a baseline built without copying the legacy logo, while POST init copies it,
|
|
382
|
+
* so `document.branding.logo` can differ between them: clients rebase onto init's document.
|
|
383
|
+
*/
|
|
362
384
|
interface StorefrontDesignBaselineResult {
|
|
363
385
|
draftRevision: null;
|
|
364
386
|
publishedRevision: null;
|
|
@@ -387,6 +409,17 @@ declare global {
|
|
|
387
409
|
items: StorefrontDesignHistoryEntry[];
|
|
388
410
|
cursor?: string;
|
|
389
411
|
}
|
|
412
|
+
/** A page of the live publication: the slug it answers at and the former slugs that 301 to it. */
|
|
413
|
+
interface StorefrontDesignPublishedPage {
|
|
414
|
+
slug: string;
|
|
415
|
+
aliases: string[];
|
|
416
|
+
}
|
|
417
|
+
/** Where a resolved setting's value came from: the registry default, a platform layer, or the document. */
|
|
418
|
+
type StorefrontDesignSettingProvenance = 'default' | 'platform' | 'design';
|
|
419
|
+
/**
|
|
420
|
+
* GET draft and every mutation that answers the draft (init, save, discard, restore) carry the
|
|
421
|
+
* same result; an idempotent replay rebuilds the four derived fields from current reads.
|
|
422
|
+
*/
|
|
390
423
|
interface StorefrontDesignDraftResult {
|
|
391
424
|
draftRevision: string;
|
|
392
425
|
publishedRevision: string | null;
|
|
@@ -394,6 +427,28 @@ declare global {
|
|
|
394
427
|
dirty: boolean;
|
|
395
428
|
mappingConflicts: StorefrontDesignMappingConflict[];
|
|
396
429
|
legacySourceRevision: string;
|
|
430
|
+
/**
|
|
431
|
+
* The live publication's page addresses, keyed by page id: every page that has ever been
|
|
432
|
+
* published and still owns its slug, a since-removed or disabled page's included. Renaming
|
|
433
|
+
* an ENABLED page listed here makes its current slug a 301 alias on the next publish; a
|
|
434
|
+
* page absent here has no live address, so renaming it creates no alias. Absent when the
|
|
435
|
+
* shop has never published.
|
|
436
|
+
*/
|
|
437
|
+
publishedPages?: Record<string, StorefrontDesignPublishedPage>;
|
|
438
|
+
/**
|
|
439
|
+
* What publishing this saved document would refuse today, as field errors: missing required
|
|
440
|
+
* content, a link to a disabled or missing page (`DANGLING_REFERENCE`), a slug a published
|
|
441
|
+
* page still owns (`ALIAS_CONFLICT`), a page out of aliases (`ALIAS_LIMIT`), and one
|
|
442
|
+
* `MAPPING_CONFLICT` per unresolved `mappingConflicts` entry, at that entry's path (the
|
|
443
|
+
* publish refusal itself names the single path `mappingConflicts`). Empty when publishable.
|
|
444
|
+
* Never blocks a save, and never covers asset readiness, which publish probes on its own
|
|
445
|
+
* (`STOREFRONT_ASSET_NOT_READY`).
|
|
446
|
+
*/
|
|
447
|
+
publishIssues?: StorefrontDesignFieldError[];
|
|
448
|
+
/** The document's settings over the live platform layer: what the published shop would render. */
|
|
449
|
+
effectiveSettings?: StorefrontDesignSettings;
|
|
450
|
+
/** Per setting, which layer `effectiveSettings` took it from. */
|
|
451
|
+
settingsProvenance?: Record<keyof StorefrontDesignSettings, StorefrontDesignSettingProvenance>;
|
|
397
452
|
}
|
|
398
453
|
interface StorefrontDesignDeliveryTarget {
|
|
399
454
|
/**
|
|
@@ -503,6 +558,27 @@ declare global {
|
|
|
503
558
|
assetId: string;
|
|
504
559
|
variant?: StorefrontAssetVariant;
|
|
505
560
|
};
|
|
561
|
+
/**
|
|
562
|
+
* The codes `GET /preview/read` answers itself, on the common `{ message, error }` body:
|
|
563
|
+
* 400 `STOREFRONT_PREVIEW_INVALID` (a query the read schema refuses, a repeated parameter or a
|
|
564
|
+
* body), 401 `STOREFRONT_PREVIEW_INVALID` (no or a malformed bearer grant), 401 `…_EXPIRED`,
|
|
565
|
+
* 403 `…_REVOKED` / `…_FORBIDDEN` / `PLAN_UPGRADE_REQUIRED` (bare, without the editor
|
|
566
|
+
* routes' plan fields), 404 `ASSET_NOT_FOUND` (an asset the pinned draft does not reference or
|
|
567
|
+
* that is not ready), 405 `METHOD_NOT_ALLOWED` (anything but GET and OPTIONS), 413
|
|
568
|
+
* `STOREFRONT_PREVIEW_IMAGE_TOO_LARGE` (asset bytes over 4 MiB) and 503 `…_UNAVAILABLE`.
|
|
569
|
+
*/
|
|
570
|
+
type StorefrontPreviewReadErrorCode = 'STOREFRONT_PREVIEW_INVALID' | 'STOREFRONT_PREVIEW_EXPIRED' | 'STOREFRONT_PREVIEW_REVOKED' | 'STOREFRONT_PREVIEW_FORBIDDEN' | 'STOREFRONT_PREVIEW_UNAVAILABLE' | 'STOREFRONT_PREVIEW_IMAGE_TOO_LARGE' | 'PLAN_UPGRADE_REQUIRED' | 'ASSET_NOT_FOUND' | 'METHOD_NOT_ALLOWED';
|
|
571
|
+
/**
|
|
572
|
+
* The codes a catalogue operation answers through its public web route's own core, as that
|
|
573
|
+
* route does: 404 `PRODUCT_NOT_FOUND` / `PRODUCT_GROUP_NOT_FOUND` / `COLLECTION_NOT_FOUND`,
|
|
574
|
+
* 400 `INVALID_CURSOR` / `INVALID_DISCOVERY_QUERY`, 409 `CURSOR_STALE` (a collection that
|
|
575
|
+
* changed between the reads bracketing a page) and 503 `PRODUCT_LOOKUP_INCOMPLETE`.
|
|
576
|
+
*/
|
|
577
|
+
type StorefrontPreviewCatalogueErrorCode = 'PRODUCT_NOT_FOUND' | 'PRODUCT_GROUP_NOT_FOUND' | 'COLLECTION_NOT_FOUND' | 'INVALID_CURSOR' | 'INVALID_DISCOVERY_QUERY' | 'CURSOR_STALE' | 'PRODUCT_LOOKUP_INCOMPLETE';
|
|
578
|
+
interface StorefrontPreviewReadFailure {
|
|
579
|
+
message: string;
|
|
580
|
+
error: StorefrontPreviewReadErrorCode | StorefrontPreviewCatalogueErrorCode;
|
|
581
|
+
}
|
|
506
582
|
interface StorefrontPreviewDesignResult {
|
|
507
583
|
mode: 'preview';
|
|
508
584
|
draftRevision: string;
|
package/dist/storefrontDesign.js
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
|
+
/** The social kinds a contact may link, each to its own hosts; a contact links at most one per kind. */
|
|
2
|
+
export const STOREFRONT_SOCIAL_HOSTS = {
|
|
3
|
+
instagram: ['instagram.com', 'www.instagram.com'],
|
|
4
|
+
facebook: ['facebook.com', 'www.facebook.com'],
|
|
5
|
+
youtube: ['youtube.com', 'www.youtube.com', 'youtu.be', 'www.youtu.be'],
|
|
6
|
+
tiktok: ['tiktok.com', 'www.tiktok.com'],
|
|
7
|
+
linkedin: ['linkedin.com', 'www.linkedin.com'],
|
|
8
|
+
x: ['x.com', 'www.x.com'],
|
|
9
|
+
};
|
|
1
10
|
export const STOREFRONT_DESIGN_LIMITS = {
|
|
2
11
|
customPages: 20,
|
|
12
|
+
pageAliases: 20,
|
|
13
|
+
pageAliasMetadataBytes: 64 * 1024,
|
|
3
14
|
sectionsPerPage: 10,
|
|
4
15
|
productCountMin: 4,
|
|
5
16
|
productCountMax: 24,
|
|
@@ -34,9 +45,17 @@ export const STOREFRONT_DESIGN_LIMITS = {
|
|
|
34
45
|
contactAddress: 500,
|
|
35
46
|
contactNote: 200,
|
|
36
47
|
contactHours: 14,
|
|
37
|
-
|
|
48
|
+
/** One link per social kind, so the cap is the number of kinds. */
|
|
49
|
+
contactSocials: Object.keys(STOREFRONT_SOCIAL_HOSTS).length,
|
|
38
50
|
contactEmail: 254,
|
|
39
51
|
};
|
|
52
|
+
/**
|
|
53
|
+
* A custom page's slug: lowercase ASCII letters, digits and inner hyphens, 1 to
|
|
54
|
+
* `STOREFRONT_DESIGN_LIMITS.slug` characters, starting and ending alphanumeric. The one
|
|
55
|
+
* definition: the api's validators, its page renderer and the edge viewer request read it.
|
|
56
|
+
*/
|
|
57
|
+
export const STOREFRONT_PAGE_SLUG_RE = /^[a-z0-9](?:[a-z0-9-]{0,78}[a-z0-9])?$/;
|
|
58
|
+
export const isStorefrontPageSlug = (value) => typeof value === 'string' && STOREFRONT_PAGE_SLUG_RE.test(value);
|
|
40
59
|
export const STOREFRONT_SECTION_TYPES = [
|
|
41
60
|
'banner',
|
|
42
61
|
'imageText',
|
|
@@ -115,14 +134,6 @@ export const STOREFRONT_RESERVED_SLUGS = [
|
|
|
115
134
|
'mantenimiento',
|
|
116
135
|
];
|
|
117
136
|
export const STOREFRONT_BENEFIT_ICONS = ['truck', 'shield', 'credit-card', 'store', 'heart', 'check'];
|
|
118
|
-
export const STOREFRONT_SOCIAL_HOSTS = {
|
|
119
|
-
instagram: ['instagram.com', 'www.instagram.com'],
|
|
120
|
-
facebook: ['facebook.com', 'www.facebook.com'],
|
|
121
|
-
youtube: ['youtube.com', 'www.youtube.com', 'youtu.be', 'www.youtu.be'],
|
|
122
|
-
tiktok: ['tiktok.com', 'www.tiktok.com'],
|
|
123
|
-
linkedin: ['linkedin.com', 'www.linkedin.com'],
|
|
124
|
-
x: ['x.com', 'www.x.com'],
|
|
125
|
-
};
|
|
126
137
|
export function createStorefrontDesignV1() {
|
|
127
138
|
return {
|
|
128
139
|
schemaVersion: 1,
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Information-page paths and the storefront's SEO inheritance chain — one
|
|
3
|
+
* definition for the server that renders a shared link's head and the editor
|
|
4
|
+
* that previews it.
|
|
5
|
+
*/
|
|
6
|
+
/** Path prefix of a published information page: `/pagina/<slug>`. */
|
|
7
|
+
export declare const STOREFRONT_PAGE_PATH_PREFIX = "/pagina/";
|
|
8
|
+
/** The path an information page with `slug` is served at. The slug is neither validated nor encoded here. */
|
|
9
|
+
export declare const storefrontPagePath: (slug: string) => string;
|
|
10
|
+
/** The description a shop is shared with when neither the page nor the shop sets one. */
|
|
11
|
+
export declare const storefrontDefaultSeoDescription: (storeName: string) => string;
|
|
12
|
+
/**
|
|
13
|
+
* Where a resolved SEO field came from: the page itself, the shop-wide `seo`
|
|
14
|
+
* block, the shop logo (image only), or the platform default.
|
|
15
|
+
*/
|
|
16
|
+
export type StorefrontSeoSource = 'page' | 'shop' | 'logo' | 'default';
|
|
17
|
+
/** The SEO fields a page and the shop each carry; `Placement` is the image shape of the document passed in. */
|
|
18
|
+
export interface StorefrontSeoFields<Placement = StorefrontDesignImagePlacement> {
|
|
19
|
+
title?: string;
|
|
20
|
+
description?: string;
|
|
21
|
+
image?: Placement;
|
|
22
|
+
noindex?: boolean;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* What {@link resolveStorefrontSeo} reads from a design document. A draft
|
|
26
|
+
* (`StorefrontDesignV1`), the published projection and `PublicStorefrontDesign`
|
|
27
|
+
* all satisfy it; with the last, a resolved image keeps its `url`.
|
|
28
|
+
*/
|
|
29
|
+
export interface StorefrontSeoDocument<Placement = StorefrontDesignImagePlacement> {
|
|
30
|
+
seo: StorefrontSeoFields<Placement>;
|
|
31
|
+
branding: {
|
|
32
|
+
logo?: Placement;
|
|
33
|
+
};
|
|
34
|
+
pages: readonly {
|
|
35
|
+
id: string;
|
|
36
|
+
title: string;
|
|
37
|
+
seo?: StorefrontSeoFields<Placement>;
|
|
38
|
+
}[];
|
|
39
|
+
}
|
|
40
|
+
/** The SEO a page is shared with, each field with where it came from. */
|
|
41
|
+
export interface ResolvedStorefrontSeo<Placement = StorefrontDesignImagePlacement> {
|
|
42
|
+
title: string;
|
|
43
|
+
description: string;
|
|
44
|
+
/**
|
|
45
|
+
* The placement shared as `og:image` (cropped to 1200x630), or `null` for
|
|
46
|
+
* the platform's own share image (`source.image === 'default'`).
|
|
47
|
+
*/
|
|
48
|
+
image: Placement | null;
|
|
49
|
+
noindex: boolean;
|
|
50
|
+
source: {
|
|
51
|
+
title: Exclude<StorefrontSeoSource, 'logo'>;
|
|
52
|
+
description: Exclude<StorefrontSeoSource, 'logo'>;
|
|
53
|
+
image: StorefrontSeoSource;
|
|
54
|
+
noindex: Exclude<StorefrontSeoSource, 'logo'>;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The SEO the storefront shares a page with: `'home'`, or an information page
|
|
59
|
+
* by `id`.
|
|
60
|
+
*
|
|
61
|
+
* - title: the page's `seo.title`, else its `title`, else the shop's
|
|
62
|
+
* `seo.title`, else `storeName`. Home skips the page steps — its own `title`
|
|
63
|
+
* is never used.
|
|
64
|
+
* - description: the page's `seo.description`, else the shop's, else
|
|
65
|
+
* {@link storefrontDefaultSeoDescription}.
|
|
66
|
+
* - image: the page's `seo.image`, else the shop's, else the logo, else `null`
|
|
67
|
+
* (the platform share image).
|
|
68
|
+
* - noindex: the page's `seo.noindex` when set — an explicit `false` beats a
|
|
69
|
+
* shop-wide `true` — else the shop's, else `false`.
|
|
70
|
+
*
|
|
71
|
+
* An empty title or description counts as unset and falls through. Returns
|
|
72
|
+
* `undefined` for a page id the document does not carry; `'home'` always
|
|
73
|
+
* resolves. A shop with no published design is out of scope: its head uses
|
|
74
|
+
* the store's own name, photo and the default description.
|
|
75
|
+
*/
|
|
76
|
+
export declare function resolveStorefrontSeo<Placement>(document: StorefrontSeoDocument<Placement>, target: 'home', storeName: string): ResolvedStorefrontSeo<Placement>;
|
|
77
|
+
export declare function resolveStorefrontSeo<Placement>(document: StorefrontSeoDocument<Placement>, target: string, storeName: string): ResolvedStorefrontSeo<Placement> | undefined;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Information-page paths and the storefront's SEO inheritance chain — one
|
|
3
|
+
* definition for the server that renders a shared link's head and the editor
|
|
4
|
+
* that previews it.
|
|
5
|
+
*/
|
|
6
|
+
/** Path prefix of a published information page: `/pagina/<slug>`. */
|
|
7
|
+
export const STOREFRONT_PAGE_PATH_PREFIX = '/pagina/';
|
|
8
|
+
/** The path an information page with `slug` is served at. The slug is neither validated nor encoded here. */
|
|
9
|
+
export const storefrontPagePath = (slug) => `${STOREFRONT_PAGE_PATH_PREFIX}${slug}`;
|
|
10
|
+
/** The description a shop is shared with when neither the page nor the shop sets one. */
|
|
11
|
+
export const storefrontDefaultSeoDescription = (storeName) => `Comprá online en ${storeName}: catálogo, precios y stock actualizados.`;
|
|
12
|
+
const shopImage = (document) => {
|
|
13
|
+
if (document.seo.image)
|
|
14
|
+
return { image: document.seo.image, source: 'shop' };
|
|
15
|
+
if (document.branding.logo)
|
|
16
|
+
return { image: document.branding.logo, source: 'logo' };
|
|
17
|
+
return { image: null, source: 'default' };
|
|
18
|
+
};
|
|
19
|
+
/** Home's SEO — and the default every information page inherits field by field. */
|
|
20
|
+
const shopSeo = (document, storeName) => {
|
|
21
|
+
const { seo } = document;
|
|
22
|
+
const image = shopImage(document);
|
|
23
|
+
return {
|
|
24
|
+
title: seo.title || storeName,
|
|
25
|
+
description: seo.description || storefrontDefaultSeoDescription(storeName),
|
|
26
|
+
image: image.image,
|
|
27
|
+
noindex: seo.noindex === true,
|
|
28
|
+
source: {
|
|
29
|
+
title: seo.title ? 'shop' : 'default',
|
|
30
|
+
description: seo.description ? 'shop' : 'default',
|
|
31
|
+
image: image.source,
|
|
32
|
+
noindex: typeof seo.noindex === 'boolean' ? 'shop' : 'default',
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
export function resolveStorefrontSeo(document, target, storeName) {
|
|
37
|
+
const shop = shopSeo(document, storeName);
|
|
38
|
+
if (target === 'home')
|
|
39
|
+
return shop;
|
|
40
|
+
const page = document.pages.find(({ id }) => id === target);
|
|
41
|
+
if (!page)
|
|
42
|
+
return undefined;
|
|
43
|
+
const own = page.seo;
|
|
44
|
+
const pageTitle = own?.title || page.title;
|
|
45
|
+
const pageNoindex = own?.noindex;
|
|
46
|
+
return {
|
|
47
|
+
title: pageTitle || shop.title,
|
|
48
|
+
description: own?.description || shop.description,
|
|
49
|
+
image: own?.image || shop.image,
|
|
50
|
+
noindex: typeof pageNoindex === 'boolean' ? pageNoindex : shop.noindex,
|
|
51
|
+
source: {
|
|
52
|
+
title: pageTitle ? 'page' : shop.source.title,
|
|
53
|
+
description: own?.description ? 'page' : shop.source.description,
|
|
54
|
+
image: own?.image ? 'page' : shop.source.image,
|
|
55
|
+
noindex: typeof pageNoindex === 'boolean' ? 'page' : shop.source.noindex,
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type StorefrontDesignSettings } from './storefrontDesignRegistry.ts';
|
|
1
|
+
import { type StorefrontDesignSettings, type StorefrontDesignSettingsLayer } from './storefrontDesignRegistry.ts';
|
|
2
2
|
type StorefrontThemeMode = 'light' | 'dark';
|
|
3
3
|
type StorefrontThemeSurfaceId = 'page' | 'header' | 'footer';
|
|
4
4
|
export interface StorefrontThemeSurface {
|
|
@@ -51,11 +51,27 @@ export interface StorefrontResolvedTheme {
|
|
|
51
51
|
}
|
|
52
52
|
export declare const storefrontContrastRatio: (foreground: string, background: string) => number;
|
|
53
53
|
/**
|
|
54
|
-
* The
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* overrides ONLY — no GLOBAL or WEB layer takes part — so a consumer uses this
|
|
58
|
-
* as shipped rather than merging layers of its own first.
|
|
54
|
+
* The GLOBAL design-settings layer of the platform globals row as `getPlatformGlobalsRow`
|
|
55
|
+
* returns it (`{ [key]: { value, ... } }`): each entry's `value`, kept only when it is a
|
|
56
|
+
* valid design setting, so a non-design global or a malformed value never reaches a theme.
|
|
59
57
|
*/
|
|
60
|
-
export declare const
|
|
58
|
+
export declare const storefrontGlobalSettingsLayer: (globalsRow?: Record<string, unknown>) => StorefrontDesignSettingsLayer;
|
|
59
|
+
/**
|
|
60
|
+
* The settings a storefront is served with: registry defaults, then the platform GLOBAL
|
|
61
|
+
* layer, then the design's own overrides. The one resolver behind the public envelope's
|
|
62
|
+
* `effectiveSettings`, the draft's `effectiveSettings` and the publish contrast report —
|
|
63
|
+
* a caller passes the globals row it read, never a layer stack of its own. The edge
|
|
64
|
+
* renderer's `theme-color` is design-only: its role reads no GLOBALS row.
|
|
65
|
+
*/
|
|
66
|
+
export declare const resolveStorefrontEffectiveSettings: (overrides?: Partial<StorefrontDesignSettings>, globalsRow?: Record<string, unknown>) => StorefrontDesignSettings;
|
|
67
|
+
/**
|
|
68
|
+
* The storefront theme for a design's settings overrides, resolved through
|
|
69
|
+
* `resolveStorefrontEffectiveSettings`: the GLOBAL layer of `globalsRow` sits under the
|
|
70
|
+
* design, exactly as in the public envelope's `effectiveSettings`. Omitting `globalsRow`
|
|
71
|
+
* resolves defaults plus the design only — correct solely while no GLOBAL design setting
|
|
72
|
+
* is set, so a caller serving or reporting on a storefront passes the row (the edge
|
|
73
|
+
* renderer's `theme-color` is the one exception, design-only by its read grant). Passing
|
|
74
|
+
* already-effective settings as `overrides` rebuilds the same theme.
|
|
75
|
+
*/
|
|
76
|
+
export declare const resolveStorefrontTheme: (overrides?: Partial<StorefrontDesignSettings>, globalsRow?: Record<string, unknown>) => StorefrontResolvedTheme;
|
|
61
77
|
export {};
|
package/dist/storefrontTheme.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resolveStorefrontDesignSettings } from "./storefrontDesignRegistry.js";
|
|
1
|
+
import { resolveStorefrontDesignSettings, validateStorefrontDesignSetting, } from "./storefrontDesignRegistry.js";
|
|
2
2
|
const channels = (color) => [
|
|
3
3
|
Number.parseInt(color.slice(1, 3), 16),
|
|
4
4
|
Number.parseInt(color.slice(3, 5), 16),
|
|
@@ -62,14 +62,36 @@ const surfaceTokens = (background, settings, mode, surface, warnings) => {
|
|
|
62
62
|
};
|
|
63
63
|
};
|
|
64
64
|
/**
|
|
65
|
-
* The
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
* overrides ONLY — no GLOBAL or WEB layer takes part — so a consumer uses this
|
|
69
|
-
* as shipped rather than merging layers of its own first.
|
|
65
|
+
* The GLOBAL design-settings layer of the platform globals row as `getPlatformGlobalsRow`
|
|
66
|
+
* returns it (`{ [key]: { value, ... } }`): each entry's `value`, kept only when it is a
|
|
67
|
+
* valid design setting, so a non-design global or a malformed value never reaches a theme.
|
|
70
68
|
*/
|
|
71
|
-
export const
|
|
72
|
-
|
|
69
|
+
export const storefrontGlobalSettingsLayer = (globalsRow) => ({
|
|
70
|
+
layer: 'GLOBAL',
|
|
71
|
+
values: Object.fromEntries(Object.entries(globalsRow ?? {}).flatMap(([key, entry]) => {
|
|
72
|
+
const value = entry !== null && typeof entry === 'object' ? entry.value : undefined;
|
|
73
|
+
return validateStorefrontDesignSetting(key, value) ? [[key, value]] : [];
|
|
74
|
+
})),
|
|
75
|
+
});
|
|
76
|
+
/**
|
|
77
|
+
* The settings a storefront is served with: registry defaults, then the platform GLOBAL
|
|
78
|
+
* layer, then the design's own overrides. The one resolver behind the public envelope's
|
|
79
|
+
* `effectiveSettings`, the draft's `effectiveSettings` and the publish contrast report —
|
|
80
|
+
* a caller passes the globals row it read, never a layer stack of its own. The edge
|
|
81
|
+
* renderer's `theme-color` is design-only: its role reads no GLOBALS row.
|
|
82
|
+
*/
|
|
83
|
+
export const resolveStorefrontEffectiveSettings = (overrides = {}, globalsRow) => resolveStorefrontDesignSettings(overrides, [storefrontGlobalSettingsLayer(globalsRow)]);
|
|
84
|
+
/**
|
|
85
|
+
* The storefront theme for a design's settings overrides, resolved through
|
|
86
|
+
* `resolveStorefrontEffectiveSettings`: the GLOBAL layer of `globalsRow` sits under the
|
|
87
|
+
* design, exactly as in the public envelope's `effectiveSettings`. Omitting `globalsRow`
|
|
88
|
+
* resolves defaults plus the design only — correct solely while no GLOBAL design setting
|
|
89
|
+
* is set, so a caller serving or reporting on a storefront passes the row (the edge
|
|
90
|
+
* renderer's `theme-color` is the one exception, design-only by its read grant). Passing
|
|
91
|
+
* already-effective settings as `overrides` rebuilds the same theme.
|
|
92
|
+
*/
|
|
93
|
+
export const resolveStorefrontTheme = (overrides = {}, globalsRow) => {
|
|
94
|
+
const settings = resolveStorefrontEffectiveSettings(overrides, globalsRow);
|
|
73
95
|
const warnings = [];
|
|
74
96
|
const makeMode = (mode) => {
|
|
75
97
|
const background = (requested) => mode === 'light' ? requested.toUpperCase() : mix(requested, '#000000', 0.9);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every storefront `WEB` literal key a shop may override, in code-unit order.
|
|
3
|
+
*
|
|
4
|
+
* This list is what the api's override validator accepts — one definition, so
|
|
5
|
+
* an editor offers exactly the keys a save will keep. A key outside it is
|
|
6
|
+
* refused as `UNKNOWN_KEY` on save and dropped on read.
|
|
7
|
+
*
|
|
8
|
+
* ⚠️ It is NOT every key the storefront renders. Withheld on purpose:
|
|
9
|
+
* - every key starting with `PRIVACY_` — privacy and terms are platform text,
|
|
10
|
+
* the same on every shop, and the storefront ignores a shop value for them;
|
|
11
|
+
* - the keys only the not-found and unavailable screens render, which read no
|
|
12
|
+
* shop layer;
|
|
13
|
+
* - keys whose storefront value is not a plain `{name}`-token sentence (it
|
|
14
|
+
* branches, computes, or is not a string), which a shop string cannot
|
|
15
|
+
* express.
|
|
16
|
+
* An editor that lists any of those offers an edit the server refuses.
|
|
17
|
+
*
|
|
18
|
+
* Interpolated values use `{name}` tokens named after the storefront's own
|
|
19
|
+
* parameters.
|
|
20
|
+
*/
|
|
21
|
+
export declare const STOREFRONT_WEB_LITERAL_KEYS: readonly string[];
|
|
22
|
+
/** Whether a shop may override `key` — membership in {@link STOREFRONT_WEB_LITERAL_KEYS}. */
|
|
23
|
+
export declare const isStorefrontLiteralKey: (key: string) => boolean;
|