denwa-web-shared
Version:
A shared library for Next.js App Router projects containing reusable components, hooks, schemas, and utilities.
1,228 lines (1,227 loc) • 42.5 kB
JavaScript
import { clsx } from "clsx";
import { twMerge } from "tailwind-merge";
import { z } from "zod";
import queryString from "query-string";
import axios from "axios";
import { jsx, jsxs } from "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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
};
/**
* @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 twMerge(clsx(inputs));
}
//#endregion
//#region src/server/schemas/index.ts
var responseSchema = z.object({
statusCode: z.number().optional(),
message: z.string().optional().nullable(),
messages: z.string().array().optional().nullable(),
data: z.any().optional().nullable(),
error: z.object({
statusCode: z.number(),
message: z.string().optional().nullable(),
messages: z.string().array().optional().nullable()
}).optional().nullable(),
response: z.any().optional().nullable()
});
var serverImageSchema = z.object({
altRU: z.string().optional().nullable(),
altEN: z.string().optional().nullable(),
altAR: z.string().optional().nullable(),
name: z.string(),
originalFileExtension: z.string(),
fileExtensions: z.string().array().optional(),
prefixes: z.string().array().optional(),
folder: z.string().optional()
});
var serverFileSchema = z.object({
name: z.string(),
fullName: z.string(),
extension: z.string(),
entityId: z.string().uuid(),
fullPathExample: z.string(),
folder: 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 queryString.stringify(flatObjects(params), { encode: true });
else return queryString.stringify(ObjectsToArray(params), {
arrayFormat: "comma",
encode: true
});
case QuerySerializerStyle.SpaceDelimited: if (explode) return queryString.stringify(flatObjects(params), { encode: true });
else return queryString.stringify(params, {
encode: true,
arrayFormat: "separator",
arrayFormatSeparator: " "
});
case QuerySerializerStyle.PipeDelimited: if (explode) return queryString.stringify(flatObjects(params), { encode: true });
else return queryString.stringify(params, {
encode: true,
arrayFormat: "separator",
arrayFormatSeparator: "|"
});
case QuerySerializerStyle.DeepObject: return queryString.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.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.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__ */ jsxs("picture", {
...pictureProps,
children: [
!!data?.mobileImage1xAvif && /* @__PURE__ */ jsx("source", {
srcSet: `${data?.mobileImage1xAvif} 1x${data?.mobileImage2xAvif ? `, ${data?.mobileImage2xAvif} 2x` : ""}`,
media: `(max-width: ${mobileMaxWidth}px)`,
type: "image/avif"
}),
!!data?.mobileImage1xWebp && /* @__PURE__ */ jsx("source", {
srcSet: `${data?.mobileImage1xWebp} 1x${data?.mobileImage2xWebp ? `, ${data?.mobileImage2xWebp} 2x` : ""}`,
media: `(max-width: ${mobileMaxWidth}px)`,
type: "image/webp"
}),
!!data?.mobileImage1x && /* @__PURE__ */ jsx("source", {
srcSet: `${data?.mobileImage1x} 1x${data?.mobileImage2x ? `, ${data?.mobileImage2x} 2x` : ""}`,
media: `(max-width: ${mobileMaxWidth}px)`,
type
}),
!!data?.image1xAvif && /* @__PURE__ */ jsx("source", {
srcSet: `${data?.image1xAvif} 1x${data?.image2xAvif ? `, ${data?.image2xAvif} 2x` : ""}`,
type: "image/avif"
}),
!!data?.image1xWebp && /* @__PURE__ */ jsx("source", {
srcSet: `${data?.image1xWebp} 1x${data?.image2xWebp ? `, ${data?.image2xWebp} 2x` : ""}`,
type: "image/webp"
}),
!!data?.image1x && /* @__PURE__ */ jsx("source", {
srcSet: `${data?.image1x} 1x${data?.image2x ? `, ${data?.image2x} 2x` : ""}`,
type
}),
/* @__PURE__ */ 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__ */ 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__ */ jsx("div", {
...props,
className: containerClasses,
children
});
};
//#endregion
export { BasePicture, CITY_MASK, Container, OpenApiAxios, THEME, TIME, Video, arrayToKeyValueObject, checkCorrectFileObject, checkCorrectImageObject, cn, convertPhoneMask, createCityLink, escapeXml, generatePaginationArray, generatePlaceholderColor, generateYandexFeedXML, getByKey, getDateFormatter, getImageData, getImagePrefix, getLocaleField, getNumberFormatter, getSubdomain, getUploadFileUrl, getUploadImageUrl, hslToHex, isBotUserAgent, isNotEmptyHtml, parseStringToKeyValue, prepareColor, prepareLocalMetaData, preparePageParam, prepareServerFiles, prepareServerImages, responseSchema, serverFileSchema, serverImageSchema, updateTextByTemplate, uuidToNumericId, uuidToStringId };