@zenky/storefront-vue 0.9.47 → 0.10.3

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.
Files changed (98) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/LICENSE.md +2 -2
  3. package/README.md +213 -6
  4. package/dist/cart/index.d.ts +588 -0
  5. package/dist/cart/index.js +12 -0
  6. package/dist/cart/index.js.map +1 -0
  7. package/dist/catalog/index.d.ts +73 -0
  8. package/dist/catalog/index.js +4 -0
  9. package/dist/catalog/index.js.map +1 -0
  10. package/dist/chunk-57IOMDAM.js +38 -0
  11. package/dist/chunk-57IOMDAM.js.map +1 -0
  12. package/dist/chunk-5QCUH3FF.js +15 -0
  13. package/dist/chunk-5QCUH3FF.js.map +1 -0
  14. package/dist/chunk-7FRMMCR5.js +93 -0
  15. package/dist/chunk-7FRMMCR5.js.map +1 -0
  16. package/dist/chunk-BWUKMWHL.js +45 -0
  17. package/dist/chunk-BWUKMWHL.js.map +1 -0
  18. package/dist/chunk-E3G7NGCH.js +45 -0
  19. package/dist/chunk-E3G7NGCH.js.map +1 -0
  20. package/dist/chunk-EMGQXBU3.js +3 -0
  21. package/dist/chunk-EMGQXBU3.js.map +1 -0
  22. package/dist/chunk-FFBZKQRV.js +393 -0
  23. package/dist/chunk-FFBZKQRV.js.map +1 -0
  24. package/dist/chunk-G2X4SBCO.js +2011 -0
  25. package/dist/chunk-G2X4SBCO.js.map +1 -0
  26. package/dist/chunk-GLTOJTZO.js +41 -0
  27. package/dist/chunk-GLTOJTZO.js.map +1 -0
  28. package/dist/chunk-GW2QNTAD.js +327 -0
  29. package/dist/chunk-GW2QNTAD.js.map +1 -0
  30. package/dist/chunk-JLFBAAFT.js +566 -0
  31. package/dist/chunk-JLFBAAFT.js.map +1 -0
  32. package/dist/chunk-MZ66ARDL.js +244 -0
  33. package/dist/chunk-MZ66ARDL.js.map +1 -0
  34. package/dist/chunk-N3NOTJUC.js +111 -0
  35. package/dist/chunk-N3NOTJUC.js.map +1 -0
  36. package/dist/chunk-RO25KNM5.js +36 -0
  37. package/dist/chunk-RO25KNM5.js.map +1 -0
  38. package/dist/chunk-RYYY7MVP.js +102 -0
  39. package/dist/chunk-RYYY7MVP.js.map +1 -0
  40. package/dist/chunk-TEG7EL4N.js +106 -0
  41. package/dist/chunk-TEG7EL4N.js.map +1 -0
  42. package/dist/chunk-V3VVDQIR.js +27 -0
  43. package/dist/chunk-V3VVDQIR.js.map +1 -0
  44. package/dist/chunk-XUGXLJZB.js +14 -0
  45. package/dist/chunk-XUGXLJZB.js.map +1 -0
  46. package/dist/chunk-Z3IW4ODZ.js +36 -0
  47. package/dist/chunk-Z3IW4ODZ.js.map +1 -0
  48. package/dist/index.d.ts +26 -0
  49. package/dist/index.js +21 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/layout/index.d.ts +54 -0
  52. package/dist/layout/index.js +106 -0
  53. package/dist/layout/index.js.map +1 -0
  54. package/dist/money/index.d.ts +26 -0
  55. package/dist/money/index.js +3 -0
  56. package/dist/money/index.js.map +1 -0
  57. package/dist/order/index.d.ts +34 -0
  58. package/dist/order/index.js +209 -0
  59. package/dist/order/index.js.map +1 -0
  60. package/dist/pagination/index.d.ts +87 -0
  61. package/dist/pagination/index.js +97 -0
  62. package/dist/pagination/index.js.map +1 -0
  63. package/dist/ports/index.d.ts +124 -0
  64. package/dist/ports/index.js +3 -0
  65. package/dist/ports/index.js.map +1 -0
  66. package/dist/products/index.d.ts +105 -0
  67. package/dist/products/index.js +7 -0
  68. package/dist/products/index.js.map +1 -0
  69. package/dist/promotions/index.d.ts +10 -0
  70. package/dist/promotions/index.js +7 -0
  71. package/dist/promotions/index.js.map +1 -0
  72. package/dist/schedule-Dd3zwHEb.d.ts +27 -0
  73. package/dist/seo/index.d.ts +91 -0
  74. package/dist/seo/index.js +289 -0
  75. package/dist/seo/index.js.map +1 -0
  76. package/dist/snippets/index.d.ts +17 -0
  77. package/dist/snippets/index.js +3 -0
  78. package/dist/snippets/index.js.map +1 -0
  79. package/dist/snippets-BC5bkSjm.d.ts +23 -0
  80. package/dist/stores/index.d.ts +196 -0
  81. package/dist/stores/index.js +8 -0
  82. package/dist/stores/index.js.map +1 -0
  83. package/dist/theme/index.d.ts +60 -0
  84. package/dist/theme/index.js +3 -0
  85. package/dist/theme/index.js.map +1 -0
  86. package/dist/utils/index.d.ts +66 -0
  87. package/dist/utils/index.js +4 -0
  88. package/dist/utils/index.js.map +1 -0
  89. package/dist/website-settings/index.d.ts +38 -0
  90. package/dist/website-settings/index.js +3 -0
  91. package/dist/website-settings/index.js.map +1 -0
  92. package/docs/consumer-setup.md +275 -0
  93. package/docs/ports.md +144 -0
  94. package/docs/releasing.md +46 -0
  95. package/package.json +143 -47
  96. package/dist/cjs/index.cjs +0 -3742
  97. package/dist/esm/index.mjs +0 -3642
  98. package/dist/zenky.d.ts +0 -10377
@@ -0,0 +1,105 @@
1
+ import { UnitType, Product, ProductVariant, NestedCategory, ProductModifiersRequest, Discount, ProductVariantOptionValue, CatalogItem } from '@zenky/storefront-api';
2
+ import { ComputedRef, Ref } from 'vue';
3
+
4
+ declare function getVariantWeight(variant: ProductVariant | null | undefined): number | null;
5
+ declare function getProductWeight(product: Product): number | null;
6
+ declare function toGrams(weight: number | null, unitType: UnitType): number | null;
7
+ declare function formatWeight(weight: number | null, unitType: UnitType): string | null;
8
+
9
+ interface CategoryWithProducts {
10
+ id: string;
11
+ shortId: string;
12
+ slug: string | null;
13
+ name: string | null;
14
+ products: Product[];
15
+ }
16
+ declare function useProductsByCategory(products: ComputedRef<Product[]>, categories: ComputedRef<NestedCategory[]>): {
17
+ grouped: ComputedRef<CategoryWithProducts[]>;
18
+ };
19
+
20
+ declare function useProductVariantModifiers(variant: ComputedRef<ProductVariant | null>): {
21
+ modifiersModel: Ref<ProductModifiersRequest[]>;
22
+ selectedModifiersHash: Ref<string | null>;
23
+ hasAnyModifier: ComputedRef<boolean>;
24
+ };
25
+
26
+ interface ResolvedPrice {
27
+ price: number;
28
+ originalPrice: number | null;
29
+ discount: Discount | null;
30
+ }
31
+ /**
32
+ * Определяет цену варианта с учётом склада.
33
+ * Приоритет: prices[stockId] → prices[null] → top-level price.
34
+ */
35
+ declare function resolveVariantPrice(variant: ProductVariant, stockId?: string | null): ResolvedPrice;
36
+ declare function useProductPricing(product: ComputedRef<Product>, stockId?: ComputedRef<string | null | undefined>): {
37
+ displayPrice: ComputedRef<number>;
38
+ originalDisplayPrice: ComputedRef<number | null>;
39
+ hasDifferentPrices: ComputedRef<boolean>;
40
+ needsSelection: ComputedRef<boolean>;
41
+ };
42
+
43
+ interface ProductVariantOptionListItem {
44
+ id: string;
45
+ name: string | null;
46
+ values: ProductVariantOptionValue[];
47
+ }
48
+ declare function useProductVariant(product: ComputedRef<Product>): {
49
+ optionsModel: Ref<Record<string, string>>;
50
+ optionsList: Ref<ProductVariantOptionListItem[]>;
51
+ selectedVariant: ComputedRef<ProductVariant | null>;
52
+ disabledValues: ComputedRef<Record<string, Set<string>>>;
53
+ };
54
+
55
+ interface PriceRecalculationOptions {
56
+ product: ComputedRef<Product>;
57
+ selectedVariant: ComputedRef<ProductVariant | null>;
58
+ modifiersModel: Ref<ProductModifiersRequest[]>;
59
+ requiredModifiersGroupIsMissing: ComputedRef<boolean>;
60
+ selectedModifiersHash: Ref<string | null>;
61
+ }
62
+ declare function usePriceRecalculation({ product, selectedVariant, modifiersModel, requiredModifiersGroupIsMissing, selectedModifiersHash, }: PriceRecalculationOptions): {
63
+ price: ComputedRef<number>;
64
+ originalPrice: ComputedRef<number | null>;
65
+ priceWasModified: ComputedRef<boolean>;
66
+ priceIsRecalculating: Ref<boolean>;
67
+ };
68
+
69
+ declare function useProductVariantSelector(product: ComputedRef<Product>): {
70
+ optionsModel: Ref<Record<string, string>>;
71
+ optionsList: Ref<ProductVariantOptionListItem[]>;
72
+ disabledValues: ComputedRef<Record<string, Set<string>>>;
73
+ modifiersModel: Ref<ProductModifiersRequest[]>;
74
+ selectedVariant: ComputedRef<ProductVariant | null>;
75
+ price: ComputedRef<number>;
76
+ originalPrice: ComputedRef<number | null>;
77
+ priceWasModified: ComputedRef<boolean>;
78
+ priceIsRecalculating: Ref<boolean>;
79
+ selectedModifiersHash: Ref<string | null>;
80
+ requiredModifiersGroupIsMissing: ComputedRef<boolean>;
81
+ };
82
+
83
+ interface OrderCredentials {
84
+ id: string;
85
+ token: string;
86
+ }
87
+ interface UseRelatedProductsApi {
88
+ fetchForProduct: (productId: string, orderCredentials: OrderCredentials | null, signal?: AbortSignal) => Promise<Product[]>;
89
+ fetchForOrder: (orderId: string, orderToken: string, signal?: AbortSignal) => Promise<Product[]>;
90
+ }
91
+ /**
92
+ * Один из вариантов товара и ни одного модификатора — такой товар можно класть
93
+ * в корзину одним кликом по рекомендации, минуя открытие карточки.
94
+ */
95
+ declare function canAddDirectly(product: Product): boolean;
96
+ declare function useRelatedProducts(): UseRelatedProductsApi;
97
+
98
+ interface CatalogProductMatch {
99
+ product: Product;
100
+ catalogItem: CatalogItem;
101
+ }
102
+ /** Поиск товара по short_id в статичном каталоге. null — если не найден. */
103
+ declare function findProductInCatalog(catalog: CatalogItem[], shortId: string): CatalogProductMatch | null;
104
+
105
+ export { type CatalogProductMatch, type CategoryWithProducts, type OrderCredentials, type ProductVariantOptionListItem, type ResolvedPrice, canAddDirectly, findProductInCatalog, formatWeight, getProductWeight, getVariantWeight, resolveVariantPrice, toGrams, usePriceRecalculation, useProductPricing, useProductVariant, useProductVariantModifiers, useProductVariantSelector, useProductsByCategory, useRelatedProducts };
@@ -0,0 +1,7 @@
1
+ export { canAddDirectly, findProductInCatalog, formatWeight, getProductWeight, getVariantWeight, resolveVariantPrice, toGrams, usePriceRecalculation, useProductPricing, useProductVariant, useProductVariantModifiers, useProductVariantSelector, useProductsByCategory, useRelatedProducts } from '../chunk-JLFBAAFT.js';
2
+ import '../chunk-5QCUH3FF.js';
3
+ import '../chunk-TEG7EL4N.js';
4
+ import '../chunk-XUGXLJZB.js';
5
+ import '../chunk-V3VVDQIR.js';
6
+ //# sourceMappingURL=index.js.map
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
@@ -0,0 +1,10 @@
1
+ interface PromotionsChecker {
2
+ dispatchCheck: () => Promise<void>;
3
+ }
4
+ /**
5
+ * Проверка промо-акций. Вызывает dispatchPromotionsChecker после изменения корзины.
6
+ * Результат приходит через WebSocket (обрабатывается в useOrderChannel).
7
+ */
8
+ declare function usePromotionsChecker(): PromotionsChecker;
9
+
10
+ export { type PromotionsChecker, usePromotionsChecker };
@@ -0,0 +1,7 @@
1
+ export { usePromotionsChecker } from '../chunk-N3NOTJUC.js';
2
+ import '../chunk-BWUKMWHL.js';
3
+ import '../chunk-TEG7EL4N.js';
4
+ import '../chunk-FFBZKQRV.js';
5
+ import '../chunk-V3VVDQIR.js';
6
+ //# sourceMappingURL=index.js.map
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
@@ -0,0 +1,27 @@
1
+ import { ContactType, Contact, Schedule } from '@zenky/storefront-api';
2
+
3
+ interface SocialLink {
4
+ type: ContactType;
5
+ url: string;
6
+ label: string;
7
+ }
8
+ declare function getSocialLinks(contacts: Contact[]): SocialLink[];
9
+ declare function getEmailLink(contacts: Contact[]): string | null;
10
+ declare function getWebsiteLink(contacts: Contact[]): string | null;
11
+
12
+ interface DayScheduleInfo {
13
+ dayKey: string;
14
+ name: string;
15
+ hours: string | null;
16
+ }
17
+ interface TodayScheduleInfo {
18
+ isOpenNow: boolean;
19
+ todayHours: string | null;
20
+ openingAt: string | null;
21
+ closingAt: string | null;
22
+ }
23
+ declare function getTodayInfo(schedule: Schedule | null | undefined): TodayScheduleInfo | null;
24
+ declare function getWeeklySchedule(schedule: Schedule | null | undefined, locale?: string): DayScheduleInfo[];
25
+ declare function formatScheduleStatus(schedule: Schedule | null | undefined, locale?: string): string | null;
26
+
27
+ export { type DayScheduleInfo as D, type SocialLink as S, type TodayScheduleInfo as T, getSocialLinks as a, getTodayInfo as b, getWebsiteLink as c, getWeeklySchedule as d, formatScheduleStatus as f, getEmailLink as g };
@@ -0,0 +1,91 @@
1
+ import { Media, Store, Product, NestedCategory, Article, OfferArticle, Seo, SeoMetaTag as SeoMetaTag$1 } from '@zenky/storefront-api';
2
+
3
+ /**
4
+ * Shared SEO types used across storefront composables.
5
+ * Kept framework-agnostic for future extraction to a standalone library.
6
+ */
7
+ interface BreadcrumbItem {
8
+ label: string;
9
+ to?: string;
10
+ }
11
+
12
+ declare const SEO_META_TAG_PRIORITY = "critical";
13
+ type SeoMetaTag = Record<string, string>;
14
+ declare function withSeoMetaPriority<T extends SeoMetaTag>(tag: T): T & {
15
+ tagPriority: typeof SEO_META_TAG_PRIORITY;
16
+ };
17
+
18
+ /**
19
+ * Resolves the best available URL from a Media object.
20
+ */
21
+ declare function resolveMediaUrl(media: Media): string | null;
22
+ /**
23
+ * Resolves store logo URL, preferring square logo over rectangular.
24
+ * Tries all available size variants so CDN assets with only hd/xlarge are found too.
25
+ */
26
+ declare function resolveStoreLogoUrl(store: Store | null | undefined): string | null;
27
+ /**
28
+ * Strips HTML tags from a string.
29
+ */
30
+ declare function stripHtml(html: string): string;
31
+ /**
32
+ * Prepares a string for use as og:description or meta description:
33
+ * strips HTML tags and truncates to maxLength (default 160).
34
+ */
35
+ declare function prepareOgDescription(text: string | null | undefined, maxLength?: number): string | null;
36
+
37
+ /**
38
+ * Каноничный публичный origin сайта из `store.domain`. Fallback (хост запроса)
39
+ * приносит потребитель — ядро не знает про useRequestURL.
40
+ */
41
+ declare function resolveSiteOrigin(storeDomain: string | null | undefined, fallbackOrigin: string): string;
42
+
43
+ /**
44
+ * JSON-LD schema.org/Product. Google → Rich Results (цена/наличие/рейтинг в выдаче).
45
+ * Возвращает массив (0 или 1 объект) — потребитель оборачивает в `<script ld+json>`.
46
+ * Spec: https://schema.org/Product
47
+ */
48
+ declare function buildProductSchema(product: Product, currency: string): Record<string, unknown>[];
49
+
50
+ /**
51
+ * JSON-LD для страницы категории: BreadcrumbList (хлебные крошки) + ItemList
52
+ * (первые 10 товаров). Возвращает массив JSON-LD объектов (0–2).
53
+ * Spec: https://schema.org/ItemList · https://schema.org/BreadcrumbList
54
+ */
55
+ declare function buildCategorySchemas(category: NestedCategory, products: Product[], breadcrumbs: BreadcrumbItem[], origin: string): Record<string, unknown>[];
56
+
57
+ /**
58
+ * JSON-LD schema.org/Article для страницы акции (редакционный контент).
59
+ * Spec: https://schema.org/Article
60
+ */
61
+ declare function buildOfferArticleSchema(article: OfferArticle, storeName: string, origin: string): Record<string, unknown>[];
62
+ /**
63
+ * JSON-LD schema.org/BlogPosting для статьи блога (Google Rich Results).
64
+ * Spec: https://schema.org/BlogPosting
65
+ */
66
+ declare function buildBlogPostSchema(article: Article, storeName: string, origin: string): Record<string, unknown>[];
67
+
68
+ /**
69
+ * JSON-LD schema.org/Organization (или LocalBusiness при наличии физической точки)
70
+ * для главной. Google → Knowledge Panel. Все данные из уже загруженного профиля.
71
+ * Spec: https://schema.org/Organization · https://schema.org/LocalBusiness
72
+ */
73
+ declare function buildOrganizationSchema(store: Store, origin: string): Record<string, unknown>[];
74
+
75
+ /** Уже разрешённые (с учётом дефолтов потребителя) значения OG/Twitter-тегов. */
76
+ interface OgTagsInput {
77
+ title: string | null;
78
+ description?: string | null;
79
+ image?: string | null;
80
+ type?: string;
81
+ url?: string | null;
82
+ }
83
+ /** OG + Twitter meta-теги (critical-приоритет). Telegram читает twitter:image. */
84
+ declare function buildOgTags(input: OgTagsInput): Record<string, string>[];
85
+
86
+ /** SDK-теги `Seo.meta` → head-meta records (с critical-приоритетом для og/twitter/description). */
87
+ declare function mapSeoMetaTags(tags: SeoMetaTag$1[]): Record<string, string>[];
88
+ /** meta-теги из `Seo`-сущности (description + маппинг `Seo.meta`). Title резолвит потребитель. */
89
+ declare function buildEntitySeoMeta(seo: Seo | null | undefined): Record<string, string>[];
90
+
91
+ export { type BreadcrumbItem, type OgTagsInput, buildBlogPostSchema, buildCategorySchemas, buildEntitySeoMeta, buildOfferArticleSchema, buildOgTags, buildOrganizationSchema, buildProductSchema, mapSeoMetaTags, prepareOgDescription, resolveMediaUrl, resolveSiteOrigin, resolveStoreLogoUrl, stripHtml, withSeoMetaPriority };
@@ -0,0 +1,289 @@
1
+ import { resolveVariantPrice } from '../chunk-JLFBAAFT.js';
2
+ import '../chunk-5QCUH3FF.js';
3
+ import '../chunk-TEG7EL4N.js';
4
+ import { getSocialLinks } from '../chunk-MZ66ARDL.js';
5
+ import '../chunk-XUGXLJZB.js';
6
+ import '../chunk-V3VVDQIR.js';
7
+ import { minorToMajorString } from '../chunk-RO25KNM5.js';
8
+ import { ContactType } from '@zenky/storefront-api';
9
+
10
+ // src/seo/headPriority.ts
11
+ var SEO_META_TAG_PRIORITY = "critical";
12
+ function withSeoMetaPriority(tag) {
13
+ return { ...tag, tagPriority: SEO_META_TAG_PRIORITY };
14
+ }
15
+
16
+ // src/seo/utils.ts
17
+ function resolveMediaUrl(media) {
18
+ return media.hd?.url || media.xlarge?.url || media.large?.url || media.medium?.url || media.placeholder?.url || null;
19
+ }
20
+ function resolveStoreLogoUrl(store) {
21
+ if (!store) return null;
22
+ if (store.square_logo) {
23
+ const url = resolveMediaUrl(store.square_logo);
24
+ if (url) return url;
25
+ }
26
+ if (store.logo) {
27
+ return resolveMediaUrl(store.logo);
28
+ }
29
+ return null;
30
+ }
31
+ function stripHtml(html) {
32
+ return html.replace(/<[^>]*>/g, "").trim();
33
+ }
34
+ function prepareOgDescription(text, maxLength = 160) {
35
+ if (!text) return null;
36
+ const plain = stripHtml(text);
37
+ if (!plain) return null;
38
+ return plain.length > maxLength ? `${plain.slice(0, maxLength - 3)}...` : plain;
39
+ }
40
+
41
+ // src/seo/siteOrigin.ts
42
+ function resolveSiteOrigin(storeDomain, fallbackOrigin) {
43
+ const domain = storeDomain?.trim();
44
+ if (domain) {
45
+ const host = domain.replace(/^https?:\/\//, "").replace(/\/+$/, "");
46
+ return `https://${host}`;
47
+ }
48
+ return fallbackOrigin;
49
+ }
50
+
51
+ // src/seo/productSchema.ts
52
+ function isProductInStock(variants) {
53
+ if (!variants.length) return false;
54
+ return variants.some((variant) => {
55
+ const remainders = variant.remainders;
56
+ if (!remainders?.length) return true;
57
+ return remainders.some((remainder) => remainder.quantity > 0);
58
+ });
59
+ }
60
+ function buildProductSchema(product, currency) {
61
+ const variants = product.variants ?? [];
62
+ const firstVariant = variants[0];
63
+ let offerBlock;
64
+ if (firstVariant) {
65
+ const resolved = resolveVariantPrice(firstVariant);
66
+ offerBlock = {
67
+ "@type": "Offer",
68
+ price: minorToMajorString(resolved.price, currency),
69
+ priceCurrency: currency,
70
+ availability: isProductInStock(variants) ? "https://schema.org/InStock" : "https://schema.org/OutOfStock",
71
+ ...firstVariant.sku ? { sku: firstVariant.sku } : {}
72
+ };
73
+ }
74
+ const images = (product.images ?? []).map(resolveMediaUrl).filter((url) => !!url);
75
+ const schema = {
76
+ "@context": "https://schema.org",
77
+ "@type": "Product",
78
+ name: product.name
79
+ };
80
+ if (product.description) schema.description = product.description;
81
+ if (images.length) schema.image = images;
82
+ if (offerBlock) schema.offers = offerBlock;
83
+ return [schema];
84
+ }
85
+
86
+ // src/seo/categorySchema.ts
87
+ function buildCategorySchemas(category, products, breadcrumbs, origin) {
88
+ const schemas = [];
89
+ if (breadcrumbs.length) {
90
+ schemas.push({
91
+ "@context": "https://schema.org",
92
+ "@type": "BreadcrumbList",
93
+ itemListElement: breadcrumbs.map((crumb, index) => ({
94
+ "@type": "ListItem",
95
+ position: index + 1,
96
+ name: crumb.label,
97
+ ...crumb.to ? { item: `${origin}${crumb.to}` } : {}
98
+ }))
99
+ });
100
+ }
101
+ const items = products.slice(0, 10);
102
+ if (items.length) {
103
+ schemas.push({
104
+ "@context": "https://schema.org",
105
+ "@type": "ItemList",
106
+ name: category.name,
107
+ numberOfItems: items.length,
108
+ itemListElement: items.map((product, index) => {
109
+ const url = `${origin}/products/${product.short_id}/${product.slug || product.short_id}`;
110
+ const firstImage = product.images?.[0];
111
+ const image = firstImage ? resolveMediaUrl(firstImage) : null;
112
+ const item = {
113
+ "@type": "ListItem",
114
+ position: index + 1,
115
+ url,
116
+ name: product.name
117
+ };
118
+ if (image) item.image = image;
119
+ return item;
120
+ })
121
+ });
122
+ }
123
+ return schemas;
124
+ }
125
+
126
+ // src/seo/articleSchema.ts
127
+ function articleSchema(type, opts) {
128
+ const schema = {
129
+ "@context": "https://schema.org",
130
+ "@type": type,
131
+ headline: opts.title ?? "",
132
+ author: {
133
+ "@type": "Organization",
134
+ name: opts.storeName,
135
+ url: opts.origin
136
+ }
137
+ };
138
+ const description = prepareOgDescription(opts.intro);
139
+ if (description) schema.description = description;
140
+ const image = opts.cover ? resolveMediaUrl(opts.cover) : null;
141
+ if (image) schema.image = image;
142
+ return schema;
143
+ }
144
+ function buildOfferArticleSchema(article, storeName, origin) {
145
+ if (!article.title) return [];
146
+ return [
147
+ articleSchema("Article", {
148
+ title: article.title,
149
+ intro: article.intro,
150
+ cover: article.cover,
151
+ storeName,
152
+ origin
153
+ })
154
+ ];
155
+ }
156
+ function buildBlogPostSchema(article, storeName, origin) {
157
+ const schema = articleSchema("BlogPosting", {
158
+ title: article.title,
159
+ intro: article.intro,
160
+ cover: article.cover,
161
+ storeName,
162
+ origin
163
+ });
164
+ if (article.updated_at) {
165
+ schema.dateModified = article.updated_at;
166
+ schema.datePublished = article.updated_at;
167
+ }
168
+ return [schema];
169
+ }
170
+ var SCHEMA_DAY = {
171
+ monday: "Monday",
172
+ tuesday: "Tuesday",
173
+ wednesday: "Wednesday",
174
+ thursday: "Thursday",
175
+ friday: "Friday",
176
+ saturday: "Saturday",
177
+ sunday: "Sunday"
178
+ };
179
+ function buildOpeningHours(schedule) {
180
+ if (!schedule) return [];
181
+ const result = [];
182
+ for (const key of Object.keys(SCHEMA_DAY)) {
183
+ const day = schedule.days[key];
184
+ const match = day?.hours?.match(/(\d{1,2}:\d{2}).*?(\d{1,2}:\d{2})/);
185
+ if (!match) continue;
186
+ result.push({
187
+ "@type": "OpeningHoursSpecification",
188
+ dayOfWeek: SCHEMA_DAY[key],
189
+ opens: match[1],
190
+ closes: match[2]
191
+ });
192
+ }
193
+ return result;
194
+ }
195
+ function buildOrganizationSchema(store, origin) {
196
+ const logoUrl = store.square_logo?.medium?.url || store.square_logo?.large?.url || store.logo?.medium?.url || store.logo?.large?.url || null;
197
+ const phones = (store.phones ?? []).map((p) => p.number).filter((n) => !!n);
198
+ const email = store.links?.find((c) => c.type === ContactType.Email)?.value ?? null;
199
+ const socialLinks = getSocialLinks(store.links ?? []).map((l) => l.url);
200
+ const city = (store.cities ?? []).find((c) => c.is_default) ?? store.cities?.[0] ?? null;
201
+ const stock = city?.stocks?.find((st) => st.address) ?? city?.stocks?.[0] ?? null;
202
+ const streetAddress = stock?.address ?? null;
203
+ const geo = stock?.coordinates ?? city?.coordinates ?? null;
204
+ const openingHours = buildOpeningHours(stock?.schedule ?? city?.schedule);
205
+ const isLocalBusiness = Boolean(streetAddress || geo || openingHours.length);
206
+ const schema = {
207
+ "@context": "https://schema.org",
208
+ "@type": isLocalBusiness ? "LocalBusiness" : "Organization",
209
+ name: store.name,
210
+ url: origin
211
+ };
212
+ if (logoUrl) {
213
+ schema.logo = logoUrl;
214
+ schema.image = logoUrl;
215
+ }
216
+ if (phones.length) schema.telephone = phones.length === 1 ? phones[0] : phones;
217
+ if (email) schema.email = email;
218
+ if (socialLinks.length) schema.sameAs = socialLinks;
219
+ if (streetAddress || city?.name) {
220
+ schema.address = {
221
+ "@type": "PostalAddress",
222
+ ...streetAddress ? { streetAddress } : {},
223
+ ...city?.name ? { addressLocality: city.name } : {},
224
+ addressCountry: "RU"
225
+ };
226
+ }
227
+ if (geo) {
228
+ schema.geo = {
229
+ "@type": "GeoCoordinates",
230
+ latitude: geo.latitude,
231
+ longitude: geo.longitude
232
+ };
233
+ }
234
+ if (openingHours.length) schema.openingHoursSpecification = openingHours;
235
+ return [schema];
236
+ }
237
+
238
+ // src/seo/ogTags.ts
239
+ function buildOgTags(input) {
240
+ const tags = [];
241
+ if (input.title) {
242
+ tags.push(withSeoMetaPriority({ property: "og:title", content: input.title }));
243
+ tags.push(withSeoMetaPriority({ name: "twitter:title", content: input.title }));
244
+ }
245
+ if (input.description) {
246
+ tags.push(withSeoMetaPriority({ property: "og:description", content: input.description }));
247
+ tags.push(withSeoMetaPriority({ name: "twitter:description", content: input.description }));
248
+ }
249
+ if (input.image) {
250
+ tags.push(withSeoMetaPriority({ property: "og:image", content: input.image }));
251
+ tags.push(withSeoMetaPriority({ name: "twitter:image", content: input.image }));
252
+ }
253
+ tags.push(withSeoMetaPriority({ property: "og:type", content: input.type ?? "website" }));
254
+ if (input.url) {
255
+ tags.push(withSeoMetaPriority({ property: "og:url", content: input.url }));
256
+ }
257
+ return tags;
258
+ }
259
+
260
+ // src/seo/entitySeo.ts
261
+ function mapSeoMetaTags(tags) {
262
+ return tags.map((tag) => {
263
+ const attrs = {
264
+ [tag.name.attribute]: tag.name.value,
265
+ [tag.value.attribute]: tag.value.value
266
+ };
267
+ for (const additional of tag.additional) {
268
+ attrs[additional.attribute] = additional.value;
269
+ }
270
+ if (attrs.name === "description" || attrs.name?.startsWith("twitter:") || attrs.property?.startsWith("og:")) {
271
+ return withSeoMetaPriority(attrs);
272
+ }
273
+ return attrs;
274
+ });
275
+ }
276
+ function buildEntitySeoMeta(seo) {
277
+ const tags = [];
278
+ if (seo?.description) {
279
+ tags.push(withSeoMetaPriority({ name: "description", content: seo.description }));
280
+ }
281
+ if (seo?.meta?.length) {
282
+ tags.push(...mapSeoMetaTags(seo.meta));
283
+ }
284
+ return tags;
285
+ }
286
+
287
+ export { buildBlogPostSchema, buildCategorySchemas, buildEntitySeoMeta, buildOfferArticleSchema, buildOgTags, buildOrganizationSchema, buildProductSchema, mapSeoMetaTags, prepareOgDescription, resolveMediaUrl, resolveSiteOrigin, resolveStoreLogoUrl, stripHtml, withSeoMetaPriority };
288
+ //# sourceMappingURL=index.js.map
289
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/seo/headPriority.ts","../../src/seo/utils.ts","../../src/seo/siteOrigin.ts","../../src/seo/productSchema.ts","../../src/seo/categorySchema.ts","../../src/seo/articleSchema.ts","../../src/seo/organizationSchema.ts","../../src/seo/ogTags.ts","../../src/seo/entitySeo.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["const SEO_META_TAG_PRIORITY = 'critical'\n\ntype SeoMetaTag = Record<string, string>\n\nexport function withSeoMetaPriority<T extends SeoMetaTag>(\n tag: T\n): T & { tagPriority: typeof SEO_META_TAG_PRIORITY } {\n return { ...tag, tagPriority: SEO_META_TAG_PRIORITY }\n}\n","import type { Media, Store } from '@zenky/storefront-api'\n\n/**\n * Resolves the best available URL from a Media object.\n */\nexport function resolveMediaUrl(media: Media): string | null {\n return (\n media.hd?.url ||\n media.xlarge?.url ||\n media.large?.url ||\n media.medium?.url ||\n media.placeholder?.url ||\n null\n )\n}\n\n/**\n * Resolves store logo URL, preferring square logo over rectangular.\n * Tries all available size variants so CDN assets with only hd/xlarge are found too.\n */\nexport function resolveStoreLogoUrl(store: Store | null | undefined): string | null {\n if (!store) return null\n if (store.square_logo) {\n const url = resolveMediaUrl(store.square_logo)\n if (url) return url\n }\n if (store.logo) {\n return resolveMediaUrl(store.logo)\n }\n return null\n}\n\n/**\n * Strips HTML tags from a string.\n */\nexport function stripHtml(html: string): string {\n return html.replace(/<[^>]*>/g, '').trim()\n}\n\n/**\n * Prepares a string for use as og:description or meta description:\n * strips HTML tags and truncates to maxLength (default 160).\n */\nexport function prepareOgDescription(\n text: string | null | undefined,\n maxLength = 160\n): string | null {\n if (!text) return null\n const plain = stripHtml(text)\n if (!plain) return null\n return plain.length > maxLength ? `${plain.slice(0, maxLength - 3)}...` : plain\n}\n","/**\n * Каноничный публичный origin сайта из `store.domain`. Fallback (хост запроса)\n * приносит потребитель — ядро не знает про useRequestURL.\n */\nexport function resolveSiteOrigin(\n storeDomain: string | null | undefined,\n fallbackOrigin: string\n): string {\n const domain = storeDomain?.trim()\n if (domain) {\n const host = domain.replace(/^https?:\\/\\//, '').replace(/\\/+$/, '')\n return `https://${host}`\n }\n return fallbackOrigin\n}\n","import type { Product, ProductVariant } from '@zenky/storefront-api'\n\nimport { minorToMajorString } from '../money'\nimport { resolveVariantPrice } from '../products'\nimport { resolveMediaUrl } from './utils'\n\nfunction isProductInStock(variants: ProductVariant[]): boolean {\n if (!variants.length) return false\n return variants.some((variant) => {\n const remainders = variant.remainders\n if (!remainders?.length) return true\n return remainders.some((remainder) => remainder.quantity > 0)\n })\n}\n\n/**\n * JSON-LD schema.org/Product. Google → Rich Results (цена/наличие/рейтинг в выдаче).\n * Возвращает массив (0 или 1 объект) — потребитель оборачивает в `<script ld+json>`.\n * Spec: https://schema.org/Product\n */\nexport function buildProductSchema(product: Product, currency: string): Record<string, unknown>[] {\n const variants = product.variants ?? []\n\n const firstVariant = variants[0]\n let offerBlock: Record<string, unknown> | undefined\n\n if (firstVariant) {\n const resolved = resolveVariantPrice(firstVariant)\n\n offerBlock = {\n '@type': 'Offer',\n price: minorToMajorString(resolved.price, currency),\n priceCurrency: currency,\n availability: isProductInStock(variants)\n ? 'https://schema.org/InStock'\n : 'https://schema.org/OutOfStock',\n ...(firstVariant.sku ? { sku: firstVariant.sku } : {}),\n }\n }\n\n const images = (product.images ?? []).map(resolveMediaUrl).filter((url): url is string => !!url)\n\n const schema: Record<string, unknown> = {\n '@context': 'https://schema.org',\n '@type': 'Product',\n name: product.name,\n }\n\n if (product.description) schema.description = product.description\n if (images.length) schema.image = images\n if (offerBlock) schema.offers = offerBlock\n\n return [schema]\n}\n","import type { NestedCategory, Product } from '@zenky/storefront-api'\n\nimport { resolveMediaUrl } from './utils'\nimport type { BreadcrumbItem } from './types'\n\n/**\n * JSON-LD для страницы категории: BreadcrumbList (хлебные крошки) + ItemList\n * (первые 10 товаров). Возвращает массив JSON-LD объектов (0–2).\n * Spec: https://schema.org/ItemList · https://schema.org/BreadcrumbList\n */\nexport function buildCategorySchemas(\n category: NestedCategory,\n products: Product[],\n breadcrumbs: BreadcrumbItem[],\n origin: string\n): Record<string, unknown>[] {\n const schemas: Record<string, unknown>[] = []\n\n if (breadcrumbs.length) {\n schemas.push({\n '@context': 'https://schema.org',\n '@type': 'BreadcrumbList',\n itemListElement: breadcrumbs.map((crumb, index) => ({\n '@type': 'ListItem',\n position: index + 1,\n name: crumb.label,\n ...(crumb.to ? { item: `${origin}${crumb.to}` } : {}),\n })),\n })\n }\n\n const items = products.slice(0, 10)\n if (items.length) {\n schemas.push({\n '@context': 'https://schema.org',\n '@type': 'ItemList',\n name: category.name,\n numberOfItems: items.length,\n itemListElement: items.map((product, index) => {\n const url = `${origin}/products/${product.short_id}/${product.slug || product.short_id}`\n const firstImage = product.images?.[0]\n const image = firstImage ? resolveMediaUrl(firstImage) : null\n\n const item: Record<string, unknown> = {\n '@type': 'ListItem',\n position: index + 1,\n url,\n name: product.name,\n }\n\n if (image) item.image = image\n\n return item\n }),\n })\n }\n\n return schemas\n}\n","import type { Article, Media, OfferArticle } from '@zenky/storefront-api'\n\nimport { prepareOgDescription, resolveMediaUrl } from './utils'\n\nfunction articleSchema(\n type: 'Article' | 'BlogPosting',\n opts: {\n title: string | null | undefined\n intro: string | null | undefined\n cover: Media | null | undefined\n storeName: string\n origin: string\n }\n): Record<string, unknown> {\n const schema: Record<string, unknown> = {\n '@context': 'https://schema.org',\n '@type': type,\n headline: opts.title ?? '',\n author: {\n '@type': 'Organization',\n name: opts.storeName,\n url: opts.origin,\n },\n }\n\n const description = prepareOgDescription(opts.intro)\n if (description) schema.description = description\n\n const image = opts.cover ? resolveMediaUrl(opts.cover) : null\n if (image) schema.image = image\n\n return schema\n}\n\n/**\n * JSON-LD schema.org/Article для страницы акции (редакционный контент).\n * Spec: https://schema.org/Article\n */\nexport function buildOfferArticleSchema(\n article: OfferArticle,\n storeName: string,\n origin: string\n): Record<string, unknown>[] {\n if (!article.title) return []\n\n return [\n articleSchema('Article', {\n title: article.title,\n intro: article.intro,\n cover: article.cover,\n storeName,\n origin,\n }),\n ]\n}\n\n/**\n * JSON-LD schema.org/BlogPosting для статьи блога (Google Rich Results).\n * Spec: https://schema.org/BlogPosting\n */\nexport function buildBlogPostSchema(\n article: Article,\n storeName: string,\n origin: string\n): Record<string, unknown>[] {\n const schema = articleSchema('BlogPosting', {\n title: article.title,\n intro: article.intro,\n cover: article.cover,\n storeName,\n origin,\n })\n\n if (article.updated_at) {\n schema.dateModified = article.updated_at\n // Нет отдельного datePublished — используем updated_at.\n schema.datePublished = article.updated_at\n }\n\n return [schema]\n}\n","import { ContactType } from '@zenky/storefront-api'\n\nimport type { Schedule, Store } from '@zenky/storefront-api'\n\nimport { getSocialLinks } from '../utils'\n\n/** Ключ дня в Schedule.days → schema.org DayOfWeek. */\nconst SCHEMA_DAY: Record<string, string> = {\n monday: 'Monday',\n tuesday: 'Tuesday',\n wednesday: 'Wednesday',\n thursday: 'Thursday',\n friday: 'Friday',\n saturday: 'Saturday',\n sunday: 'Sunday',\n}\n\n/**\n * Schedule (по дням `hours` — «10:00–22:00») → массив schema.org/OpeningHoursSpecification.\n * Дни без распознаваемого интервала (выходной / круглосуточно / пусто) пропускаем.\n */\nfunction buildOpeningHours(schedule: Schedule | null | undefined): Record<string, unknown>[] {\n if (!schedule) return []\n\n const result: Record<string, unknown>[] = []\n for (const key of Object.keys(SCHEMA_DAY)) {\n const day = schedule.days[key as keyof typeof schedule.days]\n const match = day?.hours?.match(/(\\d{1,2}:\\d{2}).*?(\\d{1,2}:\\d{2})/)\n if (!match) continue\n\n result.push({\n '@type': 'OpeningHoursSpecification',\n dayOfWeek: SCHEMA_DAY[key],\n opens: match[1],\n closes: match[2],\n })\n }\n\n return result\n}\n\n/**\n * JSON-LD schema.org/Organization (или LocalBusiness при наличии физической точки)\n * для главной. Google → Knowledge Panel. Все данные из уже загруженного профиля.\n * Spec: https://schema.org/Organization · https://schema.org/LocalBusiness\n */\nexport function buildOrganizationSchema(store: Store, origin: string): Record<string, unknown>[] {\n const logoUrl =\n store.square_logo?.medium?.url ||\n store.square_logo?.large?.url ||\n store.logo?.medium?.url ||\n store.logo?.large?.url ||\n null\n\n const phones = (store.phones ?? []).map((p) => p.number).filter((n): n is string => !!n)\n const email = store.links?.find((c) => c.type === ContactType.Email)?.value ?? null\n const socialLinks = getSocialLinks(store.links ?? []).map((l) => l.url)\n\n const city = (store.cities ?? []).find((c) => c.is_default) ?? store.cities?.[0] ?? null\n const stock = city?.stocks?.find((st) => st.address) ?? city?.stocks?.[0] ?? null\n\n const streetAddress = stock?.address ?? null\n const geo = stock?.coordinates ?? city?.coordinates ?? null\n const openingHours = buildOpeningHours(stock?.schedule ?? city?.schedule)\n\n const isLocalBusiness = Boolean(streetAddress || geo || openingHours.length)\n\n const schema: Record<string, unknown> = {\n '@context': 'https://schema.org',\n '@type': isLocalBusiness ? 'LocalBusiness' : 'Organization',\n name: store.name,\n url: origin,\n }\n\n if (logoUrl) {\n schema.logo = logoUrl\n schema.image = logoUrl\n }\n\n if (phones.length) schema.telephone = phones.length === 1 ? phones[0] : phones\n if (email) schema.email = email\n if (socialLinks.length) schema.sameAs = socialLinks\n\n if (streetAddress || city?.name) {\n schema.address = {\n '@type': 'PostalAddress',\n ...(streetAddress ? { streetAddress } : {}),\n ...(city?.name ? { addressLocality: city.name } : {}),\n addressCountry: 'RU',\n }\n }\n\n if (geo) {\n schema.geo = {\n '@type': 'GeoCoordinates',\n latitude: geo.latitude,\n longitude: geo.longitude,\n }\n }\n\n if (openingHours.length) schema.openingHoursSpecification = openingHours\n\n return [schema]\n}\n","import { withSeoMetaPriority } from './headPriority'\n\n/** Уже разрешённые (с учётом дефолтов потребителя) значения OG/Twitter-тегов. */\nexport interface OgTagsInput {\n title: string | null\n description?: string | null\n image?: string | null\n type?: string\n url?: string | null\n}\n\n/** OG + Twitter meta-теги (critical-приоритет). Telegram читает twitter:image. */\nexport function buildOgTags(input: OgTagsInput): Record<string, string>[] {\n const tags: Record<string, string>[] = []\n\n if (input.title) {\n tags.push(withSeoMetaPriority({ property: 'og:title', content: input.title }))\n tags.push(withSeoMetaPriority({ name: 'twitter:title', content: input.title }))\n }\n\n if (input.description) {\n tags.push(withSeoMetaPriority({ property: 'og:description', content: input.description }))\n tags.push(withSeoMetaPriority({ name: 'twitter:description', content: input.description }))\n }\n\n if (input.image) {\n tags.push(withSeoMetaPriority({ property: 'og:image', content: input.image }))\n tags.push(withSeoMetaPriority({ name: 'twitter:image', content: input.image }))\n }\n\n tags.push(withSeoMetaPriority({ property: 'og:type', content: input.type ?? 'website' }))\n\n if (input.url) {\n tags.push(withSeoMetaPriority({ property: 'og:url', content: input.url }))\n }\n\n return tags\n}\n","import type { Seo, SeoMetaTag } from '@zenky/storefront-api'\n\nimport { withSeoMetaPriority } from './headPriority'\n\n/** SDK-теги `Seo.meta` → head-meta records (с critical-приоритетом для og/twitter/description). */\nexport function mapSeoMetaTags(tags: SeoMetaTag[]): Record<string, string>[] {\n return tags.map((tag) => {\n const attrs: Record<string, string> = {\n [tag.name.attribute]: tag.name.value,\n [tag.value.attribute]: tag.value.value,\n }\n\n for (const additional of tag.additional) {\n attrs[additional.attribute] = additional.value\n }\n\n if (\n attrs.name === 'description' ||\n attrs.name?.startsWith('twitter:') ||\n attrs.property?.startsWith('og:')\n ) {\n return withSeoMetaPriority(attrs)\n }\n\n return attrs\n })\n}\n\n/** meta-теги из `Seo`-сущности (description + маппинг `Seo.meta`). Title резолвит потребитель. */\nexport function buildEntitySeoMeta(seo: Seo | null | undefined): Record<string, string>[] {\n const tags: Record<string, string>[] = []\n\n if (seo?.description) {\n tags.push(withSeoMetaPriority({ name: 'description', content: seo.description }))\n }\n\n if (seo?.meta?.length) {\n tags.push(...mapSeoMetaTags(seo.meta))\n }\n\n return tags\n}\n"]}
@@ -0,0 +1,17 @@
1
+ import { S as Snippet } from '../snippets-BC5bkSjm.js';
2
+ export { R as RawSnippetItem, a as SnippetGroup, b as SnippetLanguage, c as SnippetPosition, n as normalizeSnippets } from '../snippets-BC5bkSjm.js';
3
+
4
+ /** Head-нагрузка для инъекции сниппетов (потребитель отдаёт её в useHead). */
5
+ interface SnippetHead {
6
+ meta: Record<string, string | boolean>[];
7
+ link: Record<string, string | boolean>[];
8
+ script: Record<string, unknown>[];
9
+ style: Record<string, string | boolean>[];
10
+ }
11
+ /**
12
+ * Раскладывает сниппеты магазина в head-нагрузку: JS (обёрнут в try/catch),
13
+ * CSS (style), head-HTML (распарсенные meta/link/script). Инъекция — у потребителя.
14
+ */
15
+ declare function buildSnippetHead(snippets: Snippet[]): SnippetHead;
16
+
17
+ export { Snippet, type SnippetHead, buildSnippetHead };
@@ -0,0 +1,3 @@
1
+ export { buildSnippetHead, normalizeSnippets } from '../chunk-7FRMMCR5.js';
2
+ //# sourceMappingURL=index.js.map
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
@@ -0,0 +1,23 @@
1
+ type SnippetPosition = 'head_start' | 'head_end' | 'body_start' | 'body_end';
2
+ type SnippetLanguage = 'html' | 'css' | 'javascript';
3
+ interface Snippet {
4
+ id: string;
5
+ name?: string;
6
+ position: SnippetPosition;
7
+ language: SnippetLanguage;
8
+ code: string;
9
+ enabled: boolean;
10
+ }
11
+ interface RawSnippetItem {
12
+ language: SnippetLanguage;
13
+ content: string;
14
+ enabled?: boolean;
15
+ name?: string;
16
+ }
17
+ interface SnippetGroup {
18
+ region: SnippetPosition;
19
+ snippets: RawSnippetItem[];
20
+ }
21
+ declare function normalizeSnippets(groups: SnippetGroup[] | null | undefined): Snippet[];
22
+
23
+ export { type RawSnippetItem as R, type Snippet as S, type SnippetGroup as a, type SnippetLanguage as b, type SnippetPosition as c, normalizeSnippets as n };