- Implement PostgreSQL database schema with users and bookmarks tables - Add database connection pooling with retry logic and error handling - Create migration system with automatic schema initialization - Add database CLI tools for management (init, status, validate, etc.) - Include comprehensive error handling and diagnostics - Add development seed data and testing utilities - Implement health monitoring and connection pool statistics - Create detailed documentation and troubleshooting guide Database features: - Users table with authentication fields and email verification - Bookmarks table with user association and metadata - Proper indexes for performance optimization - Automatic timestamp triggers - Transaction support with rollback handling - Connection pooling (20 max connections, 30s idle timeout) - Graceful shutdown handling CLI commands available: - npm run db:init - Initialize database - npm run db:status - Check database status - npm run db:validate - Validate schema - npm run db:test - Run database tests - npm run db:diagnostics - Full diagnostics
339 lines
13 KiB
HTML
339 lines
13 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Test Organization Features - Bookmark Manager</title>
|
|
<link rel="stylesheet" href="styles.css">
|
|
<style>
|
|
.test-section {
|
|
background: white;
|
|
padding: 20px;
|
|
margin: 20px 0;
|
|
border-radius: 8px;
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
}
|
|
.test-section h3 {
|
|
color: #2c3e50;
|
|
margin-bottom: 15px;
|
|
}
|
|
.test-button {
|
|
margin: 5px;
|
|
padding: 10px 15px;
|
|
background: #3498db;
|
|
color: white;
|
|
border: none;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
}
|
|
.test-button:hover {
|
|
background: #2980b9;
|
|
}
|
|
.test-results {
|
|
margin-top: 15px;
|
|
padding: 10px;
|
|
background: #f8f9fa;
|
|
border-radius: 4px;
|
|
font-family: monospace;
|
|
font-size: 14px;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<header>
|
|
<h1>Organization Features Test</h1>
|
|
</header>
|
|
|
|
<div class="test-section">
|
|
<h3>Test Data Setup</h3>
|
|
<button class="test-button" onclick="createTestBookmarks()">Create Test Bookmarks</button>
|
|
<button class="test-button" onclick="clearTestData()">Clear Test Data</button>
|
|
<div id="setupResults" class="test-results"></div>
|
|
</div>
|
|
|
|
<div class="test-section">
|
|
<h3>Drag and Drop Test</h3>
|
|
<p>After creating test bookmarks, try dragging bookmarks between folders to test the drag-and-drop functionality.</p>
|
|
<button class="test-button" onclick="testDragAndDrop()">Test Programmatic Move</button>
|
|
<div id="dragDropResults" class="test-results"></div>
|
|
</div>
|
|
|
|
<div class="test-section">
|
|
<h3>Bulk Operations Test</h3>
|
|
<button class="test-button" onclick="testBulkMode()">Toggle Bulk Mode</button>
|
|
<button class="test-button" onclick="testBulkSelection()">Test Bulk Selection</button>
|
|
<button class="test-button" onclick="testBulkMove()">Test Bulk Move</button>
|
|
<div id="bulkResults" class="test-results"></div>
|
|
</div>
|
|
|
|
<div class="test-section">
|
|
<h3>Sorting Test</h3>
|
|
<button class="test-button" onclick="testSortByTitle()">Sort by Title</button>
|
|
<button class="test-button" onclick="testSortByDate()">Sort by Date</button>
|
|
<button class="test-button" onclick="testSortByFolder()">Sort by Folder</button>
|
|
<div id="sortResults" class="test-results"></div>
|
|
</div>
|
|
|
|
<div class="test-section">
|
|
<h3>Folder Management Test</h3>
|
|
<button class="test-button" onclick="testCreateFolder()">Create Test Folder</button>
|
|
<button class="test-button" onclick="testRenameFolder()">Rename Folder</button>
|
|
<button class="test-button" onclick="testMergeFolder()">Merge Folders</button>
|
|
<button class="test-button" onclick="testDeleteFolder()">Delete Folder</button>
|
|
<div id="folderResults" class="test-results"></div>
|
|
</div>
|
|
|
|
<!-- Include the main bookmark manager interface -->
|
|
<div id="bookmarksList" class="bookmarks-list"></div>
|
|
|
|
<!-- Bulk Actions Bar -->
|
|
<div id="bulkActions" class="bulk-actions" style="display: none;">
|
|
<div class="bulk-selection-info">
|
|
<span class="selection-count">0 selected</span>
|
|
</div>
|
|
<div class="bulk-action-buttons">
|
|
<select id="bulkMoveFolder" class="bulk-folder-select">
|
|
<option value="">Move to folder...</option>
|
|
</select>
|
|
<button id="bulkMoveBtn" class="btn btn-secondary" disabled>Move</button>
|
|
<button id="bulkDeleteBtn" class="btn btn-danger" disabled>Delete Selected</button>
|
|
<button id="bulkSelectAllBtn" class="btn btn-secondary">Select All</button>
|
|
<button id="bulkClearSelectionBtn" class="btn btn-secondary">Clear Selection</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="script.js"></script>
|
|
<script>
|
|
let testManager;
|
|
|
|
// Initialize test manager
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
testManager = new BookmarkManager();
|
|
});
|
|
|
|
function createTestBookmarks() {
|
|
const testBookmarks = [
|
|
{
|
|
id: Date.now() + 1,
|
|
title: 'Google',
|
|
url: 'https://www.google.com',
|
|
folder: 'Search Engines',
|
|
addDate: Date.now() - 86400000, // 1 day ago
|
|
icon: '',
|
|
status: 'unknown'
|
|
},
|
|
{
|
|
id: Date.now() + 2,
|
|
title: 'GitHub',
|
|
url: 'https://github.com',
|
|
folder: 'Development',
|
|
addDate: Date.now() - 172800000, // 2 days ago
|
|
icon: '',
|
|
status: 'unknown'
|
|
},
|
|
{
|
|
id: Date.now() + 3,
|
|
title: 'Stack Overflow',
|
|
url: 'https://stackoverflow.com',
|
|
folder: 'Development',
|
|
addDate: Date.now() - 259200000, // 3 days ago
|
|
icon: '',
|
|
status: 'unknown'
|
|
},
|
|
{
|
|
id: Date.now() + 4,
|
|
title: 'YouTube',
|
|
url: 'https://www.youtube.com',
|
|
folder: 'Entertainment',
|
|
addDate: Date.now(),
|
|
icon: '',
|
|
status: 'unknown'
|
|
},
|
|
{
|
|
id: Date.now() + 5,
|
|
title: 'Wikipedia',
|
|
url: 'https://www.wikipedia.org',
|
|
folder: '', // No folder
|
|
addDate: Date.now() - 345600000, // 4 days ago
|
|
icon: '',
|
|
status: 'unknown'
|
|
}
|
|
];
|
|
|
|
testManager.bookmarks = testBookmarks;
|
|
testManager.saveBookmarksToStorage();
|
|
testManager.renderBookmarks();
|
|
testManager.updateStats();
|
|
|
|
document.getElementById('setupResults').textContent =
|
|
`Created ${testBookmarks.length} test bookmarks across multiple folders.`;
|
|
}
|
|
|
|
function clearTestData() {
|
|
testManager.bookmarks = [];
|
|
testManager.saveBookmarksToStorage();
|
|
testManager.renderBookmarks();
|
|
testManager.updateStats();
|
|
|
|
document.getElementById('setupResults').textContent = 'Test data cleared.';
|
|
}
|
|
|
|
function testDragAndDrop() {
|
|
if (testManager.bookmarks.length === 0) {
|
|
document.getElementById('dragDropResults').textContent = 'No bookmarks to test. Create test bookmarks first.';
|
|
return;
|
|
}
|
|
|
|
// Test programmatic move
|
|
const bookmark = testManager.bookmarks[0];
|
|
const oldFolder = bookmark.folder;
|
|
const newFolder = 'Test Folder';
|
|
|
|
testManager.moveBookmarkToFolder(bookmark.id, newFolder);
|
|
|
|
document.getElementById('dragDropResults').textContent =
|
|
`Moved "${bookmark.title}" from "${oldFolder || 'Uncategorized'}" to "${newFolder}". Check the UI to verify the change.`;
|
|
}
|
|
|
|
function testBulkMode() {
|
|
testManager.toggleBulkMode();
|
|
const isActive = testManager.bulkMode;
|
|
|
|
document.getElementById('bulkResults').textContent =
|
|
`Bulk mode ${isActive ? 'activated' : 'deactivated'}. ${isActive ? 'Checkboxes should now be visible on bookmark items.' : 'Checkboxes should be hidden.'}`;
|
|
}
|
|
|
|
function testBulkSelection() {
|
|
if (!testManager.bulkMode) {
|
|
document.getElementById('bulkResults').textContent = 'Bulk mode must be active first.';
|
|
return;
|
|
}
|
|
|
|
if (testManager.bookmarks.length === 0) {
|
|
document.getElementById('bulkResults').textContent = 'No bookmarks to select. Create test bookmarks first.';
|
|
return;
|
|
}
|
|
|
|
// Select first two bookmarks
|
|
const firstTwo = testManager.bookmarks.slice(0, 2);
|
|
firstTwo.forEach(bookmark => {
|
|
testManager.toggleBookmarkSelection(bookmark.id);
|
|
});
|
|
|
|
document.getElementById('bulkResults').textContent =
|
|
`Selected ${firstTwo.length} bookmarks: ${firstTwo.map(b => b.title).join(', ')}`;
|
|
}
|
|
|
|
function testBulkMove() {
|
|
if (testManager.bulkSelection.size === 0) {
|
|
document.getElementById('bulkResults').textContent = 'No bookmarks selected. Use bulk selection test first.';
|
|
return;
|
|
}
|
|
|
|
const targetFolder = 'Bulk Moved';
|
|
const selectedCount = testManager.bulkSelection.size;
|
|
|
|
testManager.bulkMoveToFolder(targetFolder);
|
|
|
|
document.getElementById('bulkResults').textContent =
|
|
`Moved ${selectedCount} bookmarks to "${targetFolder}" folder.`;
|
|
}
|
|
|
|
function testSortByTitle() {
|
|
if (testManager.bookmarks.length === 0) {
|
|
document.getElementById('sortResults').textContent = 'No bookmarks to sort. Create test bookmarks first.';
|
|
return;
|
|
}
|
|
|
|
const beforeTitles = testManager.bookmarks.map(b => b.title);
|
|
testManager.sortBookmarks('title', 'asc');
|
|
const afterTitles = testManager.bookmarks.map(b => b.title);
|
|
|
|
document.getElementById('sortResults').textContent =
|
|
`Sorted by title. Before: [${beforeTitles.join(', ')}] After: [${afterTitles.join(', ')}]`;
|
|
}
|
|
|
|
function testSortByDate() {
|
|
if (testManager.bookmarks.length === 0) {
|
|
document.getElementById('sortResults').textContent = 'No bookmarks to sort. Create test bookmarks first.';
|
|
return;
|
|
}
|
|
|
|
testManager.sortBookmarks('date', 'desc');
|
|
const sortedTitles = testManager.bookmarks.map(b => b.title);
|
|
|
|
document.getElementById('sortResults').textContent =
|
|
`Sorted by date (newest first): [${sortedTitles.join(', ')}]`;
|
|
}
|
|
|
|
function testSortByFolder() {
|
|
if (testManager.bookmarks.length === 0) {
|
|
document.getElementById('sortResults').textContent = 'No bookmarks to sort. Create test bookmarks first.';
|
|
return;
|
|
}
|
|
|
|
testManager.sortBookmarks('folder', 'asc');
|
|
const sortedByFolder = testManager.bookmarks.map(b => `${b.title} (${b.folder || 'No folder'})`);
|
|
|
|
document.getElementById('sortResults').textContent =
|
|
`Sorted by folder: [${sortedByFolder.join(', ')}]`;
|
|
}
|
|
|
|
function testCreateFolder() {
|
|
const folderName = 'Test Created Folder';
|
|
testManager.createFolder(folderName);
|
|
|
|
document.getElementById('folderResults').textContent =
|
|
`Created folder "${folderName}". Check the bookmark list for the new folder.`;
|
|
}
|
|
|
|
function testRenameFolder() {
|
|
const folders = Object.keys(testManager.getFolderStats());
|
|
if (folders.length === 0) {
|
|
document.getElementById('folderResults').textContent = 'No folders to rename. Create test bookmarks first.';
|
|
return;
|
|
}
|
|
|
|
const oldName = folders[0];
|
|
const newName = oldName + ' (Renamed)';
|
|
|
|
testManager.renameFolder(oldName, newName);
|
|
|
|
document.getElementById('folderResults').textContent =
|
|
`Renamed folder "${oldName}" to "${newName}".`;
|
|
}
|
|
|
|
function testMergeFolder() {
|
|
const folders = Object.keys(testManager.getFolderStats());
|
|
if (folders.length < 2) {
|
|
document.getElementById('folderResults').textContent = 'Need at least 2 folders to test merge. Create more test bookmarks.';
|
|
return;
|
|
}
|
|
|
|
const sourceFolder = folders[0];
|
|
const targetFolder = folders[1];
|
|
|
|
testManager.mergeFolder(sourceFolder, targetFolder);
|
|
|
|
document.getElementById('folderResults').textContent =
|
|
`Merged folder "${sourceFolder}" into "${targetFolder}".`;
|
|
}
|
|
|
|
function testDeleteFolder() {
|
|
const folders = Object.keys(testManager.getFolderStats());
|
|
if (folders.length === 0) {
|
|
document.getElementById('folderResults').textContent = 'No folders to delete. Create test bookmarks first.';
|
|
return;
|
|
}
|
|
|
|
const folderToDelete = folders[0];
|
|
testManager.deleteFolder(folderToDelete);
|
|
|
|
document.getElementById('folderResults').textContent =
|
|
`Deleted folder "${folderToDelete}". Bookmarks moved to Uncategorized.`;
|
|
}
|
|
</script>
|
|
</body>
|
|
</html> |