UNPKG

denwa-web-shared

Version:

A shared library for Next.js App Router projects containing reusable components, hooks, schemas, and utilities.

1,293 lines (1,292 loc) 45.1 kB
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); //#region \0rolldown/runtime.js var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) { key = keys[i]; if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: ((k) => from[k]).bind(null, key), enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod)); //#endregion let clsx = require("clsx"); let tailwind_merge = require("tailwind-merge"); let zod = require("zod"); let query_string = require("query-string"); query_string = __toESM(query_string, 1); let axios = require("axios"); axios = __toESM(axios, 1); let react_jsx_runtime = require("react/jsx-runtime"); //#region src/server/constants/index.ts var TIME = { seconds: { minutes10: 600 }, milliseconds: { milliseconds100: 100, milliseconds200: 200, milliseconds500: 500, seconds1: 1e3, seconds2: 2e3, seconds5: 5e3, minutes1: 6e4 } }; var THEME = { VIEW_PORT: { EXTRA_SMALL: 280, SMALL: 320, SMALL_MOBILE: 380, MOBILE: 450, MEDIUM: 500, EXTRA_MEDIUM: 600, TABLET: 768, LAPTOP: 1024, LAPTOP_BIG: 1200, BIG: 1440, VERY_BIG: 1920 }, OFFSET: { 1: 8, 2: 16, 3: 24, 4: 32, 5: 40, 6: 48, 7: 56, 8: 64 } }; var CITY_MASK = { CITY: "{{CITY}}", CITY_DECL: "{{CITY_DECL}}", CITY_REGION: "{{CITY_REGION}}", CITY_REGION_DECL: "{{CITY_REGION_DECL}}" }; //#endregion //#region src/server/lib/utils.ts /** * @description Хелпер для генерации путей к адаптивным статичным изображениям (PictureData) * @param basePath - Базовый путь к директории (например, '/compressed/cta') * @param filename - Имя файла без расширения (например, 'cta-arrow') * @param ext - Расширение исходного fallback-файла (по умолчанию 'jpg') * @return Объект PictureData со всеми нужными форматами */ var getImageData = (basePath, filename, ext) => ({ image1x: `${basePath}/${filename}.${ext}`, image2x: `${basePath}/${filename}_2x.${ext}`, image1xWebp: `${basePath}/${filename}.webp`, image2xWebp: `${basePath}/${filename}_2x.webp`, image1xAvif: `${basePath}/${filename}.avif`, image2xAvif: `${basePath}/${filename}_2x.avif`, mobileImage1x: `${basePath}/${filename}_mobile.${ext}`, mobileImage2x: `${basePath}/${filename}_mobile_2x.${ext}`, mobileImage1xWebp: `${basePath}/${filename}_mobile.webp`, mobileImage2xWebp: `${basePath}/${filename}_mobile_2x.webp`, mobileImage1xAvif: `${basePath}/${filename}_mobile.avif`, mobileImage2xAvif: `${basePath}/${filename}_mobile_2x.avif` }); /** * @description Получить Intl для нужного языка * @param locale - Локаль * @param options - Опции */ var getNumberFormatter = (locale, options) => { return new Intl.NumberFormat(locale, options); }; /** * @description Получить Intl даты для нужного языка * @param locale - Локаль * @param options - Опции */ var getDateFormatter = (locale, options) => { return new Intl.DateTimeFormat(locale, options || { year: "numeric", month: "long", day: "numeric" }); }; /** * @description Получить значение по ключу * @param obj - Объект * @param key - Ключ */ var getByKey = (obj, key) => { return obj[key]; }; /** * @description Преобразует value у инпута в поле маски телефона * @param eventValue - исходное значение инпута * @response Возвращает либо обработанную строку, либо пустую строку * @example * 79881234567 -> +79881234567 * 89881234567 -> +79881234567 * 19881234567 -> +19881234567 * 8 (988) 505-42-19 -> +79885054219 */ var convertPhoneMask = (eventValue) => { const digits = eventValue.replace(/\D/g, ""); if (digits.length === 0) return ""; return `+${digits.slice(0, 15)}`; }; /** * @description Получить корректный поддомен из адреса хоста * @param host - url хоста * @param SUB_DOMAIN - Объект ключ-значение со списком поддоменов */ var getSubdomain = (host, SUB_DOMAIN) => { if (!host) return SUB_DOMAIN.main; const item = SUB_DOMAIN[host.split(".")[0]]; if (!item) return SUB_DOMAIN.main; return item; }; /** * @description Заменяет текст по маске {{}} * @param text - Исходный текст * @param subdomain - Значение города, которое подставится * @param SUBDOMAIN_NAME - Объект ключ-значение со списком поддоменов * @param SUBDOMAIN_MASK - Объект ключ-значение со списком масок поддоменов * @return Обновленная строка */ var updateTextByTemplate = ({ text, subdomain, SUBDOMAIN_NAME, SUBDOMAIN_MASK }) => { if (!subdomain || !SUBDOMAIN_NAME || !SUBDOMAIN_MASK) return text; const key = SUBDOMAIN_NAME[subdomain]; if (!subdomain || !key) return text; const subdomainData = getByKey(SUBDOMAIN_NAME, subdomain); if (!subdomainData) return text; let result = text; const { name, declination, region, regionDeclination } = subdomainData; const cityReg = new RegExp(SUBDOMAIN_MASK.CITY, "g"); const cityDeclReg = new RegExp(SUBDOMAIN_MASK.CITY_DECL, "g"); const cityRegionReg = new RegExp(SUBDOMAIN_MASK.CITY_REGION, "g"); const cityRegionDeclReg = new RegExp(SUBDOMAIN_MASK.CITY_REGION_DECL, "g"); result = result.replace(cityReg, name); result = result.replace(cityDeclReg, declination); result = result.replace(cityRegionReg, region); result = result.replace(cityRegionDeclReg, regionDeclination); return result; }; /** * @description Заменят мета текст по маске, переданный напрямую * @param subdomain - Поддомен * @param metaData - Объект с title, descrption, keywords * @param host - Хост сайта * @param lang - Объект для локализации * @param isSubdomain - Нужны ли поддомены в адресе хоста * @param SUBDOMAIN_NAME - Объект ключ-значение со списком поддоменов * @param SUBDOMAIN_MASK - Объект ключ-значение со списком масок поддоменов * @param DEFAULT_SEO_TEXT - Объект ключ-значение со списком масок поддоменов * @return Объект с мета тегами */ var prepareLocalMetaData = ({ subdomain, metaData, host, lang, isSubdomain = true, SUBDOMAIN_NAME, SUBDOMAIN_MASK, DEFAULT_SEO_TEXT }) => { const { title, description, keywords, canonical } = metaData; let correctHost = subdomain === "main" ? host : `${subdomain}.${host}`; if (!isSubdomain) correctHost = host; let currentTitle; let currentDescription; let currentKeywords; const alternates = {}; if (title) currentTitle = updateTextByTemplate({ text: title, subdomain, SUBDOMAIN_MASK, SUBDOMAIN_NAME }); else currentTitle = updateTextByTemplate({ text: DEFAULT_SEO_TEXT.title, subdomain, SUBDOMAIN_MASK, SUBDOMAIN_NAME }); if (description) currentDescription = updateTextByTemplate({ text: description, subdomain, SUBDOMAIN_MASK, SUBDOMAIN_NAME }); else currentDescription = updateTextByTemplate({ text: DEFAULT_SEO_TEXT.description, subdomain, SUBDOMAIN_MASK, SUBDOMAIN_NAME }); if (keywords) currentKeywords = updateTextByTemplate({ text: keywords, subdomain, SUBDOMAIN_MASK, SUBDOMAIN_NAME }); else currentKeywords = updateTextByTemplate({ text: DEFAULT_SEO_TEXT.keywords, subdomain, SUBDOMAIN_MASK, SUBDOMAIN_NAME }); if (canonical !== void 0) alternates.canonical = `https://${correctHost}${canonical}`; if (lang) { const langs = lang.locales.filter((item) => item !== lang.locale); const languages = {}; langs.forEach((item) => { let url = `https://${correctHost}/${item}`; if (lang.city) url += `/${lang.city}`; url += lang.route[0] === "/" ? lang.route : `/${lang.route}`; languages[item] = url; }); let url = ``; if (lang.locale !== lang.defaultLocale) url += `/${lang.locale}`; if (lang.city) url += `/${lang.city}`; url += lang.route[0] === "/" ? lang.route : `/${lang.route}`; alternates.canonical = `https://${correctHost}${url}`; alternates.languages = languages; } return { title: currentTitle, description: currentDescription, keywords: currentKeywords, alternates: Object.keys(alternates).length ? alternates : void 0 }; }; /** * @description Превращает строку формата /catalog/price-from__1000--price-to__5000 в { price-from: '1000', price-to: '5000' } * @param input - изначальная строка * @return Объект с парами ключ-значение */ var parseStringToKeyValue = (input) => { const keyValuePairs = decodeURI(input).replace(/^\/[^\/]*\//, "").split("--"); const result = {}; keyValuePairs.forEach((pair) => { const [key, value] = pair.split("__"); if (key && value) result[key] = value.replace(/_/g, " "); }); return result; }; /** * @description Конвентирует цвет из enum в строку * @param color1 - цвет 1 * @param color2 - цвет 2 * @param notFoundText - текст при отсутствии цвета * @param COLORS_NAMES - Объект ключ-значение со списком названий цветов * @return Строка с названием цветов */ var prepareColor = ({ color1, color2, notFoundText, COLORS_NAMES }) => { const name1 = COLORS_NAMES[color1]; const name2 = color2 ? COLORS_NAMES[color2] : null; if (name1 && name2) return `${name1}/${name2}`; else if (name1) return name1; else return notFoundText; }; /** * @description Подготавливает серверную пагинацию для клиента * @param pagination - Серверная пагинация * @param baseUrl - Url страницы * @param initialParams - Параметры url */ var generatePaginationArray = (pagination, baseUrl, initialParams) => { if (!pagination) return { buttons: [] }; const { page, pages } = pagination; if (!page || !pages) return { buttons: [] }; const params = new URLSearchParams(initialParams); const paginationArray = []; const maxVisibleButtons = 8; const halfMaxVisibleButtons = Math.floor(maxVisibleButtons / 2); let startPage = Math.max(1, page - halfMaxVisibleButtons); let endPage = Math.min(pages, page + halfMaxVisibleButtons); if (endPage - startPage + 1 < maxVisibleButtons) { startPage = Math.max(1, endPage - maxVisibleButtons + 1); endPage = Math.min(pages, startPage + maxVisibleButtons - 1); } for (let i = startPage; i <= endPage; i++) { const currentParams = new URLSearchParams(params); currentParams.set("page", i.toString()); paginationArray.push({ label: i.toString(), href: `${baseUrl}?${currentParams.toString()}`, isActive: i === page }); } return { buttons: paginationArray, firstPage: paginationArray.some((btn) => btn.label === "1") ? void 0 : { label: "1", href: `${baseUrl}?${new URLSearchParams({ ...Object.fromEntries(params), page: "1" }).toString()}`, isActive: page === 1 }, lastPage: paginationArray.some((btn) => btn.label === pages.toString()) ? void 0 : { label: pages.toString(), href: `${baseUrl}?${new URLSearchParams({ ...Object.fromEntries(params), page: pages.toString() }).toString()}`, isActive: page === pages } }; }; /** * @description Превращение массива в объект ключ-значение * @param arr - Массив */ var arrayToKeyValueObject = (arr) => { return arr.reduce((acc, item) => { acc[item] = item; return acc; }, {}); }; /** * @description Подготовка номера страницы, полученной из query параметров * @param page - Номер страницы */ var preparePageParam = (page) => { if (!page) return 1; if (typeof page === "number") { if (page < 1) return 1; return page; } if (typeof page === "string") { const parsed = parseInt(page); if (Number.isNaN(parsed)) return 1; return parsed; } return 1; }; /** * @description Проверка html на пустоту * @param html - Html разметка редактора */ var isNotEmptyHtml = (html) => { return !!html && html !== "<p class=\"content-paragraph\" ></p>"; }; /** * @description Подстановка города в путь ссылки * @param city - Город * @param href - Ссылка */ var createCityLink = (city, href) => { return `/${city}${href}`; }; /** * @description Получить текстовое поле по локали * @param locale - Локаль. Пример ru * @param field - Пример text1 * @param data - Объект с полями. К примеру text1RU и text1EN * @result - Текст поля */ var getLocaleField = ({ locale, field, data }) => { if (!data) return ""; const result = data[`${field}${locale.toUpperCase()}`] ?? ""; if (typeof result !== "string") return ""; return result; }; /** * @description Проверка user agent на бота * @param userAgent - user agent * @result - boolean */ var isBotUserAgent = (userAgent) => { return [ "bot", "crawler", "spider", "scraper", "googlebot", "bingbot", "yandexbot", "baiduspider", "facebookexternalhit", "twitterbot", "whatsapp", "pinterest", "duckduckbot", "slurp", "archive.org", "headless", "phantomjs", "selenium" ].some((pattern) => userAgent.toLowerCase().includes(pattern)); }; /** * Генерирует приятный цвет для заглушек фотографий карточек * @param seed - Необязательное число или строка для предсказуемой генерации цвета * @returns Цвет в формате HEX (#RRGGBB) */ var generatePlaceholderColor = (seed) => { const stringToHash = (str) => { let hash = 0; for (let i = 0; i < str.length; i++) { const char = str.charCodeAt(i); hash = (hash << 5) - hash + char; hash = hash & hash; } return Math.abs(hash); }; let numericSeed; if (seed !== void 0) if (typeof seed === "string") numericSeed = stringToHash(seed); else numericSeed = seed; const getRandom = numericSeed !== void 0 ? (() => { let val = (numericSeed * 9301 + 49297) % 233280; return () => { val = (val * 9301 + 49297) % 233280; return val / 233280; }; })() : Math.random; const randomValue = getRandom(); let hue; if (randomValue < .5) hue = Math.floor(randomValue * 2 * 90); else hue = 160 + Math.floor((randomValue - .5) * 2 * 200); const saturation = 40 + getRandom() * 40; const lightness = 50 + getRandom() * 30; return hslToHex(hue, saturation, lightness); }; /** * Конвертирует цвет из формата HSL в HEX * @param h - Оттенок (0-360) * @param s - Насыщенность (0-100) * @param l - Яркость (0-100) * @returns Цвет в формате HEX (#RRGGBB) */ var hslToHex = (h, s, l) => { const normalizedH = h / 360; const normalizedS = s / 100; const normalizedL = l / 100; let r, g, b; if (normalizedS === 0) r = g = b = normalizedL; else { const hue2rgb = (p, q, t) => { if (t < 0) t += 1; if (t > 1) t -= 1; if (t < 1 / 6) return p + (q - p) * 6 * t; if (t < 1 / 2) return q; if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; return p; }; const q = normalizedL < .5 ? normalizedL * (1 + normalizedS) : normalizedL + normalizedS - normalizedL * normalizedS; const p = 2 * normalizedL - q; r = hue2rgb(p, q, normalizedH + 1 / 3); g = hue2rgb(p, q, normalizedH); b = hue2rgb(p, q, normalizedH - 1 / 3); } const toHex = (x) => { const hex = Math.round(x * 255).toString(16); return hex.length === 1 ? "0" + hex : hex; }; return `#${toHex(r)}${toHex(g)}${toHex(b)}`; }; /** * Преобразует UUID в короткий ID * @param {string} uuid - UUID v4 строка (с дефисами или без) * @param {number} length - нужная длина (по умолчанию 18) * @returns {string} короткий ID без дефисов */ var uuidToStringId = (uuid, length = 18) => { return uuid.replace(/-/g, "").slice(-length); }; /** * Преобразует UUID в числовой ID * @param {string} uuid - UUID v4 строка (с дефисами или без) * @param {number} maxDigits - максимальное количество цифр в результате (по умолчанию 18) * @returns {string} числовой ID в виде строки */ var uuidToNumericId = (uuid, maxDigits = 18) => { const cleanUuid = uuid.replace(/-/g, ""); let hash = 0; for (let i = 0; i < cleanUuid.length; i++) { const char = cleanUuid.charCodeAt(i); hash = (hash << 5) - hash + char; hash = hash & hash; } let result = Math.abs(hash).toString(); if (result.length < maxDigits) { const segments = cleanUuid.match(/.{1,4}/g) || []; for (const segment of segments) { result += Math.abs(parseInt(segment, 16)).toString(); if (result.length >= maxDigits) break; } } return result.slice(0, maxDigits); }; /** * @description Экранирует XML-специальные символы в строке. * @param str - Строка */ var escapeXml = (str) => { if (!str) return ""; return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;"); }; /** * @description Генерирует XML-фид для яндекс товаров. * @param shopName - Название магазина * @param shopCompany - Компания * @param delivery - Доставка * @param categoriesData - Категории * @param offersData - Товары * @param host - Хост * @response Готовый xml фид товаров */ var generateYandexFeedXML = ({ shopName, shopCompany, delivery, categoriesData, offersData, host }) => { const url = `https://${host}`; const offersXml = offersData.map((item) => { const id = typeof item.id === "number" ? item.id : uuidToNumericId(item.id); const categoryId = typeof item.categoryId === "number" ? item.categoryId : uuidToNumericId(item.categoryId); return ` <offer id="${id}" available="true"> <url>${escapeXml(`${url}${item.href}`)}</url> <price>${item.price}</price> <currencyId>RUR</currencyId> <categoryId>${categoryId}</categoryId> ${item.images.length ? item.images.map((item) => `<picture>${escapeXml(item.image1x)}</picture>`).join("") : ""} <name>${escapeXml(item.name)}</name> <vendor>${escapeXml(item.vendor)}</vendor> <description>${escapeXml(item.description)}</description> ${item.vendorCode ? `<vendorCode>${escapeXml(item.vendorCode)}</vendorCode>` : ""} <pickup>${item.pickup}</pickup> ${item.isPopular ? "<param name=\"Популярный\">Да</param>" : ""} ${item.params?.map((item) => { return `<param name="${item.name}">${escapeXml(item.value)}</param>`; }).join("")} </offer> `; }).join(""); const categoriesXml = categoriesData.map((item) => { return `<category id="${typeof item.id === "number" ? item.id : uuidToNumericId(item.id)}"${item.parentId ? ` parentId="${typeof item.parentId === "number" ? item.parentId : uuidToNumericId(item.parentId)}"` : ""}>${escapeXml(item.name)}</category>`; }).join(""); return `<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE yml_catalog SYSTEM "shops.dtd"> <yml_catalog date="${(/* @__PURE__ */ new Date()).toISOString()}"> <shop> <name>${escapeXml(shopName)}</name> <company>${escapeXml(shopCompany)}</company> <url>${url}</url> <delivery>${delivery}</delivery> <currencies> <currency id="RUR" rate="1"/> </currencies> <categories> ${categoriesXml} </categories> <offers> ${offersXml} </offers> </shop> </yml_catalog>`; }; //#endregion //#region src/server/lib/css.ts function cn(...inputs) { return (0, tailwind_merge.twMerge)((0, clsx.clsx)(inputs)); } //#endregion //#region src/server/schemas/index.ts var responseSchema = zod.z.object({ statusCode: zod.z.number().optional(), message: zod.z.string().optional().nullable(), messages: zod.z.string().array().optional().nullable(), data: zod.z.any().optional().nullable(), error: zod.z.object({ statusCode: zod.z.number(), message: zod.z.string().optional().nullable(), messages: zod.z.string().array().optional().nullable() }).optional().nullable(), response: zod.z.any().optional().nullable() }); var serverImageSchema = zod.z.object({ altRU: zod.z.string().optional().nullable(), altEN: zod.z.string().optional().nullable(), altAR: zod.z.string().optional().nullable(), name: zod.z.string(), originalFileExtension: zod.z.string(), fileExtensions: zod.z.string().array().optional(), prefixes: zod.z.string().array().optional(), folder: zod.z.string().optional() }); var serverFileSchema = zod.z.object({ name: zod.z.string(), fullName: zod.z.string(), extension: zod.z.string(), entityId: zod.z.string().uuid(), fullPathExample: zod.z.string(), folder: zod.z.string().optional() }); //#endregion //#region src/server/lib/files.ts /** * @description Создает url картинки * @param {string} name - название картинки * @param {string} extension - расширение картинки * @param {string} entityId - id сущности * @param {string} bucketName - название бакета картинки * @param {string} prefixe - префикс файла * @param {string} uploadUrl - url бакета * @param {string | undefined} folder - подпапка * @param bucketFolder - enum с папкой бакета * @response Возвращает url */ var getUploadImageUrl = ({ name, extension, entityId, prefixe, bucketFolder, folder, uploadUrl }) => { return `${uploadUrl}/${bucketFolder}/${entityId}${folder ? `/${folder}` : ""}/${name}-${prefixe}.${extension}`; }; /** * @description Создает url файла * @param { string } fullName - название файла * @param { string } entityId - id сущности * @param { string | undefined } folder - название подпапки * @param { string } uploadUrl - url бакета * @param bucketFolder - enum с папкой бакета * @response Возвращает url */ var getUploadFileUrl = ({ fullName, entityId, folder, bucketFolder, uploadUrl }) => { return `${uploadUrl}/${bucketFolder}/${entityId}${folder ? `/${folder}` : ""}/${fullName}`; }; /** * @description Проверяет валидность объекта картинки по схеме * @param {object} object - объект с информацией о картинке * @response Возвращает true/false */ var checkCorrectImageObject = (object, getError) => { try { serverImageSchema.parse(object); return true; } catch (error) { getError({ error }); return false; } }; /** * @description Проверяет валидность объекта файла по схеме * @param {object} object - объект с информацией о файле * @response Возвращает true/false */ var checkCorrectFileObject = (object, getError) => { try { serverFileSchema.parse(object); return true; } catch (error) { getError({ error }); return false; } }; /** * @description Преобразует файлы с сервера в формат для работы * @param {string | undefined | null} files - json stringify строка с информацией * @param bucketFolder - название папки в бакете * @param uploadUrl - url бакета * @param getError - функция обработки ошибок * @response Возвращает массив с подготовленными файлами */ var prepareServerFiles = ({ files, bucketFolder, uploadUrl, getError }) => { if (!files) return { serverFiles: [], serverFilesUrls: [] }; try { const filesArray = JSON.parse(files); if (!Array.isArray(filesArray)) return { serverFiles: [], serverFilesUrls: [] }; const serverFiles = filesArray.map((item) => { if (!checkCorrectFileObject(item, getError)) return null; return item; }).filter(Boolean); return { serverFiles, serverFilesUrls: serverFiles.map((item) => { return getUploadFileUrl({ fullName: item.fullName, entityId: item.entityId, folder: item.folder, bucketFolder, uploadUrl }); }) }; } catch (error) { getError({ error }); return { serverFiles: [], serverFilesUrls: [] }; } }; /** * @description Преобразует фотографии с сервера в формат для работы * @param {string | undefined | null} images - json stringify строка с информацией * @param bucketFolder - название папки в бакете * @param uploadUrl - url бакета * @param getError - функция обработки ошибок * @response Возвращает массив с подготовленными картинками */ var prepareServerImages = ({ images, bucketFolder, uploadUrl, getError }) => { if (!images) return []; try { const imagesArray = JSON.parse(images); if (!Array.isArray(imagesArray)) return []; return imagesArray.map((item) => { if (!checkCorrectImageObject(item, getError)) return null; const type = item.originalFileExtension === "png" ? "image/png" : "image/jpeg"; const avifExtension = item.fileExtensions?.find((extension) => extension === "avif") ?? ""; const webpExtension = item.fileExtensions?.find((extension) => extension === "webp") ?? ""; const extension = item.fileExtensions?.find((extension) => extension === "jpg" || extension === "png") ?? item.originalFileExtension; const image1xPrefix = getImagePrefix(item.prefixes ?? [], "1hd"); const image2xPrefix = getImagePrefix(item.prefixes ?? [], "2hd"); const mobileImage1xPrefix = getImagePrefix(item.prefixes ?? [], "0.5hd"); const mobileImage2xPrefix = getImagePrefix(item.prefixes ?? [], "1hd"); const image1x = extension ? getUploadImageUrl({ name: item.name, extension, folder: item.folder, entityId: item.entityId, prefixe: image1xPrefix, bucketFolder, uploadUrl }) : ""; const image1xWebp = webpExtension ? getUploadImageUrl({ name: item.name, extension: webpExtension, folder: item.folder, entityId: item.entityId, prefixe: image1xPrefix, bucketFolder, uploadUrl }) : ""; const image1xAvif = avifExtension ? getUploadImageUrl({ name: item.name, extension: avifExtension, folder: item.folder, entityId: item.entityId, prefixe: image1xPrefix, bucketFolder, uploadUrl }) : ""; const image2x = extension ? getUploadImageUrl({ name: item.name, extension, entityId: item.entityId, folder: item.folder, prefixe: image2xPrefix, bucketFolder, uploadUrl }) : ""; const image2xWebp = webpExtension ? getUploadImageUrl({ name: item.name, extension: webpExtension, folder: item.folder, entityId: item.entityId, prefixe: image2xPrefix, bucketFolder, uploadUrl }) : ""; const image2xAvif = avifExtension ? getUploadImageUrl({ name: item.name, extension: avifExtension, folder: item.folder, entityId: item.entityId, prefixe: image2xPrefix, bucketFolder, uploadUrl }) : ""; const mobileImage1x = extension ? getUploadImageUrl({ name: item.name, extension, entityId: item.entityId, folder: item.folder, prefixe: mobileImage1xPrefix, bucketFolder, uploadUrl }) : ""; const mobileImage1xWebp = webpExtension ? getUploadImageUrl({ name: item.name, extension: webpExtension, folder: item.folder, entityId: item.entityId, prefixe: mobileImage1xPrefix, bucketFolder, uploadUrl }) : ""; const mobileImage1xAvif = avifExtension ? getUploadImageUrl({ name: item.name, extension: avifExtension, folder: item.folder, entityId: item.entityId, prefixe: mobileImage1xPrefix, bucketFolder, uploadUrl }) : ""; return { image1x, image2x, image1xWebp, image2xWebp, image1xAvif, image2xAvif, mobileImage1x, mobileImage2x: extension ? getUploadImageUrl({ name: item.name, extension, entityId: item.entityId, folder: item.folder, prefixe: mobileImage2xPrefix, bucketFolder, uploadUrl }) : "", mobileImage1xWebp, mobileImage2xWebp: webpExtension ? getUploadImageUrl({ name: item.name, extension: webpExtension, entityId: item.entityId, folder: item.folder, prefixe: mobileImage2xPrefix, bucketFolder, uploadUrl }) : "", mobileImage1xAvif, mobileImage2xAvif: avifExtension ? getUploadImageUrl({ name: item.name, extension: avifExtension, entityId: item.entityId, folder: item.folder, prefixe: mobileImage2xPrefix, bucketFolder, uploadUrl }) : "", altRU: item.altRU, altEN: item.altEN, type }; }).filter((item) => !!item); } catch (error) { getError({ error }); return []; } }; var getImagePrefix = (prefixes, type) => { let item; switch (type) { case "original": item = prefixes[0]; break; case "0.25hd": item = prefixes[1]; break; case "0.5hd": item = prefixes[2]; break; case "1hd": item = prefixes[3]; break; case "2hd": item = prefixes[4]; break; case "4hd": item = prefixes[5]; break; } return item ?? "original"; }; //#endregion //#region src/server/lib/openapi-axios/types/serializer.ts /** * @description Enumeration for different styles of query parameter serialization */ var QuerySerializerStyle = /* @__PURE__ */ function(QuerySerializerStyle) { QuerySerializerStyle["From"] = "form"; QuerySerializerStyle["SpaceDelimited"] = "spaceDelimited"; QuerySerializerStyle["PipeDelimited"] = "pipeDelimited"; QuerySerializerStyle["DeepObject"] = "deepObject"; return QuerySerializerStyle; }({}); //#endregion //#region src/server/lib/openapi-axios/const/defaultOptions.ts /** * @description Default options for OpenAPI requests using Axios. */ var defaultOptions = { validStatus: "axios", querySerializationParams: { style: QuerySerializerStyle.From, explode: true } }; //#endregion //#region src/server/lib/openapi-axios/interpolate-params.ts var paramRegex = /{[a-zA-Z_]+}/g; /** * @description Default mapping function used during parameter interpolation. * It simply returns the value provided without modification. * * @param _param - The parameter name (unused). * @param value - The value to map. * @returns The mapped value. */ function defaultMap(_param, value) { return value; } /** * @description Creates a shallow copy of the given parameters object. * * @param params - The parameters object to copy. * @returns A shallow copy of the `params` object. */ function copyParams(params) { const result = {}; for (const param in params) if (Object.prototype.hasOwnProperty.call(params, param)) result[param] = params[param]; return result; } /** * @description Replaces placeholders in a pattern string with corresponding values from the `params` object. * * @param pattern - The string pattern containing placeholders in the format `{paramName}`. * @param params - An object containing parameter values keyed by their names. * @param map - An optional mapping function to transform values before interpolation. * @returns The interpolated string with placeholders replaced by their corresponding values. */ function interpolateParams(pattern, params, map) { const actualMap = map || defaultMap; const remainingParams = copyParams(params); return pattern.replace(paramRegex, function(paramWithBrackets) { const param = paramWithBrackets.slice(1, paramWithBrackets.length - 1); if (param in params) { const value = actualMap(param, params[param]); if (value === null) return ""; delete remainingParams[param]; return value; } }); } //#endregion //#region src/server/lib/openapi-axios/utils/querySerializer.ts function getQuerySerializer({ style, explode }) { return (params) => { switch (style) { case QuerySerializerStyle.From: if (explode) return query_string.default.stringify(flatObjects(params), { encode: true }); else return query_string.default.stringify(ObjectsToArray(params), { arrayFormat: "comma", encode: true }); case QuerySerializerStyle.SpaceDelimited: if (explode) return query_string.default.stringify(flatObjects(params), { encode: true }); else return query_string.default.stringify(params, { encode: true, arrayFormat: "separator", arrayFormatSeparator: " " }); case QuerySerializerStyle.PipeDelimited: if (explode) return query_string.default.stringify(flatObjects(params), { encode: true }); else return query_string.default.stringify(params, { encode: true, arrayFormat: "separator", arrayFormatSeparator: "|" }); case QuerySerializerStyle.DeepObject: return query_string.default.stringify(toDeepObject(params), { encode: true }); } }; } function flatObjects(params) { return Object.fromEntries(Object.entries(params).map(([key, value]) => { if (typeof value === "object" && !Array.isArray(value) && value !== null) return Object.entries(value); return [[key, value]]; }).flat()); } function ObjectsToArray(params) { return Object.fromEntries(Object.entries(params).map(([key, value]) => { if (typeof value === "object" && !Array.isArray(value) && value !== null) return [key, Object.entries(value).map(([innerKey, innerValue]) => `${innerKey},${innerValue}`).join(",")]; return [key, value]; })); } function toDeepObject(params, prevKeys = []) { return Object.fromEntries(Object.entries(params).map(([key, value]) => { if (typeof value === "object" && !Array.isArray(value) && value !== null) return Object.entries(toDeepObject(value, [...prevKeys, key])); const [firstKey, ...restKeys] = [...prevKeys, key]; return [[`${firstKey}${restKeys.map((k) => `[${k}]`).join("")}`, value]]; }).flat()); } //#endregion //#region src/server/lib/openapi-axios/utils/response-converters/convertToAll.ts async function convertToAll(response) { return response.then((response) => ({ response, error: null, status: response.status, data: response.data })).catch((error) => { if (!axios.default.isAxiosError(error)) return { error, data: void 0, response: void 0, status: void 0 }; return { error, status: Number(error.response?.status) || void 0, data: error.response?.data || void 0, response: error.response }; }); } //#endregion //#region src/server/lib/openapi-axios/utils/response-converters/convertToAxios.ts async function convertToAxios(response) { return response.then((response) => ({ response, status: response.status, data: response.data })); } //#endregion //#region src/server/lib/openapi-axios/utils/response-converters/convertToFetch.ts async function convertToFetch(response) { return response.then((response) => ({ response, error: void 0, status: response.status, data: response.data })).catch((error) => { if (!axios.default.isAxiosError(error) || !error.response?.status) throw error; return { error, status: +error.response.status, data: error.response.data, response: error.response }; }); } //#endregion //#region src/server/lib/openapi-axios/index.ts /** * @description OpenApiAxios class is a wrapper around Axios that provides methods to handle API requests * and responses based on OpenAPI specifications. * * @template Schema - The OpenAPI schema type. * @template ClassValidStatus - The type that configures the error handling strategy. */ var OpenApiAxios = class { axios; opt; /** * @description Creates an instance of OpenApiAxios. * * @param axios - The Axios instance to use for requests. * @param options - Configuration options for the OpenApiAxios instance. */ constructor(axios, options) { this.axios = axios; this.opt = Object.assign({}, defaultOptions, options); } get = this.factoryWithoutBody("get"); head = this.factoryWithoutBody("head"); delete = this.factoryWithoutBody("delete"); options = this.factoryWithoutBody("options"); put = this.factoryWithBody("put"); post = this.factoryWithBody("post"); patch = this.factoryWithBody("patch"); /** * @description Generates a URI for a given API endpoint, including query parameters. * * @param method - The HTTP method to use. * @param path - The API route. * @param options - Additional options for the request. * @returns The generated URI as a string. */ async getUri(method, path, options) { const { urlString, newOptions } = this.prepareOptions(path, options); const { paramsSerializer, params, ...axios } = this.optionsToAxiosOptions(newOptions); const queryUri = Object.keys(params || {}).length > 0 ? `?${paramsSerializer(params)}` : ""; return this.axios.getUri({ url: `${urlString}${queryUri}`, method, ...axios }); } /** * @description Prepares the options and URL for an API request. * * @param path - The API route. * @param options - Additional options for the request. * @returns An object containing the prepared URL string and new options. */ prepareOptions(path, options) { let urlString = path; const newOptions = Object.assign({}, options, { validStatus: this.opt.validStatus }); const optionsWithParams = newOptions; if (optionsWithParams?.params) urlString = interpolateParams(urlString, optionsWithParams.params); const querySerializationParams = options?.querySerializationParams || this.opt.querySerializationParams; if (!options?.axios?.paramsSerializer && querySerializationParams) newOptions.axios = Object.assign({}, newOptions.axios, { paramsSerializer: getQuerySerializer(querySerializationParams) }); return { urlString, newOptions }; } /** * @description Prepares the API response based on the valid status type. * * @param response - The Axios response promise. * @param options - Options for the request. * @returns A promise that resolves to the processed API response. */ async prepareResponse(response, options) { switch (options.validStatus) { case "all": return convertToAll(response); case "fetch": return convertToFetch(response); default: return convertToAxios(response); } } /** * @description Converts OpenAPI options to Axios request options. * * @param options - The options to convert. * @returns The converted Axios request configuration. */ optionsToAxiosOptions(options) { return { params: options?.query, ...options.axios }; } /** * @description Creates a fetcher method for HTTP methods that do not have a request body (e.g., GET, DELETE). * * @param method - The HTTP method to create a fetcher for. * @returns A function that performs the API request. */ factoryWithoutBody(method) { return (...args) => { const [path, options] = args; const { urlString, newOptions } = this.prepareOptions(path, options); return this.prepareResponse(this.axios[method](urlString, this.optionsToAxiosOptions(newOptions)), newOptions); }; } /** * @description Creates a fetcher method for HTTP methods that have a request body (e.g., POST, PUT). * * @param method - The HTTP method to create a fetcher for. * @returns A function that performs the API request. */ factoryWithBody(method) { return (...args) => { const [path, body, options] = args; const { urlString, newOptions } = this.prepareOptions(path, options); return this.prepareResponse(this.axios[method](urlString, body, this.optionsToAxiosOptions(newOptions)), newOptions); }; } }; //#endregion //#region src/server/ui/image.tsx var BasePicture = ({ imgProps, data, type, alt, mobileMaxWidth = 450, loading = "lazy", ...pictureProps }) => { return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("picture", { ...pictureProps, children: [ !!data?.mobileImage1xAvif && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("source", { srcSet: `${data?.mobileImage1xAvif} 1x${data?.mobileImage2xAvif ? `, ${data?.mobileImage2xAvif} 2x` : ""}`, media: `(max-width: ${mobileMaxWidth}px)`, type: "image/avif" }), !!data?.mobileImage1xWebp && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("source", { srcSet: `${data?.mobileImage1xWebp} 1x${data?.mobileImage2xWebp ? `, ${data?.mobileImage2xWebp} 2x` : ""}`, media: `(max-width: ${mobileMaxWidth}px)`, type: "image/webp" }), !!data?.mobileImage1x && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("source", { srcSet: `${data?.mobileImage1x} 1x${data?.mobileImage2x ? `, ${data?.mobileImage2x} 2x` : ""}`, media: `(max-width: ${mobileMaxWidth}px)`, type }), !!data?.image1xAvif && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("source", { srcSet: `${data?.image1xAvif} 1x${data?.image2xAvif ? `, ${data?.image2xAvif} 2x` : ""}`, type: "image/avif" }), !!data?.image1xWebp && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("source", { srcSet: `${data?.image1xWebp} 1x${data?.image2xWebp ? `, ${data?.image2xWebp} 2x` : ""}`, type: "image/webp" }), !!data?.image1x && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("source", { srcSet: `${data?.image1x} 1x${data?.image2x ? `, ${data?.image2x} 2x` : ""}`, type }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", { ...imgProps, src: data?.image1x, alt, loading, decoding: loading === "eager" ? "sync" : "async", fetchPriority: loading === "eager" ? "high" : "low" }) ] }); }; //#endregion //#region src/server/ui/video.tsx var Video = ({ className, preload = "auto", loop = true, autoPlay = true, muted = true, playsInline = true, controls = false, ...props }) => { return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("video", { ...props, className: cn("object-cover", className), preload, loop, autoPlay, muted, playsInline, controls }); }; //#endregion //#region src/server/ui/container.tsx var Container = ({ children, className, variant = "default", ...props }) => { const containerClasses = cn("my-0 mx-auto py-0 px-4", { "max-w-[1440px]": variant === "default", "max-w-[1920px]": variant === "large" }, className); return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { ...props, className: containerClasses, children }); }; //#endregion exports.BasePicture = BasePicture; exports.CITY_MASK = CITY_MASK; exports.Container = Container; exports.OpenApiAxios = OpenApiAxios; exports.THEME = THEME; exports.TIME = TIME; exports.Video = Video; exports.arrayToKeyValueObject = arrayToKeyValueObject; exports.checkCorrectFileObject = checkCorrectFileObject; exports.checkCorrectImageObject = checkCorrectImageObject; exports.cn = cn; exports.convertPhoneMask = convertPhoneMask; exports.createCityLink = createCityLink; exports.escapeXml = escapeXml; exports.generatePaginationArray = generatePaginationArray; exports.generatePlaceholderColor = generatePlaceholderColor; exports.generateYandexFeedXML = generateYandexFeedXML; exports.getByKey = getByKey; exports.getDateFormatter = getDateFormatter; exports.getImageData = getImageData; exports.getImagePrefix = getImagePrefix; exports.getLocaleField = getLocaleField; exports.getNumberFormatter = getNumberFormatter; exports.getSubdomain = getSubdomain; exports.getUploadFileUrl = getUploadFileUrl; exports.getUploadImageUrl = getUploadImageUrl; exports.hslToHex = hslToHex; exports.isBotUserAgent = isBotUserAgent; exports.isNotEmptyHtml = isNotEmptyHtml; exports.parseStringToKeyValue = parseStringToKeyValue; exports.prepareColor = prepareColor; exports.prepareLocalMetaData = prepareLocalMetaData; exports.preparePageParam = preparePageParam; exports.prepareServerFiles = prepareServerFiles; exports.prepareServerImages = prepareServerImages; exports.responseSchema = responseSchema; exports.serverFileSchema = serverFileSchema; exports.serverImageSchema = serverImageSchema; exports.updateTextByTemplate = updateTextByTemplate; exports.uuidToNumericId = uuidToNumericId; exports.uuidToStringId = uuidToStringId;