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();
}