From 293c638e2d3a0d82569e76b89ad14d0fc593366f Mon Sep 17 00:00:00 2001 From: ZorahM Date: Thu, 8 May 2025 23:07:03 +0000 Subject: [PATCH] 1.1.0 --- index.html | 317 +++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 235 insertions(+), 82 deletions(-) diff --git a/index.html b/index.html index f74f1e2..b802a1b 100644 --- a/index.html +++ b/index.html @@ -1,43 +1,4 @@ - + @@ -48,6 +9,14 @@ body { background: linear-gradient(to bottom, #fff5f5, #ffffff); } + .password-cell { + -webkit-text-security: disc; /* Скрываем пароли по умолчанию */ + font-family: monospace; + } + .reveal-btn { + cursor: pointer; + margin-left: 8px; + } @@ -58,7 +27,12 @@
- +
+ + + + +
@@ -68,39 +42,127 @@ // Load existing passwords from local storage on page load window.onload = function() { - const savedData = localStorage.getItem('passwords'); - if (savedData) { - tableData = JSON.parse(savedData); - renderTable(); - } + loadPasswords(); }; + function loadPasswords() { + const savedData = localStorage.getItem('passwords'); + if (savedData) { + try { + tableData = JSON.parse(savedData); + renderTable(); + } catch (e) { + console.error("Ошибка загрузки паролей:", e); + alert("Ошибка загрузки сохранённых паролей"); + } + } + } + + function savePasswords() { + localStorage.setItem('passwords', JSON.stringify(tableData)); + } + function parsePasswords() { - const input = document.getElementById('inputData').value; + const input = document.getElementById('inputData').value.trim(); const output = document.getElementById('output'); output.innerHTML = ''; - const lines = input.trim().split('\n'); - if (lines.length === 0 || lines[0] === '') { + if (!input) { renderTable(); return; } + const lines = input.split('\n'); + let addedCount = 0; + lines.forEach(line => { - const parts = line.trim().split(' '); + const trimmedLine = line.trim(); + if (!trimmedLine) return; + + // Поддержка разделителей: пробел, табуляция или запятая + const parts = trimmedLine.split(/[\s\t,]+/).filter(part => part); + if (parts.length >= 3) { const password = parts.pop(); const login = parts.pop(); const service = parts.join(' '); + if (service && login && password) { - tableData.push({ service, login, password }); + // Проверка на дубликаты + const isDuplicate = tableData.some(item => + item.service === service && item.login === login + ); + + if (!isDuplicate) { + tableData.push({ service, login, password }); + addedCount++; + } } } }); - localStorage.setItem('passwords', JSON.stringify(tableData)); - document.getElementById('inputData').value = ''; - renderTable(); + if (addedCount > 0) { + savePasswords(); + document.getElementById('inputData').value = ''; + renderTable(); + alert(`Добавлено ${addedCount} новых записей`); + } else { + alert("Не удалось добавить новые записи. Проверьте формат ввода или возможно записи уже существуют."); + } + } + + function clearAllPasswords() { + if (confirm("Вы уверены, что хотите удалить все сохранённые пароли? Это действие нельзя отменить.")) { + tableData = []; + savePasswords(); + renderTable(); + } + } + + function exportPasswords() { + const dataStr = JSON.stringify(tableData, null, 2); + const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr); + + const exportFileDefaultName = 'passwords-backup.json'; + + const linkElement = document.createElement('a'); + linkElement.setAttribute('href', dataUri); + linkElement.setAttribute('download', exportFileDefaultName); + linkElement.click(); + } + + function importPasswords() { + const fileInput = document.createElement('input'); + fileInput.type = 'file'; + fileInput.accept = '.json'; + + fileInput.onchange = e => { + const file = e.target.files[0]; + if (!file) return; + + const reader = new FileReader(); + reader.onload = event => { + try { + const importedData = JSON.parse(event.target.result); + if (Array.isArray(importedData) && importedData.length > 0) { + if (confirm(`Найдено ${importedData.length} записей. Добавить их к существующим?`)) { + tableData = [...tableData, ...importedData]; + savePasswords(); + renderTable(); + alert(`Импортировано ${importedData.length} записей`); + } + } else { + alert("Файл не содержит данных для импорта"); + } + } catch (error) { + alert("Ошибка при чтении файла. Убедитесь, что выбран правильный файл."); + console.error(error); + } + }; + reader.readAsText(file); + }; + + fileInput.click(); } function renderTable() { @@ -108,59 +170,150 @@ output.innerHTML = ''; if (tableData.length === 0) { - output.innerHTML = '

Пожалуйста, введите данные!

'; + output.innerHTML = '

Нет сохранённых паролей. Введите данные в поле выше.

'; return; } + // Создаем контейнер для таблицы и поиска + const tableContainer = document.createElement('div'); + tableContainer.className = 'overflow-x-auto'; + + // Добавляем поле поиска + const searchDiv = document.createElement('div'); + searchDiv.className = 'mb-4'; + searchDiv.innerHTML = ` + +
Найдено записей: ${tableData.length}
+ `; + tableContainer.appendChild(searchDiv); + + const searchInput = searchDiv.querySelector('#searchInput'); + searchInput.addEventListener('input', () => filterTable(searchInput.value.toLowerCase())); + const table = document.createElement('table'); table.className = 'w-full border-collapse rounded-2xl overflow-hidden shadow-md'; + let header = table.insertRow(); - const headers = ['Сервис', 'Логин', 'Пароль']; + const headers = ['Сервис', 'Логин', 'Пароль', 'Действия']; let sortDirection = { 0: 1, 1: 1, 2: 1 }; // 1 for ascending, -1 for descending headers.forEach((text, index) => { const th = document.createElement('th'); th.textContent = text; th.className = 'border border-gray-200 p-4 bg-red-50 text-gray-700 font-semibold text-left cursor-pointer'; - th.addEventListener('click', () => { - sortTable(index, sortDirection[index]); - sortDirection[index] *= -1; - renderTable(); - }); + if (index < 3) { + th.addEventListener('click', () => { + sortTable(index, sortDirection[index]); + sortDirection[index] *= -1; + renderTable(); + }); + } header.appendChild(th); }); tableData.forEach((row, rowIndex) => { const tableRow = table.insertRow(); - let i = 0; - for (let key in row) { - const td = tableRow.insertCell(); - const text = row[key]; - td.className = 'border border-gray-200 p-4 bg-white text-sm break-words max-w-xs'; - const copyBtn = document.createElement('button'); - copyBtn.textContent = '📋'; - copyBtn.className = 'ml-2 text-blue-500 hover:text-blue-700'; - copyBtn.onclick = (e) => { - e.stopPropagation(); - navigator.clipboard.writeText(text).then(() => alert('Пароль скопирован!')); - }; - td.appendChild(document.createTextNode(text)); - td.appendChild(copyBtn); - i++; - } + tableRow.className = 'hover:bg-gray-50'; + tableRow.dataset.service = row.service.toLowerCase(); + tableRow.dataset.login = row.login.toLowerCase(); + + // Ячейка сервиса + const serviceCell = tableRow.insertCell(); + serviceCell.className = 'border border-gray-200 p-4 bg-white text-sm break-words max-w-xs'; + serviceCell.textContent = row.service; + + // Ячейка логина + const loginCell = tableRow.insertCell(); + loginCell.className = 'border border-gray-200 p-4 bg-white text-sm break-words max-w-xs'; + loginCell.textContent = row.login; + + // Ячейка пароля + const passwordCell = tableRow.insertCell(); + passwordCell.className = 'border border-gray-200 p-4 bg-white text-sm break-words max-w-xs password-cell'; + passwordCell.textContent = '••••••••'; + passwordCell.dataset.password = row.password; + + // Ячейка действий + const actionsCell = tableRow.insertCell(); + actionsCell.className = 'border border-gray-200 p-4 bg-white text-sm flex gap-2'; + + // Кнопка показать/скрыть пароль + const toggleBtn = document.createElement('button'); + toggleBtn.textContent = '👁️'; + toggleBtn.className = 'reveal-btn text-blue-500 hover:text-blue-700'; + toggleBtn.onclick = () => { + if (passwordCell.classList.contains('password-cell')) { + passwordCell.textContent = passwordCell.dataset.password; + passwordCell.classList.remove('password-cell'); + toggleBtn.textContent = '🙈'; + } else { + passwordCell.textContent = '••••••••'; + passwordCell.classList.add('password-cell'); + toggleBtn.textContent = '👁️'; + } + }; + + // Кнопка копирования пароля + const copyBtn = document.createElement('button'); + copyBtn.textContent = '📋'; + copyBtn.className = 'text-blue-500 hover:text-blue-700'; + copyBtn.onclick = () => { + navigator.clipboard.writeText(row.password).then(() => { + const originalText = copyBtn.textContent; + copyBtn.textContent = '✓'; + setTimeout(() => copyBtn.textContent = originalText, 2000); + }); + }; + + // Кнопка удаления + const deleteBtn = document.createElement('button'); + deleteBtn.textContent = '🗑️'; + deleteBtn.className = 'text-red-500 hover:text-red-700'; + deleteBtn.onclick = () => { + if (confirm(`Удалить запись для ${row.service}?`)) { + tableData.splice(rowIndex, 1); + savePasswords(); + renderTable(); + } + }; + + actionsCell.appendChild(toggleBtn); + actionsCell.appendChild(copyBtn); + actionsCell.appendChild(deleteBtn); }); - output.appendChild(table); + tableContainer.appendChild(table); + output.appendChild(tableContainer); + } + + function filterTable(searchTerm) { + const rows = document.querySelectorAll('tbody tr'); + let matchCount = 0; + + rows.forEach(row => { + const service = row.dataset.service; + const login = row.dataset.login; + const isVisible = service.includes(searchTerm) || login.includes(searchTerm); + + row.style.display = isVisible ? '' : 'none'; + if (isVisible) matchCount++; + }); + + document.getElementById('matchCount').textContent = matchCount; } function sortTable(columnIndex, direction) { + const columns = ['service', 'login', 'password']; + const key = columns[columnIndex]; + tableData.sort((a, b) => { - const keys = ['service', 'login', 'password']; - const valueA = a[keys[columnIndex]]; - const valueB = b[keys[columnIndex]]; + const valueA = a[key].toLowerCase(); + const valueB = b[key].toLowerCase(); return direction * valueA.localeCompare(valueB); }); - localStorage.setItem('passwords', JSON.stringify(tableData)); + + savePasswords(); }