Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -497,6 +497,106 @@ const content = document.getElementById('content');
|
|
| 497 |
let active = "";
|
| 498 |
let currentPage = 1;
|
| 499 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 500 |
// Simple utility functions
|
| 501 |
function loadHTML(url, callback) {
|
| 502 |
const xhr = new XMLHttpRequest();
|
|
@@ -635,6 +735,14 @@ function loadManage() {
|
|
| 635 |
|
| 636 |
<h2>Manage Saved URLs</h2>
|
| 637 |
<div id="url-list" class="url-list">Loading...</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 638 |
</div>
|
| 639 |
`;
|
| 640 |
|
|
@@ -643,14 +751,53 @@ function loadManage() {
|
|
| 643 |
|
| 644 |
// URL management functions
|
| 645 |
function loadUrlList() {
|
|
|
|
|
|
|
|
|
|
| 646 |
makeRequest('/api/favorites?per_page=100', 'GET', null, function(data) {
|
| 647 |
const urlList = document.getElementById('url-list');
|
| 648 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 649 |
if(data.items.length === 0) {
|
| 650 |
urlList.innerHTML = '<p style="text-align:center;padding:20px">No URLs saved yet.</p>';
|
| 651 |
return;
|
| 652 |
}
|
| 653 |
|
|
|
|
|
|
|
|
|
|
| 654 |
let html = '';
|
| 655 |
data.items.forEach(item => {
|
| 656 |
// Escape the URL to prevent JavaScript injection when used in onclick handlers
|
|
@@ -670,6 +817,7 @@ function loadUrlList() {
|
|
| 670 |
urlList.innerHTML = html;
|
| 671 |
});
|
| 672 |
}
|
|
|
|
| 673 |
|
| 674 |
function addUrl() {
|
| 675 |
const url = document.getElementById('new-url').value.trim();
|
|
@@ -686,6 +834,14 @@ function addUrl() {
|
|
| 686 |
showStatus('add-status', data.message, data.success);
|
| 687 |
if(data.success) {
|
| 688 |
document.getElementById('new-url').value = '';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 689 |
loadUrlList();
|
| 690 |
// If currently in Favorites tab, reload to see changes immediately
|
| 691 |
if(active === 'Favorites') {
|
|
@@ -708,6 +864,14 @@ function editUrl(url) {
|
|
| 708 |
|
| 709 |
makeRequest('/api/url/update', 'POST', formData, function(data) {
|
| 710 |
if(data.success) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 711 |
loadUrlList();
|
| 712 |
// If currently in Favorites tab, reload to see changes immediately
|
| 713 |
if(active === 'Favorites') {
|
|
@@ -729,6 +893,14 @@ function deleteUrl(url) {
|
|
| 729 |
|
| 730 |
makeRequest('/api/url/delete', 'POST', formData, function(data) {
|
| 731 |
if(data.success) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 732 |
loadUrlList();
|
| 733 |
// If currently in Favorites tab, reload to see changes immediately
|
| 734 |
if(active === 'Favorites') {
|
|
@@ -778,6 +950,52 @@ tabs.appendChild(manageTab);
|
|
| 778 |
|
| 779 |
// Start with Favorites tab
|
| 780 |
loadFavorites(1);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 781 |
</script>
|
| 782 |
</body>
|
| 783 |
</html>''')
|
|
|
|
| 497 |
let active = "";
|
| 498 |
let currentPage = 1;
|
| 499 |
|
| 500 |
+
// LocalStorage functionality for URL persistence
|
| 501 |
+
function saveToLocalStorage(urls) {
|
| 502 |
+
try {
|
| 503 |
+
localStorage.setItem('favoriteUrls', JSON.stringify(urls));
|
| 504 |
+
console.log('Saved URLs to localStorage:', urls.length);
|
| 505 |
+
return true;
|
| 506 |
+
} catch (e) {
|
| 507 |
+
console.error('Error saving to localStorage:', e);
|
| 508 |
+
return false;
|
| 509 |
+
}
|
| 510 |
+
}
|
| 511 |
+
|
| 512 |
+
function loadFromLocalStorage() {
|
| 513 |
+
try {
|
| 514 |
+
const data = localStorage.getItem('favoriteUrls');
|
| 515 |
+
if (data) {
|
| 516 |
+
const urls = JSON.parse(data);
|
| 517 |
+
console.log('Loaded URLs from localStorage:', urls.length);
|
| 518 |
+
return urls;
|
| 519 |
+
}
|
| 520 |
+
} catch (e) {
|
| 521 |
+
console.error('Error loading from localStorage:', e);
|
| 522 |
+
}
|
| 523 |
+
return null;
|
| 524 |
+
}
|
| 525 |
+
|
| 526 |
+
// Export/Import functionality
|
| 527 |
+
function exportUrls() {
|
| 528 |
+
makeRequest('/api/favorites?per_page=1000', 'GET', null, function(data) {
|
| 529 |
+
if (data.items && data.items.length > 0) {
|
| 530 |
+
const urls = data.items.map(item => item.url);
|
| 531 |
+
|
| 532 |
+
// Save to localStorage as backup
|
| 533 |
+
saveToLocalStorage(urls);
|
| 534 |
+
|
| 535 |
+
// Create file for download
|
| 536 |
+
const blob = new Blob([JSON.stringify(urls, null, 2)], { type: 'application/json' });
|
| 537 |
+
const a = document.createElement('a');
|
| 538 |
+
a.href = URL.createObjectURL(blob);
|
| 539 |
+
a.download = 'favorite_urls.json';
|
| 540 |
+
document.body.appendChild(a);
|
| 541 |
+
a.click();
|
| 542 |
+
document.body.removeChild(a);
|
| 543 |
+
} else {
|
| 544 |
+
alert('No URLs to export');
|
| 545 |
+
}
|
| 546 |
+
});
|
| 547 |
+
}
|
| 548 |
+
|
| 549 |
+
function importUrls() {
|
| 550 |
+
document.getElementById('import-file').click();
|
| 551 |
+
}
|
| 552 |
+
|
| 553 |
+
function handleImportFile(files) {
|
| 554 |
+
if (files.length === 0) return;
|
| 555 |
+
|
| 556 |
+
const file = files[0];
|
| 557 |
+
const reader = new FileReader();
|
| 558 |
+
|
| 559 |
+
reader.onload = function(e) {
|
| 560 |
+
try {
|
| 561 |
+
const urls = JSON.parse(e.target.result);
|
| 562 |
+
if (Array.isArray(urls)) {
|
| 563 |
+
// Save to localStorage
|
| 564 |
+
saveToLocalStorage(urls);
|
| 565 |
+
|
| 566 |
+
// Add each URL to the server
|
| 567 |
+
let processed = 0;
|
| 568 |
+
|
| 569 |
+
function addNextUrl(index) {
|
| 570 |
+
if (index >= urls.length) {
|
| 571 |
+
alert(`Import complete. Added ${processed} URLs.`);
|
| 572 |
+
loadUrlList();
|
| 573 |
+
if (active === 'Favorites') {
|
| 574 |
+
loadFavorites(currentPage);
|
| 575 |
+
}
|
| 576 |
+
return;
|
| 577 |
+
}
|
| 578 |
+
|
| 579 |
+
const formData = new FormData();
|
| 580 |
+
formData.append('url', urls[index]);
|
| 581 |
+
|
| 582 |
+
makeRequest('/api/url/add', 'POST', formData, function(data) {
|
| 583 |
+
if (data.success) processed++;
|
| 584 |
+
addNextUrl(index + 1);
|
| 585 |
+
});
|
| 586 |
+
}
|
| 587 |
+
|
| 588 |
+
addNextUrl(0);
|
| 589 |
+
} else {
|
| 590 |
+
alert('Invalid format. File must contain a JSON array of URLs.');
|
| 591 |
+
}
|
| 592 |
+
} catch (e) {
|
| 593 |
+
alert('Error parsing file: ' + e.message);
|
| 594 |
+
}
|
| 595 |
+
};
|
| 596 |
+
|
| 597 |
+
reader.readAsText(file);
|
| 598 |
+
}
|
| 599 |
+
|
| 600 |
// Simple utility functions
|
| 601 |
function loadHTML(url, callback) {
|
| 602 |
const xhr = new XMLHttpRequest();
|
|
|
|
| 735 |
|
| 736 |
<h2>Manage Saved URLs</h2>
|
| 737 |
<div id="url-list" class="url-list">Loading...</div>
|
| 738 |
+
|
| 739 |
+
<div style="margin-top: 30px;">
|
| 740 |
+
<h3>Backup & Restore</h3>
|
| 741 |
+
<p>Server storage may not persist across restarts. Use these options to save your data:</p>
|
| 742 |
+
<button onclick="exportUrls()" class="btn btn-success">Export URLs</button>
|
| 743 |
+
<button onclick="importUrls()" class="btn btn-primary">Import URLs</button>
|
| 744 |
+
<input type="file" id="import-file" style="display:none" onchange="handleImportFile(this.files)">
|
| 745 |
+
</div>
|
| 746 |
</div>
|
| 747 |
`;
|
| 748 |
|
|
|
|
| 751 |
|
| 752 |
// URL management functions
|
| 753 |
function loadUrlList() {
|
| 754 |
+
// First try to load from localStorage as a fallback
|
| 755 |
+
const localUrls = loadFromLocalStorage();
|
| 756 |
+
|
| 757 |
makeRequest('/api/favorites?per_page=100', 'GET', null, function(data) {
|
| 758 |
const urlList = document.getElementById('url-list');
|
| 759 |
|
| 760 |
+
// If server has no URLs but localStorage does, restore from localStorage
|
| 761 |
+
if (data.items.length === 0 && localUrls && localUrls.length > 0) {
|
| 762 |
+
showStatus('add-status', 'Restoring URLs from local backup...', true);
|
| 763 |
+
|
| 764 |
+
// Add each URL from localStorage to the server
|
| 765 |
+
let restored = 0;
|
| 766 |
+
|
| 767 |
+
function restoreNextUrl(index) {
|
| 768 |
+
if (index >= localUrls.length) {
|
| 769 |
+
showStatus('add-status', `Restored ${restored} URLs from local backup`, true);
|
| 770 |
+
// Reload the list after restoration
|
| 771 |
+
setTimeout(() => {
|
| 772 |
+
loadUrlList();
|
| 773 |
+
if (active === 'Favorites') {
|
| 774 |
+
loadFavorites(currentPage);
|
| 775 |
+
}
|
| 776 |
+
}, 1000);
|
| 777 |
+
return;
|
| 778 |
+
}
|
| 779 |
+
|
| 780 |
+
const formData = new FormData();
|
| 781 |
+
formData.append('url', localUrls[index]);
|
| 782 |
+
|
| 783 |
+
makeRequest('/api/url/add', 'POST', formData, function(data) {
|
| 784 |
+
if (data.success) restored++;
|
| 785 |
+
restoreNextUrl(index + 1);
|
| 786 |
+
});
|
| 787 |
+
}
|
| 788 |
+
|
| 789 |
+
restoreNextUrl(0);
|
| 790 |
+
return;
|
| 791 |
+
}
|
| 792 |
+
|
| 793 |
if(data.items.length === 0) {
|
| 794 |
urlList.innerHTML = '<p style="text-align:center;padding:20px">No URLs saved yet.</p>';
|
| 795 |
return;
|
| 796 |
}
|
| 797 |
|
| 798 |
+
// Update localStorage with server data
|
| 799 |
+
saveToLocalStorage(data.items.map(item => item.url));
|
| 800 |
+
|
| 801 |
let html = '';
|
| 802 |
data.items.forEach(item => {
|
| 803 |
// Escape the URL to prevent JavaScript injection when used in onclick handlers
|
|
|
|
| 817 |
urlList.innerHTML = html;
|
| 818 |
});
|
| 819 |
}
|
| 820 |
+
}
|
| 821 |
|
| 822 |
function addUrl() {
|
| 823 |
const url = document.getElementById('new-url').value.trim();
|
|
|
|
| 834 |
showStatus('add-status', data.message, data.success);
|
| 835 |
if(data.success) {
|
| 836 |
document.getElementById('new-url').value = '';
|
| 837 |
+
|
| 838 |
+
// Update localStorage
|
| 839 |
+
const localUrls = loadFromLocalStorage() || [];
|
| 840 |
+
if (!localUrls.includes(url)) {
|
| 841 |
+
localUrls.unshift(url); // Add to beginning
|
| 842 |
+
saveToLocalStorage(localUrls);
|
| 843 |
+
}
|
| 844 |
+
|
| 845 |
loadUrlList();
|
| 846 |
// If currently in Favorites tab, reload to see changes immediately
|
| 847 |
if(active === 'Favorites') {
|
|
|
|
| 864 |
|
| 865 |
makeRequest('/api/url/update', 'POST', formData, function(data) {
|
| 866 |
if(data.success) {
|
| 867 |
+
// Update localStorage
|
| 868 |
+
let localUrls = loadFromLocalStorage() || [];
|
| 869 |
+
const index = localUrls.indexOf(decodedUrl);
|
| 870 |
+
if (index !== -1) {
|
| 871 |
+
localUrls[index] = newUrl;
|
| 872 |
+
saveToLocalStorage(localUrls);
|
| 873 |
+
}
|
| 874 |
+
|
| 875 |
loadUrlList();
|
| 876 |
// If currently in Favorites tab, reload to see changes immediately
|
| 877 |
if(active === 'Favorites') {
|
|
|
|
| 893 |
|
| 894 |
makeRequest('/api/url/delete', 'POST', formData, function(data) {
|
| 895 |
if(data.success) {
|
| 896 |
+
// Update localStorage
|
| 897 |
+
let localUrls = loadFromLocalStorage() || [];
|
| 898 |
+
const index = localUrls.indexOf(decodedUrl);
|
| 899 |
+
if (index !== -1) {
|
| 900 |
+
localUrls.splice(index, 1);
|
| 901 |
+
saveToLocalStorage(localUrls);
|
| 902 |
+
}
|
| 903 |
+
|
| 904 |
loadUrlList();
|
| 905 |
// If currently in Favorites tab, reload to see changes immediately
|
| 906 |
if(active === 'Favorites') {
|
|
|
|
| 950 |
|
| 951 |
// Start with Favorites tab
|
| 952 |
loadFavorites(1);
|
| 953 |
+
|
| 954 |
+
// Check for localStorage on page load
|
| 955 |
+
window.addEventListener('load', function() {
|
| 956 |
+
// Try to load URLs from localStorage
|
| 957 |
+
const localUrls = loadFromLocalStorage();
|
| 958 |
+
|
| 959 |
+
// If we have URLs in localStorage, make sure they're on the server
|
| 960 |
+
if (localUrls && localUrls.length > 0) {
|
| 961 |
+
console.log(`Found ${localUrls.length} URLs in localStorage`);
|
| 962 |
+
|
| 963 |
+
// First get server URLs to compare
|
| 964 |
+
makeRequest('/api/favorites?per_page=1000', 'GET', null, function(data) {
|
| 965 |
+
const serverUrls = data.items.map(item => item.url);
|
| 966 |
+
|
| 967 |
+
// Find URLs that are in localStorage but not on server
|
| 968 |
+
const missingUrls = localUrls.filter(url => !serverUrls.includes(url));
|
| 969 |
+
|
| 970 |
+
if (missingUrls.length > 0) {
|
| 971 |
+
console.log(`Found ${missingUrls.length} URLs missing from server, restoring...`);
|
| 972 |
+
|
| 973 |
+
// Add missing URLs to server
|
| 974 |
+
let restored = 0;
|
| 975 |
+
|
| 976 |
+
function restoreNextUrl(index) {
|
| 977 |
+
if (index >= missingUrls.length) {
|
| 978 |
+
console.log(`Restored ${restored} URLs from localStorage`);
|
| 979 |
+
if (active === 'Favorites') {
|
| 980 |
+
loadFavorites(currentPage);
|
| 981 |
+
}
|
| 982 |
+
return;
|
| 983 |
+
}
|
| 984 |
+
|
| 985 |
+
const formData = new FormData();
|
| 986 |
+
formData.append('url', missingUrls[index]);
|
| 987 |
+
|
| 988 |
+
makeRequest('/api/url/add', 'POST', formData, function(data) {
|
| 989 |
+
if (data.success) restored++;
|
| 990 |
+
restoreNextUrl(index + 1);
|
| 991 |
+
});
|
| 992 |
+
}
|
| 993 |
+
|
| 994 |
+
restoreNextUrl(0);
|
| 995 |
+
}
|
| 996 |
+
});
|
| 997 |
+
}
|
| 998 |
+
});
|
| 999 |
</script>
|
| 1000 |
</body>
|
| 1001 |
</html>''')
|