/** * Format Utilities * * Функции для форматирования данных (время, текст, UUID). */ /** * Генерация UUID v4 * @returns {string} UUID */ export function generateUUID() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { const r = Math.random() * 16 | 0 const v = c === 'x' ? r : (r & 0x3 | 0x8) return v.toString(16) }) } /** * Форматировать время в секундах в читаемый вид * @param {number} seconds - Количество секунд * @returns {string} Отформатированное время (например, "2.5 сек", "1 мин 30 сек") */ export function formatTime(seconds) { if (seconds < 60) { return `${seconds.toFixed(1)} сек` } const minutes = Math.floor(seconds / 60) const secs = Math.floor(seconds % 60) return `${minutes} мин ${secs} сек` } /** * Форматировать ISO timestamp в локальное время * @param {string} isoString - ISO строка времени * @returns {string} Отформатированное локальное время */ export function formatTimestamp(isoString) { try { const date = new Date(isoString) return date.toLocaleString('ru-RU', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' }) } catch (e) { return isoString } } /** * Проверить является ли текст таблицей * @param {string} text - Текст для проверки * @returns {boolean} True если текст похож на таблицу */ export function isTableText(text) { const lines = text.split('\n').filter(line => line.trim()) if (lines.length < 3) return false // Проверить наличие разделителей const hasSeparator = lines.some(line => /^[\s\-|+]+$/.test(line)) const hasPipes = lines.filter(line => line.includes('|')).length > 2 return hasSeparator || hasPipes } /** * Парсинг текстовой таблицы в HTML * @param {string} text - Текст таблицы * @returns {string} HTML таблица */ export function parseTextTable(text) { const lines = text.split('\n').filter(line => line.trim()) if (lines.length < 2) { return `
${escapeHtml(text)}
` } let html = '' let inHeader = true for (const line of lines) { // Skip separator lines if (/^[\s\-|+]+$/.test(line)) { inHeader = false continue } // Parse cells const cells = line.split('|') .map(cell => cell.trim()) .filter((cell, i, arr) => i > 0 && i < arr.length - 1) // Remove first and last empty cells if (cells.length === 0) continue html += '' const tag = inHeader ? 'th' : 'td' for (const cell of cells) { html += `<${tag}>${escapeHtml(cell)}` } html += '' if (inHeader) { inHeader = false } } html += '
' return html } /** * Экранировать HTML спецсимволы * @param {string} text - Исходный текст * @returns {string} Экранированный текст */ export function escapeHtml(text) { const div = document.createElement('div') div.textContent = text return div.innerHTML } /** * Склонение числительных (русский язык) * @param {number} count - Число * @param {string} one - Форма для 1 (например, "вопрос") * @param {string} few - Форма для 2-4 (например, "вопроса") * @param {string} many - Форма для 5+ (например, "вопросов") * @returns {string} Правильная форма */ export function pluralize(count, one, few, many) { const mod10 = count % 10 const mod100 = count % 100 if (mod10 === 1 && mod100 !== 11) { return one } if (mod10 >= 2 && mod10 <= 4 && (mod100 < 10 || mod100 >= 20)) { return few } return many }