Skip to content

Instantly share code, notes, and snippets.

@tomasr8
Last active February 14, 2023 12:41
Show Gist options
  • Save tomasr8/853e70a7b497fe85632d287c5386a05e to your computer and use it in GitHub Desktop.
Save tomasr8/853e70a7b497fe85632d287c5386a05e to your computer and use it in GitHub Desktop.
User script which lets you view candidate's resumes without having to open their profile. Saves you time and extra clicks.
// ==UserScript==
// @name Smart Recruiters
// @version 1
// @match https://www.smartrecruiters.com/app/jobs/details/*
// @grant none
// @run-at document-idle
// ==/UserScript==
const BASE_URL = "https://www.smartrecruiters.com"
const CANDIDATE_LIST = "sr-list-content ul.st-candidate-list"
// Set to 'true' to hide profile pictures
const HIDE_AVATARS = false
const css = `
.btn-view-resume {
position: relative;
padding: 6px 8px;
margin-top: -20px;
font-size: 13px;
}
.btn-view-resume > span {
font-size: 13px;
text-transform: initial;
}
.btn-view-resume.loading > span {
visibility: hidden;
}
/*
* https://dev.to/dcodeyt/create-a-button-with-a-loading-spinner-in-html-css-1c0h
*/
.btn-view-resume.loading::after {
content: "";
position: absolute;
width: 16px;
height: 16px;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
border: 4px solid transparent;
border-top-color: #ffffff;
border-radius: 50%;
animation: btn-loading-spinner 1s ease infinite;
}
.dimmer-resume {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: 100;
background-color: rgba(0, 0, 0, 0.8);
}
.dimmer-resume embed {
display: block;
margin: 0 auto;
height: 100vh;
width: min(100%, 1000px);
}
.hidden-overflow {
overflow: hidden !important;
}
@keyframes btn-loading-spinner {
from {
transform: rotate(0turn);
}
to {
transform: rotate(1turn);
}
}
`
main()
async function main() {
injectStylesheet(css)
await waitForSelector(CANDIDATE_LIST)
const obs = new MutationObserver(() => {
addResumeButtons()
if (HIDE_AVATARS) {
removeAvatars()
}
})
obs.observe(document.querySelector(CANDIDATE_LIST), {
childList: true,
subtree: true,
})
addResumeButtons()
if (HIDE_AVATARS) {
removeAvatars()
}
}
function removeAvatars() {
;[...document.querySelectorAll("sr-candidate-avatar")].forEach(node => node.remove())
}
function addResumeButtons() {
const candidates = [...document.querySelectorAll(`${CANDIDATE_LIST} li.st-candidate-item`)]
if (candidates.length === 0) {
return
}
candidates.forEach(node => {
const alreadyInjected = !!node.querySelector('button[data-injected="1"]')
if (alreadyInjected) {
return
}
const href = node.querySelector("h3 a.st-candidate-name").href
const id = getApplicationId(href)
const button = createButton(id)
node.appendChild(button)
})
}
function createButton(id) {
const button = document.createElement("button")
button.dataset.injected = 1
button.dataset.id = id
button.addEventListener("click", async e => {
e.stopPropagation()
button.classList.add("loading")
const response = await fetch(`${BASE_URL}/app/people/api/attachments/applications/${id}`)
if (!response.ok) {
button.classList.remove("loading")
return
}
const attachments = await response.json()
const cv = attachments.find(att => att.type === "RESUME")
if (!cv) {
button.innerHTML = "<span>No resume found - check manually</span>"
button.disabled = true
button.classList.remove("loading")
return
}
const dimmer = createEmbeddedPdf(cv.id)
document.body.appendChild(dimmer)
button.classList.remove("loading")
})
button.innerHTML = "<span>Resume</span>"
// Styles from the SR website
button.classList.add("topbar-button", "topbar-button--primary")
button.classList.add("btn-view-resume")
return button
}
function createEmbeddedPdf(id) {
const dimmer = document.createElement("div")
const src = `${BASE_URL}/app/people/api/attachments/${id}`
dimmer.innerHTML = `<embed src="${src}"></embed>`
dimmer.classList.add("dimmer-resume")
dimmer.addEventListener("click", () => {
document.body.classList.remove("hidden-overflow")
dimmer.remove()
})
document.onkeydown = e => {
if (e.key === "Escape") {
document.body.classList.remove("hidden-overflow")
dimmer.remove()
}
}
document.body.classList.add("hidden-overflow") // Disable vertical scroll while dimmer is active
return dimmer
}
function getApplicationId(href) {
href = href.replace(`${BASE_URL}/app/people/applications/`, "").replace(/\?.*/g, "").split("/")
return href[0]
}
function injectStylesheet(cssString) {
const head = document.getElementsByTagName("head")[0]
const elem = document.createElement("style")
elem.innerHTML = cssString
head.appendChild(elem)
}
// https://stackoverflow.com/a/61511955
function waitForSelector(selector) {
return new Promise(resolve => {
if (document.querySelector(selector)) {
return resolve(document.querySelector(selector))
}
const observer = new MutationObserver(mutations => {
if (document.querySelector(selector)) {
resolve(document.querySelector(selector))
observer.disconnect()
}
})
observer.observe(document.body, {
childList: true,
subtree: true,
})
})
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment