Last active
November 28, 2024 19:30
-
-
Save torbiak/6343a9e9ceff64eff9c5384b31005dfd to your computer and use it in GitHub Desktop.
Track viewed jobs on LinkedIn
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Add "Mark Viewed" buttons to job cards on LinkedIn, to save | |
// "<company>__<title>" in local storage and mark job cards that match with a | |
// different background color. LinkedIn does apply a little "Viewed" badge to | |
// viewed jobs, but it doesn't stick across job reposts, which happen | |
// frequently, and the badge isn't nearly visible enough. | |
// | |
// LinkedIn seems to change its HTML pretty frequently, so you'll probably need | |
// to update the CSS queries to make this work. | |
// | |
// You could put this in a bookmarklet, but I've just been pasting it into | |
// my browser's dev tools console. | |
JobTracker = class { | |
constructor() { | |
// Key for localStorage to store viewed jobs | |
this.VIEWED_JOBS_KEY = 'linkedin_viewed_jobs'; | |
this.BG_COLOR = 'lightcoral'; | |
// Retrieve existing viewed jobs or initialize empty map | |
this.viewedJobs = JSON.parse(localStorage.getItem(this.VIEWED_JOBS_KEY) || '{}'); | |
// Modify current job cards. | |
this.applyViewedJobStyling(); | |
this.addViewedButtons(); | |
// Observe DOM changes to handle dynamically loaded job cards | |
const observer = new MutationObserver(() => { | |
this.applyViewedJobStyling(); | |
this.addViewedButtons(); | |
}); | |
const jobsContainer = document.querySelector('.scaffold-layout__list > div > ul'); | |
observer.observe(jobsContainer, { | |
childList: true, | |
subtree: true | |
}); | |
} | |
// Apply styling to viewed jobs | |
applyViewedJobStyling() { | |
this.jobCards().forEach(jobCard => { | |
if (this.isJobViewed(jobCard)) { | |
jobCard.style.backgroundColor = this.BG_COLOR; | |
} | |
}); | |
} | |
jobCards() { | |
return document.querySelectorAll('.job-card-container'); | |
} | |
// Check if a job has been viewed | |
isJobViewed(jobCard) { | |
const jobKey = this.generateJobKey(jobCard); | |
return jobKey in this.viewedJobs; | |
} | |
// Generate a unique key for a job based on company and title | |
generateJobKey(jobCard) { | |
const companyName = jobCard.querySelector('.artdeco-entity-lockup__subtitle').textContent.trim() | |
.replace(/ [^ ] .*/, "");; | |
const jobTitle = jobCard.querySelector('.artdeco-entity-lockup__title strong').textContent.trim(); | |
return `${companyName}__${jobTitle}`; | |
} | |
// Add "Viewed" button to job cards | |
addViewedButtons() { | |
this.jobCards().forEach(jobCard => { | |
let button = jobCard.querySelector('.job-viewed-btn'); | |
if (!button) { | |
button = document.createElement('button'); | |
button.textContent = 'Mark Viewed'; | |
button.classList.add('job-viewed-btn'); | |
button.style.cssText = ` | |
position: absolute; | |
bottom: 10px; | |
right: 10px; | |
z-index: 100; | |
background-color: green; | |
color: white; | |
border: none; | |
padding: 5px 10px; | |
border-radius: 4px; | |
`; | |
jobCard.appendChild(button); | |
} | |
button.addEventListener('click', () => { | |
console.log(this.generateJobKey(jobCard)); | |
this.markJobAsViewed(jobCard); | |
}); | |
}); | |
} | |
// Mark a job as viewed | |
markJobAsViewed(jobCard) { | |
const jobKey = this.generateJobKey(jobCard); | |
this.viewedJobs[jobKey] = Date.now(); // epoch seconds | |
localStorage.setItem(this.VIEWED_JOBS_KEY, JSON.stringify(this.viewedJobs)); | |
// Add visual indicator | |
jobCard.style.backgroundColor = this.BG_COLOR; | |
} | |
} | |
jobTracker = new JobTracker(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment