/
UzhvaD
/
Web_application_development_Course_work
Обзор
Документация
Войти
/
UzhvaD
/
Web_application_development_Course_work
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
js/ui.js
993 строки
37 KB
UzhvaD
update: ui.js
03 май 2026, 16:15
Верифицирован
03 май 2026, 16:15
0b58814
Код
Авторство
О чём код?
// ==================== UI ФУНКЦИИ ==================== async function addLogToUI(message, type = 'info') { const logContainer = document.getElementById('logContainer'); if (!logContainer) return; const entry = document.createElement('div'); entry.className = `log-entry log-${type}`; const time = new Date().toLocaleTimeString(); const icons = { ok: '✅', error: '❌', warn: '⚠️', info: '📌' }; entry.innerHTML = `[${time}] ${icons[type] || '📌'} ${message}`; logContainer.appendChild(entry); entry.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); await saveLogEntry({ message, type }); } async function updateSchedulerStatusUI(isRunning) { const statusSpan = document.getElementById('schedulerStatusText'); if (statusSpan) { if (isRunning) { statusSpan.className = 'status-running'; statusSpan.innerHTML = '● Запущен'; } else { statusSpan.className = 'status-stopped'; statusSpan.innerHTML = '● Остановлен'; } } } async function createNewDatabase() { const dbName = document.getElementById('dbName').value.trim(); const csvContent = document.getElementById('csvContent').value; if (!dbName) { addLogToUI('❌ Введите имя базы данных', 'error'); return; } if (!csvContent) { addLogToUI('❌ Введите содержимое CSV', 'error'); return; } const validation = validateCSV(csvContent); if (!validation.valid) { addLogToUI(`❌ Ошибка валидации: ${validation.error}`, 'error'); return; } const existing = await getDatabase(dbName); if (existing) { addLogToUI(`❌ БД с именем "${dbName}" уже существует`, 'error'); return; } await saveDatabase(dbName, csvContent, validation.headers); addLogToUI(`✅ Создана БД "${dbName}" (${validation.headers.length} колонок, ${validation.rowCount} строк)`, 'ok'); document.getElementById('dbName').value = ''; document.getElementById('csvContent').value = ''; await renderDatabaseList(); await updateEditSelect(); } async function importFromAnyFormat(file) { const content = await file.text(); const format = detectFormat(content, file.name); addLogToUI(`🔍 Определён формат: ${format.toUpperCase()}`, 'info'); try { if (format === 'json') { // ========== СПЕЦИАЛЬНАЯ ОБРАБОТКА JSON (МНОГО БД) ========== const jsonData = JSON.parse(content); // Проверяем структуру экспорта всех БД if (jsonData.databases && Array.isArray(jsonData.databases)) { let imported = 0; let skipped = 0; for (const db of jsonData.databases) { if (!db.name || !db.dataCsv || !db.columns) { skipped++; continue; } const validation = validateCSV(db.dataCsv); if (!validation.valid) { addLogToUI(`⚠️ БД "${db.name}" пропущена: ${validation.error}`, 'warn'); skipped++; continue; } const existing = await getDatabase(db.name); let finalName = db.name; if (existing) { const overwrite = confirm(`БД "${db.name}" уже существует. Перезаписать?`); if (!overwrite) { // Создаём с новым именем let counter = 1; finalName = `${db.name}_${counter}`; while (await getDatabase(finalName)) { counter++; finalName = `${db.name}_${counter}`; } addLogToUI(`📝 БД импортирована как "${finalName}"`, 'info'); } else { await deleteDatabase(db.name); } } await saveDatabase(finalName, db.dataCsv, db.columns); imported++; addLogToUI(`✅ Импортирована БД: "${finalName}" (${db.columns.length} колонок)`, 'ok'); } addLogToUI(`📊 Импорт завершён: добавлено ${imported}, пропущено ${skipped}`, imported > 0 ? 'ok' : 'warn'); await renderDatabaseList(); await updateEditSelect(); return; // Проверяем структуру экспорта одной БД } else if (jsonData.database && jsonData.database.dataCsv) { const db = jsonData.database; const validation = validateCSV(db.dataCsv); if (!validation.valid) { throw new Error(validation.error); } const existing = await getDatabase(db.name); let finalName = db.name; if (existing) { const overwrite = confirm(`БД "${db.name}" уже существует. Перезаписать?`); if (!overwrite) { let counter = 1; finalName = `${db.name}_${counter}`; while (await getDatabase(finalName)) { counter++; finalName = `${db.name}_${counter}`; } addLogToUI(`📝 БД импортирована как "${finalName}"`, 'info'); } else { await deleteDatabase(db.name); } } await saveDatabase(finalName, db.dataCsv, db.columns); addLogToUI(`✅ Импортирована БД "${finalName}" (${db.columns.length} колонок)`, 'ok'); await renderDatabaseList(); await updateEditSelect(); return; } else { throw new Error('JSON не распознан: ожидается формат экспорта (databases[] или database)'); } } // ========== ОСТАЛЬНЫЕ ФОРМАТЫ (CSV, HTML, MD, SQL) ========== let csvContent; let dbName = file.name.replace(/\.[^/.]+$/, ''); switch(format) { case 'csv': csvContent = content; const firstLine = content.split('\n')[0]; const detectedDelimiter = detectDelimiter(firstLine); addLogToUI(`📊 Определён разделитель: ${detectedDelimiter === '\t' ? 'табуляция' : detectedDelimiter === ';' ? 'точка с запятой' : 'запятая'}`, 'info'); break; case 'html': csvContent = parseHTMLtoCSV(content); break; case 'md': csvContent = parseMarkdownToCSV(content); break; case 'sql': csvContent = parseSQLtoCSV(content); break; default: throw new Error('Неподдерживаемый формат'); } const validation = validateCSV(csvContent); if (!validation.valid) { throw new Error(validation.error); } const existing = await getDatabase(dbName); if (existing) { const newName = prompt(`БД "${dbName}" уже существует. Введите новое имя для импорта:`, `${dbName}_imported`); if (!newName) { addLogToUI('❌ Импорт отменён пользователем', 'warn'); return; } dbName = newName; } await saveDatabase(dbName, csvContent, validation.headers); addLogToUI(`✅ Импортирована БД "${dbName}" из ${format.toUpperCase()} (${validation.headers.length} колонок, ${validation.rowCount} строк)`, 'ok'); await renderDatabaseList(); await updateEditSelect(); } catch (error) { addLogToUI(`❌ Ошибка импорта: ${error.message}`, 'error'); console.error(error); } } async function importFromAnyFormatWrapper() { const fileInput = document.getElementById('importFile'); const file = fileInput.files[0]; if (!file) { addLogToUI('❌ Выберите файл для импорта', 'error'); return; } await importFromAnyFormat(file); fileInput.value = ''; } async function exportAllDatabasesUI() { const exportData = await exportAllDatabases(); const jsonStr = JSON.stringify(exportData, null, 2); const blob = new Blob([jsonStr], { type: 'application/json' }); const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = `etl_export_${Date.now()}.json`; link.click(); URL.revokeObjectURL(link.href); addLogToUI(`💾 Экспортировано ${exportData.databases.length} БД`, 'ok'); } async function exportSingleDatabaseJSON(name) { const db = await getDatabase(name); if (!db) { addLogToUI(`БД "${name}" не найдена`, 'error'); return; } const exportData = { exportedAt: new Date().toISOString(), version: '1.0', database: { name: db.name, columns: db.columns, updatedAt: db.updatedAt, dataCsv: db.dataCsv } }; const jsonStr = JSON.stringify(exportData, null, 2); downloadFile(jsonStr, `${db.name}.json`, 'application/json'); addLogToUI(`📋 Экспортирована БД "${db.name}" в JSON`, 'ok'); } async function exportToHTML(name) { const db = await getDatabase(name); if (!db) { addLogToUI(`БД "${name}" не найдена`, 'error'); return; } const { headers, data } = csvToObjects(db.dataCsv); let html = `<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <title>${escapeHtml(db.name)} - экспорт из ETL-сервиса</title> <style> body { font-family: Arial, sans-serif; margin: 20px; } h1 { color: #1b4f6e; } table { border-collapse: collapse; width: 100%; margin-top: 20px; } th, td { border: 1px solid #ddd; padding: 8px; text-align: left; } th { background-color: #1b4f6e; color: white; } tr:nth-child(even) { background-color: #f9f9f9; } .info { margin: 20px 0; padding: 10px; background: #eef2ff; border-radius: 8px; } </style> </head> <body> <h1>📊 ${escapeHtml(db.name)}</h1> <div class="info"> <strong>Колонок:</strong> ${headers.length}<br> <strong>Строк:</strong> ${data.length}<br> <strong>Дата экспорта:</strong> ${new Date().toLocaleString()} </div> <table> <thead> <tr> ${headers.map(h => `<th>${escapeHtml(h)}</th>`).join('')} </tr> </thead> <tbody> ${data.map(row => ` <tr> ${headers.map(h => `<td>${escapeHtml(String(row[h] || ''))}</td>`).join('')} </tr> `).join('')} </tbody> </table> </body> </html>`; downloadFile(html, `${name}.html`, 'text/html'); addLogToUI(`📄 Экспортирована БД "${name}" в HTML`, 'ok'); } async function exportToMarkdown(name) { const db = await getDatabase(name); if (!db) { addLogToUI(`БД "${name}" не найдена`, 'error'); return; } const { headers, data } = csvToObjects(db.dataCsv); let md = `# 📊 ${db.name}\n\n`; md += `**Колонок:** ${headers.length} \n`; md += `**Строк:** ${data.length} \n`; md += `**Дата экспорта:** ${new Date().toLocaleString()} \n\n`; md += `| ${headers.join(' | ')} |\n`; md += `|${headers.map(() => ' --- ').join('|')}|\n`; data.forEach(row => { md += `| ${headers.map(h => String(row[h] || '').replace(/\|/g, '\\|')).join(' | ')} |\n`; }); downloadFile(md, `${db.name}.md`, 'text/markdown'); addLogToUI(`📝 Экспортирована БД "${db.name}" в Markdown`, 'ok'); } async function exportToSQL(name) { const db = await getDatabase(name); if (!db) { addLogToUI(`БД "${name}" не найдена`, 'error'); return; } // Запрашиваем имя таблицы у пользователя const suggestedName = transliterate(db.name) || 'table'; let tableName = prompt('Введите имя таблицы для SQL:', suggestedName); if (!tableName) { addLogToUI('❌ Экспорт в SQL отменён', 'warn'); return; } // Валидация имени таблицы const validNamePattern = /^[a-zA-Z_][a-zA-Z0-9_]*$/; if (!validNamePattern.test(tableName)) { addLogToUI(`❌ Некорректное имя таблицы: "${tableName}". Используйте латиницу, цифры и подчёркивания. Имя не может начинаться с цифры.`, 'error'); return; } const { headers, data } = csvToObjects(db.dataCsv); // Определяем типы колонок const columnDefs = headers.map(col => { // Очищаем имя колонки для SQL const safeColName = col.toLowerCase().replace(/[^a-z0-9_]/g, '_'); let sample = ''; for (let i = 0; i < data.length; i++) { if (data[i][col] !== undefined && data[i][col] !== null && data[i][col] !== '') { sample = String(data[i][col]); break; } } let sqlType = 'TEXT'; if (sample && !isNaN(parseFloat(sample)) && isFinite(sample)) { if (Number.isInteger(parseFloat(sample))) { sqlType = 'INTEGER'; } else { sqlType = 'REAL'; } } return ` "${safeColName}" ${sqlType}`; }); let sql = `-- ============================================\n`; sql += `-- Экспорт базы данных: ${db.name}\n`; sql += `-- Таблица: ${tableName}\n`; sql += `-- Дата: ${new Date().toLocaleString()}\n`; sql += `-- Кол-во строк: ${data.length}\n`; sql += `-- Кол-во колонок: ${headers.length}\n`; sql += `-- ============================================\n\n`; sql += `CREATE TABLE IF NOT EXISTS "${tableName}" (\n`; sql += columnDefs.join(',\n'); sql += `\n);\n\n`; if (data.length > 0) { sql += `-- Вставка данных\n\n`; data.forEach((row, idx) => { const values = headers.map(h => { let val = row[h]; if (val === undefined || val === null || val === '') { return 'NULL'; } if (!isNaN(parseFloat(val)) && isFinite(val)) { return val; } // Экранируем одинарные кавычки для русского текста return `'${String(val).replace(/'/g, "''")}'`; }); sql += `INSERT INTO "${tableName}" (${headers.map(h => `"${h.toLowerCase().replace(/[^a-z0-9_]/g, '_')}"`).join(', ')}) VALUES (${values.join(', ')});\n`; if ((idx + 1) % 100 === 0 && idx + 1 < data.length) { sql += `COMMIT;\n\n`; } }); sql += `\nCOMMIT;\n`; } downloadFile(sql, `${tableName}.sql`, 'text/plain'); addLogToUI(`🗄️ Экспортирована БД "${db.name}" в SQL (таблица: ${tableName}, ${data.length} INSERT)`, 'ok'); } function downloadFile(content, filename, mimeType) { const blob = new Blob([content], { type: mimeType }); const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = filename; link.click(); URL.revokeObjectURL(link.href); } function showExportMenu(dbName, buttonElement) { const menu = document.createElement('div'); menu.className = 'export-menu'; menu.innerHTML = ` <div class="export-option" data-format="csv">📄 CSV</div> <div class="export-option" data-format="json">📋 JSON</div> <div class="export-option" data-format="html">🌐 HTML</div> <div class="export-option" data-format="md">📝 Markdown</div> <div class="export-option" data-format="sql">🗄️ SQL</div> `; const rect = buttonElement.getBoundingClientRect(); menu.style.top = `${rect.bottom + window.scrollY}px`; menu.style.left = `${rect.left + window.scrollX}px`; menu.querySelectorAll('.export-option').forEach(opt => { opt.style.cssText = ` padding: 8px 12px; cursor: pointer; border-radius: 4px; transition: background 0.2s; `; opt.onmouseenter = () => opt.style.background = 'var(--badge-bg)'; opt.onmouseleave = () => opt.style.background = 'transparent'; opt.onclick = async () => { const format = opt.dataset.format; switch(format) { case 'csv': await downloadDatabase(dbName); break; case 'json': await exportSingleDatabaseJSON(dbName); break; case 'html': await exportToHTML(dbName); break; case 'md': await exportToMarkdown(dbName); break; case 'sql': await exportToSQL(dbName); break; } menu.remove(); }; }); document.body.appendChild(menu); setTimeout(() => { document.addEventListener('click', function closeMenu(e) { if (!menu.contains(e.target) && e.target !== buttonElement) { menu.remove(); document.removeEventListener('click', closeMenu); } }); }, 0); } async function clearLogsUI() { await clearAllLogs(); const logContainer = document.getElementById('logContainer'); if (logContainer) { logContainer.innerHTML = '<div class="log-entry log-info">🔄 Логи очищены</div>'; } addLogToUI('Логи очищены', 'info'); } async function previewDatabase(name) { const db = await getDatabase(name); if (!db) { addLogToUI(`БД "${name}" не найдена`, 'error'); return; } const lines = db.dataCsv.split('\n'); const previewLines = lines.slice(0, 15); const preview = previewLines.join('\n'); alert(`📊 Предпросмотр "${name}":\n\n${preview}\n\n${lines.length > 15 ? `\n... и ещё ${lines.length - 15} строк` : ''}`); } async function downloadDatabase(name) { const db = await getDatabase(name); if (!db) { addLogToUI(`БД "${name}" не найдена`, 'error'); return; } const blob = new Blob([db.dataCsv], { type: 'text/csv' }); const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = `${name}.csv`; link.click(); URL.revokeObjectURL(link.href); addLogToUI(`Скачана БД "${name}"`, 'ok'); } async function deleteDatabaseUI(name) { if (!confirm(`Удалить БД "${name}"?`)) return; await deleteDatabase(name); addLogToUI(`🗑️ Удалена БД "${name}"`, 'warn'); await renderDatabaseList(); await updateEditSelect(); } async function loadLogHistory() { const logs = await getAllLogs(); const logContainer = document.getElementById('logContainer'); if (logContainer && logs.length > 0) { logContainer.innerHTML = ''; logs.forEach(log => { const entry = document.createElement('div'); entry.className = `log-entry log-${log.type}`; const time = new Date(log.timestamp).toLocaleTimeString(); const icons = { ok: '✅', error: '❌', warn: '⚠️', info: '📌' }; entry.innerHTML = `[${time}] ${icons[log.type] || '📌'} ${log.message}`; logContainer.appendChild(entry); }); logContainer.scrollTop = logContainer.scrollHeight; } } // ==================== РЕЖИМ РЕДАКТИРОВАНИЯ ==================== async function updateEditSelect() { const allDBs = await getAllDatabases(); const select = document.getElementById('editDbSelect'); if (select) { select.innerHTML = '<option value="">— выберите БД —</option>' + allDBs.map(db => `<option value="${escapeHtml(db.name)}">${escapeHtml(db.name)}</option>`).join(''); } } async function loadDatabaseForEdit() { const select = document.getElementById('editDbSelect'); const selectedName = select.value; if (!selectedName) { document.getElementById('editCsvContent').value = ''; document.getElementById('editDbName').value = ''; return; } const db = await getDatabase(selectedName); if (db) { document.getElementById('editCsvContent').value = db.dataCsv; document.getElementById('editDbName').value = ''; addLogToUI(`📂 Загружена БД "${selectedName}" для редактирования`, 'info'); const validation = validateCSV(db.dataCsv); if (validation.valid) { addLogToUI(`✅ Текущий формат: CSV (${validation.headers.length} колонок, ${validation.rowCount} строк)`, 'ok'); } } } async function saveEditedDatabase() { const select = document.getElementById('editDbSelect'); const originalName = select.value; const newName = document.getElementById('editDbName').value.trim(); let csvContent = document.getElementById('editCsvContent').value; const finalName = newName || originalName; if (!originalName) { addLogToUI('❌ Выберите базу данных для редактирования', 'error'); return; } if (!csvContent) { addLogToUI('❌ Содержимое не может быть пустым', 'error'); return; } // Автоопределение и конвертация формата try { const format = detectFormat(csvContent, ''); if (format !== 'csv') { addLogToUI(`🔄 Обнаружен формат ${format.toUpperCase()}, конвертирую в CSV...`, 'info'); switch(format) { case 'json': const jsonData = JSON.parse(csvContent); if (jsonData.database?.dataCsv) { csvContent = jsonData.database.dataCsv; } else if (jsonData.dataCsv) { csvContent = jsonData.dataCsv; } else if (jsonData.databases?.[0]?.dataCsv) { csvContent = jsonData.databases[0].dataCsv; } else { throw new Error('JSON не содержит CSV данных'); } break; case 'html': csvContent = parseHTMLtoCSV(csvContent); break; case 'md': csvContent = parseMarkdownToCSV(csvContent); break; case 'sql': csvContent = parseSQLtoCSV(csvContent); break; default: break; } addLogToUI(`✅ Конвертация из ${format.toUpperCase()} выполнена`, 'ok'); document.getElementById('editCsvContent').value = csvContent; } } catch (error) { addLogToUI(`❌ Ошибка конвертации: ${error.message}`, 'error'); return; } const validation = validateCSV(csvContent); if (!validation.valid) { addLogToUI(`❌ Ошибка валидации: ${validation.error}`, 'error'); return; } if (newName && newName !== originalName) { const existing = await getDatabase(newName); if (existing) { addLogToUI(`❌ БД с именем "${newName}" уже существует`, 'error'); return; } } await saveDatabase(finalName, csvContent, validation.headers); if (newName && newName !== originalName) { await deleteDatabase(originalName); addLogToUI(`✏️ БД переименована: "${originalName}" → "${finalName}"`, 'ok'); } addLogToUI(`✏️ БД "${finalName}" обновлена (${validation.headers.length} колонок, ${validation.rowCount} строк)`, 'ok'); document.getElementById('editDbSelect').value = ''; document.getElementById('editDbName').value = ''; document.getElementById('editCsvContent').value = ''; await renderDatabaseList(); await updateEditSelect(); switchMode('create'); } function cancelEdit() { document.getElementById('editDbSelect').value = ''; document.getElementById('editDbName').value = ''; document.getElementById('editCsvContent').value = ''; switchMode('create'); addLogToUI('Редактирование отменено', 'info'); } // ==================== ПЕРЕКЛЮЧЕНИЕ РЕЖИМОВ ==================== function switchMode(mode) { const createPanel = document.getElementById('createModePanel'); const importPanel = document.getElementById('importModePanel'); const editPanel = document.getElementById('editModePanel'); const createBtn = document.getElementById('btnCreateMode'); const importBtn = document.getElementById('btnImportMode'); const editBtn = document.getElementById('btnEditMode'); if (!createPanel || !importPanel || !editPanel) return; createPanel.style.display = 'none'; importPanel.style.display = 'none'; editPanel.style.display = 'none'; createBtn.classList.remove('active'); importBtn.classList.remove('active'); editBtn.classList.remove('active'); if (mode === 'create') { createPanel.style.display = 'block'; createBtn.classList.add('active'); } else if (mode === 'import') { importPanel.style.display = 'block'; importBtn.classList.add('active'); } else if (mode === 'edit') { editPanel.style.display = 'block'; editBtn.classList.add('active'); updateEditSelect(); } } // ==================== РЕНДЕР СПИСКА БД ==================== async function renderDatabaseList() { const allDBs = await getAllDatabases(); const container = document.getElementById('dbList'); const sourceSelect = document.getElementById('scheduleSource'); if (!container) return; if (allDBs.length === 0) { container.innerHTML = '<div class="empty-message">📭 Нет сохранённых БД. Создайте первую!</div>'; if (sourceSelect) { sourceSelect.innerHTML = '<option value="">— выберите БД —</option>'; } return; } container.innerHTML = ''; for (const db of allDBs) { const item = document.createElement('div'); item.className = 'db-item'; item.innerHTML = ` <div class="db-info"> <span class="db-name">📌 ${escapeHtml(db.name)}</span> <span class="db-cols">${db.columns.length} колонок</span> <span class="db-cols">📅 ${formatDate(db.updatedAt)}</span> </div> <div class="db-buttons"> <button class="preview-btn" data-name="${escapeHtml(db.name)}">👁️</button> <button class="export-btn" data-name="${escapeHtml(db.name)}">💾 Экспорт ▼</button> <button class="delete-btn" data-name="${escapeHtml(db.name)}">🗑️</button> </div> `; container.appendChild(item); } if (sourceSelect) { sourceSelect.innerHTML = '<option value="">— выберите БД —</option>' + allDBs.map(db => `<option value="${escapeHtml(db.name)}">${escapeHtml(db.name)}</option>`).join(''); } document.querySelectorAll('.preview-btn').forEach(btn => { btn.onclick = () => previewDatabase(btn.dataset.name); }); document.querySelectorAll('.export-btn').forEach(btn => { btn.onclick = (e) => { e.stopPropagation(); showExportMenu(btn.dataset.name, btn); }; }); document.querySelectorAll('.delete-btn').forEach(btn => { btn.onclick = () => deleteDatabaseUI(btn.dataset.name); }); } // ==================== ПАРСЕРЫ ФОРМАТОВ ==================== function detectFormat(content, filename) { const lowerFilename = filename.toLowerCase(); const trimmed = content.trim(); if (lowerFilename.endsWith('.csv')) return 'csv'; if (lowerFilename.endsWith('.json')) return 'json'; if (lowerFilename.endsWith('.html') || lowerFilename.endsWith('.htm')) return 'html'; if (lowerFilename.endsWith('.md') || lowerFilename.endsWith('.markdown')) return 'md'; if (lowerFilename.endsWith('.sql')) return 'sql'; if (trimmed.startsWith('{') || trimmed.startsWith('[')) return 'json'; if (trimmed.startsWith('<html') || trimmed.startsWith('<!DOCTYPE')) return 'html'; if (trimmed.startsWith('|') && trimmed.includes('|')) return 'md'; if (trimmed.toUpperCase().includes('INSERT INTO')) return 'sql'; return 'csv'; } function parseHTMLtoCSV(htmlContent) { const parser = new DOMParser(); const doc = parser.parseFromString(htmlContent, 'text/html'); const table = doc.querySelector('table'); if (!table) { throw new Error('HTML не содержит таблицы'); } const headers = []; const headerCells = table.querySelectorAll('thead th, tr:first-child th, tr:first-child td'); if (headerCells.length === 0) { throw new Error('Не найдены заголовки таблицы'); } headerCells.forEach(cell => { headers.push(cell.textContent.trim()); }); const rows = table.querySelectorAll('tbody tr'); const dataRows = []; rows.forEach(row => { const cells = row.querySelectorAll('td'); if (cells.length === headers.length) { const rowData = []; cells.forEach(cell => { rowData.push(cell.textContent.trim()); }); dataRows.push(rowData); } }); let csv = headers.join(',') + '\n'; dataRows.forEach(row => { csv += row.map(cell => { if (cell.includes(',') || cell.includes('"')) { return `"${cell.replace(/"/g, '""')}"`; } return cell; }).join(',') + '\n'; }); return csv; } function parseMarkdownToCSV(mdContent) { const lines = mdContent.split('\n'); let headerLine = null; let separatorLine = null; let dataLines = []; let foundTable = false; for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); if (line.startsWith('|') && line.endsWith('|')) { if (!foundTable) { headerLine = line; foundTable = true; } else if (separatorLine === null && line.includes('---')) { separatorLine = line; } else if (headerLine && separatorLine) { dataLines.push(line); } } else if (foundTable && !line.startsWith('|')) { break; } } if (!headerLine) { throw new Error('Markdown не содержит таблицы'); } const headers = headerLine.split('|').filter(cell => cell.trim() !== '').map(cell => cell.trim()); const dataRows = []; dataLines.forEach(line => { const cells = line.split('|').filter(cell => cell.trim() !== '').map(cell => cell.trim()); if (cells.length === headers.length) { dataRows.push(cells); } }); let csv = headers.join(',') + '\n'; dataRows.forEach(row => { csv += row.map(cell => { if (cell.includes(',') || cell.includes('"')) { return `"${cell.replace(/"/g, '""')}"`; } return cell; }).join(',') + '\n'; }); return csv; } function parseSQLtoCSV(sqlContent) { const lines = sqlContent.split('\n'); let headers = null; const dataRows = []; for (const line of lines) { // Регулярка для поиска INSERT с учётом русских символов const insertMatch = line.match(/INSERT INTO ["']?([^"'\s]+)["']?\s*\(([^)]+)\)\s*VALUES\s*\((.+)\)/i); if (insertMatch) { const headerStr = insertMatch[2]; headers = headerStr.split(',').map(h => h.trim().replace(/["']/g, '')); let valuesStr = insertMatch[3]; const values = []; let current = ''; let inQuotes = false; for (let i = 0; i < valuesStr.length; i++) { const char = valuesStr[i]; if (char === "'" && !inQuotes) { inQuotes = true; current += char; } else if (char === "'" && inQuotes && valuesStr[i+1] === "'") { current += char; i++; } else if (char === "'" && inQuotes) { inQuotes = false; current += char; } else if (char === ',' && !inQuotes) { values.push(current.trim()); current = ''; } else { current += char; } } if (current) values.push(current.trim()); const cleanValues = values.map(v => { if (v.startsWith("'") && v.endsWith("'")) { return v.slice(1, -1).replace(/''/g, "'"); } return v; }); dataRows.push(cleanValues); } } if (!headers || dataRows.length === 0) { throw new Error('SQL не содержит INSERT данных'); } let csv = headers.join(',') + '\n'; dataRows.forEach(row => { csv += row.map(cell => { if (cell.includes(',') || cell.includes('"')) { return `"${cell.replace(/"/g, '""')}"`; } return cell; }).join(',') + '\n'; }); return csv; } // ==================== ЭКСПОРТ В ГЛОБАЛЬНУЮ ОБЛАСТЬ ==================== window.addLogToUI = addLogToUI; window.renderDatabaseList = renderDatabaseList; window.updateSchedulerStatusUI = updateSchedulerStatusUI; window.createNewDatabase = createNewDatabase; window.importFromAnyFormatWrapper = importFromAnyFormatWrapper; window.exportAllDatabasesUI = exportAllDatabasesUI; window.clearLogsUI = clearLogsUI; window.switchMode = switchMode; window.previewDatabase = previewDatabase; window.downloadDatabase = downloadDatabase; window.deleteDatabaseUI = deleteDatabaseUI; window.loadLogHistory = loadLogHistory; window.loadDatabaseForEdit = loadDatabaseForEdit; window.saveEditedDatabase = saveEditedDatabase; window.cancelEdit = cancelEdit; window.updateEditSelect = updateEditSelect; window.exportToHTML = exportToHTML; window.exportToMarkdown = exportToMarkdown; window.exportToSQL = exportToSQL; window.showExportMenu = showExportMenu; window.exportSingleDatabaseJSON = exportSingleDatabaseJSON; window.downloadFile = downloadFile;