CSS Selector Copy

Click elements to get their CSS selector instantly

  1. // ==UserScript==
  2. // @name CSS Selector Copy
  3. // @namespace https://greasyfork.org/
  4. // @version 1.0
  5. // @description Click elements to get their CSS selector instantly
  6. // @author Bui Quoc Dung
  7. // @match *://*/*
  8. // @grant GM_setClipboard
  9. // @grant GM_addStyle
  10. // ==/UserScript==
  11.  
  12. (function() {
  13. 'use strict';
  14.  
  15. // Add styles for our dialog
  16. GM_addStyle(`
  17. .instant-selector-dialog {
  18. position: fixed;
  19. z-index: 999999;
  20. background: white;
  21. border-radius: 8px;
  22. box-shadow: 0 4px 20px rgba(0,0,0,0.15);
  23. padding: 16px;
  24. max-width: 400px;
  25. width: 90%;
  26. font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  27. animation: dialogFadeIn 0.15s ease-out;
  28. border: 1px solid #e0e0e0;
  29. }
  30.  
  31. @keyframes dialogFadeIn {
  32. from { opacity: 0; transform: translateY(10px); }
  33. to { opacity: 1; transform: translateY(0); }
  34. }
  35.  
  36. .selector-header {
  37. font-size: 16px;
  38. font-weight: 600;
  39. margin-bottom: 12px;
  40. color: #202124;
  41. display: flex;
  42. align-items: center;
  43. gap: 8px;
  44. }
  45.  
  46. .selector-header svg {
  47. width: 18px;
  48. height: 18px;
  49. fill: #5f6368;
  50. }
  51.  
  52. .selector-display {
  53. background: #f8f9fa;
  54. border-radius: 6px;
  55. padding: 12px;
  56. font-family: 'Roboto Mono', monospace;
  57. font-size: 13px;
  58. line-height: 1.5;
  59. margin-bottom: 16px;
  60. max-height: 200px;
  61. overflow-y: auto;
  62. border: 1px solid #e0e0e0;
  63. white-space: pre-wrap;
  64. word-break: break-all;
  65. }
  66.  
  67. .selector-options {
  68. display: flex;
  69. gap: 16px;
  70. margin-bottom: 16px;
  71. padding-bottom: 16px;
  72. border-bottom: 1px solid #f1f1f1;
  73. }
  74.  
  75. .option-radio {
  76. display: flex;
  77. align-items: center;
  78. gap: 6px;
  79. cursor: pointer;
  80. font-size: 14px;
  81. }
  82.  
  83. .selector-buttons {
  84. display: flex;
  85. justify-content: flex-end;
  86. gap: 8px;
  87. }
  88.  
  89. .selector-button {
  90. padding: 8px 16px;
  91. border-radius: 4px;
  92. font-weight: 500;
  93. font-size: 14px;
  94. cursor: pointer;
  95. border: none;
  96. transition: all 0.2s;
  97. }
  98.  
  99. .copy-button {
  100. background: #1a73e8;
  101. color: white;
  102. }
  103.  
  104. .copy-button:hover {
  105. background: #1765cc;
  106. box-shadow: 0 1px 3px rgba(0,0,0,0.1);
  107. }
  108.  
  109. .copy-button.copied {
  110. background: #34a853;
  111. }
  112.  
  113. .close-button {
  114. background: transparent;
  115. color: #5f6368;
  116. border: 1px solid #dadce0;
  117. }
  118.  
  119. .close-button:hover {
  120. background: #f8f9fa;
  121. }
  122. `);
  123.  
  124. // Create dialog element
  125. const dialog = document.createElement('div');
  126. dialog.className = 'instant-selector-dialog';
  127. dialog.style.display = 'none';
  128. document.body.appendChild(dialog);
  129.  
  130. // Add dialog content
  131. dialog.innerHTML = `
  132. <div class="selector-header">
  133. <svg viewBox="0 0 24 24"><path d="M19 3h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 0c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm4 12h-2v2c0 .55-.45 1-1 1s-1-.45-1-1v-2H9v-2h2v-2c0-.55.45-1 1-1s1 .45 1 1v2h2v2z"></path></svg>
  134. <span>CSS Selector</span>
  135. </div>
  136. <div class="selector-display" id="selector-display"></div>
  137. <div class="selector-options">
  138. <label class="option-radio">
  139. <input type="radio" name="selector-option" value="with" checked> With :nth-of-type
  140. </label>
  141. <label class="option-radio">
  142. <input type="radio" name="selector-option" value="without"> Without :nth-of-type
  143. </label>
  144. </div>
  145. <div class="selector-buttons">
  146. <button class="selector-button copy-button" id="copy-button">Copy</button>
  147. <button class="selector-button close-button" id="close-button">Close</button>
  148. </div>
  149. `;
  150.  
  151. // Get references to dialog elements
  152. const selectorDisplay = dialog.querySelector('#selector-display');
  153. const copyButton = dialog.querySelector('#copy-button');
  154. const closeButton = dialog.querySelector('#close-button');
  155. const optionRadios = dialog.querySelectorAll('input[name="selector-option"]');
  156.  
  157. // Your improved selector function
  158. function getFullCssSelector(el, includeNth = true) {
  159. if (!(el instanceof Element)) return;
  160. const path = [];
  161. while (el) {
  162. let selector = el.nodeName.toLowerCase();
  163. if (el.id) {
  164. selector += "#" + el.id;
  165. path.unshift(selector);
  166. break;
  167. } else {
  168. if (el.className && typeof el.className === 'string') {
  169. const classes = el.className.trim().split(/\s+/).join(".");
  170. if (classes) selector += "." + classes;
  171. }
  172.  
  173. if (includeNth) {
  174. let sibling = el, nth = 1;
  175. while ((sibling = sibling.previousElementSibling)) {
  176. if (sibling.nodeName === el.nodeName) nth++;
  177. }
  178. selector += `:nth-of-type(${nth})`;
  179. }
  180.  
  181. path.unshift(selector);
  182. el = el.parentElement;
  183. }
  184. }
  185. return path.join(" > ");
  186. }
  187.  
  188. // Position and show dialog
  189. function showDialog(element, event) {
  190. const includeNth = dialog.querySelector('input[name="selector-option"]:checked').value === 'with';
  191. const selector = getFullCssSelector(element, includeNth);
  192.  
  193. selectorDisplay.textContent = selector;
  194.  
  195. // Position dialog near the click
  196. const x = event.clientX;
  197. const y = event.clientY;
  198.  
  199. dialog.style.left = `${Math.min(x + 20, window.innerWidth - 420)}px`;
  200. dialog.style.top = `${Math.min(y + 20, window.innerHeight - dialog.offsetHeight - 20)}px`;
  201. dialog.style.display = 'block';
  202. }
  203.  
  204. // Hide dialog
  205. function hideDialog() {
  206. dialog.style.display = 'none';
  207. }
  208.  
  209. // Handle copy button click
  210. copyButton.addEventListener('click', () => {
  211. GM_setClipboard(selectorDisplay.textContent, 'text');
  212. copyButton.textContent = 'Copied!';
  213. copyButton.classList.add('copied');
  214. setTimeout(() => {
  215. copyButton.textContent = 'Copy';
  216. copyButton.classList.remove('copied');
  217. }, 2000);
  218. });
  219.  
  220. // Handle close button click
  221. closeButton.addEventListener('click', hideDialog);
  222.  
  223. // Close dialog when clicking outside
  224. document.addEventListener('click', (e) => {
  225. if (dialog.style.display === 'block' && !dialog.contains(e.target)) {
  226. hideDialog();
  227. }
  228. });
  229.  
  230. // Close with Escape key
  231. document.addEventListener('keydown', (e) => {
  232. if (e.key === 'Escape' && dialog.style.display === 'block') {
  233. hideDialog();
  234. }
  235. });
  236.  
  237. // Handle Ctrl+Click or Alt+Click on elements
  238. document.addEventListener('click', (e) => {
  239. // Only activate with Ctrl+Click or Alt+Click to avoid conflict with normal clicks
  240. if (e.ctrlKey || e.altKey) {
  241. e.preventDefault();
  242. e.stopPropagation();
  243. showDialog(e.target, e);
  244. }
  245. }, true);
  246. })();