🏠 Home 

Pinterest环形收藏夹

在Pinterest详情页环形展开收藏夹


安装此脚本?
  1. // ==UserScript==
  2. // @name Pinterest环形收藏夹
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.0
  5. // @description 在Pinterest详情页环形展开收藏夹
  6. // @author You
  7. // @match https://www.pinterest.com/*
  8. // @grant none
  9. // ==/UserScript==
  10. (function () {
  11. 'use strict';
  12. // 为环形菜单添加CSS样式
  13. const styleElement = document.createElement('style');
  14. styleElement.textContent = `
  15. .radial-menu-container {
  16. position: fixed;
  17. z-index: 9999;
  18. pointer-events: none;
  19. width: 0;
  20. height: 0;
  21. }
  22. .radial-menu {
  23. position: absolute;
  24. width: 500px;
  25. height: 250px; /* 半高,因为是半圆 */
  26. border-radius: 250px 250px 0 0; /* 修改为上半部分为圆形,底部为直线 */
  27. /* 移除背景颜色和阴影效果 */
  28. background-color: transparent;
  29. box-shadow: none;
  30. pointer-events: auto;
  31. transform: translate(-50%, 0); /* 修改为向左平移一半,向下平移0 */
  32. }
  33. .radial-menu-item {
  34. position: absolute;
  35. display: flex;
  36. align-items: center;
  37. justify-content: center;
  38. cursor: pointer;
  39. font-weight: bold;
  40. color: #333;
  41. transition: all 0.2s ease;
  42. text-align: center;
  43. overflow: hidden;
  44. text-overflow: ellipsis;
  45. padding: 5px;
  46. border-radius: 6px;
  47. background-color: rgba(255, 255, 255, 0.85);
  48. box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
  49. }
  50. .radial-menu-item:hover {
  51. background-color: rgba(230, 0, 35, 0.1);
  52. color: #e60023;
  53. transform: scale(1.05);
  54. }
  55. .radial-menu-center {
  56. position: absolute;
  57. width: 70px;
  58. height: 70px;
  59. background-color: #e60023;
  60. border-radius: 50%;
  61. left: 50%;
  62. bottom: 20px; /* 放在半圆的底部 */
  63. transform: translate(-50%, 0);
  64. display: flex;
  65. align-items: center;
  66. justify-content: center;
  67. color: white;
  68. font-weight: bold;
  69. cursor: pointer;
  70. box-shadow: 0 2px 8px rgba(230, 0, 35, 0.5);
  71. z-index: 2;
  72. }
  73. .radial-menu-ring {
  74. position: absolute;
  75. border-radius: 0 0 250px 250px; /* 下半圆 */
  76. border: 1px dashed rgba(200, 200, 200, 0.1);
  77. bottom: 0; /* 底部对齐 */
  78. left: 50%;
  79. transform: translateX(-50%);
  80. height: 50%; /* 半高 */
  81. }
  82. .radial-menu-overlay {
  83. position: fixed;
  84. top: 0;
  85. left: 0;
  86. right: 0;
  87. bottom: 0;
  88. // 修改透明度为 70%
  89. background-color: rgba(0, 0, 0, 0.7);
  90. z-index: 9998;
  91. pointer-events: auto;
  92. }
  93. .radial-menu-item-icon {
  94. width: 16px;
  95. height: 16px;
  96. margin-right: 5px;
  97. }
  98. .radial-menu-item-inner {
  99. display: flex;
  100. flex-direction: column;
  101. align-items: center;
  102. justify-content: center;
  103. max-width: 100%;
  104. }
  105. .radial-menu-item-name {
  106. text-overflow: ellipsis;
  107. overflow: hidden;
  108. white-space: nowrap;
  109. max-width: 100%;
  110. font-size: 12px;
  111. }
  112. .radial-save-button {
  113. background-color: #efefef;
  114. border: none;
  115. border-radius: 20px;
  116. padding: 8px 12px;
  117. margin-left: 8px;
  118. color: #333;
  119. font-weight: bold;
  120. display: flex;
  121. align-items: center;
  122. cursor: pointer;
  123. transition: background-color 0.2s;
  124. }
  125. .radial-save-button:hover {
  126. background-color: #e2e2e2;
  127. }
  128. .radial-save-icon {
  129. margin-right: 4px;
  130. width: 16px;
  131. height: 16px;
  132. }
  133. `;
  134. document.head.appendChild(styleElement);
  135. // 存储用户收藏夹的数组
  136. let userBoards = [];
  137. let menuVisible = false;
  138. let menuContainer = null;
  139. let overlayElement = null;
  140. let currentPinData = null;
  141. // 显示半圆形菜单的函数
  142. function showRadialMenu() {
  143. // 先获取用户的收藏夹
  144. fetchUserBoards().then(boards => {
  145. // 查找原始保存按钮
  146. const originSaveBtn = document.querySelector('button[aria-label="保存"]');
  147. if (!originSaveBtn) {
  148. console.log('未找到原始保存按钮');
  149. return;
  150. }
  151. // 获取原始保存按钮的位置
  152. const rect = originSaveBtn.getBoundingClientRect();
  153. const centerX = rect.left + rect.width / 2;
  154. const startY = rect.top;
  155. // 创建遮罩层
  156. overlayElement = document.createElement('div');
  157. overlayElement.className = 'radial-menu-overlay';
  158. overlayElement.addEventListener('click', hideRadialMenu);
  159. console.log('遮罩层元素创建:', overlayElement); // 添加日志输出
  160. document.body.appendChild(overlayElement);
  161. console.log('遮罩层元素添加到文档:', overlayElement); // 添加日志输出
  162. // 创建新的遮罩层(遮罩2)
  163. const overlayElement2 = document.createElement('div');
  164. overlayElement2.className = 'radial-menu-overlay2'; // 新的类名
  165. overlayElement2.style.backgroundColor = 'rgba(0, 0, 0, 0.7)'; // 可以根据需要调整样式
  166. overlayElement2.style.position = 'fixed';
  167. overlayElement2.style.top = '0';
  168. overlayElement2.style.left = '0';
  169. overlayElement2.style.right = '0';
  170. overlayElement2.style.bottom = '0';
  171. overlayElement2.style.zIndex = '9997'; // 确保在原有遮罩层之下
  172. overlayElement2.addEventListener('click', hideRadialMenu);
  173. document.body.appendChild(overlayElement2);
  174. // 创建菜单容器
  175. menuContainer = document.createElement('div');
  176. menuContainer.className = 'radial-menu-container';
  177. menuContainer.style.left = `${centerX}px`;
  178. menuContainer.style.top = `${startY}px`;
  179. // 创建半圆形菜单
  180. const radialMenu = document.createElement('div');
  181. radialMenu.className = 'radial-menu';
  182. // 创建指示环(半圆)
  183. createRing(radialMenu, 100, 100); // 第一圈,修改半径为100
  184. createRing(radialMenu, 220, 220); // 第二圈,修改半径为220
  185. createRing(radialMenu, 340, 340); // 第三圈,修改半径为340
  186. // 根据收藏夹数量自动分配半圆形布局
  187. distributeItemsInSemiCircle(radialMenu, boards);
  188. menuContainer.appendChild(radialMenu);
  189. document.body.appendChild(menuContainer);
  190. menuVisible = true;
  191. });
  192. }
  193. // 创建指示环(半圆)
  194. function createRing(parent, width, height) {
  195. const ring = document.createElement('div');
  196. ring.className = 'radial-menu-ring';
  197. ring.style.width = `${width}px`;
  198. ring.style.height = `${height / 2}px`; // 半高,形成半圆
  199. parent.appendChild(ring);
  200. }
  201. // 在半圆中分配项目
  202. function distributeItemsInSemiCircle(radialMenu, boards) {
  203. // 第一圈最多6个
  204. createItemsSemiCircle(radialMenu, boards, 0, Math.min(6, boards.length), 100, 50, 90);
  205. // 如果收藏夹数量大于6,第二圈最多10个
  206. if (boards.length > 6) {
  207. createItemsSemiCircle(radialMenu, boards, 6, Math.min(16, boards.length), 220, 50, 90);
  208. }
  209. // 如果收藏夹数量大于16,剩余的放在第三圈
  210. if (boards.length > 16) {
  211. createItemsSemiCircle(radialMenu, boards, 16, boards.length, 340, 50, 90);
  212. }
  213. }
  214. // 创建半圆形中的项目 - 调整角度计算为下半圆
  215. function createItemsSemiCircle(radialMenu, boards, startIdx, endIdx, radius, width, height) {
  216. const totalItems = endIdx - startIdx;
  217. for (let i = startIdx; i < endIdx; i++) {
  218. const board = boards[i];
  219. // 角度范围从0到π(上半圆)
  220. const angle = (Math.PI * (i - startIdx)) / totalItems;
  221. const posX = radius * Math.cos(angle);
  222. const posY = radius * Math.sin(angle);
  223. const item = document.createElement('div');
  224. item.className = 'radial-menu-item';
  225. const itemInner = document.createElement('div');
  226. itemInner.className = 'radial-menu-item-inner';
  227. // 移除添加图标的逻辑
  228. // 添加收藏夹名称
  229. const nameSpan = document.createElement('span');
  230. nameSpan.className = 'radial-menu-item-name';
  231. nameSpan.textContent = board.name;
  232. itemInner.appendChild(nameSpan);
  233. item.appendChild(itemInner);
  234. // 设置高度固定为12px
  235. height = 12;
  236. // 计算宽度,为标题显示完整的宽度加上5px,最小为60px
  237. const tempDiv = document.createElement('div');
  238. tempDiv.style.position = 'absolute';
  239. tempDiv.style.visibility = 'hidden';
  240. tempDiv.style.whiteSpace = 'nowrap';
  241. tempDiv.textContent = board.name;
  242. // 复制收藏夹名称的样式
  243. const style = window.getComputedStyle(nameSpan);
  244. tempDiv.style.fontFamily = style.fontFamily;
  245. tempDiv.style.fontSize = style.fontSize;
  246. tempDiv.style.fontWeight = style.fontWeight;
  247. tempDiv.style.padding = style.padding;
  248. document.body.appendChild(tempDiv);
  249. width = Math.max(60, tempDiv.offsetWidth + 5);
  250. document.body.removeChild(tempDiv);
  251. // 位置计算,对于下半圆形布局
  252. item.style.left = `${250 + posX - width / 2}px`;
  253. item.style.top = `${posY - height / 2}px`;
  254. item.style.width = `${width}px`;
  255. item.style.height = `${height}px`;
  256. // 计算旋转角度,使长边朝着圆心
  257. let rotationAngle = (angle - Math.PI / 2 + Math.PI / 2) * (180 / Math.PI);
  258. // 如果在圆心左侧,额外旋转180度
  259. if (posX < 0) {
  260. rotationAngle += 180;
  261. }
  262. item.style.transform = `rotate(${rotationAngle}deg)`;
  263. item.addEventListener('click', () => {
  264. console.log(`保存到"${board.name}"`);
  265. hideRadialMenu();
  266. // 保存收藏夹ID和名称
  267. const boardId = board.id;
  268. const boardName = board.name;
  269. // 使用新方法保存到指定收藏夹
  270. saveToBoard(boardId, boardName);
  271. });
  272. radialMenu.appendChild(item);
  273. }
  274. }
  275. // 优化后的保存到指定收藏夹的函数
  276. function saveToBoard(boardId, boardName) {
  277. console.log(`正在尝试保存到收藏夹: ${boardName} (ID: ${boardId})`);
  278. // 查找选择收藏夹的下拉按钮
  279. const boardSelectionButton = document.querySelector('[data-test-id="PinBetterSaveDropdown"]');
  280. if (!boardSelectionButton) {
  281. console.log('未找到收藏夹选择按钮');
  282. return;
  283. }
  284. // 模拟点击下拉按钮,打开收藏夹列表
  285. triggerMouseEvent(boardSelectionButton, "mousedown");
  286. triggerMouseEvent(boardSelectionButton, "mouseup");
  287. triggerMouseEvent(boardSelectionButton, "click");
  288. // 等待下拉菜单出现
  289. setTimeout(() => {
  290. // 查找所有收藏夹项目
  291. const boardItems = document.querySelectorAll('[data-test-id="boardWithoutSection"]');
  292. console.log(`找到${boardItems.length}个收藏夹项目`);
  293. for (const item of boardItems) {
  294. const titleEl = item.querySelector('.X8m.zDA');
  295. if (titleEl && titleEl.textContent === boardName) {
  296. console.log(`找到匹配的收藏夹: ${boardName}`);
  297. // 查找收藏夹后面的保存按钮
  298. let saveBtn = item.querySelector('button[aria-label="收藏按钮"], button[aria-label="Save"], button[aria-label="保存"]');
  299. if (!saveBtn) {
  300. // 如果没找到,尝试查找收藏夹项目内的任何按钮
  301. saveBtn = item.querySelector('button');
  302. }
  303. if (!saveBtn) {
  304. // 如果仍然没找到,尝试查找可能包含保存功能的div元素
  305. const divs = item.querySelectorAll('div[role="button"]');
  306. for (const div of divs) {
  307. if (div !== item) { // 避免选中整个收藏夹项目
  308. saveBtn = div;
  309. break;
  310. }
  311. }
  312. }
  313. if (saveBtn) {
  314. console.log(`找到收藏夹 "${boardName}" 的按钮元素,点击它`);
  315. // 在点击前先添加日志,输出找到的元素详情,有助于调试
  316. console.log('找到的按钮元素:', saveBtn.outerHTML);
  317. triggerMouseEvent(saveBtn, "mousedown");
  318. triggerMouseEvent(saveBtn, "mouseup");
  319. triggerMouseEvent(saveBtn, "click");
  320. // 关闭下拉菜单
  321. document.body.click();
  322. return;
  323. } else {
  324. console.log(`在收藏夹 "${boardName}" 中未找到保存按钮`);
  325. }
  326. }
  327. }
  328. console.log(`未找到匹配的收藏夹: ${boardName}`);
  329. // 关闭下拉菜单
  330. document.body.click();
  331. }, 500); // 适当增加等待时间确保菜单完全打开
  332. }
  333. // 隐藏环形菜单的函数
  334. function hideRadialMenu() {
  335. if (menuContainer) {
  336. document.body.removeChild(menuContainer);
  337. menuContainer = null;
  338. }
  339. if (overlayElement) {
  340. document.body.removeChild(overlayElement);
  341. overlayElement = null;
  342. }
  343. // 新增:移除遮罩层2
  344. const overlayElement2 = document.querySelector('.radial-menu-overlay2');
  345. if (overlayElement2) {
  346. document.body.removeChild(overlayElement2);
  347. }
  348. menuVisible = false;
  349. }
  350. // 获取用户收藏夹的函数
  351. async function fetchUserBoards() {
  352. // 清空之前的收藏夹数据
  353. userBoards = [];
  354. // 查找选择收藏夹的下拉按钮
  355. const boardSelectionButton = document.querySelector('[data-test-id="PinBetterSaveDropdown"]');
  356. if (!boardSelectionButton) {
  357. console.log('未找到收藏夹选择按钮');
  358. return userBoards;
  359. }
  360. // 模拟点击下拉按钮,打开收藏夹列表
  361. triggerMouseEvent(boardSelectionButton, "mousedown");
  362. triggerMouseEvent(boardSelectionButton, "mouseup");
  363. triggerMouseEvent(boardSelectionButton, "click");
  364. // 等待下拉菜单出现(等待500ms)
  365. await new Promise(resolve => setTimeout(resolve, 500));
  366. // 查找所有收藏夹项目
  367. const boardItems = document.querySelectorAll('[data-test-id="boardWithoutSection"]');
  368. // 解析收藏夹数据
  369. boardItems.forEach(item => {
  370. // 查找收藏夹名称
  371. const titleEl = item.querySelector('.X8m.zDA');
  372. if (!titleEl) return;
  373. const boardName = titleEl.textContent;
  374. // 获取收藏夹ID(如果有)
  375. let boardId = "";
  376. // 尝试从data-test-id属性获取ID
  377. const testId = item.getAttribute('data-test-id');
  378. if (testId && testId.includes("board-row-")) {
  379. boardId = testId.replace("board-row-", "");
  380. }
  381. // 查找收藏夹图标(如果有)
  382. const iconEl = item.querySelector('img');
  383. const iconUrl = iconEl ? iconEl.src : null;
  384. const icon = iconUrl ? `<img src="${iconUrl}" width="16" height="16" style="border-radius: 4px;">` : null;
  385. console.log('收藏夹', boardName, '图标 URL:', iconUrl); // 添加日志输出
  386. userBoards.push({
  387. id: boardId,
  388. name: boardName,
  389. icon: icon,
  390. element: item
  391. });
  392. });
  393. // 关闭下拉菜单
  394. document.body.click();
  395. console.log(`找到${userBoards.length}个收藏夹`);
  396. // 如果没有找到收藏夹,创建一些测试数据
  397. if (userBoards.length === 0) {
  398. // 创建更多测试数据以测试多层环状布局
  399. const testBoards = [
  400. "个人资料", "美食", "旅行", "时尚", "艺术", "设计", "科技", "摄影",
  401. "健身", "家居", "手工", "园艺", "宠物", "电影", "音乐", "书籍",
  402. "动漫", "教育", "建筑", "美容", "汽车", "婚礼", "绘画", "户外",
  403. "游戏", "历史", "儿童", "节日", "引用", "灵感"
  404. ];
  405. testBoards.forEach((name, index) => {
  406. userBoards.push({
  407. id: `test-${index}`,
  408. name: name,
  409. icon: null,
  410. element: null
  411. });
  412. });
  413. console.log('未找到收藏夹,使用测试数据');
  414. }
  415. return userBoards;
  416. }
  417. // 用于触发鼠标事件的辅助函数
  418. function triggerMouseEvent(element, eventType) {
  419. const mouseEvent = document.createEvent("MouseEvents");
  420. mouseEvent.initEvent(eventType, true, true);
  421. element.dispatchEvent(mouseEvent);
  422. }
  423. // 添加环形保存按钮的函数,现在改为添加A按钮
  424. function addRadialSaveButton() {
  425. // 创建一个MutationObserver来监听DOM变化
  426. const observer = new MutationObserver((mutations) => {
  427. for (const mutation of mutations) {
  428. if (mutation.addedNodes.length) {
  429. // 查找原始保存按钮
  430. const saveButtons = document.querySelectorAll('button[aria-label="保存"]:not([data-radial-menu])');
  431. saveButtons.forEach(button => {
  432. // 标记按钮为已处理
  433. button.setAttribute('data-radial-menu', 'true');
  434. // 创建A按钮
  435. const radialButton = document.createElement('button');
  436. radialButton.className = 'radial-save-button';
  437. radialButton.innerHTML = `
  438. <svg class="radial-save-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
  439. <circle cx="12" cy="12" r="10" stroke="#333" stroke-width="2" fill="none"/>
  440. <path d="M6 12 L18 12 M12 6 L12 18" stroke="#333" stroke-width="2"/>
  441. </svg>
  442. A按钮
  443. `;
  444. // 添加点击事件
  445. radialButton.addEventListener('click', (event) => {
  446. event.preventDefault();
  447. event.stopPropagation();
  448. // 显示半圆形菜单
  449. showRadialMenu();
  450. return false;
  451. });
  452. // 将A按钮添加到保存按钮旁边
  453. const parent = button.parentNode;
  454. if (parent && !parent.querySelector('.radial-save-button')) {
  455. parent.appendChild(radialButton);
  456. }
  457. });
  458. }
  459. }
  460. });
  461. // 开始观察文档
  462. observer.observe(document.body, {
  463. childList: true,
  464. subtree: true
  465. });
  466. // 检查脚本初始加载时已存在的按钮
  467. const existingSaveButtons = document.querySelectorAll('button[aria-label="保存"]:not([data-radial-menu])');
  468. existingSaveButtons.forEach(button => {
  469. button.setAttribute('data-radial-menu', 'true');
  470. // 创建A按钮
  471. const radialButton = document.createElement('button');
  472. radialButton.className = 'radial-save-button';
  473. radialButton.innerHTML = `
  474. <svg class="radial-save-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
  475. <circle cx="12" cy="12" r="10" stroke="#333" stroke-width="2" fill="none"/>
  476. <path d="M6 12 L18 12 M12 6 L12 18" stroke="#333" stroke-width="2"/>
  477. </svg>
  478. A按钮
  479. `;
  480. // 添加点击事件
  481. radialButton.addEventListener('click', (event) => {
  482. event.preventDefault();
  483. event.stopPropagation();
  484. // 获取按钮位置,从这里向上展开反向半圆菜单
  485. const rect = radialButton.getBoundingClientRect();
  486. const centerX = rect.left + rect.width / 2;
  487. const startY = rect.top; // 使用按钮顶部作为菜单底部
  488. // 显示半圆形菜单
  489. showRadialMenu(centerX, startY);
  490. return false;
  491. });
  492. // 将A按钮添加到保存按钮旁边
  493. const parent = button.parentNode;
  494. if (parent && !parent.querySelector('.radial-save-button')) {
  495. parent.appendChild(radialButton);
  496. }
  497. });
  498. }
  499. // 初始化脚本
  500. function init() {
  501. console.log('Pinterest反向半圆形保存菜单脚本已初始化');
  502. // 添加环形保存按钮
  503. addRadialSaveButton();
  504. // 添加关闭菜单的键盘快捷键(Escape键)
  505. document.addEventListener('keydown', (event) => {
  506. if (event.key === 'Escape' && menuVisible) {
  507. hideRadialMenu();
  508. }
  509. });
  510. }
  511. // 页面完全加载后运行初始化
  512. if (document.readyState === 'loading') {
  513. document.addEventListener('DOMContentLoaded', init);
  514. } else {
  515. init();
  516. }
  517. })();