diff --git a/content.js b/content.js index 7971d5f..37140d6 100644 --- a/content.js +++ b/content.js @@ -472,12 +472,100 @@ '}', '.magnet-empty-state-text{', ' font-size:14px;line-height:1.6;', + '}', + + /* === 进度条 === */', + '.magnet-progress-container{', + ' width:100%;height:6px;background:var(--m-bg-secondary);border-radius:3px;overflow:hidden;margin-top:8px;', + '}', + '.magnet-progress-bar{', + ' height:100%;background:linear-gradient(90deg, var(--m-accent), #00f5c4);border-radius:3px;transition:width 0.3s ease;', + '}', + '.magnet-progress-text{', + ' display:flex;justify-content:space-between;font-size:11px;color:var(--m-text-muted);margin-top:4px;', + '}', + + /* === 收藏按钮 === */', + '.magnet-favorite-btn{', + ' padding:6px 10px;background:transparent;border:1px solid var(--m-border);border-radius:8px;cursor:pointer;font-size:11px;color:var(--m-text-muted);transition:all 0.2s ease;', + '}', + '.magnet-favorite-btn:hover{', + ' border-color:var(--m-accent-secondary);color:var(--m-accent-secondary);', + '}', + '.magnet-favorite-btn.is-favorite{', + ' background:rgba(167,139,250,0.15);border-color:var(--m-accent-secondary);color:var(--m-accent-secondary);', + '}', + + /* === 收藏视图 === */', + '#magnet-favorites-view .magnet-favorite-item{', + ' display:flex;align-items:center;gap:10px;padding:10px 12px;background:var(--m-bg-card);border:1px solid var(--m-border);border-radius:var(--m-radius-md);margin-bottom:8px;', + '}', + '#magnet-favorites-view .magnet-favorite-item:hover{', + ' border-color:var(--m-border-accent);', + '}', + '#magnet-favorites-view .magnet-favorite-title{', + ' flex:1;font-size:12px;color:var(--m-text-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;', + '}', + '#magnet-favorites-view .magnet-favorite-actions{', + ' display:flex;gap:6px;', + '}', + '#magnet-favorites-view .magnet-favorite-copy,', + '#magnet-favorites-view .magnet-favorite-remove{', + ' padding:5px 8px;border:none;border-radius:6px;cursor:pointer;font-size:10px;font-weight:600;transition:all 0.2s ease;', + '}', + '#magnet-favorites-view .magnet-favorite-copy{', + ' background:linear-gradient(135deg, var(--m-accent), #00f5c4);color:var(--m-bg-deep);', + '}', + '#magnet-favorites-view .magnet-favorite-remove{', + ' background:rgba(239,68,68,0.15);color:var(--m-error);', + '}', + + /* === 历史记录下拉 === */', + '.magnet-history-dropdown{', + ' position:absolute;top:100%;left:0;right:0;background:var(--m-bg-card);border:1px solid var(--m-border);border-radius:var(--m-radius-md);box-shadow:var(--m-shadow-lg);z-index:100;max-height:200px;overflow-y:auto;', + '}', + '.magnet-history-item{', + ' padding:10px 14px;cursor:pointer;font-size:12px;color:var(--m-text-primary);transition:background 0.2s ease;', + '}', + '.magnet-history-item:hover{', + ' background:var(--m-bg-secondary);', + '}', + '.magnet-history-item:first-child{', + ' border-radius:var(--m-radius-md) var(--m-radius-md) 0 0;', + '}', + '.magnet-history-item:last-child{', + ' border-radius:0 0 var(--m-radius-md) var(--m-radius-md);', + '}', + '.magnet-history-clear{', + ' padding:10px 14px;border-top:1px solid var(--m-border);font-size:11px;color:var(--m-error);cursor:pointer;text-align:center;', '}' ].join(''); document.head.appendChild(style); } function setPanelView(viewName) { + var resultsView = document.getElementById('magnet-results-view'); + var cacheView = document.getElementById('magnet-cache-view'); + var favoritesView = document.getElementById('magnet-favorites-view'); + var resultsBtn = document.getElementById('magnet-view-results'); + var cacheBtn = document.getElementById('magnet-view-cache'); + var favoritesBtn = document.getElementById('magnet-view-favorites'); + + if (!resultsView || !cacheView || !favoritesView || !resultsBtn || !cacheBtn || !favoritesBtn) { + return; + } + + resultsView.classList.toggle('is-active', viewName === 'results'); + cacheView.classList.toggle('is-active', viewName === 'cache'); + favoritesView.classList.toggle('is-active', viewName === 'favorites'); + resultsBtn.classList.toggle('is-active', viewName === 'results'); + cacheBtn.classList.toggle('is-active', viewName === 'cache'); + favoritesBtn.classList.toggle('is-active', viewName === 'favorites'); + + if (viewName === 'favorites') { + renderFavoritesList(); + } + } var resultsView = document.getElementById('magnet-results-view'); var cacheView = document.getElementById('magnet-cache-view'); var resultsBtn = document.getElementById('magnet-view-results'); @@ -527,7 +615,7 @@ var panel = document.createElement('div'); panel.id = 'magnet-floating-panel'; - panel.innerHTML = '
MAGNET LINKS
智能抓取 · 缓存加速 · 一键复制
搜索结果
关键词命中的磁力链接
0
缓存总览
数据统计与快照管理
'; + panel.innerHTML = '
MAGNET LINKS
智能抓取 · 缓存加速 · 一键复制
搜索结果
关键词命中的磁力链接
0
我的收藏
持久保存的磁力链接
缓存总览
数据统计与快照管理
'; document.body.appendChild(panel); setPanelView('results'); @@ -569,7 +657,48 @@ panel.style.display = 'none'; ball.style.display = 'flex'; }; + var favoritesSwitch = panel.querySelector('#magnet-view-favorites'); + if (favoritesSwitch) { + favoritesSwitch.onclick = function() { + setPanelView('favorites'); + }; + } + var clearFavoritesBtn = panel.querySelector('#magnet-clear-favorites'); + if (clearFavoritesBtn) { + clearFavoritesBtn.onclick = function() { + if (confirm('确定要清空所有收藏吗')) { + saveFavorites([]); + renderFavoritesList(); + } + }; + } + + var copyAllBtn = panel.querySelector('#magnet-copy-all'); + if (copyAllBtn) { + copyAllBtn.onclick = function() { + var links = allMagnetLinks.length > 0 + ? allMagnetLinks.slice() + : Array.from(document.querySelectorAll('.magnet-item .magnet-copy-btn')) + .map(function(btn) { return btn.getAttribute('data-magnet'); }) + .filter(function(link) { return !!link; }); + if (links.length === 0) { + alert('暂无可复制的磁力链接'); + return; + } + var allLinks = links.join('\n'); + navigator.clipboard.writeText(allLinks) + .then(function() { + alert('已复制 ' + links.length + ' 个磁力链接!'); + }) + .catch(function(err) { + var errorMsg = err && err.message ? err.message : '复制失败'; + alert('复制失败: ' + errorMsg); + }); + }; + } + + return panel; var copyAllBtn = panel.querySelector('#magnet-copy-all'); if (copyAllBtn) { copyAllBtn.onclick = function() { @@ -754,6 +883,280 @@ if (countEl) countEl.textContent = count; } + // === 进度条功能 === + function updateProgress(current, total, label) { + var progressBar = document.getElementById('magnet-progress-bar'); + var progressLabel = document.getElementById('magnet-progress-label'); + var progressPercent = document.getElementById('magnet-progress-percent'); + + if (progressBar) { + var percent = total > 0 ? Math.round((current / total) * 100) : 0; + progressBar.style.width = percent + '%'; + } + if (progressLabel) { + progressLabel.textContent = label || ('进度: ' + current + '/' + total); + } + if (progressPercent) { + var percent = total > 0 ? Math.round((current / total) * 100) : 0; + progressPercent.textContent = percent + '%'; + } + } + + function resetProgress() { + updateProgress(0, 0, '等待开始'); + } + + // === 收藏夹功能 === + var FAVORITES_KEY = 'magnet-favorites'; + var favoritesCache = null; + + function loadFavorites() { + if (favoritesCache !== null) { + return favoritesCache; + } + try { + var stored = localStorage.getItem(FAVORITES_KEY); + favoritesCache = stored ? JSON.parse(stored) : []; + } catch (e) { + favoritesCache = []; + } + return favoritesCache; + } + + function saveFavorites(favorites) { + try { + localStorage.setItem(FAVORITES_KEY, JSON.stringify(favorites)); + favoritesCache = favorites; + } catch (e) { + log('保存收藏失败: ' + e); + } + } + + function isFavorited(link) { + var favorites = loadFavorites(); + return favorites.some(function(f) { return f.link === link; }); + } + + function toggleFavorite(title, link, btn) { + var favorites = loadFavorites(); + var existingIndex = favorites.findIndex(function(f) { return f.link === link; }); + + if (existingIndex >= 0) { + favorites.splice(existingIndex, 1); + btn.classList.remove('is-favorite'); + btn.innerHTML = '♡'; + btn.title = '收藏'; + } else { + favorites.push({ + title: title, + link: link, + addedAt: Date.now() + }); + btn.classList.add('is-favorite'); + btn.innerHTML = '♥'; + btn.title = '取消收藏'; + } + + saveFavorites(favorites); + } + + function renderFavoritesList() { + var list = document.getElementById('magnet-favorites-list'); + if (!list) return; + + var favorites = loadFavorites(); + list.innerHTML = ''; + + if (favorites.length === 0) { + list.innerHTML = '
📭
暂无收藏\br>点击结果列表中的 ♡ 按钮添加收藏
'; + return; + } + + favorites.forEach(function(fav) { + var item = document.createElement('div'); + item.className = 'magnet-favorite-item'; + + var titleEl = document.createElement('div'); + titleEl.className = 'magnet-favorite-title'; + titleEl.textContent = fav.title; + titleEl.title = fav.title; + + var actionsEl = document.createElement('div'); + actionsEl.className = 'magnet-favorite-actions'; + + var copyBtn = document.createElement('button'); + copyBtn.className = 'magnet-favorite-copy'; + copyBtn.textContent = '复制'; + copyBtn.onclick = function() { + navigator.clipboard.writeText(fav.link) + .then(function() { + copyBtn.textContent = '已复制'; + setTimeout(function() { copyBtn.textContent = '复制'; }, 1000); + }); + }; + + var removeBtn = document.createElement('button'); + removeBtn.className = 'magnet-favorite-remove'; + removeBtn.textContent = '删除'; + removeBtn.onclick = function() { + var favorites = loadFavorites(); + var idx = favorites.findIndex(function(f) { return f.link === fav.link; }); + if (idx >= 0) { + favorites.splice(idx, 1); + saveFavorites(favorites); + renderFavoritesList(); + } + }; + + actionsEl.appendChild(copyBtn); + actionsEl.appendChild(removeBtn); + item.appendChild(titleEl); + item.appendChild(actionsEl); + list.appendChild(item); + }); + } + + // === 历史记录功能 === + var HISTORY_KEY = 'magnet-search-history'; + var MAX_HISTORY = 20; + + function loadSearchHistory() { + try { + var stored = localStorage.getItem(HISTORY_KEY); + return stored ? JSON.parse(stored) : []; + } catch (e) { + return []; + } + } + + function saveSearchHistory(keyword) { + if (!keyword || typeof keyword !== 'string' || !keyword.trim()) { + return; + } + keyword = keyword.trim(); + var history = loadSearchHistory(); + // 移除已存在的相同关键词 + var idx = history.indexOf(keyword); + if (idx >= 0) { + history.splice(idx, 1); + } + // 添加到开头 + history.unshift(keyword); + // 限制数量 + if (history.length > MAX_HISTORY) { + history = history.slice(0, MAX_HISTORY); + } + try { + localStorage.setItem(HISTORY_KEY, JSON.stringify(history)); + } catch (e) { + log('保存历史记录失败: ' + e); + } + } + + function showHistoryDropdown(input) { + var history = loadSearchHistory(); + if (history.length === 0) { + return; + } + + // 移除已存在的下拉框 + var existing = document.querySelector('.magnet-history-dropdown'); + if (existing) existing.remove(); + + var dropdown = document.createElement('div'); + dropdown.className = 'magnet-history-dropdown'; + + history.forEach(function(kw) { + var item = document.createElement('div'); + item.className = 'magnet-history-item'; + item.textContent = kw; + item.onclick = function() { + input.value = kw; + dropdown.remove(); + input.focus(); + }; + dropdown.appendChild(item); + }); + + var clearItem = document.createElement('div'); + clearItem.className = 'magnet-history-clear'; + clearItem.textContent = '清空历史'; + clearItem.onclick = function() { + localStorage.removeItem(HISTORY_KEY); + dropdown.remove(); + }; + dropdown.appendChild(clearItem); + + input.parentNode.style.position = 'relative'; + input.parentNode.appendChild(dropdown); + + // 点击外部关闭 + setTimeout(function() { + document.addEventListener('click', function closeDropdown(e) { + if (!dropdown.contains(e.target)) { + dropdown.remove(); + document.removeEventListener('click', closeDropdown); + } + }); + }, 100); + } + + // === 通知功能 === + function playNotificationSound() { + try { + var audioContext = new (window.AudioContext || window.webkitAudioContext)(); + var oscillator = audioContext.createOscillator(); + var gainNode = audioContext.createGain(); + + oscillator.connect(gainNode); + gainNode.connect(audioContext.destination); + + oscillator.frequency.value = 800; + oscillator.type = 'sine'; + + gainNode.gain.setValue(0.3); + gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.3); + + oscillator.start(audioContext.currentTime); + oscillator.stop(audioContext.currentTime + 0.3); + } catch (e) { + log('播放提示音失败: ' + e); + } + } + + function showBrowserNotification(title, body) { + if (!('Notification' in window)) { + return; + } + + if (Notification.permission === 'granted') { + new Notification(title, { body: body, icon: chrome.runtime ? chrome.runtime.getURL('icon.png') : undefined }); + } else if (Notification.permission !== 'denied') { + Notification.requestPermission().then(function(permission) { + if (permission === 'granted') { + new Notification(title, { body: body, icon: chrome.runtime ? chrome.runtime.getURL('icon.png') : undefined }); + } + }); + } + } + + function notifyComplete(count, duration) { + playNotificationSound(); + var durationText = ''; + if (duration && duration > 0) { + var seconds = Math.floor(duration / 1000); + if (seconds >= 60) { + durationText = ',耗时 ' + Math.floor(seconds / 60) + ' 分 ' + (seconds % 60) + ' 秒'; + } else { + durationText = ',耗时 ' + seconds + ' 秒'; + } + } + showBrowserNotification('磁力链接抓取完成', '共获取 ' + count + ' 个磁力链接' + durationText); + } + var countEl = document.getElementById('magnet-count-num'); + if (countEl) countEl.textContent = count; + } + function clearMagnetList(skipPersist) { var list = document.getElementById('magnet-list'); if (list) list.innerHTML = ''; @@ -794,6 +1197,100 @@ titleEl.title = safeTitle; titleEl.textContent = safeTitle; + var btnContainer = document.createElement('div'); + btnContainer.style.cssText = 'display:flex;gap:6px;flex-shrink:0;'; + + var copyBtn = document.createElement('button'); + copyBtn.className = 'magnet-copy-btn'; + copyBtn.setAttribute('data-magnet', safeLink); + copyBtn.textContent = '复制'; + + // 收藏按钮 + var favoriteBtn = document.createElement('button'); + favoriteBtn.className = 'magnet-favorite-btn'; + favoriteBtn.innerHTML = '♡'; + favoriteBtn.title = '收藏'; + favoriteBtn.onclick = function() { + toggleFavorite(safeTitle, safeLink, favoriteBtn); + }; + + // 检查是否已收藏 + isFavorite(safeLink).then(function(isFav) { + if (isFav) { + favoriteBtn.classList.add('is-favorite'); + favoriteBtn.title = '取消收藏'; + } + }); + + titleEl.onclick = function() { + navigator.clipboard.writeText(safeLink) + .then(function() { + titleEl.textContent = '已复制: ' + safeTitle.substring(0, 20) + '...'; + setTimeout(function() { + titleEl.textContent = safeTitle; + }, 1500); + }) + .catch(function(err) { + var errorMsg = err && err.message ? err.message : '复制失败'; + log('标题复制失败: ' + errorMsg); + updateStatus('复制失败,请检查剪贴板权限', 'error'); + }); + }; + + copyBtn.onclick = function() { + navigator.clipboard.writeText(safeLink) + .then(function() { + copyBtn.textContent = '已复制'; + setTimeout(function() { + copyBtn.textContent = '复制'; + }, 1000); + }) + .catch(function(err) { + var errorMsg = err && err.message ? err.message : '复制失败'; + log('按钮复制失败: ' + errorMsg); + updateStatus('复制失败,请检查剪贴板权限', 'error'); + }); + }; + + item.appendChild(titleEl); + btnContainer.appendChild(favoriteBtn); + btnContainer.appendChild(copyBtn); + item.appendChild(btnContainer); + + list.appendChild(item); + setPanelView('results'); + updateCount(list.children.length); + + if (!options || !options.skipPersist) { + scheduleStatePersist(); + } + } + var list = document.getElementById('magnet-list'); + if (!list) return; + + var safeTitle = typeof title === 'string' ? title : String(title || ''); + var safeLink = typeof link === 'string' ? link : String(link || ''); + if (!safeLink) return; + + if (magnetRecordMap[safeLink]) { + return; + } + + magnetRecordMap[safeLink] = safeTitle || '恢复记录'; + allMagnetRecords.push({ + title: magnetRecordMap[safeLink], + link: safeLink + }); + allMagnetLinks.push(safeLink); + + var item = document.createElement('div'); + item.className = 'magnet-item'; + + var titleEl = document.createElement('span'); + titleEl.className = 'magnet-title'; + titleEl.title = safeTitle; + titleEl.textContent = safeTitle; + var copyBtn = document.createElement('button'); copyBtn.className = 'magnet-copy-btn'; copyBtn.setAttribute('data-magnet', safeLink); @@ -1796,6 +2293,11 @@ } updateStatus('第' + page + '/' + context.normalizedEnd + '页...', 'loading'); + + // 更新进度条 + var totalPages = context.normalizedEnd - (context.startPage || startPage) + 1; + var currentPage = page - (context.startPage || startPage) + 1; + updateProgress(currentPage, totalPages, '第' + page + '/' + context.normalizedEnd + '页'); var pageUrl = context.baseUrl + page + '.html'; try { @@ -1876,6 +2378,18 @@ progressRuntime.startPage = earlyStart; progressRuntime.endPage = earlyEnd; progressRuntime.resumeFromPage = earlyStart; + + // 重置进度条 + resetProgress(); + + // 记录开始时间 + var startTime = Date.now(); + stopFetching = false; + progressRuntime.isRunning = true; + progressRuntime.stoppedByUser = false; + progressRuntime.startPage = earlyStart; + progressRuntime.endPage = earlyEnd; + progressRuntime.resumeFromPage = earlyStart; var panel = createFloatingPanel(); var ball = document.getElementById('magnet-float-ball'); @@ -2010,6 +2524,28 @@ var keywordMsg = keyword ? ' (关键词:' + keyword + ' 匹配:' + searchContext.matchedThreads + '帖)' : ''; var failedMsg = searchContext.failedPages > 0 ? ',失败页:' + searchContext.failedPages : ''; + + // 更新进度条为100% + updateProgress(normalizedEnd - normalizedStart + 1, normalizedEnd - normalizedStart + 1, '已完成'); + + if (stopFetching) { + progressRuntime.stoppedByUser = true; + updateStatus('已停止 - 找到' + searchContext.allMagnets.size + '个磁力' + keywordMsg + ',已处理帖子:' + searchContext.totalFetched + failedMsg, 'error'); + } else { + progressRuntime.stoppedByUser = false; + progressRuntime.resumeFromPage = normalizedEnd + 1; + updateStatus('完成! 共' + searchContext.allMagnets.size + '个磁力' + keywordMsg + ',已处理帖子:' + searchContext.totalFetched + failedMsg, 'done'); + + // 发送完成通知 + var duration = Date.now() - startTime; + notifyComplete(searchContext.allMagnets.size, duration); + + // 保存搜索历史 + if (keyword) { + saveSearchHistory(keyword); + } + } + var failedMsg = searchContext.failedPages > 0 ? ',失败页:' + searchContext.failedPages : ''; if (stopFetching) { progressRuntime.stoppedByUser = true; updateStatus('已停止 - 找到' + searchContext.allMagnets.size + '个磁力' + keywordMsg + ',已处理帖子:' + searchContext.totalFetched + failedMsg, 'error'); @@ -2058,6 +2594,14 @@ var keywordDiv = document.createElement('div'); keywordDiv.className = 'magnet-control-row'; keywordDiv.innerHTML = ''; + + // 添加历史记录下拉功能 + var keywordInput = keywordDiv.querySelector('#keyword-input'); + if (keywordInput) { + keywordInput.addEventListener('focus', function() { + showHistoryDropdown(keywordInput); + }); + } var pageRange = document.createElement('div'); pageRange.className = 'magnet-control-row'; diff --git a/manifest.json b/manifest.json index 026964c..cc52dfa 100644 --- a/manifest.json +++ b/manifest.json @@ -2,12 +2,13 @@ "manifest_version": 3, "name": "涩花塘磁力助手", "version": "1.3", - "description": "一键获取磁力链接 - 暗色科技风UI", + "description": "一键获取磁力链接 - 暗色科技风UI + 收藏夹 + 吜索历史 + 宔完成通知", "permissions": [ "activeTab", "clipboardWrite", "storage", - "unlimitedStorage" + "unlimitedStorage", + "notifications" ], "host_permissions": [ "http://sehuatang.net/*",