|
382 | 382 | <button class="tool-btn" id="btnBookmarklet" title="Bookmarklet tool" popovertarget="bookmarkletSection">Bookmarklet</button> |
383 | 383 | <button class="tool-btn" id="btnFavorites" title="View favorites">Favorites</button> |
384 | 384 | <button class="tool-btn" id="btnImportInstalled" title="Import installed scripts">Installed</button> |
| 385 | + <button class="tool-btn" id="btnExportInstalled" title="Export installed scripts">Export Installed</button> |
385 | 386 | <button class="tool-btn" id="btnDiagnostics" title="Source diagnostics" popovertarget="diagnosticsSection">Diagnostics</button> |
386 | 387 | <button class="tool-btn" id="btnExportFavs" title="Export favorites" style="display:none">Export</button> |
387 | 388 | <button class="tool-btn" id="btnImportFavs" title="Import favorites" style="display:none">Import</button> |
@@ -835,6 +836,13 @@ <h3>Search userscripts everywhere</h3> |
835 | 836 | function isFav(id) { return getFavs().some(function(f) { return f.id === id; }); } |
836 | 837 |
|
837 | 838 | function cleanUrl(u) { return (u || '').trim(); } |
| 839 | + function validImportUrl(u) { |
| 840 | + if (!u) return true; |
| 841 | + try { |
| 842 | + var url = new URL(String(u).trim()); |
| 843 | + return url.protocol === 'http:' || url.protocol === 'https:'; |
| 844 | + } catch(e) { return false; } |
| 845 | + } |
838 | 846 | function normalizeScriptUrls(item) { |
839 | 847 | item.installUrl = cleanUrl(item.installUrl || item.downloadUrl || item.updateUrl || ''); |
840 | 848 | item.downloadUrl = cleanUrl(item.downloadUrl || item.installUrl || ''); |
@@ -863,6 +871,8 @@ <h3>Search userscripts everywhere</h3> |
863 | 871 |
|
864 | 872 | function normalizeImportedScript(item) { |
865 | 873 | if (!item || typeof item !== 'object') return null; |
| 874 | + var rawUrls = [item.url, item.homepageURL, item.homepage, item.installUrl, item.install_url, item.downloadURL, item.downloadUrl, item.download_url, item.updateUrl, item.updateURL, item.update_url]; |
| 875 | + if (rawUrls.some(function(u) { return u && !validImportUrl(u); })) return null; |
866 | 876 | var out = exportScriptRecord({ |
867 | 877 | id: item.id || item.uuid || '', |
868 | 878 | name: item.name || item.title || '', |
@@ -944,14 +954,25 @@ <h3>Search userscripts everywhere</h3> |
944 | 954 | }; |
945 | 955 | } |
946 | 956 |
|
| 957 | + function buildInstalledExport() { |
| 958 | + return { |
| 959 | + schema: 'scripthunt-installed', |
| 960 | + version: 1, |
| 961 | + exportedAt: new Date().toISOString(), |
| 962 | + installed: getInstalledScripts().map(normalizeImportedScript).filter(Boolean), |
| 963 | + }; |
| 964 | + } |
| 965 | + |
947 | 966 | function parseScriptImportPayload(payload, preferredType) { |
948 | 967 | var type = preferredType || 'favorites'; |
949 | 968 | var rows = []; |
950 | 969 | if (Array.isArray(payload)) { |
951 | 970 | rows = payload; |
952 | 971 | } else if (payload && typeof payload === 'object') { |
| 972 | + if (payload.schema && payload.version !== 1) throw new Error('Unsupported import version'); |
953 | 973 | if (payload.schema === 'scripthunt-installed') { type = 'installed'; rows = payload.installed || payload.scripts || []; } |
954 | 974 | else if (payload.schema === 'scripthunt-favorites') { type = 'favorites'; rows = payload.favorites || []; } |
| 975 | + else if (payload.schema) throw new Error('Unsupported import schema'); |
955 | 976 | else if (Array.isArray(payload.installed) || Array.isArray(payload.scripts)) { type = 'installed'; rows = payload.installed || payload.scripts; } |
956 | 977 | else if (Array.isArray(payload.favorites)) { type = 'favorites'; rows = payload.favorites; } |
957 | 978 | } |
@@ -2937,20 +2958,29 @@ <h3>Search userscripts everywhere</h3> |
2937 | 2958 | }); |
2938 | 2959 |
|
2939 | 2960 | // Favorites button |
2940 | | - var btnExport = document.getElementById('btnExportFavs'), btnImport = document.getElementById('btnImportFavs'), btnImportInstalled = document.getElementById('btnImportInstalled'); |
| 2961 | + var btnExport = document.getElementById('btnExportFavs'), btnImport = document.getElementById('btnImportFavs'), btnImportInstalled = document.getElementById('btnImportInstalled'), btnExportInstalled = document.getElementById('btnExportInstalled'); |
2941 | 2962 | document.getElementById('btnFavorites').addEventListener('click', function() { |
2942 | 2963 | this.classList.toggle('active'); |
2943 | 2964 | var active = this.classList.contains('active'); |
2944 | 2965 | btnExport.style.display = active ? '' : 'none'; btnImport.style.display = active ? '' : 'none'; |
2945 | 2966 | if (active) { showFavorites(); } else { if (state.rawQuery) { state.showFavs = false; sortAndRender(); } else { clearBtn.click(); } } |
2946 | 2967 | }); |
2947 | 2968 |
|
| 2969 | + function downloadJson(payload, filename) { |
| 2970 | + var blob = new Blob([JSON.stringify(payload, null, 2)], { type: 'application/json' }); |
| 2971 | + var a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = filename; a.click(); |
| 2972 | + URL.revokeObjectURL(a.href); |
| 2973 | + } |
| 2974 | + |
2948 | 2975 | // Export favorites |
2949 | 2976 | btnExport.addEventListener('click', function() { |
2950 | 2977 | var favs = getFavs(); if (!favs.length) { showToast('No favorites to export', 'error', 2000); return; } |
2951 | | - var blob = new Blob([JSON.stringify(buildFavoritesExport(), null, 2)], { type: 'application/json' }); |
2952 | | - var a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = 'scripthunt-favorites.json'; a.click(); |
2953 | | - URL.revokeObjectURL(a.href); showToast('Exported ' + favs.length + ' favorites', 'info', 2000); |
| 2978 | + downloadJson(buildFavoritesExport(), 'scripthunt-favorites.json'); showToast('Exported ' + favs.length + ' favorites', 'info', 2000); |
| 2979 | + }); |
| 2980 | + |
| 2981 | + btnExportInstalled.addEventListener('click', function() { |
| 2982 | + var installed = getInstalledScripts(); if (!installed.length) { showToast('No installed scripts to export', 'error', 2000); return; } |
| 2983 | + downloadJson(buildInstalledExport(), 'scripthunt-installed.json'); showToast('Exported ' + installed.length + ' installed scripts', 'info', 2000); |
2954 | 2984 | }); |
2955 | 2985 |
|
2956 | 2986 | // Import favorites |
|
0 commit comments