Skip to content

Instantly share code, notes, and snippets.

@torbiak
Last active November 28, 2024 19:30
Show Gist options
  • Save torbiak/6343a9e9ceff64eff9c5384b31005dfd to your computer and use it in GitHub Desktop.
Save torbiak/6343a9e9ceff64eff9c5384b31005dfd to your computer and use it in GitHub Desktop.
Track viewed jobs on LinkedIn
// 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