DLsite Record Enhanced Tag Manager

亮色系 DLsite 記錄工具,支援記錄時間、標籤管理和匯出匯入功能

  1. // 實時重新排序標籤
  2. // ==UserScript==
  3. // @name DLsite Record Enhanced Tag Manager
  4. // @namespace http://tampermonkey.net/
  5. // @version 1.2
  6. // @description 亮色系 DLsite 記錄工具,支援記錄時間、標籤管理和匯出匯入功能
  7. // @author Enhanced by Claude
  8. // @match https://www.dlsite.com/maniax/*
  9. // @grant none
  10. // @license MIT
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. 'use strict';
  15.  
  16. // CSS 樣式
  17. const styles = `
  18. .dlsite-record-btn {
  19. margin: 5px;
  20. padding: 5px 10px;
  21. border: none;
  22. border-radius: 4px;
  23. color: white;
  24. font-weight: bold;
  25. cursor: pointer;
  26. transition: all 0.3s;
  27. display: inline-flex;
  28. align-items: center;
  29. box-shadow: 0 2px 5px rgba(0,0,0,0.2);
  30. }
  31. .dlsite-record-btn:hover {
  32. transform: translateY(-2px);
  33. box-shadow: 0 4px 8px rgba(0,0,0,0.3);
  34. }
  35. .dlsite-record-btn svg {
  36. margin-right: 5px;
  37. }
  38. .dlsite-record-add {
  39. background-color: #1a73e8; /* 深藍色 */
  40. }
  41. .dlsite-record-toggle {
  42. background-color: #34a853; /* 綠色 */
  43. }
  44. .dlsite-record-export {
  45. background-color: #ea4335; /* 紅色 */
  46. }
  47. .dlsite-record-import {
  48. background-color: #fbbc05; /* 黃色 */
  49. color: #333;
  50. }
  51. .dlsite-record-info {
  52. display: inline-flex;
  53. align-items: center;
  54. padding: 5px 10px;
  55. margin: 5px;
  56. border-radius: 4px;
  57. background-color: #f0f8ff;
  58. color: #333;
  59. font-weight: bold;
  60. border: 1px solid #d0e5ff;
  61. }
  62. .dlsite-record-info svg {
  63. margin-right: 5px;
  64. color: #4285f4;
  65. }
  66. .dlsite-record-highlight {
  67. background-color: rgba(232, 240, 254, 0.5) !important; /* 非常淡的藍色背景 + 半透明 */
  68. border-left: 4px solid #4a86e8 !important; /* 淡藍色邊框 */
  69. }
  70. .dlsite-record-time {
  71. font-size: 12px;
  72. color: #666;
  73. margin-left: 8px;
  74. white-space: nowrap;
  75. }
  76. .dlsite-record-item-button {
  77. background-color: #ea4335;
  78. color: white;
  79. border: none;
  80. border-radius: 3px;
  81. cursor: pointer;
  82. padding: 2px 8px;
  83. font-size: 12px;
  84. margin-left: 5px;
  85. transition: all 0.2s;
  86. font-weight: bold;
  87. }
  88. .dlsite-record-item-button:hover {
  89. background-color: #d73125;
  90. transform: translateY(-1px);
  91. }
  92. .dlsite-record-item-button.add {
  93. background-color: #1a73e8;
  94. }
  95. .dlsite-record-item-button.add:hover {
  96. background-color: #1967d2;
  97. }
  98. .dlsite-toast {
  99. position: fixed;
  100. top: 20px;
  101. right: 20px;
  102. padding: 10px 20px;
  103. background-color: rgba(66, 133, 244, 0.9);
  104. color: white;
  105. border-radius: 4px;
  106. z-index: 10000;
  107. animation: fadeInOut 3s;
  108. box-shadow: 0 4px 8px rgba(0,0,0,0.2);
  109. }
  110. @keyframes fadeInOut {
  111. 0% { opacity: 0; }
  112. 10% { opacity: 1; }
  113. 80% { opacity: 1; }
  114. 100% { opacity: 0; }
  115. }
  116. .dlsite-record-controls {
  117. display: flex;
  118. justify-content: space-between;
  119. padding: 10px;
  120. background-color: #f9fcff; /* 非常淡的藍色背景 */
  121. border-radius: 4px;
  122. margin: 10px 0;
  123. position: sticky;
  124. top: 0;
  125. z-index: 100;
  126. box-shadow: 0 2px 10px rgba(0,0,0,0.1);
  127. border: 1px solid #d0e5ff;
  128. }
  129. .dlsite-record-controls-left,
  130. .dlsite-record-controls-center,
  131. .dlsite-record-controls-right {
  132. display: flex;
  133. align-items: center;
  134. }
  135. .dlsite-modal {
  136. position: fixed;
  137. top: 0;
  138. left: 0;
  139. width: 100%;
  140. height: 100%;
  141. background-color: rgba(0, 0, 0, 0.5);
  142. display: flex;
  143. justify-content: center;
  144. align-items: center;
  145. z-index: 10001;
  146. }
  147. .dlsite-modal-content {
  148. background-color: #ffffff;
  149. padding: 20px;
  150. border-radius: 8px;
  151. width: 80%;
  152. max-width: 600px;
  153. box-shadow: 0 4px 20px rgba(0,0,0,0.2);
  154. color: #333;
  155. }
  156. .dlsite-modal-header {
  157. display: flex;
  158. justify-content: space-between;
  159. align-items: center;
  160. margin-bottom: 15px;
  161. border-bottom: 1px solid #e0e0e0;
  162. padding-bottom: 10px;
  163. }
  164. .dlsite-modal-close {
  165. background: none;
  166. border: none;
  167. font-size: 24px;
  168. cursor: pointer;
  169. color: #888;
  170. }
  171. .dlsite-modal-close:hover {
  172. color: #333;
  173. }
  174. .dlsite-modal-body {
  175. margin-bottom: 15px;
  176. }
  177. .dlsite-modal-footer {
  178. display: flex;
  179. justify-content: flex-end;
  180. gap: 10px;
  181. }
  182. .dlsite-modal textarea {
  183. width: 100%;
  184. height: 200px;
  185. background-color: #f5f5f5;
  186. color: #333;
  187. border: 1px solid #ddd;
  188. padding: 10px;
  189. border-radius: 4px;
  190. resize: vertical;
  191. font-family: monospace;
  192. }
  193. .dlsite-modal-btn {
  194. padding: 8px 15px;
  195. border: none;
  196. border-radius: 4px;
  197. cursor: pointer;
  198. font-weight: bold;
  199. }
  200. .dlsite-modal-btn-primary {
  201. background-color: #1a73e8;
  202. color: white;
  203. }
  204. .dlsite-modal-btn-secondary {
  205. background-color: #e0e0e0;
  206. color: #333;
  207. }
  208. /* 新增的 Tag 和公司 相關樣式 */
  209. /* 標籤包裝容器 */
  210. .dlsite-tag-wrapper {
  211. display: inline-flex;
  212. align-items: stretch;
  213. margin-right: 8px;
  214. margin-bottom: 5px;
  215. border-radius: 4px;
  216. overflow: hidden;
  217. box-shadow: 0 1px 2px rgba(0,0,0,0.1);
  218. position: relative;
  219. }
  220. /* 標籤樣式 */
  221. .dlsite-tag-wrapper a {
  222. display: inline-block;
  223. background-color: #e8f0ff;
  224. padding: 3px 8px;
  225. color: #2a5db0;
  226. text-decoration: none;
  227. transition: all 0.2s ease;
  228. font-size: 11px;
  229. border-radius: 4px;
  230. }
  231. .dlsite-tag-wrapper a:hover {
  232. background-color: #d4e4ff;
  233. }
  234. /* 按鈕樣式 */
  235. .dlsite-tag-toggle-btn {
  236. display: none; /* 默認隱藏 */
  237. align-items: center;
  238. justify-content: center;
  239. min-width: 20px;
  240. background-color: #4a86e8;
  241. color: white;
  242. font-size: 14px;
  243. font-weight: bold;
  244. cursor: pointer;
  245. border: none;
  246. transition: all 0.2s ease;
  247. border-radius: 0 4px 4px 0;
  248. padding: 0;
  249. }
  250. /* 當滑鼠懸停在標籤上時顯示按鈕 */
  251. .dlsite-tag-wrapper:hover .dlsite-tag-toggle-btn {
  252. display: inline-flex;
  253. }
  254. /* 將被收藏標籤的按鈕始終顯示 */
  255. .dlsite-tag-toggle-btn.active {
  256. display: inline-flex;
  257. }
  258. .dlsite-tag-toggle-btn:hover {
  259. background-color: #3b78e0;
  260. }
  261. .dlsite-tag-toggle-btn.active {
  262. background-color: #ea4335;
  263. }
  264. .dlsite-tag-toggle-btn.active:hover {
  265. background-color: #d73125;
  266. }
  267. /* 高亮標籤樣式 */
  268. .dlsite-tag-highlight {
  269. background-color: #fff0d9 !important;
  270. color: #c05e11 !important;
  271. }
  272. .dlsite-maker-highlight {
  273. background-color: #e0f2ff !important;
  274. color: #0f5a9c !important;
  275. }
  276. /* 收藏標籤區域 */
  277. .dlsite-priority-tags {
  278. background-color: #f5f5f5;
  279. border-radius: 6px;
  280. padding: 8px 12px;
  281. margin-bottom: 12px;
  282. }
  283. /* 公司名稱與按鈕容器 */
  284. .dlsite-maker-wrapper {
  285. display: inline-flex;
  286. align-items: center;
  287. margin-right: 10px;
  288. }
  289. /* 公司按鈕特殊調整 */
  290. .dlsite-maker-btn {
  291. margin-left: 6px;
  292. border-radius: 50%;
  293. width: 18px;
  294. height: 18px;
  295. font-size: 12px;
  296. display: inline-flex;
  297. align-items: center;
  298. justify-content: center;
  299. background-color: #4a86e8;
  300. color: white;
  301. border: none;
  302. cursor: pointer;
  303. }
  304. .dlsite-maker-btn:hover {
  305. background-color: #3b78e0;
  306. }
  307. .dlsite-maker-btn.active {
  308. background-color: #ea4335;
  309. }
  310. .dlsite-maker-btn.active:hover {
  311. background-color: #d73125;
  312. }
  313. `;
  314.  
  315. const reorderTags = (container) => {
  316. if (!container) return;
  317.  
  318. // 獲取所有標籤包裝器
  319. const wrappers = Array.from(container.querySelectorAll('.dlsite-tag-wrapper'));
  320.  
  321. // 分離收藏和未收藏的標籤
  322. const favWrappers = [];
  323. const nonFavWrappers = [];
  324.  
  325. wrappers.forEach(wrapper => {
  326. const tagLink = wrapper.querySelector('a');
  327. if (tagLink && tagLink.classList.contains('dlsite-tag-highlight')) {
  328. favWrappers.push(wrapper.cloneNode(true));
  329. } else {
  330. nonFavWrappers.push(wrapper.cloneNode(true));
  331. }
  332. });
  333.  
  334. // 清空容器
  335. container.innerHTML = '';
  336.  
  337. // 先添加收藏的標籤
  338. if (favWrappers.length > 0) {
  339. favWrappers.forEach(wrapper => {
  340. // 重新綁定事件
  341. const btn = wrapper.querySelector('.dlsite-tag-toggle-btn');
  342. const tagLink = wrapper.querySelector('a');
  343. if (btn && tagLink) {
  344. const tagName = tagLink.textContent.trim();
  345. const tagHref = tagLink.getAttribute('href');
  346. btn.addEventListener('click', (e) => {
  347. e.preventDefault();
  348. e.stopPropagation();
  349. const newState = toggleFavoriteTag(tagName, tagHref);
  350. if (!newState) {
  351. // 立即更新UI
  352. reorderTags(container);
  353. showToast(`已取消收藏標籤:${tagName}`);
  354. }
  355. });
  356. }
  357. container.appendChild(wrapper);
  358. });
  359.  
  360. // 添加分隔符
  361. if (nonFavWrappers.length > 0) {
  362. const separator = document.createElement('div');
  363. separator.style.borderBottom = '1px dashed #ddd';
  364. separator.style.margin = '6px 0';
  365. separator.style.width = '100%';
  366. container.appendChild(separator);
  367. }
  368. }
  369.  
  370. // 添加未收藏的標籤
  371. nonFavWrappers.forEach(wrapper => {
  372. // 重新綁定事件
  373. const btn = wrapper.querySelector('.dlsite-tag-toggle-btn');
  374. const tagLink = wrapper.querySelector('a');
  375. if (btn && tagLink) {
  376. const tagName = tagLink.textContent.trim();
  377. const tagHref = tagLink.getAttribute('href');
  378. btn.addEventListener('click', (e) => {
  379. e.preventDefault();
  380. e.stopPropagation();
  381. const newState = toggleFavoriteTag(tagName, tagHref);
  382. if (newState) {
  383. // 立即更新UI
  384. reorderTags(container);
  385. showToast(`已收藏標籤:${tagName}`);
  386. }
  387. });
  388. }
  389. container.appendChild(wrapper);
  390. });
  391. };
  392.  
  393. // 添加 CSS
  394. const addStyle = (css) => {
  395. const style = document.createElement('style');
  396. style.textContent = css;
  397. document.head.appendChild(style);
  398. };
  399.  
  400. addStyle(styles);
  401.  
  402. // 工具函數
  403. const getTableBody = () => {
  404. const table = document.querySelector('table.work_1col_table.n_worklist');
  405. if (table && table.tBodies.length > 0) {
  406. return table.tBodies[0];
  407. }
  408. return null;
  409. };
  410.  
  411. const getRecord = () => {
  412. const recordStr = localStorage.getItem("record");
  413. if (recordStr) {
  414. try {
  415. return JSON.parse(recordStr);
  416. } catch (e) {
  417. return [];
  418. }
  419. }
  420. return [];
  421. };
  422.  
  423. const setRecord = (record) => {
  424. localStorage.setItem("record", JSON.stringify(record));
  425. };
  426.  
  427. // 標籤和公司收藏相關函數
  428. const getFavoriteTags = () => {
  429. const tagsStr = localStorage.getItem("dlsite-favorite-tags");
  430. if (tagsStr) {
  431. try {
  432. return JSON.parse(tagsStr);
  433. } catch (e) {
  434. return [];
  435. }
  436. }
  437. return [];
  438. };
  439.  
  440. const setFavoriteTags = (tags) => {
  441. localStorage.setItem("dlsite-favorite-tags", JSON.stringify(tags));
  442. };
  443.  
  444. const getFavoriteMakers = () => {
  445. const makersStr = localStorage.getItem("dlsite-favorite-makers");
  446. if (makersStr) {
  447. try {
  448. return JSON.parse(makersStr);
  449. } catch (e) {
  450. return [];
  451. }
  452. }
  453. return [];
  454. };
  455.  
  456. const setFavoriteMakers = (makers) => {
  457. localStorage.setItem("dlsite-favorite-makers", JSON.stringify(makers));
  458. };
  459.  
  460. const toggleFavoriteTag = (tagName, tagHref) => {
  461. const tags = getFavoriteTags();
  462. const existingIndex = tags.findIndex(t => t.name === tagName);
  463.  
  464. if (existingIndex > -1) {
  465. // 移除
  466. tags.splice(existingIndex, 1);
  467. setFavoriteTags(tags);
  468. return false; // 返回當前狀態(未收藏)
  469. } else {
  470. // 添加
  471. tags.push({ name: tagName, href: tagHref });
  472. setFavoriteTags(tags);
  473. return true; // 返回當前狀態(已收藏)
  474. }
  475. };
  476.  
  477. const toggleFavoriteMaker = (makerName, makerId) => {
  478. const makers = getFavoriteMakers();
  479. const existingIndex = makers.findIndex(m => m.name === makerName);
  480.  
  481. if (existingIndex > -1) {
  482. // 移除
  483. makers.splice(existingIndex, 1);
  484. setFavoriteMakers(makers);
  485. return false; // 返回當前狀態(未收藏)
  486. } else {
  487. // 添加
  488. makers.push({ name: makerName, id: makerId });
  489. setFavoriteMakers(makers);
  490. return true; // 返回當前狀態(已收藏)
  491. }
  492. };
  493.  
  494. const isTagFavorited = (tagName) => {
  495. const tags = getFavoriteTags();
  496. return tags.some(t => t.name === tagName);
  497. };
  498.  
  499. const isMakerFavorited = (makerName) => {
  500. const makers = getFavoriteMakers();
  501. return makers.some(m => m.name === makerName);
  502. };
  503.  
  504. const formatDate = (dateString) => {
  505. const date = new Date(dateString);
  506. const year = date.getFullYear();
  507. const month = String(date.getMonth() + 1).padStart(2, '0');
  508. const day = String(date.getDate()).padStart(2, '0');
  509. const hours = String(date.getHours()).padStart(2, '0');
  510. const minutes = String(date.getMinutes()).padStart(2, '0');
  511.  
  512. return `${year}-${month}-${day} ${hours}:${minutes}`;
  513. };
  514.  
  515. const showToast = (message) => {
  516. const toast = document.createElement('div');
  517. toast.className = 'dlsite-toast';
  518. toast.textContent = message;
  519. document.body.appendChild(toast);
  520.  
  521. setTimeout(() => {
  522. document.body.removeChild(toast);
  523. }, 3000);
  524. };
  525.  
  526. // 檢查ID是否在記錄中
  527. const isRecorded = (workId) => {
  528. const record = getRecord();
  529. return record.some(item => item.id === workId);
  530. };
  531.  
  532. // 獲取ID的記錄時間
  533. const getRecordTime = (workId) => {
  534. const record = getRecord();
  535. const item = record.find(item => item.id === workId);
  536. return item ? item.time : null;
  537. };
  538.  
  539. // 獲取當前頁面隱藏的作品數量
  540. const getHiddenCount = () => {
  541. const tableBody = getTableBody();
  542. if (!tableBody) return 0;
  543.  
  544. const rows = Array.from(tableBody.rows);
  545. let hiddenCount = 0;
  546.  
  547. rows.forEach(row => {
  548. const link = row.querySelector('dl dt a');
  549. if (!link) return;
  550.  
  551. const url = link.href.split("/").filter(i => i !== '');
  552. const workId = url[url.length - 1];
  553.  
  554. if (isRecorded(workId) && row.style.display === "none") {
  555. hiddenCount++;
  556. }
  557. });
  558.  
  559. return hiddenCount;
  560. };
  561.  
  562. // 更新隱藏計數顯示
  563. const updateHiddenCountDisplay = () => {
  564. const hiddenCount = getHiddenCount();
  565. const hiddenCountElem = document.getElementById('dlsite-record-hidden-count');
  566. if (hiddenCountElem) {
  567. hiddenCountElem.textContent = `本頁隱藏:${hiddenCount} 筆`;
  568. }
  569. };
  570.  
  571. // 為 Tag 添加切換按鈕並將已收藏標籤移到前面
  572. const addTagToggleButtons = () => {
  573. // 選擇搜索頁的標籤
  574. const tagContainers = document.querySelectorAll('.search_tag');
  575.  
  576. tagContainers.forEach(container => {
  577. // 獲取所有標籤
  578. const tagLinks = Array.from(container.querySelectorAll('a'));
  579. const favTagLinks = [];
  580. const nonFavTagLinks = [];
  581.  
  582. // 清空容器
  583. container.innerHTML = '';
  584.  
  585. // 根據收藏狀態分組並創建新的標籤+按鈕組合
  586. tagLinks.forEach(tagLink => {
  587. const tagName = tagLink.textContent.trim();
  588. const tagHref = tagLink.getAttribute('href');
  589. const isFavorited = isTagFavorited(tagName);
  590.  
  591. // 創建包裝元素
  592. const wrapper = document.createElement('div');
  593. wrapper.className = 'dlsite-tag-wrapper';
  594.  
  595. // 複製標籤
  596. const clonedLink = document.createElement('a');
  597. clonedLink.href = tagHref;
  598. clonedLink.textContent = tagName;
  599. if (isFavorited) {
  600. clonedLink.classList.add('dlsite-tag-highlight');
  601. }
  602.  
  603. // 創建按鈕
  604. const btn = document.createElement('button');
  605. btn.className = `dlsite-tag-toggle-btn ${isFavorited ? 'active' : ''}`;
  606. btn.textContent = isFavorited ? '-' : '+';
  607. btn.title = isFavorited ? '取消收藏標籤' : '收藏標籤';
  608.  
  609. btn.addEventListener('click', (e) => {
  610. e.preventDefault();
  611. e.stopPropagation();
  612.  
  613. const newState = toggleFavoriteTag(tagName, tagHref);
  614. btn.textContent = newState ? '-' : '+';
  615. btn.title = newState ? '取消收藏標籤' : '收藏標籤';
  616. btn.className = `dlsite-tag-toggle-btn ${newState ? 'active' : ''}`;
  617.  
  618. // 更新標籤樣式
  619. if (newState) {
  620. clonedLink.classList.add('dlsite-tag-highlight');
  621. } else {
  622. clonedLink.classList.remove('dlsite-tag-highlight');
  623. }
  624.  
  625. // 立即重新排序標籤,而不是刷新頁面
  626. reorderTags(container);
  627.  
  628. showToast(newState ? `已收藏標籤:${tagName}` : `已取消收藏標籤:${tagName}`);
  629. });
  630.  
  631. // 為了確保按鈕可點擊性,增加最小尺寸
  632. btn.style.minWidth = '22px';
  633. btn.style.minHeight = '22px';
  634.  
  635. // 將標籤和按鈕添加到包裝元素
  636. wrapper.appendChild(clonedLink);
  637. wrapper.appendChild(btn);
  638.  
  639. // 根據收藏狀態分組
  640. if (isFavorited) {
  641. favTagLinks.push(wrapper);
  642. } else {
  643. nonFavTagLinks.push(wrapper);
  644. }
  645. });
  646.  
  647. // 如果有收藏的標籤,優先顯示並添加分隔線
  648. if (favTagLinks.length > 0) {
  649. // 優先顯示收藏的標籤
  650. favTagLinks.forEach(wrapper => {
  651. container.appendChild(wrapper);
  652. });
  653.  
  654. // 添加分隔符
  655. if (nonFavTagLinks.length > 0) {
  656. const separator = document.createElement('div');
  657. separator.style.borderBottom = '1px dashed #ddd';
  658. separator.style.margin = '6px 0';
  659. separator.style.width = '100%';
  660. container.appendChild(separator);
  661. }
  662. }
  663.  
  664. // 顯示其他標籤
  665. nonFavTagLinks.forEach(wrapper => {
  666. container.appendChild(wrapper);
  667. });
  668. });
  669. };
  670.  
  671. // 為公司添加切換按鈕
  672. const addMakerToggleButtons = () => {
  673. const makerContainers = document.querySelectorAll('.maker_name');
  674.  
  675. makerContainers.forEach(container => {
  676. const makerElements = container.querySelectorAll('a');
  677.  
  678. makerElements.forEach(makerElement => {
  679. // 檢查公司名稱後面是否已有按鈕
  680. let nextNode = makerElement.nextSibling;
  681. let hasButton = false;
  682. while (nextNode) {
  683. if (nextNode.classList && nextNode.classList.contains('dlsite-maker-toggle-btn')) {
  684. hasButton = true;
  685. break;
  686. }
  687. nextNode = nextNode.nextSibling;
  688. }
  689.  
  690. if (hasButton) {
  691. return; // 已經添加過按鈕,跳過
  692. }
  693.  
  694. const makerName = makerElement.textContent.trim();
  695. const makerHref = makerElement.getAttribute('href');
  696. const makerId = makerHref.match(/maker_id\/([^.]+)\.html/)?.[1] || '';
  697. const isFavorited = isMakerFavorited(makerName);
  698.  
  699. // 初始化公司樣式
  700. if (isFavorited) {
  701. makerElement.classList.add('dlsite-maker-highlight');
  702. }
  703.  
  704. // 建立按鈕
  705. const btn = document.createElement('button');
  706. btn.className = `dlsite-maker-toggle-btn dlsite-maker-btn ${isFavorited ? 'active' : ''}`;
  707. btn.textContent = isFavorited ? '-' : '+';
  708. btn.title = isFavorited ? '取消收藏公司' : '收藏公司';
  709.  
  710. // 為了確保按鈕可點擊性,增加最小尺寸
  711. btn.style.minWidth = '20px';
  712. btn.style.minHeight = '20px';
  713.  
  714. btn.addEventListener('click', (e) => {
  715. e.preventDefault();
  716. e.stopPropagation();
  717.  
  718. const newState = toggleFavoriteMaker(makerName, makerId);
  719. btn.textContent = newState ? '-' : '+';
  720. btn.title = newState ? '取消收藏公司' : '收藏公司';
  721. btn.className = `dlsite-maker-toggle-btn dlsite-maker-btn ${newState ? 'active' : ''}`;
  722.  
  723. // 更新公司樣式
  724. if (newState) {
  725. makerElement.classList.add('dlsite-maker-highlight');
  726. } else {
  727. makerElement.classList.remove('dlsite-maker-highlight');
  728. }
  729.  
  730. showToast(newState ? `已收藏公司:${makerName}` : `已取消收藏公司:${makerName}`);
  731. });
  732.  
  733. // 為了保持樣式一致,使用包裝元素
  734. const wrapper = document.createElement('span');
  735. wrapper.className = 'dlsite-maker-wrapper';
  736. wrapper.style.position = 'relative';
  737. wrapper.style.display = 'inline-block';
  738.  
  739. // 複製公司鏈接到包裝元素
  740. const clonedMaker = makerElement.cloneNode(true);
  741.  
  742. // 將原始元素替換為包裝元素
  743. makerElement.parentNode.replaceChild(wrapper, makerElement);
  744.  
  745. // 將複製的鏈接和按鈕添加到包裝元素
  746. wrapper.appendChild(clonedMaker);
  747. wrapper.appendChild(btn);
  748. });
  749. });
  750. };
  751.  
  752. // 隱藏已記錄的作品
  753. const hideRecorded = () => {
  754. const tableBody = getTableBody();
  755. if (!tableBody) return;
  756.  
  757. const rows = Array.from(tableBody.rows);
  758. let hiddenCount = 0;
  759.  
  760. rows.forEach(row => {
  761. const link = row.querySelector('dl dt a');
  762. if (!link) return;
  763.  
  764. const url = link.href.split("/").filter(i => i !== '');
  765. const workId = url[url.length - 1];
  766.  
  767. if (isRecorded(workId)) {
  768. row.style.display = "none";
  769. hiddenCount++;
  770. }
  771. });
  772.  
  773. updateHiddenCountDisplay();
  774. return hiddenCount;
  775. };
  776.  
  777. // 顯示/隱藏已記錄的作品
  778. const toggleRecorded = () => {
  779. const tableBody = getTableBody();
  780. if (!tableBody) return;
  781.  
  782. const rows = Array.from(tableBody.rows);
  783. let visibleCount = 0;
  784. let hiddenCount = 0;
  785.  
  786. rows.forEach(row => {
  787. const link = row.querySelector('dl dt a');
  788. if (!link) return;
  789.  
  790. const url = link.href.split("/").filter(i => i !== '');
  791. const workId = url[url.length - 1];
  792.  
  793. if (isRecorded(workId)) {
  794. row.style.display = row.style.display === "none" ? "table-row" : "none";
  795. if (row.style.display === "none") {
  796. hiddenCount++;
  797. } else {
  798. visibleCount++;
  799. }
  800. }
  801. });
  802.  
  803. updateHiddenCountDisplay();
  804.  
  805. if (hiddenCount > 0) {
  806. showToast(`已隱藏 ${hiddenCount} 筆已記錄的作品`);
  807. } else if (visibleCount > 0) {
  808. showToast(`已顯示 ${visibleCount} 筆已記錄的作品`);
  809. } else {
  810. showToast('沒有已記錄的作品可以隱藏或顯示');
  811. }
  812. };
  813.  
  814. const highlightRecorded = () => {
  815. const tableBody = getTableBody();
  816. if (!tableBody) return;
  817.  
  818. const rows = Array.from(tableBody.rows);
  819.  
  820. rows.forEach(row => {
  821. const link = row.querySelector('dl dt a');
  822. if (!link) return;
  823.  
  824. const url = link.href.split("/").filter(i => i !== '');
  825. const workId = url[url.length - 1];
  826.  
  827. if (isRecorded(workId)) {
  828. row.classList.add('dlsite-record-highlight');
  829.  
  830. // 添加取消記錄按鈕和時間
  831. const titleCell = row.querySelector('dl dt');
  832. if (titleCell) {
  833. // 檢查是否已經有按鈕和時間標記,如果有則不重複添加
  834. if (!titleCell.querySelector('.dlsite-record-item-button')) {
  835. // 添加時間顯示
  836. const recordTime = getRecordTime(workId);
  837. const timeSpan = document.createElement('span');
  838. timeSpan.className = 'dlsite-record-time';
  839. timeSpan.textContent = recordTime ? `記錄於: ${formatDate(recordTime)}` : '';
  840.  
  841. // 添加取消記錄按鈕
  842. const removeButton = document.createElement('button');
  843. removeButton.className = 'dlsite-record-item-button';
  844. removeButton.textContent = '取消記錄';
  845. removeButton.addEventListener('click', (e) => {
  846. e.preventDefault();
  847. e.stopPropagation();
  848. removeFromRecord(workId, row);
  849. });
  850.  
  851. titleCell.appendChild(timeSpan);
  852. titleCell.appendChild(removeButton);
  853. }
  854. }
  855. } else {
  856. row.classList.remove('dlsite-record-highlight');
  857.  
  858. // 移除時間標記
  859. const timeSpan = row.querySelector('.dlsite-record-time');
  860. if (timeSpan) {
  861. timeSpan.remove();
  862. }
  863.  
  864. // 添加記錄按鈕
  865. const titleCell = row.querySelector('dl dt');
  866. if (titleCell && !titleCell.querySelector('.dlsite-record-item-button')) {
  867. const addButton = document.createElement('button');
  868. addButton.className = 'dlsite-record-item-button add';
  869. addButton.textContent = '記錄';
  870. addButton.addEventListener('click', (e) => {
  871. e.preventDefault();
  872. e.stopPropagation();
  873. addToRecord(workId, row);
  874. });
  875. titleCell.appendChild(addButton);
  876. }
  877. }
  878. });
  879.  
  880. // 添加標籤和公司切換按鈕
  881. addTagToggleButtons();
  882. addMakerToggleButtons();
  883.  
  884. updateHiddenCountDisplay();
  885. };
  886.  
  887. const addToRecord = (workId, row = null) => {
  888. const record = getRecord();
  889. if (isRecorded(workId)) return;
  890.  
  891. const currentTime = new Date().toISOString();
  892. record.push({
  893. id: workId,
  894. time: currentTime
  895. });
  896.  
  897. setRecord(record);
  898.  
  899. if (row) {
  900. row.classList.add('dlsite-record-highlight');
  901.  
  902. // 更新UI
  903. const titleCell = row.querySelector('dl dt');
  904. if (titleCell) {
  905. // 移除舊的記錄按鈕
  906. const oldButton = titleCell.querySelector('.dlsite-record-item-button');
  907. if (oldButton) {
  908. oldButton.remove();
  909. }
  910.  
  911. // 添加時間顯示
  912. const timeSpan = document.createElement('span');
  913. timeSpan.className = 'dlsite-record-time';
  914. timeSpan.textContent = `記錄於: ${formatDate(currentTime)}`;
  915.  
  916. // 添加取消記錄按鈕
  917. const removeButton = document.createElement('button');
  918. removeButton.className = 'dlsite-record-item-button';
  919. removeButton.textContent = '取消記錄';
  920. removeButton.addEventListener('click', (e) => {
  921. e.preventDefault();
  922. e.stopPropagation();
  923. removeFromRecord(workId, row);
  924. });
  925.  
  926. titleCell.appendChild(timeSpan);
  927. titleCell.appendChild(removeButton);
  928. }
  929.  
  930. // 不立即隱藏新記錄的行,等待頁面刷新後再隱藏
  931. }
  932.  
  933. updateCountDisplay();
  934. updateHiddenCountDisplay();
  935. showToast(`已將作品 ${workId} 加入記錄`);
  936. };
  937.  
  938. const removeFromRecord = (workId, row = null) => {
  939. const record = getRecord();
  940. const index = record.findIndex(item => item.id === workId);
  941. if (index === -1) return;
  942.  
  943. record.splice(index, 1);
  944. setRecord(record);
  945.  
  946. if (row) {
  947. row.classList.remove('dlsite-record-highlight');
  948. row.style.display = "table-row"; // 取消記錄後顯示該行
  949.  
  950. // 更新UI
  951. const titleCell = row.querySelector('dl dt');
  952. if (titleCell) {
  953. // 移除時間顯示
  954. const timeSpan = titleCell.querySelector('.dlsite-record-time');
  955. if (timeSpan) {
  956. timeSpan.remove();
  957. }
  958.  
  959. // 移除舊的取消記錄按鈕
  960. const oldButton = titleCell.querySelector('.dlsite-record-item-button');
  961. if (oldButton) {
  962. oldButton.remove();
  963. }
  964.  
  965. // 添加記錄按鈕
  966. const addButton = document.createElement('button');
  967. addButton.className = 'dlsite-record-item-button add';
  968. addButton.textContent = '記錄';
  969. addButton.addEventListener('click', (e) => {
  970. e.preventDefault();
  971. e.stopPropagation();
  972. addToRecord(workId, row);
  973. });
  974. titleCell.appendChild(addButton);
  975. }
  976. }
  977.  
  978. updateCountDisplay();
  979. updateHiddenCountDisplay();
  980. showToast(`已將作品 ${workId} 從記錄中移除`);
  981. };
  982.  
  983. const addAllRecord = () => {
  984. const tableBody = getTableBody();
  985. if (!tableBody) return;
  986.  
  987. const rows = Array.from(tableBody.rows);
  988. const record = getRecord();
  989. let newCount = 0;
  990. const currentTime = new Date().toISOString();
  991.  
  992. rows.forEach(row => {
  993. const link = row.querySelector('dl dt a');
  994. if (!link) return;
  995.  
  996. const url = link.href.split("/").filter(i => i !== '');
  997. const workId = url[url.length - 1];
  998.  
  999. if (!isRecorded(workId)) {
  1000. record.push({
  1001. id: workId,
  1002. time: currentTime
  1003. });
  1004. newCount++;
  1005.  
  1006. // 更新UI
  1007. row.classList.add('dlsite-record-highlight');
  1008.  
  1009. const titleCell = row.querySelector('dl dt');
  1010. if (titleCell) {
  1011. // 移除舊的記錄按鈕
  1012. const oldButton = titleCell.querySelector('.dlsite-record-item-button');
  1013. if (oldButton) {
  1014. oldButton.remove();
  1015. }
  1016.  
  1017. // 添加時間顯示
  1018. const timeSpan = document.createElement('span');
  1019. timeSpan.className = 'dlsite-record-time';
  1020. timeSpan.textContent = `記錄於: ${formatDate(currentTime)}`;
  1021.  
  1022. // 添加取消記錄按鈕
  1023. const removeButton = document.createElement('button');
  1024. removeButton.className = 'dlsite-record-item-button';
  1025. removeButton.textContent = '取消記錄';
  1026. removeButton.addEventListener('click', (e) => {
  1027. e.preventDefault();
  1028. e.stopPropagation();
  1029. removeFromRecord(workId, row);
  1030. });
  1031.  
  1032. titleCell.appendChild(timeSpan);
  1033. titleCell.appendChild(removeButton);
  1034. }
  1035. }
  1036. });
  1037.  
  1038. if (newCount > 0) {
  1039. setRecord(record);
  1040. updateCountDisplay();
  1041. updateHiddenCountDisplay();
  1042. showToast(`已添加 ${newCount} 筆新記錄,共有 ${record.length} 筆記錄`);
  1043. } else {
  1044. showToast('沒有新的作品可以記錄');
  1045. }
  1046. };
  1047.  
  1048. const updateCountDisplay = () => {
  1049. const record = getRecord();
  1050. const countElem = document.getElementById('dlsite-record-count');
  1051. if (countElem) {
  1052. countElem.textContent = `已記錄:${record.length} 筆`;
  1053. }
  1054. };
  1055.  
  1056. // 匯出和匯入功能
  1057. const exportRecord = () => {
  1058. const record = getRecord();
  1059. const favoriteTags = getFavoriteTags();
  1060. const favoriteMakers = getFavoriteMakers();
  1061.  
  1062. const exportData = JSON.stringify({
  1063. record: record,
  1064. favoriteTags: favoriteTags,
  1065. favoriteMakers: favoriteMakers
  1066. }, null, 2);
  1067.  
  1068. const modal = document.createElement('div');
  1069. modal.className = 'dlsite-modal';
  1070. modal.innerHTML = `
  1071. <div class="dlsite-modal-content">
  1072. <div class="dlsite-modal-header">
  1073. <h3>匯出記錄</h3>
  1074. <button class="dlsite-modal-close">&times;</button>
  1075. </div>
  1076. <div class="dlsite-modal-body">
  1077. <p>以下是您的記錄資料(包含作品記錄、收藏標籤和收藏公司),請複製並保存:</p>
  1078. <textarea readonly>${exportData}</textarea>
  1079. </div>
  1080. <div class="dlsite-modal-footer">
  1081. <button class="dlsite-modal-btn dlsite-modal-btn-primary" id="dlsite-copy-btn">複製</button>
  1082. <button class="dlsite-modal-btn dlsite-modal-btn-secondary" id="dlsite-modal-close-btn">關閉</button>
  1083. </div>
  1084. </div>
  1085. `;
  1086.  
  1087. document.body.appendChild(modal);
  1088.  
  1089. // 綁定事件
  1090. modal.querySelector('.dlsite-modal-close').addEventListener('click', () => {
  1091. document.body.removeChild(modal);
  1092. });
  1093.  
  1094. modal.querySelector('#dlsite-modal-close-btn').addEventListener('click', () => {
  1095. document.body.removeChild(modal);
  1096. });
  1097.  
  1098. modal.querySelector('#dlsite-copy-btn').addEventListener('click', () => {
  1099. const textarea = modal.querySelector('textarea');
  1100. textarea.select();
  1101. document.execCommand('copy');
  1102. showToast('已複製到剪貼簿');
  1103. });
  1104. };
  1105.  
  1106. const importRecord = () => {
  1107. const modal = document.createElement('div');
  1108. modal.className = 'dlsite-modal';
  1109. modal.innerHTML = `
  1110. <div class="dlsite-modal-content">
  1111. <div class="dlsite-modal-header">
  1112. <h3>匯入記錄</h3>
  1113. <button class="dlsite-modal-close">&times;</button>
  1114. </div>
  1115. <div class="dlsite-modal-body">
  1116. <p>請貼上之前匯出的記錄資料:</p>
  1117. <textarea placeholder="在這裡貼上 JSON 格式的記錄資料..."></textarea>
  1118. </div>
  1119. <div class="dlsite-modal-footer">
  1120. <button class="dlsite-modal-btn dlsite-modal-btn-primary" id="dlsite-import-btn">匯入</button>
  1121. <button class="dlsite-modal-btn dlsite-modal-btn-secondary" id="dlsite-modal-close-btn">取消</button>
  1122. </div>
  1123. </div>
  1124. `;
  1125.  
  1126. document.body.appendChild(modal);
  1127.  
  1128. // 綁定事件
  1129. modal.querySelector('.dlsite-modal-close').addEventListener('click', () => {
  1130. document.body.removeChild(modal);
  1131. });
  1132.  
  1133. modal.querySelector('#dlsite-modal-close-btn').addEventListener('click', () => {
  1134. document.body.removeChild(modal);
  1135. });
  1136.  
  1137. modal.querySelector('#dlsite-import-btn').addEventListener('click', () => {
  1138. const textarea = modal.querySelector('textarea');
  1139. try {
  1140. const importData = JSON.parse(textarea.value);
  1141.  
  1142. // 支持新舊格式
  1143. if (Array.isArray(importData)) {
  1144. // 舊格式(只有作品記錄)
  1145. handleImportedRecords(importData);
  1146. } else if (typeof importData === 'object') {
  1147. // 新格式(同時包含作品記錄、標籤和公司)
  1148. if (importData.record) {
  1149. handleImportedRecords(importData.record);
  1150. }
  1151.  
  1152. if (importData.favoriteTags) {
  1153. setFavoriteTags(importData.favoriteTags);
  1154. }
  1155.  
  1156. if (importData.favoriteMakers) {
  1157. setFavoriteMakers(importData.favoriteMakers);
  1158. }
  1159. } else {
  1160. throw new Error('匯入的資料格式不正確');
  1161. }
  1162.  
  1163. // 重新套用 UI
  1164. highlightRecorded();
  1165. updateCountDisplay();
  1166. updateHiddenCountDisplay();
  1167.  
  1168. document.body.removeChild(modal);
  1169. showToast(`已成功匯入記錄資料`);
  1170. } catch (error) {
  1171. showToast(`匯入失敗:${error.message}`);
  1172. }
  1173. });
  1174. };
  1175.  
  1176. // 處理匯入的作品記錄
  1177. const handleImportedRecords = (importData) => {
  1178. // 檢查資料格式
  1179. let validData = [];
  1180.  
  1181. // 支持新舊兩種格式
  1182. if (Array.isArray(importData)) {
  1183. importData.forEach(item => {
  1184. if (typeof item === 'string') {
  1185. // 舊格式,僅ID
  1186. validData.push({
  1187. id: item,
  1188. time: new Date().toISOString()
  1189. });
  1190. } else if (typeof item === 'object' && item.hasOwnProperty('id')) {
  1191. // 新格式,包含ID和時間
  1192. validData.push({
  1193. id: item.id,
  1194. time: item.time || new Date().toISOString()
  1195. });
  1196. }
  1197. });
  1198. }
  1199.  
  1200. if (validData.length === 0) {
  1201. throw new Error('匯入的資料不包含有效記錄');
  1202. }
  1203.  
  1204. // 合併現有記錄和匯入的記錄
  1205. const currentRecord = getRecord();
  1206.  
  1207. // 去重,保留時間較早的記錄
  1208. const recordMap = new Map();
  1209.  
  1210. // 先加入當前記錄
  1211. currentRecord.forEach(item => {
  1212. recordMap.set(item.id, item);
  1213. });
  1214.  
  1215. // 再加入匯入記錄,只有當ID不存在時才加入
  1216. validData.forEach(item => {
  1217. if (!recordMap.has(item.id)) {
  1218. recordMap.set(item.id, item);
  1219. }
  1220. });
  1221.  
  1222. // 轉換回陣列
  1223. const mergedRecord = Array.from(recordMap.values());
  1224. setRecord(mergedRecord);
  1225.  
  1226. return mergedRecord;
  1227. };
  1228.  
  1229. // 創建控制面板
  1230. const createControlPanel = () => {
  1231. const controlPanel = document.createElement('div');
  1232. controlPanel.className = 'dlsite-record-controls';
  1233.  
  1234. // 左側區域:顯示信息
  1235. const leftSection = document.createElement('div');
  1236. leftSection.className = 'dlsite-record-controls-left';
  1237.  
  1238. // 計數顯示
  1239. const countDisplay = document.createElement('div');
  1240. countDisplay.className = 'dlsite-record-info';
  1241. countDisplay.id = 'dlsite-record-count';
  1242. countDisplay.innerHTML = `
  1243. <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">
  1244. <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>
  1245. <polygon points="18 2 22 6 12 16 8 16 8 12 18 2"></polygon>
  1246. </svg>
  1247. 已記錄:0
  1248. `;
  1249.  
  1250. // 隱藏計數顯示
  1251. const hiddenCountDisplay = document.createElement('div');
  1252. hiddenCountDisplay.className = 'dlsite-record-info';
  1253. hiddenCountDisplay.id = 'dlsite-record-hidden-count';
  1254. hiddenCountDisplay.innerHTML = `
  1255. <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">
  1256. <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>
  1257. <line x1="1" y1="1" x2="23" y2="23"></line>
  1258. </svg>
  1259. 本頁隱藏:0
  1260. `;
  1261.  
  1262. leftSection.appendChild(countDisplay);
  1263. leftSection.appendChild(hiddenCountDisplay);
  1264.  
  1265. // 中間區域:功能按鈕
  1266. const centerSection = document.createElement('div');
  1267. centerSection.className = 'dlsite-record-controls-center';
  1268.  
  1269. // 記錄當前頁按鈕
  1270. const addButton = document.createElement('button');
  1271. addButton.className = 'dlsite-record-btn dlsite-record-add';
  1272. addButton.innerHTML = `
  1273. <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">
  1274. <circle cx="12" cy="12" r="10"></circle>
  1275. <line x1="12" y1="8" x2="12" y2="16"></line>
  1276. <line x1="8" y1="12" x2="16" y2="12"></line>
  1277. </svg>
  1278. 記錄此頁
  1279. `;
  1280. addButton.addEventListener('click', addAllRecord);
  1281.  
  1282. // 顯示/隱藏按鈕
  1283. const toggleButton = document.createElement('button');
  1284. toggleButton.className = 'dlsite-record-btn dlsite-record-toggle';
  1285. toggleButton.innerHTML = `
  1286. <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">
  1287. <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
  1288. <circle cx="12" cy="12" r="3"></circle>
  1289. </svg>
  1290. 隱藏/顯示已記錄
  1291. `;
  1292. toggleButton.addEventListener('click', toggleRecorded);
  1293.  
  1294. centerSection.appendChild(addButton);
  1295. centerSection.appendChild(toggleButton);
  1296.  
  1297. // 右側區域:資料管理按鈕
  1298. const rightSection = document.createElement('div');
  1299. rightSection.className = 'dlsite-record-controls-right';
  1300.  
  1301. // 匯出按鈕
  1302. const exportButton = document.createElement('button');
  1303. exportButton.className = 'dlsite-record-btn dlsite-record-export';
  1304. exportButton.innerHTML = `
  1305. <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">
  1306. <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
  1307. <polyline points="7 10 12 15 17 10"></polyline>
  1308. <line x1="12" y1="15" x2="12" y2="3"></line>
  1309. </svg>
  1310. 匯出記錄
  1311. `;
  1312. exportButton.addEventListener('click', exportRecord);
  1313.  
  1314. // 匯入按鈕
  1315. const importButton = document.createElement('button');
  1316. importButton.className = 'dlsite-record-btn dlsite-record-import';
  1317. importButton.innerHTML = `
  1318. <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">
  1319. <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
  1320. <polyline points="17 8 12 3 7 8"></polyline>
  1321. <line x1="12" y1="3" x2="12" y2="15"></line>
  1322. </svg>
  1323. 匯入記錄
  1324. `;
  1325. importButton.addEventListener('click', importRecord);
  1326.  
  1327. rightSection.appendChild(exportButton);
  1328. rightSection.appendChild(importButton);
  1329.  
  1330. // 組合所有區域
  1331. controlPanel.appendChild(leftSection);
  1332. controlPanel.appendChild(centerSection);
  1333. controlPanel.appendChild(rightSection);
  1334.  
  1335. // 插入到合適的位置
  1336. const target = document.querySelector('.work_list_header, .work_1col_table');
  1337. if (target) {
  1338. target.parentNode.insertBefore(controlPanel, target);
  1339. } else {
  1340. // 如果找不到合適的位置,放在頁面頂部
  1341. const header = document.getElementById('header');
  1342. if (header) {
  1343. header.parentNode.insertBefore(controlPanel, header.nextSibling);
  1344. }
  1345. }
  1346.  
  1347. updateCountDisplay();
  1348. updateHiddenCountDisplay();
  1349. };
  1350.  
  1351. // 檢查是否在作品詳情頁
  1352. const addDetailPageButton = () => {
  1353. const workId = window.location.pathname.match(/RJ(\d+)/i);
  1354. if (!workId) return;
  1355.  
  1356. const isWorkRecorded = isRecorded(workId[0]);
  1357.  
  1358. const buttonContainer = document.createElement('div');
  1359. buttonContainer.style.marginTop = '15px';
  1360.  
  1361. const recordButton = document.createElement('button');
  1362. recordButton.className = `dlsite-record-btn ${isWorkRecorded ? 'dlsite-record-toggle' : 'dlsite-record-add'}`;
  1363. recordButton.innerHTML = isWorkRecorded ?
  1364. `<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">
  1365. <polyline points="3 6 5 6 21 6"></polyline>
  1366. <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>
  1367. </svg>
  1368. 取消記錄` :
  1369. `<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">
  1370. <circle cx="12" cy="12" r="10"></circle>
  1371. <line x1="12" y1="8" x2="12" y2="16"></line>
  1372. <line x1="8" y1="12" x2="16" y2="12"></line>
  1373. </svg>
  1374. 記錄此作品`;
  1375.  
  1376. recordButton.addEventListener('click', () => {
  1377. if (isWorkRecorded) {
  1378. removeFromRecord(workId[0]);
  1379. } else {
  1380. addToRecord(workId[0]);
  1381. }
  1382. // 重新載入以更新按鈕狀態
  1383. location.reload();
  1384. });
  1385.  
  1386. buttonContainer.appendChild(recordButton);
  1387.  
  1388. // 顯示記錄時間(如果有的話)
  1389. if (isWorkRecorded) {
  1390. const recordTime = getRecordTime(workId[0]);
  1391. if (recordTime) {
  1392. const timeInfo = document.createElement('div');
  1393. timeInfo.style.marginTop = '5px';
  1394. timeInfo.style.fontSize = '12px';
  1395. timeInfo.style.color = '#666';
  1396. timeInfo.textContent = `記錄於: ${formatDate(recordTime)}`;
  1397. buttonContainer.appendChild(timeInfo);
  1398. }
  1399. }
  1400.  
  1401. // 插入到作品購買按鈕附近
  1402. const buySection = document.querySelector('.work_buy_section');
  1403. if (buySection) {
  1404. buySection.appendChild(buttonContainer);
  1405. }
  1406.  
  1407. // 在詳情頁也添加標籤和公司的收藏按鈕
  1408. addDetailPageTagsButtons();
  1409. addDetailPageMakerButtons();
  1410. };
  1411.  
  1412. // 在詳情頁添加標籤收藏按鈕
  1413. const addDetailPageTagsButtons = () => {
  1414. // 詳情頁中的標籤通常在 .main_genre 或 .work_genre 區域
  1415. const tagContainers = document.querySelectorAll('.main_genre, .work_genre');
  1416.  
  1417. tagContainers.forEach(container => {
  1418. const tagElements = Array.from(container.querySelectorAll('a'));
  1419.  
  1420. // 創建新的容器來保存修改後的標籤
  1421. const newContainer = document.createElement('div');
  1422. newContainer.className = container.className;
  1423. newContainer.style.display = 'flex';
  1424. newContainer.style.flexWrap = 'wrap';
  1425. newContainer.style.gap = '6px';
  1426.  
  1427. // 處理每個標籤
  1428. tagElements.forEach(tagElement => {
  1429. const tagName = tagElement.textContent.trim();
  1430. const tagHref = tagElement.getAttribute('href');
  1431. const isFavorited = isTagFavorited(tagName);
  1432.  
  1433. // 建立包裝元素
  1434. const wrapper = document.createElement('div');
  1435. wrapper.className = 'dlsite-tag-wrapper';
  1436.  
  1437. // 創建新標籤
  1438. const newTag = document.createElement('a');
  1439. newTag.href = tagHref;
  1440. newTag.textContent = tagName;
  1441. if (isFavorited) {
  1442. newTag.classList.add('dlsite-tag-highlight');
  1443. }
  1444.  
  1445. // 建立按鈕
  1446. const btn = document.createElement('button');
  1447. btn.className = `dlsite-tag-toggle-btn ${isFavorited ? 'active' : ''}`;
  1448. btn.textContent = isFavorited ? '-' : '+';
  1449. btn.title = isFavorited ? '取消收藏標籤' : '收藏標籤';
  1450.  
  1451. // 為了確保按鈕可點擊性,增加最小尺寸
  1452. btn.style.minWidth = '22px';
  1453. btn.style.minHeight = '22px';
  1454.  
  1455. btn.addEventListener('click', (e) => {
  1456. e.preventDefault();
  1457. e.stopPropagation();
  1458.  
  1459. const newState = toggleFavoriteTag(tagName, tagHref);
  1460. btn.textContent = newState ? '-' : '+';
  1461. btn.title = newState ? '取消收藏標籤' : '收藏標籤';
  1462. btn.className = `dlsite-tag-toggle-btn ${newState ? 'active' : ''}`;
  1463.  
  1464. // 更新標籤樣式
  1465. if (newState) {
  1466. newTag.classList.add('dlsite-tag-highlight');
  1467. } else {
  1468. newTag.classList.remove('dlsite-tag-highlight');
  1469. }
  1470.  
  1471. // 立即重新排序標籤
  1472. reorderTags(newContainer);
  1473.  
  1474. showToast(newState ? `已收藏標籤:${tagName}` : `已取消收藏標籤:${tagName}`);
  1475. });
  1476.  
  1477. // 組合標籤和按鈕
  1478. wrapper.appendChild(newTag);
  1479. wrapper.appendChild(btn);
  1480. newContainer.appendChild(wrapper);
  1481. });
  1482.  
  1483. // 用新容器替換原容器的內容
  1484. container.innerHTML = '';
  1485. container.appendChild(newContainer);
  1486.  
  1487. // 立即排序
  1488. reorderTags(newContainer);
  1489. });
  1490. };
  1491.  
  1492. // 在詳情頁添加公司收藏按鈕
  1493. const addDetailPageMakerButtons = () => {
  1494. // 詳情頁中的公司通常在 .maker_name、.author、.circle_name 區域
  1495. const makerContainers = document.querySelectorAll('.maker_name, .author, .circle_name');
  1496.  
  1497. makerContainers.forEach(container => {
  1498. const makerElements = container.querySelectorAll('a');
  1499.  
  1500. makerElements.forEach(makerElement => {
  1501. // 檢查公司名稱後面是否已有按鈕
  1502. let nextNode = makerElement.nextSibling;
  1503. let hasButton = false;
  1504. while (nextNode) {
  1505. if (nextNode.classList && nextNode.classList.contains('dlsite-maker-toggle-btn')) {
  1506. hasButton = true;
  1507. break;
  1508. }
  1509. nextNode = nextNode.nextSibling;
  1510. }
  1511.  
  1512. if (hasButton) {
  1513. return; // 已經添加過按鈕,跳過
  1514. }
  1515.  
  1516. const makerName = makerElement.textContent.trim();
  1517. const makerHref = makerElement.getAttribute('href');
  1518. const makerId = makerHref.match(/maker_id\/([^.]+)\.html/)?.[1] || '';
  1519. const isFavorited = isMakerFavorited(makerName);
  1520.  
  1521. // 建立按鈕
  1522. const btn = document.createElement('button');
  1523. btn.className = `dlsite-maker-toggle-btn ${isFavorited ? 'active' : ''}`;
  1524. btn.textContent = isFavorited ? '-' : '+';
  1525. btn.title = isFavorited ? '取消收藏公司' : '收藏公司';
  1526.  
  1527. btn.addEventListener('click', (e) => {
  1528. e.preventDefault();
  1529. e.stopPropagation();
  1530.  
  1531. const newState = toggleFavoriteMaker(makerName, makerId);
  1532. btn.textContent = newState ? '-' : '+';
  1533. btn.title = newState ? '取消收藏公司' : '收藏公司';
  1534. btn.className = `dlsite-maker-toggle-btn ${newState ? 'active' : ''}`;
  1535.  
  1536. // 更新公司樣式
  1537. if (newState) {
  1538. makerElement.classList.add('dlsite-maker-highlight');
  1539. } else {
  1540. makerElement.classList.remove('dlsite-maker-highlight');
  1541. }
  1542.  
  1543. showToast(newState ? `已收藏公司:${makerName}` : `已取消收藏公司:${makerName}`);
  1544. });
  1545.  
  1546. // 初始化公司樣式
  1547. if (isFavorited) {
  1548. makerElement.classList.add('dlsite-maker-highlight');
  1549. }
  1550.  
  1551. // 插入按鈕到公司名稱後面
  1552. makerElement.parentNode.insertBefore(btn, makerElement.nextSibling);
  1553. });
  1554. });
  1555. };
  1556.  
  1557. // 初始化
  1558. const init = () => {
  1559. if (getTableBody()) {
  1560. createControlPanel();
  1561. highlightRecorded();
  1562.  
  1563. // 頁面載入時自動隱藏已記錄的項目
  1564. const hiddenCount = hideRecorded();
  1565. if (hiddenCount > 0) {
  1566. showToast(`已自動隱藏 ${hiddenCount} 筆已記錄的作品`);
  1567. }
  1568. } else {
  1569. addDetailPageButton();
  1570. }
  1571. };
  1572.  
  1573. // 頁面載入完成後執行
  1574. window.addEventListener('load', init);
  1575.  
  1576. // 如果已經載入完成,則立即執行
  1577. if (document.readyState === 'complete') {
  1578. init();
  1579. }
  1580. })();