443 lines
19 KiB
HTML
443 lines
19 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Migration Test</title>
|
|
<link rel="stylesheet" href="styles.css">
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<h1>Migration Functionality Test</h1>
|
|
|
|
<div style="margin: 20px 0;">
|
|
<button id="setupTestData" class="btn btn-secondary">Setup Test Data in localStorage</button>
|
|
<button id="showMigrationModal" class="btn btn-primary">Show Migration Modal</button>
|
|
<button id="clearTestData" class="btn btn-danger">Clear Test Data</button>
|
|
</div>
|
|
|
|
<div id="status" style="margin: 20px 0; padding: 15px; background: #f8f9fa; border-radius: 8px;">
|
|
<h3>Status:</h3>
|
|
<p id="statusText">Ready to test migration functionality</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Data Migration Modal -->
|
|
<div id="migrationModal" class="modal" role="dialog" aria-labelledby="migrationTitle" aria-modal="true"
|
|
aria-hidden="true">
|
|
<div class="modal-content migration-modal-content">
|
|
<button class="close" aria-label="Close migration dialog">×</button>
|
|
<h2 id="migrationTitle">Import Local Bookmarks</h2>
|
|
|
|
<div class="migration-content">
|
|
<div class="migration-intro">
|
|
<p>We found <strong id="localBookmarkCount">0</strong> bookmarks stored locally in your browser.
|
|
Would you like to import them to your account?</p>
|
|
</div>
|
|
|
|
<div class="migration-options">
|
|
<h3>Import Options</h3>
|
|
<div class="form-group">
|
|
<label>
|
|
<input type="radio" name="migrationStrategy" value="merge" checked>
|
|
<strong>Merge with existing bookmarks</strong>
|
|
<div class="help-text">Add local bookmarks to your account, skipping duplicates</div>
|
|
</label>
|
|
</div>
|
|
<div class="form-group">
|
|
<label>
|
|
<input type="radio" name="migrationStrategy" value="replace">
|
|
<strong>Replace all bookmarks</strong>
|
|
<div class="help-text">Delete all existing bookmarks and import local ones</div>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="migration-preview" id="migrationPreview" style="display: none;">
|
|
<h3>Migration Preview</h3>
|
|
<div class="migration-stats">
|
|
<div class="stat-item">
|
|
<span class="stat-label">Local bookmarks found:</span>
|
|
<span class="stat-value" id="localBookmarksCount">0</span>
|
|
</div>
|
|
<div class="stat-item">
|
|
<span class="stat-label">Valid bookmarks:</span>
|
|
<span class="stat-value" id="validBookmarksCount">0</span>
|
|
</div>
|
|
<div class="stat-item">
|
|
<span class="stat-label">Invalid bookmarks:</span>
|
|
<span class="stat-value" id="invalidBookmarksCount">0</span>
|
|
</div>
|
|
<div class="stat-item">
|
|
<span class="stat-label">Duplicates to skip:</span>
|
|
<span class="stat-value" id="duplicatesCount">0</span>
|
|
</div>
|
|
<div class="stat-item">
|
|
<span class="stat-label">New bookmarks to import:</span>
|
|
<span class="stat-value" id="newBookmarksCount">0</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="migration-warning" id="replaceWarning" style="display: none;">
|
|
<div class="warning-box">
|
|
<strong>⚠️ Warning:</strong> This will permanently delete all your existing bookmarks
|
|
and replace them with local bookmarks. This action cannot be undone.
|
|
</div>
|
|
</div>
|
|
|
|
<div class="migration-progress" id="migrationProgress" style="display: none;">
|
|
<div class="progress-bar">
|
|
<div class="progress-fill" id="migrationProgressFill"></div>
|
|
</div>
|
|
<div class="progress-text" id="migrationProgressText">Preparing migration...</div>
|
|
</div>
|
|
|
|
<div class="migration-results" id="migrationResults" style="display: none;">
|
|
<h3>Migration Complete</h3>
|
|
<div class="results-summary">
|
|
<div class="result-item">
|
|
<span class="result-label">Successfully imported:</span>
|
|
<span class="result-value" id="migratedCount">0</span>
|
|
</div>
|
|
<div class="result-item">
|
|
<span class="result-label">Duplicates skipped:</span>
|
|
<span class="result-value" id="skippedCount">0</span>
|
|
</div>
|
|
<div class="result-item">
|
|
<span class="result-label">Validation errors:</span>
|
|
<span class="result-value" id="errorCount">0</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="modal-actions">
|
|
<button id="startMigrationBtn" class="btn btn-primary" aria-label="Start bookmark migration">
|
|
Import Bookmarks
|
|
</button>
|
|
<button id="previewMigrationBtn" class="btn btn-secondary" aria-label="Preview migration">
|
|
Preview
|
|
</button>
|
|
<button id="skipMigrationBtn" class="btn btn-secondary" aria-label="Skip migration">
|
|
Skip for Now
|
|
</button>
|
|
<button id="closeMigrationBtn" class="btn btn-secondary" aria-label="Close migration dialog" style="display: none;">
|
|
Close
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Mock BookmarkManager for testing
|
|
class MigrationTester {
|
|
constructor() {
|
|
this.bookmarks = []; // Mock existing bookmarks
|
|
this.apiBaseUrl = '/api';
|
|
this.initializeMigrationModal();
|
|
}
|
|
|
|
// Copy the migration methods from the main script
|
|
checkForLocalBookmarks() {
|
|
try {
|
|
const localBookmarks = localStorage.getItem('bookmarks');
|
|
if (localBookmarks) {
|
|
const bookmarks = JSON.parse(localBookmarks);
|
|
if (Array.isArray(bookmarks) && bookmarks.length > 0) {
|
|
return bookmarks;
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Error checking local bookmarks:', error);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
showModal(modalId) {
|
|
const modal = document.getElementById(modalId);
|
|
if (modal) {
|
|
modal.style.display = 'block';
|
|
modal.setAttribute('aria-hidden', 'false');
|
|
}
|
|
}
|
|
|
|
hideModal(modalId) {
|
|
const modal = document.getElementById(modalId);
|
|
if (modal) {
|
|
modal.style.display = 'none';
|
|
modal.setAttribute('aria-hidden', 'true');
|
|
}
|
|
}
|
|
|
|
initializeMigrationModal() {
|
|
const startMigrationBtn = document.getElementById('startMigrationBtn');
|
|
const previewMigrationBtn = document.getElementById('previewMigrationBtn');
|
|
const skipMigrationBtn = document.getElementById('skipMigrationBtn');
|
|
const closeMigrationBtn = document.getElementById('closeMigrationBtn');
|
|
|
|
if (startMigrationBtn) {
|
|
startMigrationBtn.addEventListener('click', () => this.startMigration());
|
|
}
|
|
|
|
if (previewMigrationBtn) {
|
|
previewMigrationBtn.addEventListener('click', () => this.previewMigration());
|
|
}
|
|
|
|
if (skipMigrationBtn) {
|
|
skipMigrationBtn.addEventListener('click', () => this.skipMigration());
|
|
}
|
|
|
|
if (closeMigrationBtn) {
|
|
closeMigrationBtn.addEventListener('click', () => this.closeMigration());
|
|
}
|
|
|
|
// Strategy change handler
|
|
const strategyRadios = document.querySelectorAll('input[name="migrationStrategy"]');
|
|
strategyRadios.forEach(radio => {
|
|
radio.addEventListener('change', () => this.handleStrategyChange());
|
|
});
|
|
|
|
// Close modal handlers
|
|
const closeButtons = document.querySelectorAll('.close');
|
|
closeButtons.forEach(btn => {
|
|
btn.addEventListener('click', (e) => {
|
|
const modal = e.target.closest('.modal');
|
|
if (modal) {
|
|
this.hideModal(modal.id);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
handleStrategyChange() {
|
|
const selectedStrategy = document.querySelector('input[name="migrationStrategy"]:checked')?.value;
|
|
const warningElement = document.getElementById('replaceWarning');
|
|
|
|
if (warningElement) {
|
|
warningElement.style.display = selectedStrategy === 'replace' ? 'block' : 'none';
|
|
}
|
|
}
|
|
|
|
previewMigration() {
|
|
const localBookmarks = this.checkForLocalBookmarks();
|
|
if (!localBookmarks) {
|
|
alert('No local bookmarks found to migrate.');
|
|
return;
|
|
}
|
|
|
|
const selectedStrategy = document.querySelector('input[name="migrationStrategy"]:checked')?.value || 'merge';
|
|
|
|
try {
|
|
// Show preview section
|
|
const previewElement = document.getElementById('migrationPreview');
|
|
if (previewElement) {
|
|
previewElement.style.display = 'block';
|
|
}
|
|
|
|
// Validate local bookmarks
|
|
const validationResult = this.validateLocalBookmarks(localBookmarks);
|
|
|
|
// Update preview stats
|
|
document.getElementById('localBookmarksCount').textContent = localBookmarks.length;
|
|
document.getElementById('validBookmarksCount').textContent = validationResult.valid.length;
|
|
document.getElementById('invalidBookmarksCount').textContent = validationResult.invalid.length;
|
|
document.getElementById('duplicatesCount').textContent = '0'; // Mock
|
|
document.getElementById('newBookmarksCount').textContent = validationResult.valid.length;
|
|
|
|
} catch (error) {
|
|
console.error('Preview migration error:', error);
|
|
alert('Error previewing migration: ' + error.message);
|
|
}
|
|
}
|
|
|
|
validateLocalBookmarks(localBookmarks) {
|
|
const valid = [];
|
|
const invalid = [];
|
|
|
|
localBookmarks.forEach((bookmark, index) => {
|
|
const errors = [];
|
|
|
|
if (!bookmark.title || bookmark.title.trim().length === 0) {
|
|
errors.push('Missing title');
|
|
}
|
|
if (!bookmark.url || bookmark.url.trim().length === 0) {
|
|
errors.push('Missing URL');
|
|
}
|
|
|
|
if (bookmark.url) {
|
|
try {
|
|
new URL(bookmark.url);
|
|
} catch (e) {
|
|
errors.push('Invalid URL format');
|
|
}
|
|
}
|
|
|
|
if (errors.length === 0) {
|
|
valid.push({
|
|
title: bookmark.title.trim(),
|
|
url: bookmark.url.trim(),
|
|
folder: bookmark.folder || '',
|
|
add_date: bookmark.addDate || bookmark.add_date || new Date(),
|
|
last_modified: bookmark.lastModified || bookmark.last_modified,
|
|
icon: bookmark.icon || bookmark.favicon,
|
|
status: bookmark.status || 'unknown'
|
|
});
|
|
} else {
|
|
invalid.push({
|
|
index,
|
|
bookmark,
|
|
errors
|
|
});
|
|
}
|
|
});
|
|
|
|
return { valid, invalid };
|
|
}
|
|
|
|
startMigration() {
|
|
alert('Migration would start here. This is a test environment - no actual API call will be made.');
|
|
|
|
// Mock progress
|
|
this.showMigrationProgress();
|
|
this.updateMigrationProgress(0, 'Preparing migration...');
|
|
|
|
setTimeout(() => {
|
|
this.updateMigrationProgress(50, 'Validating bookmarks...');
|
|
setTimeout(() => {
|
|
this.updateMigrationProgress(100, 'Migration completed!');
|
|
setTimeout(() => {
|
|
this.showMigrationResults({
|
|
summary: {
|
|
successfullyMigrated: 3,
|
|
duplicatesSkipped: 1,
|
|
invalidBookmarks: 1
|
|
}
|
|
});
|
|
}, 1000);
|
|
}, 1000);
|
|
}, 1000);
|
|
}
|
|
|
|
showMigrationProgress() {
|
|
const progressElement = document.getElementById('migrationProgress');
|
|
if (progressElement) {
|
|
progressElement.style.display = 'block';
|
|
}
|
|
|
|
const startBtn = document.getElementById('startMigrationBtn');
|
|
const previewBtn = document.getElementById('previewMigrationBtn');
|
|
const skipBtn = document.getElementById('skipMigrationBtn');
|
|
|
|
if (startBtn) startBtn.style.display = 'none';
|
|
if (previewBtn) previewBtn.style.display = 'none';
|
|
if (skipBtn) skipBtn.style.display = 'none';
|
|
}
|
|
|
|
updateMigrationProgress(percentage, message) {
|
|
const progressFill = document.getElementById('migrationProgressFill');
|
|
const progressText = document.getElementById('migrationProgressText');
|
|
|
|
if (progressFill) {
|
|
progressFill.style.width = `${percentage}%`;
|
|
}
|
|
if (progressText) {
|
|
progressText.textContent = message;
|
|
}
|
|
}
|
|
|
|
showMigrationResults(result) {
|
|
const resultsElement = document.getElementById('migrationResults');
|
|
if (resultsElement) {
|
|
resultsElement.style.display = 'block';
|
|
}
|
|
|
|
const summary = result.summary;
|
|
document.getElementById('migratedCount').textContent = summary.successfullyMigrated || 0;
|
|
document.getElementById('skippedCount').textContent = summary.duplicatesSkipped || 0;
|
|
document.getElementById('errorCount').textContent = summary.invalidBookmarks || 0;
|
|
|
|
const closeBtn = document.getElementById('closeMigrationBtn');
|
|
if (closeBtn) {
|
|
closeBtn.style.display = 'inline-block';
|
|
}
|
|
|
|
const progressElement = document.getElementById('migrationProgress');
|
|
if (progressElement) {
|
|
progressElement.style.display = 'none';
|
|
}
|
|
}
|
|
|
|
skipMigration() {
|
|
const confirmed = confirm('Are you sure you want to skip the migration?');
|
|
if (confirmed) {
|
|
this.hideModal('migrationModal');
|
|
}
|
|
}
|
|
|
|
closeMigration() {
|
|
this.hideModal('migrationModal');
|
|
}
|
|
|
|
showMigrationModalIfNeeded() {
|
|
const localBookmarks = this.checkForLocalBookmarks();
|
|
if (localBookmarks && localBookmarks.length > 0) {
|
|
const countElement = document.getElementById('localBookmarkCount');
|
|
if (countElement) {
|
|
countElement.textContent = localBookmarks.length;
|
|
}
|
|
this.showModal('migrationModal');
|
|
}
|
|
}
|
|
}
|
|
|
|
// Initialize tester
|
|
const tester = new MigrationTester();
|
|
|
|
// Test controls
|
|
document.getElementById('setupTestData').addEventListener('click', () => {
|
|
const testBookmarks = [
|
|
{
|
|
title: "Test Bookmark 1",
|
|
url: "https://example.com",
|
|
folder: "Test Folder",
|
|
addDate: new Date().toISOString(),
|
|
icon: "https://example.com/favicon.ico",
|
|
status: "unknown"
|
|
},
|
|
{
|
|
title: "Test Bookmark 2",
|
|
url: "https://google.com",
|
|
folder: "",
|
|
addDate: new Date().toISOString(),
|
|
status: "unknown"
|
|
},
|
|
{
|
|
title: "Invalid Bookmark",
|
|
url: "not-a-valid-url",
|
|
folder: "Test Folder"
|
|
},
|
|
{
|
|
title: "GitHub",
|
|
url: "https://github.com",
|
|
folder: "Development",
|
|
addDate: new Date().toISOString(),
|
|
status: "valid"
|
|
}
|
|
];
|
|
|
|
localStorage.setItem('bookmarks', JSON.stringify(testBookmarks));
|
|
document.getElementById('statusText').textContent = `Setup complete! Added ${testBookmarks.length} test bookmarks to localStorage.`;
|
|
});
|
|
|
|
document.getElementById('showMigrationModal').addEventListener('click', () => {
|
|
tester.showMigrationModalIfNeeded();
|
|
});
|
|
|
|
document.getElementById('clearTestData').addEventListener('click', () => {
|
|
localStorage.removeItem('bookmarks');
|
|
document.getElementById('statusText').textContent = 'Test data cleared from localStorage.';
|
|
});
|
|
</script>
|
|
</body>
|
|
</html> |