Bluesky Handle Link Button

Adds a globe button next to domain handles on Bluesky profiles to open the URL in a new tab.

目前为 2024-11-22 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Bluesky Handle Link Button
  3. // @version 1.1
  4. // @namespace lexd0g.eu.org
  5. // @description Adds a globe button next to domain handles on Bluesky profiles to open the URL in a new tab.
  6. // @author @lexd0g.eu.org
  7. // @match https://*.bsky.app/*
  8. // @grant none
  9. // @license CC BY-SA 4.0
  10. // ==/UserScript==
  11.  
  12. (function() {
  13. 'use strict';
  14.  
  15. // Function to add the globe button
  16. function addGlobeButton() {
  17. // Look for handle elements
  18. const handleElements = document.evaluate(
  19. "//div[starts-with(@class, 'css-')][starts-with(text(), '@') and string-length(text()) > 1]",
  20. document,
  21. null,
  22. XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
  23. null
  24. );
  25.  
  26. for (let i = 0; i < handleElements.snapshotLength; i++) {
  27. const element = handleElements.snapshotItem(i);
  28.  
  29. // Skip if it's inside a post
  30. if (element.closest('[data-testid="postText"]')) continue;
  31.  
  32. const handle = element.textContent.trim().replace('@', '');
  33.  
  34. if (element.nextElementSibling?.classList.contains('domain-link-btn')) continue;
  35.  
  36. // Get the computed color of the handle text
  37. const textColor = window.getComputedStyle(element).color;
  38.  
  39. // Get link colour
  40. const linkColor = window.getComputedStyle(document.querySelector('a[target="_blank"]')).color
  41.  
  42. const globeButton = document.createElement('button');
  43. globeButton.innerHTML = '🌐';
  44. globeButton.className = 'domain-link-btn';
  45. globeButton.style.cssText = `
  46. background: none;
  47. border: none;
  48. cursor: pointer;
  49. font-size: 18px;
  50. padding: 0;
  51. vertical-align: text-top;
  52. font-family: -apple-system, BlinkMacSystemFont;
  53. position: relative;
  54. z-index: 1000;
  55. pointer-events: all;
  56. color: transparent;
  57. text-shadow: 0 0 0 ${textColor};
  58. transition: 0.2s;
  59. `;
  60.  
  61. globeButton.addEventListener('mouseenter', () => {
  62. globeButton.style.textShadow = `0 0 0 ${linkColor}`;
  63. });
  64. globeButton.addEventListener('mouseleave', () => {
  65. globeButton.style.textShadow = `0 0 0 ${textColor}`;
  66. });
  67.  
  68. globeButton.addEventListener('click', (e) => {
  69. e.preventDefault();
  70. e.stopPropagation();
  71. e.stopImmediatePropagation();
  72. window.open(`https://${handle}`, '_blank');
  73. });
  74.  
  75. // Find closest link parent and ensure button events don't bubble
  76. const parentLink = element.closest('a');
  77. if (parentLink) {
  78. globeButton.style.position = 'relative';
  79. globeButton.style.zIndex = '1000';
  80. }
  81.  
  82. element.parentNode.insertBefore(globeButton, element.nextSibling);
  83. }
  84. }
  85.  
  86. // Watch for URL changes
  87. let lastUrl = location.href;
  88. new MutationObserver(() => {
  89. const url = location.href;
  90. if (url !== lastUrl) {
  91. lastUrl = url;
  92. setTimeout(addGlobeButton, 500);
  93. }
  94. }).observe(document, {subtree: true, childList: true});
  95.  
  96. // Watch for dynamic content changes
  97. const observer = new MutationObserver((mutations) => {
  98. const hasNewNodes = mutations.some(mutation =>
  99. mutation.addedNodes.length > 0 ||
  100. mutation.type === 'childList'
  101. );
  102. if (hasNewNodes) {
  103. addGlobeButton();
  104. }
  105. });
  106.  
  107. observer.observe(document.body, { childList: true, subtree: true });
  108.  
  109. // Initial check
  110. addGlobeButton();
  111. })();