您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
Make it easier to review and manage job search results, with faster keyboard shortcuts, read post tracking, and blacklists for companies and jobs
当前为
// ==UserScript== // @name LinkedIn Job Search Usability Improvements // @namespace http://tampermonkey.net/ // @version 0.2.3 // @description Make it easier to review and manage job search results, with faster keyboard shortcuts, read post tracking, and blacklists for companies and jobs // @author Bryan Chan // @match http://www.linkedin.com/jobs/search/* // @license GNU GPLv3 // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // ==/UserScript== (function() { 'use strict'; console.log("Starting LinkedIn Job Search Usability Improvements"); // Setup dictionaries to persist useful information across sessions class StoredDictionary { constructor(storageKey) { this.storageKey = storageKey; this.data = GM_getValue(storageKey) || {}; console.log("Initial data read from", this.storageKey, this.data); } get(key) { return this.data[key]; } set(key, value) { this.data[key] = value; GM_setValue(this.storageKey, this.data); console.log("Updated data", this.storageKey, this.data); } getDictionary() { return this.data; } } const hiddenCompanies = new StoredDictionary("hidden_companies"); const hiddenPosts = new StoredDictionary("hidden_posts"); const readPosts = new StoredDictionary("read_posts"); /** Install key handlers to allow for keyboard interactions */ const KEY_HANDLER = { "e": handleMarkRead, // mark the active post as read "j": goToNext, // open the next visible job post "k": goToPrevious, // open the previous visible job post "h": toggleHidden, // toggle showing the hidden posts "x": handleHidePost, // hide post forever "y": handleHideCompany, // hide company forever "?": handlePrintDebug, // print debug information to the console } window.addEventListener("keydown", function(e) { const handler = KEY_HANDLER[e.key] if(handler) handler(); }); /** Event handler functions */ const FEEDBACK_DELAY = 300; // Toggle whether to hide posts var showHidden = false; function toggleHidden() { showHidden = !showHidden; queueUpdate(); } // Handle a request to hide a post forever function handleHidePost() { const activeJob = getActive(); const data = getCardData(activeJob); // Show feedback activeJob.style.opacity = 0.6; const postTitle = getPostNode(activeJob); postTitle.style.textDecoration = "line-through"; const detailPostTitle = document.querySelector(".jobs-details-top-card__job-title"); detailPostTitle.style.textDecoration = "line-through"; // Wait a little and then hide post setTimeout(() => { goToNext(); detailPostTitle.style.textDecoration = "none"; hiddenPosts.set(data.postUrl, `${data.companyName}: ${data.postTitle}`); updateDisplay(); }, FEEDBACK_DELAY); } // Handle request to hide all posts from a company, forever function handleHideCompany() { const activeJob = getActive(); const data = getCardData(activeJob); // show feedback activeJob.style.opacity = 0.6; const company = getCompanyNode(activeJob); company.style.textDecoration = "line-through"; const detailCompany = document.querySelector(".jobs-details-top-card__company-url"); detailCompany.style.textDecoration = "line-through"; // Wait a little and then hide company setTimeout(() => { // go to next post and hide the company goToNext(); detailCompany.style.textDecoration = "none"; hiddenCompanies.set(data.companyUrl, data.companyName); updateDisplay(); }, FEEDBACK_DELAY); } // Handl request to mark a post as read ( function handleMarkRead() { // @TODO implement this in a useful way const activeJob = getActive(); const data = getCardData(activeJob); goToNext(); readPosts.set(data.postUrl, `${data.companyName}: ${data.postTitle}`); updateDisplay(); } // Handle requests to print debug information function handlePrintDebug() { console.log("Hidden companies"); console.log(hiddenCompanies.getDictionary()); console.log("Hidden posts"); console.log(hiddenPosts.getDictionary()); console.log("Read posts"); console.log(readPosts.getDictionary()); } /** Functions to adjust jobs list display, based on which companies, posts are hidden and which posts are read */ const jobsList = document.querySelector("ul.jobs-search-results__list"); var updateQueued = false; var updateTimer = null; function queueUpdate() { if(updateTimer) { clearTimeout(updateTimer); } updateTimer = setTimeout(function() { updateTimer = null; updateDisplay() }, 30); } function updateDisplay() { const start = +new Date(); for(var job = jobsList.firstElementChild; job.nextSibling; job = job.nextSibling.nextSibling) { try { const data = getCardData(job); const jobDiv = job.firstElementChild; if(showHidden) { jobDiv.classList.remove("hidden"); continue; } if(hiddenCompanies.get(data.companyUrl)) { jobDiv.classList.add("hidden"); } else if(hiddenPosts.get(data.postUrl)) { jobDiv.classList.add("hidden"); } else if(readPosts.get(data.postUrl)) { jobDiv.classList.add("read"); } } catch(e) { } } const elapsed = +new Date() - start; console.log("Updated display on jobs list in", elapsed, "ms"); } function triggerMouseEvent (node, eventType) { var clickEvent = document.createEvent ('MouseEvents'); clickEvent.initEvent (eventType, true, true); node.dispatchEvent (clickEvent); } /** Get active job card */ function getActive() { const active = document.querySelector(".job-card-search--is-active"); return active ? active.parentNode : undefined; } /** Select first card in the list */ function goToFirst() { const firstPost = jobsList.firstElementChild; const clickableDiv = firstPost.firstElementChild; triggerClick(clickableDiv); } function goToNext() { const active = getActive(); if(active) { var next = active.nextSibling.nextSibling; while(next.firstElementChild && isHidden(next.firstElementChild)) { next = next.nextSibling.nextSibling; } if(next.firstElementChild) { triggerClick(next.firstElementChild); } } else { goToFirst(); } } function goToPrevious() { const active = getActive(); if(active) { var prev = active.previousSibling.previousSibling; while(prev.firstElementChild && isHidden(prev.firstElementChild)) { prev = prev.previousSibling.previousSibling; } if(prev.firstElementChild) { triggerClick(prev.firstElementChild); } } else { goToFirst(); } } function triggerClick (node) { triggerMouseEvent (node, "mouseover"); triggerMouseEvent (node, "mousedown"); triggerMouseEvent (node, "mouseup"); triggerMouseEvent (node, "click"); } /** Check if a card is hidden */ function isHidden (node) { return node.classList.contains("jobs-search-results-feedback") || node.classList.contains("hidden"); } /** Extracts card data from a card */ function getCompanyNode (node) { return node.querySelector("a.job-card-search__company-name-link") } function getPostNode (node) { return node.querySelector(".job-card-search__title a.job-card-search__link-wrapper") } function getCardData (node) { var companyUrl, companyName, postUrl, postTitle; const company = getCompanyNode(node); if(company) { companyUrl = company.getAttribute("href"); companyName = company.text.trim(" "); } const post = getPostNode(node); if(post) { postUrl = post.getAttribute("href").split("/?")[0]; postTitle = post.text.replace("Promoted","").trim(" \n"); } return { companyUrl, companyName, postUrl, postTitle }; } GM_addStyle(".jobs-search-results-feedback { display: none }"); GM_addStyle(".hidden { display: none }"); GM_addStyle(".read { opacity: 0.3 }"); console.log("Adding mutation observer"); // Options for the observer (which mutations to observe) const config = { attributes: true, childList: true, subtree: true }; // Callback function to execute when mutations are observed const callback = function(mutationsList, observer) { // Use traditional 'for loops' for IE 11 for(let mutation of mutationsList) { const target = mutation.target; if (mutation.type === 'childList') { queueUpdate(); } else if (mutation.type === 'attributes') { //console.log('The ' + mutation.attributeName + ' attribute was modified.', target); } } }; // Create an observer instance linked to the callback function const observer = new MutationObserver(callback); // Start observing the target node for configured mutations console.log("Jobs List element", jobsList); observer.observe(jobsList, config); }());