LinkedIn Job Visibility Settings; year, job status highlight

11/13/2024, 5:53:40 PM

  1. // ==UserScript==
  2. // @name LinkedIn Job Visibility Settings; year, job status highlight
  3. // @namespace Violentmonkey Scripts
  4. // @match https://www.linkedin.com/jobs/search/*
  5. // @match https://www.glassdoor.ca/Job/*
  6. // @match https://ca.indeed.com/*
  7. // @grant GM_getValue
  8. // @grant GM_setValue
  9. // @version 1.0
  10. // @author LAMSTREAM
  11. // @description 11/13/2024, 5:53:40 PM
  12. // ==/UserScript==
  13.  
  14. (function() {
  15. 'use strict';
  16.  
  17. // Define colors for each status
  18. const statusColors = { viewed: 'red',
  19. applied: 'green',
  20. promoted: 'purple',
  21. year: 'red' };
  22.  
  23. // Initialize settings with stored values or defaults
  24. const settings = {
  25. hideViewed: GM_getValue('hideViewed', true),
  26. hideApplied: GM_getValue('hideApplied', true),
  27. hidePromoted: GM_getValue('hidePromoted', true),
  28. panelPosition: GM_getValue('panelPosition', { x: 20, y: 20 }),
  29. isCollapsed: GM_getValue('isCollapsed', false)
  30. };
  31.  
  32. // Create and style the settings panel
  33. function createSettingsPanel() {
  34. const panel = document.createElement('div');
  35. panel.style.cssText = `
  36. position: fixed;
  37. top: ${settings.panelPosition.y}px;
  38. left: ${settings.panelPosition.x}px;
  39. background: white;
  40. padding: 15px;
  41. border-radius: 8px;
  42. box-shadow: 0 2px 10px rgba(0,0,0,0.1);
  43. z-index: 9999;
  44. font-family: system-ui, -apple-system, sans-serif;
  45. cursor: move;
  46. min-width: 200px;
  47. user-select: none;
  48. `;
  49.  
  50. // Header container
  51. const header = document.createElement('div');
  52. header.style.cssText = `
  53. display: flex;
  54. justify-content: space-between;
  55. align-items: center;
  56. margin-bottom: 10px;
  57. cursor: move;
  58. `;
  59.  
  60. const title = document.createElement('h3');
  61. title.textContent = 'Job Visibility Settings';
  62. title.style.margin = '0';
  63. title.style.flex = '1';
  64.  
  65. // Collapse/Expand button
  66. const collapseBtn = document.createElement('button');
  67. collapseBtn.style.cssText = `
  68. background: none;
  69. border: none;
  70. cursor: pointer;
  71. font-size: 20px;
  72. padding: 0 5px;
  73. margin-left: 10px;
  74. `;
  75. collapseBtn.textContent = settings.isCollapsed ? '▼' : '▲';
  76. collapseBtn.title = settings.isCollapsed ? 'Expand' : 'Collapse';
  77.  
  78. header.appendChild(title);
  79. header.appendChild(collapseBtn);
  80. panel.appendChild(header);
  81.  
  82. // Content container
  83. const content = document.createElement('div');
  84. content.style.display = settings.isCollapsed ? 'none' : 'block';
  85.  
  86. // Create toggle switches for each setting
  87. const options = [
  88. { key: 'hideViewed', label: 'Hide Viewed', color: statusColors.viewed },
  89. { key: 'hideApplied', label: 'Hide Applied', color: statusColors.applied },
  90. { key: 'hidePromoted', label: 'Hide Promoted', color: statusColors.promoted }
  91. ];
  92.  
  93. options.forEach(({ key, label, color }) => {
  94. const container = document.createElement('div');
  95. container.style.cssText = `
  96. margin: 10px 0;
  97. padding: 8px;
  98. border-radius: 4px;
  99. background: ${color}15;
  100. border: 1px solid ${color}40;
  101. `;
  102.  
  103. const toggle = document.createElement('input');
  104. toggle.type = 'checkbox';
  105. toggle.id = key;
  106. toggle.checked = settings[key];
  107. toggle.style.marginRight = '8px';
  108.  
  109. const labelElement = document.createElement('label');
  110. labelElement.htmlFor = key;
  111. labelElement.textContent = label;
  112. labelElement.style.color = color;
  113. labelElement.style.fontWeight = 'bold';
  114.  
  115. toggle.addEventListener('change', (e) => {
  116. settings[key] = e.target.checked;
  117. GM_setValue(key, e.target.checked);
  118. hideListItems();
  119. });
  120.  
  121. container.appendChild(toggle);
  122. container.appendChild(labelElement);
  123. content.appendChild(container);
  124. });
  125.  
  126. panel.appendChild(content);
  127.  
  128. // Make panel draggable
  129. let isDragging = false;
  130. let currentX;
  131. let currentY;
  132. let initialX;
  133. let initialY;
  134.  
  135. header.addEventListener('mousedown', dragStart);
  136. document.addEventListener('mousemove', drag);
  137. document.addEventListener('mouseup', dragEnd);
  138.  
  139. function dragStart(e) {
  140. initialX = e.clientX - settings.panelPosition.x;
  141. initialY = e.clientY - settings.panelPosition.y;
  142. isDragging = true;
  143. }
  144.  
  145. function drag(e) {
  146. if (!isDragging) return;
  147.  
  148. e.preventDefault();
  149. currentX = e.clientX - initialX;
  150. currentY = e.clientY - initialY;
  151.  
  152. // Update position
  153. settings.panelPosition = { x: currentX, y: currentY };
  154. panel.style.left = `${currentX}px`;
  155. panel.style.top = `${currentY}px`;
  156. }
  157.  
  158. function dragEnd() {
  159. if (!isDragging) return;
  160.  
  161. isDragging = false;
  162. GM_setValue('panelPosition', settings.panelPosition);
  163. }
  164.  
  165. // Handle collapse/expand
  166. collapseBtn.addEventListener('click', () => {
  167. settings.isCollapsed = !settings.isCollapsed;
  168. content.style.display = settings.isCollapsed ? 'none' : 'block';
  169. collapseBtn.textContent = settings.isCollapsed ? '▼' : '▲';
  170. collapseBtn.title = settings.isCollapsed ? 'Expand' : 'Collapse';
  171. GM_setValue('isCollapsed', settings.isCollapsed);
  172. });
  173.  
  174. document.body.appendChild(panel);
  175. }
  176.  
  177. // Function to hide li elements and color the status text
  178. const hideListItems = () => {
  179. const elements = document.querySelectorAll('.job-card-container__footer-item');
  180. elements.forEach(element => {
  181. const text = element.innerText;
  182.  
  183. // Color the status text based on type
  184. if (text.includes('Viewed')) {
  185. element.style.color = statusColors.viewed;
  186. if (settings.hideViewed) hideFromAncestor(element);
  187. }
  188. else if (text.includes('Applied')) {
  189. element.style.color = statusColors.applied;
  190. if (settings.hideApplied) hideFromAncestor(element);
  191. }
  192. else if (text.includes('Promoted')) {
  193. element.style.color = statusColors.promoted;
  194. if (settings.hidePromoted) hideFromAncestor(element);
  195. }
  196. });
  197. };
  198.  
  199. // Function to hide the ancestor li element
  200. const hideFromAncestor = (element) => {
  201. let liElement = element.parentElement.closest('li');
  202. if (liElement) {
  203. liElement.style.display = 'none';
  204. }
  205. };
  206.  
  207. // Function to highlight the word "year" in text content
  208. const highlightYear = () => {
  209. const elements = [...document.querySelectorAll('.jobs-search__job-details--wrapper'), ...document.querySelectorAll('[class^="JobDetails_jobDescription"]'), ...document.querySelectorAll('#jobDescriptionText') ]
  210. observer.disconnect()
  211. elements.forEach(element => {
  212. // Get all text nodes within the element, including nested ones
  213. const walk = document.createTreeWalker(
  214. element,
  215. NodeFilter.SHOW_TEXT,
  216. null,
  217. false
  218. );
  219.  
  220. const textNodes = [];
  221. let node;
  222. while (node = walk.nextNode()) {
  223. textNodes.push(node);
  224. }
  225.  
  226. // Process each text node
  227. textNodes.forEach(textNode => {
  228. const text = textNode.textContent;
  229. if (/(year|years)/i.test(text)) {
  230. const span = document.createElement('span');
  231. span.innerHTML = text.replace(/(years|year)/gi, '<span style="color: red">$1</span>');
  232. textNode.parentNode.replaceChild(span, textNode);
  233. }
  234. });
  235. });
  236.  
  237. // Observe changes in the DOM
  238. observer.observe(document.body, {
  239. childList: true,
  240. subtree: true,
  241. characterData: true
  242. });
  243. };
  244.  
  245. const highlightCurrentTab = () => {
  246. observer.disconnect(); // Temporarily stop observing mutations
  247.  
  248. const items = document.querySelectorAll('[class*="JobsList_jobListItem"]'); // Get all job list items
  249.  
  250. items.forEach(item => {
  251. const classList = item.className; // Get full class name as string
  252. if (classList.includes("JobsList_selected")) {
  253. item.classList.add("highlight"); // Add highlight if selected
  254. } else {
  255. item.classList.remove("highlight"); // Remove highlight if not selected
  256. }
  257. });
  258.  
  259. observer.observe(document.body, { childList: true, subtree: true }); // Resume observing
  260. };
  261.  
  262.  
  263.  
  264. // Observe changes in the DOM to apply the filtering dynamically
  265. const observer = new MutationObserver((mutations) => {
  266. hideListItems();
  267. highlightYear();
  268. highlightCurrentTab();
  269. });
  270.  
  271. // Observe changes in the DOM
  272. observer.observe(document.body, {
  273. childList: true,
  274. subtree: true,
  275. characterData: true
  276. });
  277.  
  278. // Initial setup
  279. createSettingsPanel();
  280. hideListItems();
  281. highlightYear();
  282. highlightCurrentTab()
  283.  
  284. setTimeout(() => {
  285. const style = document.createElement("style");
  286. style.textContent = `
  287. body .highlight {
  288. position: relative !important;
  289. }
  290. body .highlight::before {
  291. content: "";
  292. position: absolute;
  293. top: 0;
  294. left: 0;
  295. width: 100%;
  296. height: 100%;
  297. background-color: rgba(144, 238, 144, 0.5) !important;
  298. pointer-events: none;
  299. z-index: 1000;
  300. }
  301. `;
  302. document.head.appendChild(style);
  303. }, 100);
  304.  
  305. })();