您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Make the interface easier to use
当前为
- // ==UserScript==
- // @name LinkedIn Job Search Usability Improvements
- // @namespace http://tampermonkey.net/
- // @version 0.2
- // @description Make the interface easier to use
- // @author Bryan Chan
- // @match http://www.linkedin.com/jobs/search/*
- // @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,
- "j": goToNext,
- "k": goToPrevious,
- "x": handleHidePost,
- "y": handleHideCompany,
- "?": handlePrintDebug,
- }
- window.addEventListener("keydown", function(e) {
- const handler = KEY_HANDLER[e.key]
- if(handler) handler();
- });
- /** Event handler functions */
- const FEEDBACK_DELAY = 300;
- // Handle a request to hide a post forever
- function handleHidePost() {
- const activeJob = getActive();
- const data = getCardData(activeJob);
- const postTitle = getPostNode(activeJob);
- postTitle.style.textDecoration = "line-through";
- setTimeout(() => {
- goToNext();
- 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
- const company = getCompanyNode(activeJob);
- company.style.textDecoration = "line-through";
- setTimeout(() => {
- // go to next post and hide the company
- goToNext();
- 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() {
- console.log("Updating display on jobs list");
- const start = +new Date();
- for(var job = jobsList.firstElementChild; job.nextSibling; job = job.nextSibling.nextSibling) {
- try {
- const data = getCardData(job);
- const jobDiv = job.firstElementChild;
- //console.log("Updating display on", data);
- 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 in", elapsed);
- }
- 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(isHidden(next.firstElementChild)) {
- next = next.nextSibling.nextSibling;
- }
- triggerClick(next.firstElementChild);
- } else {
- goToFirst();
- }
- }
- function goToPrevious() {
- const active = getActive();
- if(active) {
- var prev = active.previousSibling.previousSibling;
- while(isHidden(prev.firstElementChild)) {
- prev = prev.previousSibling.previousSibling;
- }
- 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) {
- const company = getCompanyNode(node);
- const companyUrl = company.getAttribute("href");
- const companyName = company.text.trim(" ");
- const post = getPostNode(node);
- const postUrl = post.getAttribute("href").split("/?")[0];
- const 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) {
- console.log("Mutation!");
- // Use traditional 'for loops' for IE 11
- for(let mutation of mutationsList) {
- const target = mutation.target;
- if (mutation.type === 'childList') {
- console.log('Children were modified on', target);
- 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);
- }());