您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
亮色系 DLsite 記錄工具,支援記錄時間、標籤管理和匯出匯入功能
// 實時重新排序標籤 // ==UserScript== // @name DLsite Record Enhanced Tag Manager // @namespace http://tampermonkey.net/ // @version 1.2 // @description 亮色系 DLsite 記錄工具,支援記錄時間、標籤管理和匯出匯入功能 // @author Enhanced by Claude // @match https://www.dlsite.com/maniax/* // @grant none // @license MIT // ==/UserScript== (function() { 'use strict'; // CSS 樣式 const styles = ` .dlsite-record-btn { margin: 5px; padding: 5px 10px; border: none; border-radius: 4px; color: white; font-weight: bold; cursor: pointer; transition: all 0.3s; display: inline-flex; align-items: center; box-shadow: 0 2px 5px rgba(0,0,0,0.2); } .dlsite-record-btn:hover { transform: translateY(-2px); box-shadow: 0 4px 8px rgba(0,0,0,0.3); } .dlsite-record-btn svg { margin-right: 5px; } .dlsite-record-add { background-color: #1a73e8; /* 深藍色 */ } .dlsite-record-toggle { background-color: #34a853; /* 綠色 */ } .dlsite-record-export { background-color: #ea4335; /* 紅色 */ } .dlsite-record-import { background-color: #fbbc05; /* 黃色 */ color: #333; } .dlsite-record-info { display: inline-flex; align-items: center; padding: 5px 10px; margin: 5px; border-radius: 4px; background-color: #f0f8ff; color: #333; font-weight: bold; border: 1px solid #d0e5ff; } .dlsite-record-info svg { margin-right: 5px; color: #4285f4; } .dlsite-record-highlight { background-color: rgba(232, 240, 254, 0.5) !important; /* 非常淡的藍色背景 + 半透明 */ border-left: 4px solid #4a86e8 !important; /* 淡藍色邊框 */ } .dlsite-record-time { font-size: 12px; color: #666; margin-left: 8px; white-space: nowrap; } .dlsite-record-item-button { background-color: #ea4335; color: white; border: none; border-radius: 3px; cursor: pointer; padding: 2px 8px; font-size: 12px; margin-left: 5px; transition: all 0.2s; font-weight: bold; } .dlsite-record-item-button:hover { background-color: #d73125; transform: translateY(-1px); } .dlsite-record-item-button.add { background-color: #1a73e8; } .dlsite-record-item-button.add:hover { background-color: #1967d2; } .dlsite-toast { position: fixed; top: 20px; right: 20px; padding: 10px 20px; background-color: rgba(66, 133, 244, 0.9); color: white; border-radius: 4px; z-index: 10000; animation: fadeInOut 3s; box-shadow: 0 4px 8px rgba(0,0,0,0.2); } @keyframes fadeInOut { 0% { opacity: 0; } 10% { opacity: 1; } 80% { opacity: 1; } 100% { opacity: 0; } } .dlsite-record-controls { display: flex; justify-content: space-between; padding: 10px; background-color: #f9fcff; /* 非常淡的藍色背景 */ border-radius: 4px; margin: 10px 0; position: sticky; top: 0; z-index: 100; box-shadow: 0 2px 10px rgba(0,0,0,0.1); border: 1px solid #d0e5ff; } .dlsite-record-controls-left, .dlsite-record-controls-center, .dlsite-record-controls-right { display: flex; align-items: center; } .dlsite-modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); display: flex; justify-content: center; align-items: center; z-index: 10001; } .dlsite-modal-content { background-color: #ffffff; padding: 20px; border-radius: 8px; width: 80%; max-width: 600px; box-shadow: 0 4px 20px rgba(0,0,0,0.2); color: #333; } .dlsite-modal-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; border-bottom: 1px solid #e0e0e0; padding-bottom: 10px; } .dlsite-modal-close { background: none; border: none; font-size: 24px; cursor: pointer; color: #888; } .dlsite-modal-close:hover { color: #333; } .dlsite-modal-body { margin-bottom: 15px; } .dlsite-modal-footer { display: flex; justify-content: flex-end; gap: 10px; } .dlsite-modal textarea { width: 100%; height: 200px; background-color: #f5f5f5; color: #333; border: 1px solid #ddd; padding: 10px; border-radius: 4px; resize: vertical; font-family: monospace; } .dlsite-modal-btn { padding: 8px 15px; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; } .dlsite-modal-btn-primary { background-color: #1a73e8; color: white; } .dlsite-modal-btn-secondary { background-color: #e0e0e0; color: #333; } /* 新增的 Tag 和公司 相關樣式 */ /* 標籤包裝容器 */ .dlsite-tag-wrapper { display: inline-flex; align-items: stretch; margin-right: 8px; margin-bottom: 5px; border-radius: 4px; overflow: hidden; box-shadow: 0 1px 2px rgba(0,0,0,0.1); position: relative; } /* 標籤樣式 */ .dlsite-tag-wrapper a { display: inline-block; background-color: #e8f0ff; padding: 3px 8px; color: #2a5db0; text-decoration: none; transition: all 0.2s ease; font-size: 11px; border-radius: 4px; } .dlsite-tag-wrapper a:hover { background-color: #d4e4ff; } /* 按鈕樣式 */ .dlsite-tag-toggle-btn { display: none; /* 默認隱藏 */ align-items: center; justify-content: center; min-width: 20px; background-color: #4a86e8; color: white; font-size: 14px; font-weight: bold; cursor: pointer; border: none; transition: all 0.2s ease; border-radius: 0 4px 4px 0; padding: 0; } /* 當滑鼠懸停在標籤上時顯示按鈕 */ .dlsite-tag-wrapper:hover .dlsite-tag-toggle-btn { display: inline-flex; } /* 將被收藏標籤的按鈕始終顯示 */ .dlsite-tag-toggle-btn.active { display: inline-flex; } .dlsite-tag-toggle-btn:hover { background-color: #3b78e0; } .dlsite-tag-toggle-btn.active { background-color: #ea4335; } .dlsite-tag-toggle-btn.active:hover { background-color: #d73125; } /* 高亮標籤樣式 */ .dlsite-tag-highlight { background-color: #fff0d9 !important; color: #c05e11 !important; } .dlsite-maker-highlight { background-color: #e0f2ff !important; color: #0f5a9c !important; } /* 收藏標籤區域 */ .dlsite-priority-tags { background-color: #f5f5f5; border-radius: 6px; padding: 8px 12px; margin-bottom: 12px; } /* 公司名稱與按鈕容器 */ .dlsite-maker-wrapper { display: inline-flex; align-items: center; margin-right: 10px; } /* 公司按鈕特殊調整 */ .dlsite-maker-btn { margin-left: 6px; border-radius: 50%; width: 18px; height: 18px; font-size: 12px; display: inline-flex; align-items: center; justify-content: center; background-color: #4a86e8; color: white; border: none; cursor: pointer; } .dlsite-maker-btn:hover { background-color: #3b78e0; } .dlsite-maker-btn.active { background-color: #ea4335; } .dlsite-maker-btn.active:hover { background-color: #d73125; } `; const reorderTags = (container) => { if (!container) return; // 獲取所有標籤包裝器 const wrappers = Array.from(container.querySelectorAll('.dlsite-tag-wrapper')); // 分離收藏和未收藏的標籤 const favWrappers = []; const nonFavWrappers = []; wrappers.forEach(wrapper => { const tagLink = wrapper.querySelector('a'); if (tagLink && tagLink.classList.contains('dlsite-tag-highlight')) { favWrappers.push(wrapper.cloneNode(true)); } else { nonFavWrappers.push(wrapper.cloneNode(true)); } }); // 清空容器 container.innerHTML = ''; // 先添加收藏的標籤 if (favWrappers.length > 0) { favWrappers.forEach(wrapper => { // 重新綁定事件 const btn = wrapper.querySelector('.dlsite-tag-toggle-btn'); const tagLink = wrapper.querySelector('a'); if (btn && tagLink) { const tagName = tagLink.textContent.trim(); const tagHref = tagLink.getAttribute('href'); btn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); const newState = toggleFavoriteTag(tagName, tagHref); if (!newState) { // 立即更新UI reorderTags(container); showToast(`已取消收藏標籤:${tagName}`); } }); } container.appendChild(wrapper); }); // 添加分隔符 if (nonFavWrappers.length > 0) { const separator = document.createElement('div'); separator.style.borderBottom = '1px dashed #ddd'; separator.style.margin = '6px 0'; separator.style.width = '100%'; container.appendChild(separator); } } // 添加未收藏的標籤 nonFavWrappers.forEach(wrapper => { // 重新綁定事件 const btn = wrapper.querySelector('.dlsite-tag-toggle-btn'); const tagLink = wrapper.querySelector('a'); if (btn && tagLink) { const tagName = tagLink.textContent.trim(); const tagHref = tagLink.getAttribute('href'); btn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); const newState = toggleFavoriteTag(tagName, tagHref); if (newState) { // 立即更新UI reorderTags(container); showToast(`已收藏標籤:${tagName}`); } }); } container.appendChild(wrapper); }); }; // 添加 CSS const addStyle = (css) => { const style = document.createElement('style'); style.textContent = css; document.head.appendChild(style); }; addStyle(styles); // 工具函數 const getTableBody = () => { const table = document.querySelector('table.work_1col_table.n_worklist'); if (table && table.tBodies.length > 0) { return table.tBodies[0]; } return null; }; const getRecord = () => { const recordStr = localStorage.getItem("record"); if (recordStr) { try { return JSON.parse(recordStr); } catch (e) { return []; } } return []; }; const setRecord = (record) => { localStorage.setItem("record", JSON.stringify(record)); }; // 標籤和公司收藏相關函數 const getFavoriteTags = () => { const tagsStr = localStorage.getItem("dlsite-favorite-tags"); if (tagsStr) { try { return JSON.parse(tagsStr); } catch (e) { return []; } } return []; }; const setFavoriteTags = (tags) => { localStorage.setItem("dlsite-favorite-tags", JSON.stringify(tags)); }; const getFavoriteMakers = () => { const makersStr = localStorage.getItem("dlsite-favorite-makers"); if (makersStr) { try { return JSON.parse(makersStr); } catch (e) { return []; } } return []; }; const setFavoriteMakers = (makers) => { localStorage.setItem("dlsite-favorite-makers", JSON.stringify(makers)); }; const toggleFavoriteTag = (tagName, tagHref) => { const tags = getFavoriteTags(); const existingIndex = tags.findIndex(t => t.name === tagName); if (existingIndex > -1) { // 移除 tags.splice(existingIndex, 1); setFavoriteTags(tags); return false; // 返回當前狀態(未收藏) } else { // 添加 tags.push({ name: tagName, href: tagHref }); setFavoriteTags(tags); return true; // 返回當前狀態(已收藏) } }; const toggleFavoriteMaker = (makerName, makerId) => { const makers = getFavoriteMakers(); const existingIndex = makers.findIndex(m => m.name === makerName); if (existingIndex > -1) { // 移除 makers.splice(existingIndex, 1); setFavoriteMakers(makers); return false; // 返回當前狀態(未收藏) } else { // 添加 makers.push({ name: makerName, id: makerId }); setFavoriteMakers(makers); return true; // 返回當前狀態(已收藏) } }; const isTagFavorited = (tagName) => { const tags = getFavoriteTags(); return tags.some(t => t.name === tagName); }; const isMakerFavorited = (makerName) => { const makers = getFavoriteMakers(); return makers.some(m => m.name === makerName); }; const formatDate = (dateString) => { const date = new Date(dateString); const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); const hours = String(date.getHours()).padStart(2, '0'); const minutes = String(date.getMinutes()).padStart(2, '0'); return `${year}-${month}-${day} ${hours}:${minutes}`; }; const showToast = (message) => { const toast = document.createElement('div'); toast.className = 'dlsite-toast'; toast.textContent = message; document.body.appendChild(toast); setTimeout(() => { document.body.removeChild(toast); }, 3000); }; // 檢查ID是否在記錄中 const isRecorded = (workId) => { const record = getRecord(); return record.some(item => item.id === workId); }; // 獲取ID的記錄時間 const getRecordTime = (workId) => { const record = getRecord(); const item = record.find(item => item.id === workId); return item ? item.time : null; }; // 獲取當前頁面隱藏的作品數量 const getHiddenCount = () => { const tableBody = getTableBody(); if (!tableBody) return 0; const rows = Array.from(tableBody.rows); let hiddenCount = 0; rows.forEach(row => { const link = row.querySelector('dl dt a'); if (!link) return; const url = link.href.split("/").filter(i => i !== ''); const workId = url[url.length - 1]; if (isRecorded(workId) && row.style.display === "none") { hiddenCount++; } }); return hiddenCount; }; // 更新隱藏計數顯示 const updateHiddenCountDisplay = () => { const hiddenCount = getHiddenCount(); const hiddenCountElem = document.getElementById('dlsite-record-hidden-count'); if (hiddenCountElem) { hiddenCountElem.textContent = `本頁隱藏:${hiddenCount} 筆`; } }; // 為 Tag 添加切換按鈕並將已收藏標籤移到前面 const addTagToggleButtons = () => { // 選擇搜索頁的標籤 const tagContainers = document.querySelectorAll('.search_tag'); tagContainers.forEach(container => { // 獲取所有標籤 const tagLinks = Array.from(container.querySelectorAll('a')); const favTagLinks = []; const nonFavTagLinks = []; // 清空容器 container.innerHTML = ''; // 根據收藏狀態分組並創建新的標籤+按鈕組合 tagLinks.forEach(tagLink => { const tagName = tagLink.textContent.trim(); const tagHref = tagLink.getAttribute('href'); const isFavorited = isTagFavorited(tagName); // 創建包裝元素 const wrapper = document.createElement('div'); wrapper.className = 'dlsite-tag-wrapper'; // 複製標籤 const clonedLink = document.createElement('a'); clonedLink.href = tagHref; clonedLink.textContent = tagName; if (isFavorited) { clonedLink.classList.add('dlsite-tag-highlight'); } // 創建按鈕 const btn = document.createElement('button'); btn.className = `dlsite-tag-toggle-btn ${isFavorited ? 'active' : ''}`; btn.textContent = isFavorited ? '-' : '+'; btn.title = isFavorited ? '取消收藏標籤' : '收藏標籤'; btn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); const newState = toggleFavoriteTag(tagName, tagHref); btn.textContent = newState ? '-' : '+'; btn.title = newState ? '取消收藏標籤' : '收藏標籤'; btn.className = `dlsite-tag-toggle-btn ${newState ? 'active' : ''}`; // 更新標籤樣式 if (newState) { clonedLink.classList.add('dlsite-tag-highlight'); } else { clonedLink.classList.remove('dlsite-tag-highlight'); } // 立即重新排序標籤,而不是刷新頁面 reorderTags(container); showToast(newState ? `已收藏標籤:${tagName}` : `已取消收藏標籤:${tagName}`); }); // 為了確保按鈕可點擊性,增加最小尺寸 btn.style.minWidth = '22px'; btn.style.minHeight = '22px'; // 將標籤和按鈕添加到包裝元素 wrapper.appendChild(clonedLink); wrapper.appendChild(btn); // 根據收藏狀態分組 if (isFavorited) { favTagLinks.push(wrapper); } else { nonFavTagLinks.push(wrapper); } }); // 如果有收藏的標籤,優先顯示並添加分隔線 if (favTagLinks.length > 0) { // 優先顯示收藏的標籤 favTagLinks.forEach(wrapper => { container.appendChild(wrapper); }); // 添加分隔符 if (nonFavTagLinks.length > 0) { const separator = document.createElement('div'); separator.style.borderBottom = '1px dashed #ddd'; separator.style.margin = '6px 0'; separator.style.width = '100%'; container.appendChild(separator); } } // 顯示其他標籤 nonFavTagLinks.forEach(wrapper => { container.appendChild(wrapper); }); }); }; // 為公司添加切換按鈕 const addMakerToggleButtons = () => { const makerContainers = document.querySelectorAll('.maker_name'); makerContainers.forEach(container => { const makerElements = container.querySelectorAll('a'); makerElements.forEach(makerElement => { // 檢查公司名稱後面是否已有按鈕 let nextNode = makerElement.nextSibling; let hasButton = false; while (nextNode) { if (nextNode.classList && nextNode.classList.contains('dlsite-maker-toggle-btn')) { hasButton = true; break; } nextNode = nextNode.nextSibling; } if (hasButton) { return; // 已經添加過按鈕,跳過 } const makerName = makerElement.textContent.trim(); const makerHref = makerElement.getAttribute('href'); const makerId = makerHref.match(/maker_id\/([^.]+)\.html/)?.[1] || ''; const isFavorited = isMakerFavorited(makerName); // 初始化公司樣式 if (isFavorited) { makerElement.classList.add('dlsite-maker-highlight'); } // 建立按鈕 const btn = document.createElement('button'); btn.className = `dlsite-maker-toggle-btn dlsite-maker-btn ${isFavorited ? 'active' : ''}`; btn.textContent = isFavorited ? '-' : '+'; btn.title = isFavorited ? '取消收藏公司' : '收藏公司'; // 為了確保按鈕可點擊性,增加最小尺寸 btn.style.minWidth = '20px'; btn.style.minHeight = '20px'; btn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); const newState = toggleFavoriteMaker(makerName, makerId); btn.textContent = newState ? '-' : '+'; btn.title = newState ? '取消收藏公司' : '收藏公司'; btn.className = `dlsite-maker-toggle-btn dlsite-maker-btn ${newState ? 'active' : ''}`; // 更新公司樣式 if (newState) { makerElement.classList.add('dlsite-maker-highlight'); } else { makerElement.classList.remove('dlsite-maker-highlight'); } showToast(newState ? `已收藏公司:${makerName}` : `已取消收藏公司:${makerName}`); }); // 為了保持樣式一致,使用包裝元素 const wrapper = document.createElement('span'); wrapper.className = 'dlsite-maker-wrapper'; wrapper.style.position = 'relative'; wrapper.style.display = 'inline-block'; // 複製公司鏈接到包裝元素 const clonedMaker = makerElement.cloneNode(true); // 將原始元素替換為包裝元素 makerElement.parentNode.replaceChild(wrapper, makerElement); // 將複製的鏈接和按鈕添加到包裝元素 wrapper.appendChild(clonedMaker); wrapper.appendChild(btn); }); }); }; // 隱藏已記錄的作品 const hideRecorded = () => { const tableBody = getTableBody(); if (!tableBody) return; const rows = Array.from(tableBody.rows); let hiddenCount = 0; rows.forEach(row => { const link = row.querySelector('dl dt a'); if (!link) return; const url = link.href.split("/").filter(i => i !== ''); const workId = url[url.length - 1]; if (isRecorded(workId)) { row.style.display = "none"; hiddenCount++; } }); updateHiddenCountDisplay(); return hiddenCount; }; // 顯示/隱藏已記錄的作品 const toggleRecorded = () => { const tableBody = getTableBody(); if (!tableBody) return; const rows = Array.from(tableBody.rows); let visibleCount = 0; let hiddenCount = 0; rows.forEach(row => { const link = row.querySelector('dl dt a'); if (!link) return; const url = link.href.split("/").filter(i => i !== ''); const workId = url[url.length - 1]; if (isRecorded(workId)) { row.style.display = row.style.display === "none" ? "table-row" : "none"; if (row.style.display === "none") { hiddenCount++; } else { visibleCount++; } } }); updateHiddenCountDisplay(); if (hiddenCount > 0) { showToast(`已隱藏 ${hiddenCount} 筆已記錄的作品`); } else if (visibleCount > 0) { showToast(`已顯示 ${visibleCount} 筆已記錄的作品`); } else { showToast('沒有已記錄的作品可以隱藏或顯示'); } }; const highlightRecorded = () => { const tableBody = getTableBody(); if (!tableBody) return; const rows = Array.from(tableBody.rows); rows.forEach(row => { const link = row.querySelector('dl dt a'); if (!link) return; const url = link.href.split("/").filter(i => i !== ''); const workId = url[url.length - 1]; if (isRecorded(workId)) { row.classList.add('dlsite-record-highlight'); // 添加取消記錄按鈕和時間 const titleCell = row.querySelector('dl dt'); if (titleCell) { // 檢查是否已經有按鈕和時間標記,如果有則不重複添加 if (!titleCell.querySelector('.dlsite-record-item-button')) { // 添加時間顯示 const recordTime = getRecordTime(workId); const timeSpan = document.createElement('span'); timeSpan.className = 'dlsite-record-time'; timeSpan.textContent = recordTime ? `記錄於: ${formatDate(recordTime)}` : ''; // 添加取消記錄按鈕 const removeButton = document.createElement('button'); removeButton.className = 'dlsite-record-item-button'; removeButton.textContent = '取消記錄'; removeButton.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); removeFromRecord(workId, row); }); titleCell.appendChild(timeSpan); titleCell.appendChild(removeButton); } } } else { row.classList.remove('dlsite-record-highlight'); // 移除時間標記 const timeSpan = row.querySelector('.dlsite-record-time'); if (timeSpan) { timeSpan.remove(); } // 添加記錄按鈕 const titleCell = row.querySelector('dl dt'); if (titleCell && !titleCell.querySelector('.dlsite-record-item-button')) { const addButton = document.createElement('button'); addButton.className = 'dlsite-record-item-button add'; addButton.textContent = '記錄'; addButton.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); addToRecord(workId, row); }); titleCell.appendChild(addButton); } } }); // 添加標籤和公司切換按鈕 addTagToggleButtons(); addMakerToggleButtons(); updateHiddenCountDisplay(); }; const addToRecord = (workId, row = null) => { const record = getRecord(); if (isRecorded(workId)) return; const currentTime = new Date().toISOString(); record.push({ id: workId, time: currentTime }); setRecord(record); if (row) { row.classList.add('dlsite-record-highlight'); // 更新UI const titleCell = row.querySelector('dl dt'); if (titleCell) { // 移除舊的記錄按鈕 const oldButton = titleCell.querySelector('.dlsite-record-item-button'); if (oldButton) { oldButton.remove(); } // 添加時間顯示 const timeSpan = document.createElement('span'); timeSpan.className = 'dlsite-record-time'; timeSpan.textContent = `記錄於: ${formatDate(currentTime)}`; // 添加取消記錄按鈕 const removeButton = document.createElement('button'); removeButton.className = 'dlsite-record-item-button'; removeButton.textContent = '取消記錄'; removeButton.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); removeFromRecord(workId, row); }); titleCell.appendChild(timeSpan); titleCell.appendChild(removeButton); } // 不立即隱藏新記錄的行,等待頁面刷新後再隱藏 } updateCountDisplay(); updateHiddenCountDisplay(); showToast(`已將作品 ${workId} 加入記錄`); }; const removeFromRecord = (workId, row = null) => { const record = getRecord(); const index = record.findIndex(item => item.id === workId); if (index === -1) return; record.splice(index, 1); setRecord(record); if (row) { row.classList.remove('dlsite-record-highlight'); row.style.display = "table-row"; // 取消記錄後顯示該行 // 更新UI const titleCell = row.querySelector('dl dt'); if (titleCell) { // 移除時間顯示 const timeSpan = titleCell.querySelector('.dlsite-record-time'); if (timeSpan) { timeSpan.remove(); } // 移除舊的取消記錄按鈕 const oldButton = titleCell.querySelector('.dlsite-record-item-button'); if (oldButton) { oldButton.remove(); } // 添加記錄按鈕 const addButton = document.createElement('button'); addButton.className = 'dlsite-record-item-button add'; addButton.textContent = '記錄'; addButton.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); addToRecord(workId, row); }); titleCell.appendChild(addButton); } } updateCountDisplay(); updateHiddenCountDisplay(); showToast(`已將作品 ${workId} 從記錄中移除`); }; const addAllRecord = () => { const tableBody = getTableBody(); if (!tableBody) return; const rows = Array.from(tableBody.rows); const record = getRecord(); let newCount = 0; const currentTime = new Date().toISOString(); rows.forEach(row => { const link = row.querySelector('dl dt a'); if (!link) return; const url = link.href.split("/").filter(i => i !== ''); const workId = url[url.length - 1]; if (!isRecorded(workId)) { record.push({ id: workId, time: currentTime }); newCount++; // 更新UI row.classList.add('dlsite-record-highlight'); const titleCell = row.querySelector('dl dt'); if (titleCell) { // 移除舊的記錄按鈕 const oldButton = titleCell.querySelector('.dlsite-record-item-button'); if (oldButton) { oldButton.remove(); } // 添加時間顯示 const timeSpan = document.createElement('span'); timeSpan.className = 'dlsite-record-time'; timeSpan.textContent = `記錄於: ${formatDate(currentTime)}`; // 添加取消記錄按鈕 const removeButton = document.createElement('button'); removeButton.className = 'dlsite-record-item-button'; removeButton.textContent = '取消記錄'; removeButton.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); removeFromRecord(workId, row); }); titleCell.appendChild(timeSpan); titleCell.appendChild(removeButton); } } }); if (newCount > 0) { setRecord(record); updateCountDisplay(); updateHiddenCountDisplay(); showToast(`已添加 ${newCount} 筆新記錄,共有 ${record.length} 筆記錄`); } else { showToast('沒有新的作品可以記錄'); } }; const updateCountDisplay = () => { const record = getRecord(); const countElem = document.getElementById('dlsite-record-count'); if (countElem) { countElem.textContent = `已記錄:${record.length} 筆`; } }; // 匯出和匯入功能 const exportRecord = () => { const record = getRecord(); const favoriteTags = getFavoriteTags(); const favoriteMakers = getFavoriteMakers(); const exportData = JSON.stringify({ record: record, favoriteTags: favoriteTags, favoriteMakers: favoriteMakers }, null, 2); const modal = document.createElement('div'); modal.className = 'dlsite-modal'; modal.innerHTML = ` <div class="dlsite-modal-content"> <div class="dlsite-modal-header"> <h3>匯出記錄</h3> <button class="dlsite-modal-close">×</button> </div> <div class="dlsite-modal-body"> <p>以下是您的記錄資料(包含作品記錄、收藏標籤和收藏公司),請複製並保存:</p> <textarea readonly>${exportData}</textarea> </div> <div class="dlsite-modal-footer"> <button class="dlsite-modal-btn dlsite-modal-btn-primary" id="dlsite-copy-btn">複製</button> <button class="dlsite-modal-btn dlsite-modal-btn-secondary" id="dlsite-modal-close-btn">關閉</button> </div> </div> `; document.body.appendChild(modal); // 綁定事件 modal.querySelector('.dlsite-modal-close').addEventListener('click', () => { document.body.removeChild(modal); }); modal.querySelector('#dlsite-modal-close-btn').addEventListener('click', () => { document.body.removeChild(modal); }); modal.querySelector('#dlsite-copy-btn').addEventListener('click', () => { const textarea = modal.querySelector('textarea'); textarea.select(); document.execCommand('copy'); showToast('已複製到剪貼簿'); }); }; const importRecord = () => { const modal = document.createElement('div'); modal.className = 'dlsite-modal'; modal.innerHTML = ` <div class="dlsite-modal-content"> <div class="dlsite-modal-header"> <h3>匯入記錄</h3> <button class="dlsite-modal-close">×</button> </div> <div class="dlsite-modal-body"> <p>請貼上之前匯出的記錄資料:</p> <textarea placeholder="在這裡貼上 JSON 格式的記錄資料..."></textarea> </div> <div class="dlsite-modal-footer"> <button class="dlsite-modal-btn dlsite-modal-btn-primary" id="dlsite-import-btn">匯入</button> <button class="dlsite-modal-btn dlsite-modal-btn-secondary" id="dlsite-modal-close-btn">取消</button> </div> </div> `; document.body.appendChild(modal); // 綁定事件 modal.querySelector('.dlsite-modal-close').addEventListener('click', () => { document.body.removeChild(modal); }); modal.querySelector('#dlsite-modal-close-btn').addEventListener('click', () => { document.body.removeChild(modal); }); modal.querySelector('#dlsite-import-btn').addEventListener('click', () => { const textarea = modal.querySelector('textarea'); try { const importData = JSON.parse(textarea.value); // 支持新舊格式 if (Array.isArray(importData)) { // 舊格式(只有作品記錄) handleImportedRecords(importData); } else if (typeof importData === 'object') { // 新格式(同時包含作品記錄、標籤和公司) if (importData.record) { handleImportedRecords(importData.record); } if (importData.favoriteTags) { setFavoriteTags(importData.favoriteTags); } if (importData.favoriteMakers) { setFavoriteMakers(importData.favoriteMakers); } } else { throw new Error('匯入的資料格式不正確'); } // 重新套用 UI highlightRecorded(); updateCountDisplay(); updateHiddenCountDisplay(); document.body.removeChild(modal); showToast(`已成功匯入記錄資料`); } catch (error) { showToast(`匯入失敗:${error.message}`); } }); }; // 處理匯入的作品記錄 const handleImportedRecords = (importData) => { // 檢查資料格式 let validData = []; // 支持新舊兩種格式 if (Array.isArray(importData)) { importData.forEach(item => { if (typeof item === 'string') { // 舊格式,僅ID validData.push({ id: item, time: new Date().toISOString() }); } else if (typeof item === 'object' && item.hasOwnProperty('id')) { // 新格式,包含ID和時間 validData.push({ id: item.id, time: item.time || new Date().toISOString() }); } }); } if (validData.length === 0) { throw new Error('匯入的資料不包含有效記錄'); } // 合併現有記錄和匯入的記錄 const currentRecord = getRecord(); // 去重,保留時間較早的記錄 const recordMap = new Map(); // 先加入當前記錄 currentRecord.forEach(item => { recordMap.set(item.id, item); }); // 再加入匯入記錄,只有當ID不存在時才加入 validData.forEach(item => { if (!recordMap.has(item.id)) { recordMap.set(item.id, item); } }); // 轉換回陣列 const mergedRecord = Array.from(recordMap.values()); setRecord(mergedRecord); return mergedRecord; }; // 創建控制面板 const createControlPanel = () => { const controlPanel = document.createElement('div'); controlPanel.className = 'dlsite-record-controls'; // 左側區域:顯示信息 const leftSection = document.createElement('div'); leftSection.className = 'dlsite-record-controls-left'; // 計數顯示 const countDisplay = document.createElement('div'); countDisplay.className = 'dlsite-record-info'; countDisplay.id = 'dlsite-record-count'; countDisplay.innerHTML = ` <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M20 14.66V20a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h5.34"></path> <polygon points="18 2 22 6 12 16 8 16 8 12 18 2"></polygon> </svg> 已記錄:0 筆 `; // 隱藏計數顯示 const hiddenCountDisplay = document.createElement('div'); hiddenCountDisplay.className = 'dlsite-record-info'; hiddenCountDisplay.id = 'dlsite-record-hidden-count'; hiddenCountDisplay.innerHTML = ` <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"></path> <line x1="1" y1="1" x2="23" y2="23"></line> </svg> 本頁隱藏:0 筆 `; leftSection.appendChild(countDisplay); leftSection.appendChild(hiddenCountDisplay); // 中間區域:功能按鈕 const centerSection = document.createElement('div'); centerSection.className = 'dlsite-record-controls-center'; // 記錄當前頁按鈕 const addButton = document.createElement('button'); addButton.className = 'dlsite-record-btn dlsite-record-add'; addButton.innerHTML = ` <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <circle cx="12" cy="12" r="10"></circle> <line x1="12" y1="8" x2="12" y2="16"></line> <line x1="8" y1="12" x2="16" y2="12"></line> </svg> 記錄此頁 `; addButton.addEventListener('click', addAllRecord); // 顯示/隱藏按鈕 const toggleButton = document.createElement('button'); toggleButton.className = 'dlsite-record-btn dlsite-record-toggle'; toggleButton.innerHTML = ` <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path> <circle cx="12" cy="12" r="3"></circle> </svg> 隱藏/顯示已記錄 `; toggleButton.addEventListener('click', toggleRecorded); centerSection.appendChild(addButton); centerSection.appendChild(toggleButton); // 右側區域:資料管理按鈕 const rightSection = document.createElement('div'); rightSection.className = 'dlsite-record-controls-right'; // 匯出按鈕 const exportButton = document.createElement('button'); exportButton.className = 'dlsite-record-btn dlsite-record-export'; exportButton.innerHTML = ` <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path> <polyline points="7 10 12 15 17 10"></polyline> <line x1="12" y1="15" x2="12" y2="3"></line> </svg> 匯出記錄 `; exportButton.addEventListener('click', exportRecord); // 匯入按鈕 const importButton = document.createElement('button'); importButton.className = 'dlsite-record-btn dlsite-record-import'; importButton.innerHTML = ` <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path> <polyline points="17 8 12 3 7 8"></polyline> <line x1="12" y1="3" x2="12" y2="15"></line> </svg> 匯入記錄 `; importButton.addEventListener('click', importRecord); rightSection.appendChild(exportButton); rightSection.appendChild(importButton); // 組合所有區域 controlPanel.appendChild(leftSection); controlPanel.appendChild(centerSection); controlPanel.appendChild(rightSection); // 插入到合適的位置 const target = document.querySelector('.work_list_header, .work_1col_table'); if (target) { target.parentNode.insertBefore(controlPanel, target); } else { // 如果找不到合適的位置,放在頁面頂部 const header = document.getElementById('header'); if (header) { header.parentNode.insertBefore(controlPanel, header.nextSibling); } } updateCountDisplay(); updateHiddenCountDisplay(); }; // 檢查是否在作品詳情頁 const addDetailPageButton = () => { const workId = window.location.pathname.match(/RJ(\d+)/i); if (!workId) return; const isWorkRecorded = isRecorded(workId[0]); const buttonContainer = document.createElement('div'); buttonContainer.style.marginTop = '15px'; const recordButton = document.createElement('button'); recordButton.className = `dlsite-record-btn ${isWorkRecorded ? 'dlsite-record-toggle' : 'dlsite-record-add'}`; recordButton.innerHTML = isWorkRecorded ? `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <polyline points="3 6 5 6 21 6"></polyline> <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path> </svg> 取消記錄` : `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <circle cx="12" cy="12" r="10"></circle> <line x1="12" y1="8" x2="12" y2="16"></line> <line x1="8" y1="12" x2="16" y2="12"></line> </svg> 記錄此作品`; recordButton.addEventListener('click', () => { if (isWorkRecorded) { removeFromRecord(workId[0]); } else { addToRecord(workId[0]); } // 重新載入以更新按鈕狀態 location.reload(); }); buttonContainer.appendChild(recordButton); // 顯示記錄時間(如果有的話) if (isWorkRecorded) { const recordTime = getRecordTime(workId[0]); if (recordTime) { const timeInfo = document.createElement('div'); timeInfo.style.marginTop = '5px'; timeInfo.style.fontSize = '12px'; timeInfo.style.color = '#666'; timeInfo.textContent = `記錄於: ${formatDate(recordTime)}`; buttonContainer.appendChild(timeInfo); } } // 插入到作品購買按鈕附近 const buySection = document.querySelector('.work_buy_section'); if (buySection) { buySection.appendChild(buttonContainer); } // 在詳情頁也添加標籤和公司的收藏按鈕 addDetailPageTagsButtons(); addDetailPageMakerButtons(); }; // 在詳情頁添加標籤收藏按鈕 const addDetailPageTagsButtons = () => { // 詳情頁中的標籤通常在 .main_genre 或 .work_genre 區域 const tagContainers = document.querySelectorAll('.main_genre, .work_genre'); tagContainers.forEach(container => { const tagElements = Array.from(container.querySelectorAll('a')); // 創建新的容器來保存修改後的標籤 const newContainer = document.createElement('div'); newContainer.className = container.className; newContainer.style.display = 'flex'; newContainer.style.flexWrap = 'wrap'; newContainer.style.gap = '6px'; // 處理每個標籤 tagElements.forEach(tagElement => { const tagName = tagElement.textContent.trim(); const tagHref = tagElement.getAttribute('href'); const isFavorited = isTagFavorited(tagName); // 建立包裝元素 const wrapper = document.createElement('div'); wrapper.className = 'dlsite-tag-wrapper'; // 創建新標籤 const newTag = document.createElement('a'); newTag.href = tagHref; newTag.textContent = tagName; if (isFavorited) { newTag.classList.add('dlsite-tag-highlight'); } // 建立按鈕 const btn = document.createElement('button'); btn.className = `dlsite-tag-toggle-btn ${isFavorited ? 'active' : ''}`; btn.textContent = isFavorited ? '-' : '+'; btn.title = isFavorited ? '取消收藏標籤' : '收藏標籤'; // 為了確保按鈕可點擊性,增加最小尺寸 btn.style.minWidth = '22px'; btn.style.minHeight = '22px'; btn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); const newState = toggleFavoriteTag(tagName, tagHref); btn.textContent = newState ? '-' : '+'; btn.title = newState ? '取消收藏標籤' : '收藏標籤'; btn.className = `dlsite-tag-toggle-btn ${newState ? 'active' : ''}`; // 更新標籤樣式 if (newState) { newTag.classList.add('dlsite-tag-highlight'); } else { newTag.classList.remove('dlsite-tag-highlight'); } // 立即重新排序標籤 reorderTags(newContainer); showToast(newState ? `已收藏標籤:${tagName}` : `已取消收藏標籤:${tagName}`); }); // 組合標籤和按鈕 wrapper.appendChild(newTag); wrapper.appendChild(btn); newContainer.appendChild(wrapper); }); // 用新容器替換原容器的內容 container.innerHTML = ''; container.appendChild(newContainer); // 立即排序 reorderTags(newContainer); }); }; // 在詳情頁添加公司收藏按鈕 const addDetailPageMakerButtons = () => { // 詳情頁中的公司通常在 .maker_name、.author、.circle_name 區域 const makerContainers = document.querySelectorAll('.maker_name, .author, .circle_name'); makerContainers.forEach(container => { const makerElements = container.querySelectorAll('a'); makerElements.forEach(makerElement => { // 檢查公司名稱後面是否已有按鈕 let nextNode = makerElement.nextSibling; let hasButton = false; while (nextNode) { if (nextNode.classList && nextNode.classList.contains('dlsite-maker-toggle-btn')) { hasButton = true; break; } nextNode = nextNode.nextSibling; } if (hasButton) { return; // 已經添加過按鈕,跳過 } const makerName = makerElement.textContent.trim(); const makerHref = makerElement.getAttribute('href'); const makerId = makerHref.match(/maker_id\/([^.]+)\.html/)?.[1] || ''; const isFavorited = isMakerFavorited(makerName); // 建立按鈕 const btn = document.createElement('button'); btn.className = `dlsite-maker-toggle-btn ${isFavorited ? 'active' : ''}`; btn.textContent = isFavorited ? '-' : '+'; btn.title = isFavorited ? '取消收藏公司' : '收藏公司'; btn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); const newState = toggleFavoriteMaker(makerName, makerId); btn.textContent = newState ? '-' : '+'; btn.title = newState ? '取消收藏公司' : '收藏公司'; btn.className = `dlsite-maker-toggle-btn ${newState ? 'active' : ''}`; // 更新公司樣式 if (newState) { makerElement.classList.add('dlsite-maker-highlight'); } else { makerElement.classList.remove('dlsite-maker-highlight'); } showToast(newState ? `已收藏公司:${makerName}` : `已取消收藏公司:${makerName}`); }); // 初始化公司樣式 if (isFavorited) { makerElement.classList.add('dlsite-maker-highlight'); } // 插入按鈕到公司名稱後面 makerElement.parentNode.insertBefore(btn, makerElement.nextSibling); }); }); }; // 初始化 const init = () => { if (getTableBody()) { createControlPanel(); highlightRecorded(); // 頁面載入時自動隱藏已記錄的項目 const hiddenCount = hideRecorded(); if (hiddenCount > 0) { showToast(`已自動隱藏 ${hiddenCount} 筆已記錄的作品`); } } else { addDetailPageButton(); } }; // 頁面載入完成後執行 window.addEventListener('load', init); // 如果已經載入完成,則立即執行 if (document.readyState === 'complete') { init(); } })();