@zenky/storefront-vue 0.10.18 → 0.12.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 +107 -0
- package/dist/cart/index.js +6 -6
- package/dist/chunk-6HOKVW4M.js +134 -0
- package/dist/chunk-6HOKVW4M.js.map +1 -0
- package/dist/{chunk-LABBB43Q.js → chunk-CQDOEP6T.js} +163 -128
- package/dist/chunk-CQDOEP6T.js.map +1 -0
- package/dist/{chunk-6BXHWR5E.js → chunk-IEHN2WNU.js} +46 -13
- package/dist/chunk-IEHN2WNU.js.map +1 -0
- package/dist/chunk-JY3UJRMF.js +59 -0
- package/dist/chunk-JY3UJRMF.js.map +1 -0
- package/dist/{chunk-YTYCSTHP.js → chunk-PBBVWWMI.js} +3 -3
- package/dist/{chunk-YTYCSTHP.js.map → chunk-PBBVWWMI.js.map} +1 -1
- package/dist/{chunk-2DUKIVKC.js → chunk-QFKA2R6D.js} +3 -3
- package/dist/{chunk-2DUKIVKC.js.map → chunk-QFKA2R6D.js.map} +1 -1
- package/dist/{chunk-T52G6JN7.js → chunk-TXR76IDL.js} +16 -3
- package/dist/chunk-TXR76IDL.js.map +1 -0
- package/dist/{chunk-RURXGVWW.js → chunk-UJKWKD3W.js} +4 -56
- package/dist/chunk-UJKWKD3W.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +13 -11
- package/dist/products/index.d.ts +42 -2
- package/dist/products/index.js +3 -2
- package/dist/promotions/index.js +2 -2
- package/dist/seo/index.d.ts +44 -16
- package/dist/seo/index.js +338 -104
- package/dist/seo/index.js.map +1 -1
- package/dist/stores/index.d.ts +3 -2
- package/dist/stores/index.js +3 -3
- package/dist/utils/index.js +2 -1
- package/docs/architecture.md +5 -3
- package/docs/consumer-setup.md +1 -1
- package/package.json +3 -3
- package/dist/chunk-6BXHWR5E.js.map +0 -1
- package/dist/chunk-LABBB43Q.js.map +0 -1
- package/dist/chunk-RURXGVWW.js.map +0 -1
- package/dist/chunk-T52G6JN7.js.map +0 -1
package/dist/seo/index.js
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { getSocialLinks } from '../chunk-RURXGVWW.js';
|
|
3
|
-
import '../chunk-XUGXLJZB.js';
|
|
4
|
-
import '../chunk-5QCUH3FF.js';
|
|
5
|
-
import '../chunk-4JCJHAL3.js';
|
|
6
|
-
import '../chunk-T52G6JN7.js';
|
|
7
|
-
import '../chunk-IOYYCA75.js';
|
|
1
|
+
import { getEmailLink, getSocialLinks } from '../chunk-JY3UJRMF.js';
|
|
8
2
|
import { minorToMajorString } from '../chunk-2Y6QTHXI.js';
|
|
9
|
-
import {
|
|
3
|
+
import { getRequiredModifiersMinCost, resolveVariantPrice, getProductWeight } from '../chunk-6HOKVW4M.js';
|
|
4
|
+
import { UnitType } from '@zenky/storefront-api';
|
|
10
5
|
|
|
11
6
|
// src/seo/utils.ts
|
|
12
7
|
function resolveMediaUrl(media) {
|
|
@@ -26,6 +21,40 @@ function resolveStoreLogoUrl(store) {
|
|
|
26
21
|
function stripHtml(html) {
|
|
27
22
|
return html.replace(/<[^>]*>/g, "").trim();
|
|
28
23
|
}
|
|
24
|
+
var INVISIBLE_CHARACTERS = /[\u00ad\u200b-\u200d\u2060\ufeff]/g;
|
|
25
|
+
var BLOCK_TAG = /<\/?(?:p|div|br|li|ul|ol|h[1-6]|tr|td|th|table|blockquote|section|article|header|footer)\b[^>]*>/gi;
|
|
26
|
+
var ANY_TAG = /<[^>]*>/g;
|
|
27
|
+
var HTML_ENTITY = /&(#x[0-9a-f]+|#\d+|[a-z]+);/gi;
|
|
28
|
+
var NAMED_ENTITIES = {
|
|
29
|
+
amp: "&",
|
|
30
|
+
lt: "<",
|
|
31
|
+
gt: ">",
|
|
32
|
+
quot: '"',
|
|
33
|
+
apos: "'",
|
|
34
|
+
nbsp: " ",
|
|
35
|
+
laquo: "\xAB",
|
|
36
|
+
raquo: "\xBB",
|
|
37
|
+
ndash: "\u2013",
|
|
38
|
+
mdash: "\u2014",
|
|
39
|
+
hellip: "\u2026"
|
|
40
|
+
};
|
|
41
|
+
function decodeEntity(entity, body) {
|
|
42
|
+
const lower = body.toLowerCase();
|
|
43
|
+
if (lower.startsWith("#")) {
|
|
44
|
+
const code = lower.startsWith("#x") ? Number.parseInt(lower.slice(2), 16) : Number.parseInt(lower.slice(1), 10);
|
|
45
|
+
return Number.isInteger(code) && code > 0 && code <= 1114111 ? String.fromCodePoint(code) : entity;
|
|
46
|
+
}
|
|
47
|
+
return NAMED_ENTITIES[lower] ?? entity;
|
|
48
|
+
}
|
|
49
|
+
function normalizeSchemaText(value) {
|
|
50
|
+
if (!value) return "";
|
|
51
|
+
return value.replace(INVISIBLE_CHARACTERS, "").replace(/\s+/g, " ").trim();
|
|
52
|
+
}
|
|
53
|
+
function htmlToPlainText(html) {
|
|
54
|
+
if (!html) return "";
|
|
55
|
+
const text = html.replace(BLOCK_TAG, " ").replace(ANY_TAG, "").replace(HTML_ENTITY, (entity, body) => decodeEntity(entity, body));
|
|
56
|
+
return normalizeSchemaText(text);
|
|
57
|
+
}
|
|
29
58
|
function prepareOgDescription(text, maxLength = 160) {
|
|
30
59
|
if (!text) return null;
|
|
31
60
|
const plain = stripHtml(text);
|
|
@@ -43,56 +72,147 @@ function resolveSiteOrigin(storeDomain, fallbackOrigin) {
|
|
|
43
72
|
return fallbackOrigin;
|
|
44
73
|
}
|
|
45
74
|
|
|
46
|
-
// src/seo/
|
|
47
|
-
function
|
|
48
|
-
|
|
49
|
-
return variants.some((variant) => {
|
|
50
|
-
const remainders = variant.remainders;
|
|
51
|
-
if (!remainders?.length) return true;
|
|
52
|
-
return remainders.some((remainder) => remainder.quantity > 0);
|
|
53
|
-
});
|
|
75
|
+
// src/seo/ids.ts
|
|
76
|
+
function organizationSchemaId(origin) {
|
|
77
|
+
return `${origin}/#organization`;
|
|
54
78
|
}
|
|
55
|
-
function
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
79
|
+
function webSiteSchemaId(origin) {
|
|
80
|
+
return `${origin}/#website`;
|
|
81
|
+
}
|
|
82
|
+
function locationAnchorId(stockId) {
|
|
83
|
+
return `location-${stockId}`;
|
|
84
|
+
}
|
|
85
|
+
function locationSchemaId(origin, stockId) {
|
|
86
|
+
return `${origin}/#${locationAnchorId(stockId)}`;
|
|
87
|
+
}
|
|
88
|
+
function productSchemaId(productUrl) {
|
|
89
|
+
return `${productUrl}#product`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// src/seo/businessType.ts
|
|
93
|
+
var BUSINESS_TYPES = [
|
|
94
|
+
"Restaurant",
|
|
95
|
+
"CafeOrCoffeeShop",
|
|
96
|
+
"FastFoodRestaurant",
|
|
97
|
+
"Bakery",
|
|
98
|
+
"BarOrPub",
|
|
99
|
+
"GroceryStore",
|
|
100
|
+
"ConvenienceStore",
|
|
101
|
+
"Florist",
|
|
102
|
+
"Pharmacy",
|
|
103
|
+
"PetStore",
|
|
104
|
+
"ClothingStore",
|
|
105
|
+
"Store",
|
|
106
|
+
"LocalBusiness"
|
|
107
|
+
];
|
|
108
|
+
var FOOD_VENUE = /(?:^|[^\p{L}])(?:ресторан\p{L}*|кафе|кофейн\p{L}*|бистро|пиццери\p{L}*|бар(?:а|у|ом|е|ы|ов)?|паб(?:а|у|ом|е|ы|ов)?|restaurants?|caf[eé]s?|coffee|bistro|pizzeria|bars?|pubs?)(?!\p{L})/iu;
|
|
109
|
+
var PICKUP_POINT = /самовывоз|выдач|pick-?up/iu;
|
|
110
|
+
function isBusinessType(value) {
|
|
111
|
+
return typeof value === "string" && BUSINESS_TYPES.includes(value);
|
|
112
|
+
}
|
|
113
|
+
function resolveLocationType(stock, businessType) {
|
|
114
|
+
const name = normalizeSchemaText(stock.name);
|
|
115
|
+
const isFoodVenue = FOOD_VENUE.test(name);
|
|
116
|
+
if (!businessType) return isFoodVenue ? "Restaurant" : "LocalBusiness";
|
|
117
|
+
if (!isFoodVenue && PICKUP_POINT.test(name)) return "LocalBusiness";
|
|
118
|
+
return businessType;
|
|
119
|
+
}
|
|
120
|
+
var IN_STOCK = "https://schema.org/InStock";
|
|
121
|
+
var WEIGHT_UNIT_CODES = {
|
|
122
|
+
[UnitType.Piece]: "GRM",
|
|
123
|
+
[UnitType.Gram]: "GRM",
|
|
124
|
+
[UnitType.Kilogram]: "KGM"
|
|
125
|
+
};
|
|
126
|
+
function buildWeight(product) {
|
|
127
|
+
const weight = getProductWeight(product);
|
|
128
|
+
const unitCode = WEIGHT_UNIT_CODES[product.unit_type];
|
|
129
|
+
if (!weight || weight < 0 || !unitCode) return null;
|
|
130
|
+
return { "@type": "QuantitativeValue", value: weight, unitCode };
|
|
131
|
+
}
|
|
132
|
+
function buildProductOffer(product, currency, stockId) {
|
|
133
|
+
const requiredModifiersCost = getRequiredModifiersMinCost(product);
|
|
134
|
+
const priced = (product.variants ?? []).map((variant) => ({
|
|
135
|
+
variant,
|
|
136
|
+
price: resolveVariantPrice(variant, stockId).price + requiredModifiersCost
|
|
137
|
+
})).filter(({ price }) => price > 0);
|
|
138
|
+
const [first] = priced;
|
|
139
|
+
if (!first) return null;
|
|
140
|
+
if (priced.length > 1) {
|
|
141
|
+
const prices = priced.map(({ price }) => price);
|
|
142
|
+
return {
|
|
143
|
+
"@type": "AggregateOffer",
|
|
144
|
+
lowPrice: minorToMajorString(Math.min(...prices), currency),
|
|
145
|
+
highPrice: minorToMajorString(Math.max(...prices), currency),
|
|
146
|
+
offerCount: priced.length,
|
|
64
147
|
priceCurrency: currency,
|
|
65
|
-
availability:
|
|
66
|
-
...firstVariant.sku ? { sku: firstVariant.sku } : {}
|
|
148
|
+
availability: IN_STOCK
|
|
67
149
|
};
|
|
68
150
|
}
|
|
69
|
-
const
|
|
151
|
+
const sku = first.variant.sku;
|
|
152
|
+
return {
|
|
153
|
+
"@type": "Offer",
|
|
154
|
+
price: minorToMajorString(first.price, currency),
|
|
155
|
+
priceCurrency: currency,
|
|
156
|
+
availability: IN_STOCK,
|
|
157
|
+
...sku ? { sku } : {}
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
function buildProductSchema(product, currency, options = {}) {
|
|
70
161
|
const schema = {
|
|
71
162
|
"@context": "https://schema.org",
|
|
72
|
-
"@type": "Product"
|
|
73
|
-
name: product.name
|
|
163
|
+
"@type": "Product"
|
|
74
164
|
};
|
|
75
|
-
if (
|
|
165
|
+
if (options.url) {
|
|
166
|
+
schema["@id"] = productSchemaId(options.url);
|
|
167
|
+
schema.url = options.url;
|
|
168
|
+
}
|
|
169
|
+
schema.name = normalizeSchemaText(product.name);
|
|
170
|
+
const description = htmlToPlainText(product.description);
|
|
171
|
+
if (description) schema.description = description;
|
|
172
|
+
const images = (product.images ?? []).map(resolveMediaUrl).filter((url) => !!url);
|
|
76
173
|
if (images.length) schema.image = images;
|
|
77
|
-
|
|
174
|
+
const variants = product.variants ?? [];
|
|
175
|
+
const sku = variants.length === 1 ? variants[0]?.sku : null;
|
|
176
|
+
if (sku) schema.sku = sku;
|
|
177
|
+
const category = normalizeSchemaText(product.categories?.[0]?.name);
|
|
178
|
+
if (category) schema.category = category;
|
|
179
|
+
const weight = buildWeight(product);
|
|
180
|
+
if (weight) schema.weight = weight;
|
|
181
|
+
const offer = buildProductOffer(product, currency, options.stockId);
|
|
182
|
+
if (offer) {
|
|
183
|
+
schema.offers = {
|
|
184
|
+
...offer,
|
|
185
|
+
...options.url ? { url: options.url } : {},
|
|
186
|
+
...options.seller ? { seller: options.seller } : {}
|
|
187
|
+
};
|
|
188
|
+
}
|
|
78
189
|
return [schema];
|
|
79
190
|
}
|
|
80
191
|
|
|
81
|
-
// src/seo/
|
|
82
|
-
function
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
192
|
+
// src/seo/breadcrumbSchema.ts
|
|
193
|
+
function toAbsoluteUrl(path, origin) {
|
|
194
|
+
return /^https?:\/\//i.test(path) ? path : `${origin}${path}`;
|
|
195
|
+
}
|
|
196
|
+
function buildBreadcrumbListSchema(items, origin) {
|
|
197
|
+
const crumbs = items.map((item) => ({ name: normalizeSchemaText(item.label), to: item.to })).filter((crumb) => crumb.name);
|
|
198
|
+
if (!crumbs.length) return [];
|
|
199
|
+
return [
|
|
200
|
+
{
|
|
86
201
|
"@context": "https://schema.org",
|
|
87
202
|
"@type": "BreadcrumbList",
|
|
88
|
-
itemListElement:
|
|
203
|
+
itemListElement: crumbs.map((crumb, index) => ({
|
|
89
204
|
"@type": "ListItem",
|
|
90
205
|
position: index + 1,
|
|
91
|
-
name: crumb.
|
|
92
|
-
...crumb.to ? { item:
|
|
206
|
+
name: crumb.name,
|
|
207
|
+
...crumb.to ? { item: toAbsoluteUrl(crumb.to, origin) } : {}
|
|
93
208
|
}))
|
|
94
|
-
}
|
|
95
|
-
|
|
209
|
+
}
|
|
210
|
+
];
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// src/seo/listingSchema.ts
|
|
214
|
+
function buildListingSchemas(name, products, breadcrumbs, origin) {
|
|
215
|
+
const schemas = buildBreadcrumbListSchema(breadcrumbs, origin);
|
|
96
216
|
const items = products.slice(0, 10);
|
|
97
217
|
if (items.length) {
|
|
98
218
|
schemas.push({
|
|
@@ -108,7 +228,7 @@ function buildListingSchemas(name, products, breadcrumbs, origin) {
|
|
|
108
228
|
"@type": "ListItem",
|
|
109
229
|
position: index + 1,
|
|
110
230
|
url,
|
|
111
|
-
name: product.name
|
|
231
|
+
name: normalizeSchemaText(product.name)
|
|
112
232
|
};
|
|
113
233
|
if (image) item.image = image;
|
|
114
234
|
return item;
|
|
@@ -168,72 +288,186 @@ function buildBlogPostSchema(article, storeName, origin) {
|
|
|
168
288
|
}
|
|
169
289
|
return [schema];
|
|
170
290
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
291
|
+
|
|
292
|
+
// src/seo/openingHours.ts
|
|
293
|
+
var SCHEMA_DAYS = [
|
|
294
|
+
["monday", "Monday"],
|
|
295
|
+
["tuesday", "Tuesday"],
|
|
296
|
+
["wednesday", "Wednesday"],
|
|
297
|
+
["thursday", "Thursday"],
|
|
298
|
+
["friday", "Friday"],
|
|
299
|
+
["saturday", "Saturday"],
|
|
300
|
+
["sunday", "Sunday"]
|
|
301
|
+
];
|
|
302
|
+
var ROUND_THE_CLOCK = /^\s*0?0:00\s*[-–—]\s*0?0:00\s*$|круглосуточно|24\s*\/\s*7|24\s*час/iu;
|
|
303
|
+
var SHIFT = /(\d{1,2}):(\d{2})\D+?(\d{1,2}):(\d{2})/g;
|
|
304
|
+
function formatTime(hours, minutes) {
|
|
305
|
+
const hour = Number(hours);
|
|
306
|
+
const minute = Number(minutes);
|
|
307
|
+
if (hour > 24 || minute > 59 || hour === 24 && minute > 0) return null;
|
|
308
|
+
if (hour === 24) return "23:59";
|
|
309
|
+
return `${String(hour).padStart(2, "0")}:${minutes}`;
|
|
310
|
+
}
|
|
311
|
+
function parseShifts(hours) {
|
|
312
|
+
if (ROUND_THE_CLOCK.test(hours)) return [["00:00", "23:59"]];
|
|
313
|
+
const shifts = [];
|
|
314
|
+
for (const [, openHour, openMinute, closeHour, closeMinute] of hours.matchAll(SHIFT)) {
|
|
315
|
+
const opens = formatTime(openHour ?? "", openMinute ?? "");
|
|
316
|
+
const closes = formatTime(closeHour ?? "", closeMinute ?? "");
|
|
317
|
+
if (opens && closes) shifts.push([opens, closes]);
|
|
193
318
|
}
|
|
194
|
-
return
|
|
195
|
-
}
|
|
196
|
-
function
|
|
197
|
-
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
"@type":
|
|
210
|
-
|
|
211
|
-
|
|
319
|
+
return shifts;
|
|
320
|
+
}
|
|
321
|
+
function buildOpeningHoursSpecification(schedule) {
|
|
322
|
+
if (!schedule?.days) return [];
|
|
323
|
+
const groups = [];
|
|
324
|
+
for (const [key, dayOfWeek] of SCHEMA_DAYS) {
|
|
325
|
+
const hours = schedule.days[key]?.hours;
|
|
326
|
+
if (!hours) continue;
|
|
327
|
+
for (const [opens, closes] of parseShifts(hours)) {
|
|
328
|
+
const group = groups.find((item) => item.opens === opens && item.closes === closes);
|
|
329
|
+
if (group) group.days.push(dayOfWeek);
|
|
330
|
+
else groups.push({ opens, closes, days: [dayOfWeek] });
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return groups.map(({ opens, closes, days }) => ({
|
|
334
|
+
"@type": "OpeningHoursSpecification",
|
|
335
|
+
dayOfWeek: days,
|
|
336
|
+
opens,
|
|
337
|
+
closes
|
|
338
|
+
}));
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// src/seo/locationSchema.ts
|
|
342
|
+
function escapeRegExp(value) {
|
|
343
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
344
|
+
}
|
|
345
|
+
function normalizeStreetAddress(address, cityName) {
|
|
346
|
+
const street = normalizeSchemaText(address);
|
|
347
|
+
if (!street || !cityName) return street;
|
|
348
|
+
const city = `(?:\u0433\\.?|\u0433\u043E\u0440\u043E\u0434)?\\s*${escapeRegExp(cityName)}`;
|
|
349
|
+
const cityPrefix = new RegExp(`^${city}\\s*,\\s*`, "iu");
|
|
350
|
+
const citySuffix = new RegExp(`\\s*,\\s*${city}$`, "iu");
|
|
351
|
+
return street.replace(cityPrefix, "").replace(citySuffix, "") || street;
|
|
352
|
+
}
|
|
353
|
+
function hasCoordinates(coordinates) {
|
|
354
|
+
if (!coordinates) return false;
|
|
355
|
+
const { latitude, longitude } = coordinates;
|
|
356
|
+
if (!Number.isFinite(latitude) || !Number.isFinite(longitude)) return false;
|
|
357
|
+
return latitude !== 0 || longitude !== 0;
|
|
358
|
+
}
|
|
359
|
+
function buildLocationNode(stock, city, store, origin, options) {
|
|
360
|
+
const cityName = normalizeSchemaText(city.name);
|
|
361
|
+
const streetAddress = normalizeStreetAddress(stock.address, cityName);
|
|
362
|
+
if (!streetAddress) return null;
|
|
363
|
+
const node = {
|
|
364
|
+
"@type": resolveLocationType(stock, options.businessType),
|
|
365
|
+
"@id": locationSchemaId(origin, stock.id),
|
|
366
|
+
name: normalizeSchemaText(stock.name) || normalizeSchemaText(store.name)
|
|
367
|
+
};
|
|
368
|
+
const description = htmlToPlainText(stock.description);
|
|
369
|
+
if (description) node.description = description;
|
|
370
|
+
const url = options.locationUrl?.(stock);
|
|
371
|
+
if (url) node.url = url;
|
|
372
|
+
const image = resolveStoreLogoUrl(store);
|
|
373
|
+
if (image) node.image = image;
|
|
374
|
+
const telephone = stock.phones?.[0]?.number || city.phones?.[0]?.number || store.phones?.[0]?.number;
|
|
375
|
+
if (telephone) node.telephone = telephone;
|
|
376
|
+
node.address = {
|
|
377
|
+
"@type": "PostalAddress",
|
|
378
|
+
streetAddress,
|
|
379
|
+
...cityName ? { addressLocality: cityName } : {},
|
|
380
|
+
addressCountry: store.settings?.country || "RU"
|
|
212
381
|
};
|
|
213
|
-
if (
|
|
214
|
-
|
|
215
|
-
|
|
382
|
+
if (hasCoordinates(stock.coordinates)) {
|
|
383
|
+
const { latitude, longitude } = stock.coordinates;
|
|
384
|
+
node.geo = { "@type": "GeoCoordinates", latitude, longitude };
|
|
385
|
+
node.hasMap = `https://yandex.ru/maps/?pt=${longitude},${latitude}&z=17&l=map`;
|
|
216
386
|
}
|
|
217
|
-
|
|
218
|
-
if (
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
387
|
+
const openingHours = buildOpeningHoursSpecification(stock.schedule);
|
|
388
|
+
if (openingHours.length) node.openingHoursSpecification = openingHours;
|
|
389
|
+
node.parentOrganization = { "@id": organizationSchemaId(origin) };
|
|
390
|
+
return node;
|
|
391
|
+
}
|
|
392
|
+
function buildLocationNodes(store, origin, options = {}) {
|
|
393
|
+
const seen = /* @__PURE__ */ new Set();
|
|
394
|
+
const nodes = [];
|
|
395
|
+
for (const city of store.cities ?? []) {
|
|
396
|
+
for (const stock of city.stocks ?? []) {
|
|
397
|
+
if (seen.has(stock.id)) continue;
|
|
398
|
+
const node = buildLocationNode(stock, city, store, origin, options);
|
|
399
|
+
if (!node) continue;
|
|
400
|
+
seen.add(stock.id);
|
|
401
|
+
nodes.push(node);
|
|
402
|
+
}
|
|
227
403
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
404
|
+
return nodes;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// src/seo/organizationSchema.ts
|
|
408
|
+
function unique(values) {
|
|
409
|
+
return [...new Set(values.filter(Boolean))];
|
|
410
|
+
}
|
|
411
|
+
function buildOrganizationReference(store, origin) {
|
|
412
|
+
return {
|
|
413
|
+
"@type": "Organization",
|
|
414
|
+
"@id": organizationSchemaId(origin),
|
|
415
|
+
name: normalizeSchemaText(store.name),
|
|
416
|
+
url: `${origin}/`
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
function buildOrganizationNode(store, origin) {
|
|
420
|
+
const node = buildOrganizationReference(store, origin);
|
|
421
|
+
const logo = resolveStoreLogoUrl(store);
|
|
422
|
+
if (logo) node.logo = logo;
|
|
423
|
+
const phones = unique((store.phones ?? []).map((phone) => phone.number));
|
|
424
|
+
if (phones.length) node.telephone = phones.length === 1 ? phones[0] : phones;
|
|
425
|
+
const email = getEmailLink(store.links ?? []);
|
|
426
|
+
if (email) node.email = email;
|
|
427
|
+
const sameAs = unique(getSocialLinks(store.links ?? []).map((link) => link.url));
|
|
428
|
+
if (sameAs.length) node.sameAs = sameAs;
|
|
429
|
+
const cities = store.cities ?? [];
|
|
430
|
+
const cityName = cities.length === 1 ? normalizeSchemaText(cities[0]?.name) : "";
|
|
431
|
+
if (cityName) {
|
|
432
|
+
node.address = {
|
|
433
|
+
"@type": "PostalAddress",
|
|
434
|
+
addressLocality: cityName,
|
|
435
|
+
addressCountry: store.settings?.country || "RU"
|
|
233
436
|
};
|
|
234
437
|
}
|
|
235
|
-
|
|
236
|
-
|
|
438
|
+
return node;
|
|
439
|
+
}
|
|
440
|
+
function buildWebSiteNode(store, origin, inLanguage) {
|
|
441
|
+
return {
|
|
442
|
+
"@type": "WebSite",
|
|
443
|
+
"@id": webSiteSchemaId(origin),
|
|
444
|
+
url: `${origin}/`,
|
|
445
|
+
name: normalizeSchemaText(store.name),
|
|
446
|
+
...inLanguage ? { inLanguage } : {},
|
|
447
|
+
publisher: { "@id": organizationSchemaId(origin) }
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
function buildOrganizationSchema(store, origin, options = {}) {
|
|
451
|
+
return [
|
|
452
|
+
{
|
|
453
|
+
"@context": "https://schema.org",
|
|
454
|
+
"@graph": [
|
|
455
|
+
buildOrganizationNode(store, origin),
|
|
456
|
+
buildWebSiteNode(store, origin, options.inLanguage),
|
|
457
|
+
...buildLocationNodes(store, origin, options)
|
|
458
|
+
]
|
|
459
|
+
}
|
|
460
|
+
];
|
|
461
|
+
}
|
|
462
|
+
function buildLocationsSchema(store, origin, options = {}) {
|
|
463
|
+
const locations = buildLocationNodes(store, origin, options);
|
|
464
|
+
if (!locations.length) return [];
|
|
465
|
+
return [
|
|
466
|
+
{
|
|
467
|
+
"@context": "https://schema.org",
|
|
468
|
+
"@graph": [buildOrganizationNode(store, origin), ...locations]
|
|
469
|
+
}
|
|
470
|
+
];
|
|
237
471
|
}
|
|
238
472
|
|
|
239
473
|
// src/seo/ogTags.ts
|
|
@@ -282,6 +516,6 @@ function buildEntitySeoMeta(seo) {
|
|
|
282
516
|
return tags;
|
|
283
517
|
}
|
|
284
518
|
|
|
285
|
-
export { buildBlogPostSchema, buildCategorySchemas, buildCollectionSchemas, buildEntitySeoMeta, buildOfferArticleSchema, buildOgTags, buildOrganizationSchema, buildProductSchema, mapSeoMetaTags, prepareOgDescription, resolveMediaUrl, resolveSiteOrigin, resolveStoreLogoUrl, stripHtml };
|
|
519
|
+
export { BUSINESS_TYPES, buildBlogPostSchema, buildBreadcrumbListSchema, buildCategorySchemas, buildCollectionSchemas, buildEntitySeoMeta, buildLocationsSchema, buildOfferArticleSchema, buildOgTags, buildOrganizationReference, buildOrganizationSchema, buildProductOffer, buildProductSchema, htmlToPlainText, isBusinessType, locationAnchorId, locationSchemaId, mapSeoMetaTags, normalizeSchemaText, organizationSchemaId, prepareOgDescription, productSchemaId, resolveMediaUrl, resolveSiteOrigin, resolveStoreLogoUrl, stripHtml, webSiteSchemaId };
|
|
286
520
|
//# sourceMappingURL=index.js.map
|
|
287
521
|
//# sourceMappingURL=index.js.map
|