Skip to content

Instantly share code, notes, and snippets.

@lwcorp
Last active June 4, 2026 11:35
Show Gist options
  • Select an option

  • Save lwcorp/81bbb6d5ce80b5f403d68fb38eb8d40d to your computer and use it in GitHub Desktop.

Select an option

Save lwcorp/81bbb6d5ce80b5f403d68fb38eb8d40d to your computer and use it in GitHub Desktop.
Bookmarklet - Enhance various LinkedIn pages: 1) highlight only relevant notifications 2) Fix auto LTR 3) Reveal timestamps 4) Preview posts 5) Filer posts 6) expose job applies/views
javascript:function init(){if("linkedin.com"===location.hostname.replace(/^www\./,""))if(location.pathname.startsWith("/notifications/"))loadAll().then(()=>{grabText("section button:last-of-type","section:nth-of-type(2)>div>div>div>div",'a[href*="/update/')});else if(location.pathname.startsWith("/jobs/view/")||location.search.startsWith("?currentJobId="))initLinkedInJobStats();else{if(!(location.pathname.startsWith("/posts/")||location.pathname.startsWith("/feed/update/")||"/feed/"==location.pathname||"/"==location.pathname||location.pathname.endsWith("/recent-activity/all/")))return void alert("Unsupported page");initDirToggle(),revealLinkedInTimestamps(),previewLinkedinPrepare(),filterPostsPre()}else alert("This script only works on LinkedIn")}async function loadAll(){const e=(()=>{let e=0,t=document.documentElement;const n=document.documentElement;if(n.scrollHeight>n.clientHeight){const t=window.getComputedStyle(n).overflowY,o=window.getComputedStyle(document.body).overflowY;"hidden"!==t&&"hidden"!==o&&(e=n.scrollHeight)}const o=document.body.querySelectorAll("*");for(let n=0;n<o.length;n++){const r=o[n];if(r.clientHeight>0&&r.scrollHeight>r.clientHeight){const n=window.getComputedStyle(r);"auto"!==n.overflowY&&"scroll"!==n.overflowY&&"overlay"!==n.overflowY||r.scrollHeight>e&&(e=r.scrollHeight,t=r)}}return t})(),t=e===document.documentElement||e===document.body;let n;const o=Date.now();for(;;){if(Date.now()-o>1e4){alert("Timeout reached, proceeding with available content");break}if(n=e.scrollHeight,t?window.scrollTo(0,n):e.scrollTo(0,n),await new Promise(e=>setTimeout(e,2e3)),n===e.scrollHeight)break}t?window.scrollTo(0,0):e.scrollTo(0,0),await new Promise(e=>setTimeout(e,1))}function grabText(e,t,n){let o=document.getElementById("highlighterParent");if(!o){o=document.createElement("span"),o.id="highlighterParent";const r=document.createElement("style");r.id="highlightSpecialStyles",r.textContent=".highlightSpecial { background-color: Lavender; } @media (prefers-color-scheme: dark) { .highlightSpecial { background-color: DarkSlateGray; } }",o.appendChild(r),highlighterElement=document.createElement("select"),highlighterElement.id="highlighter",highlighterElement.onchange=()=>stylize(highlighterElement,t,n);[{value:"remove",text:"Keep only what%27s relevant"},{value:"highlight",text:"Highlight only what%27s relevant"},{value:"reset",text:"Reset"},{value:"close",text:"Close this box"}].forEach(e=>{const t=document.createElement("option");t.value=e.value,t.textContent=e.text,highlighterElement.appendChild(t)}),o.appendChild(highlighterElement),document.querySelector(e).after(o),highlighterElement.onchange()}}function stylize(e,t,n){const o=["replied","mentioned you in a","commented"];let r=e.value;"close"==r&&(r="reset");document.querySelectorAll(t).forEach(e=>{const t=e.querySelector(n);let i=!1;if(t){const n=t.textContent.toLowerCase();o.forEach(t=>{if(n.includes(t))switch(i=!0,r){case"highlight":case"remove":e.classList.add("highlightSpecial");break;case"reset":e.classList.remove("highlightSpecial"),e.style.display=""}})}if(!i)switch(r){case"highlight":case"reset":e.style.display="";break;case"remove":e.style.display="none"}}),"close"==e.value&&document.getElementById(e.id).remove()}function log(...e){{const t="[JobStatsExt]";console.log(t,...e)}}function getCsrfToken(){const e=document.cookie.match(/JSESSIONID=(?:"([^"]+)"|([^;]+))/),t=e?e[1]||e[2]:"";return log("CSRF token:",t||"(none found)"),t}async function fetchStats(e,t){log("fetchStats called for jobId:",e);try{const n=await fetch(`/voyager/api/jobs/jobPostings/${e}`,{credentials:"include",headers:{accept:"application/json","csrf-token":t,"x-restli-protocol-version":"2.0.0"}});log("Fetch response status:",n.status);const o=await n.json();log("Raw JSON data:",o);const r=o.applies??o.data?.applies,i=o.views??o.data?.views;return log("Parsed applies/views:",r,i),"number"!=typeof r||"number"!=typeof i?(log("Stats are not numbers, aborting"),null):{applies:r,views:i}}catch(e){return log("Error fetching job stats:",e),null}}async function injectStats(){log("injectStats triggered, current URL path:",location.pathname);const e=location.pathname.match(/^\/jobs\/view\/(\d+)/)||location.search.match(/currentJobId=(\d+)/);if(!e)return void log("Not on a job view page, skipping");const t=e[1];log("Detected jobId:",t),await new Promise(e=>setTimeout(e,1));const n=document.querySelector("p > span:last-child");if(!n)return void log("No title element found");if(n.dataset.statsInjected)return void log("Stats already injected, skipping");log("Title element found:",n),location.search.match(/currentJobId=(\d+)/)||(n.dataset.statsInjected="1");const o=getCsrfToken(),r=await fetchStats(t,o);if(!r)return void log("No stats returned, aborting injection");let{applies:i,views:l}=r;log("Injecting badge with applies/views:",i,l);let a=document.getElementById("badge_container");a&&(log("Removing existing badge"),a.remove()),a=document.createElement("span"),a.id="badge_container";const c=document.createElement("span");let s=l>0?Math.round(i/l*100):0;0==i&&0==l&&(i=l=s="N/A"),c.textContent=`${i} applies (${s}%) โ€ข ${l} views`,c.style.marginLeft="0.5em",c.style.fontSize="0.9em",c.style.color="#666",n.insertAdjacentElement("afterend",a),a.appendChild(c);const d=document.createElement("input");d.type="checkbox",d.id="monitor_stats",d.checked=!0,d.onclick=()=>initLinkedInJobStats();const u=document.createElement("label");u.htmlFor="monitor_stats",u.textContent="Monitor stats",u.style.marginLeft="0.5em",u.style.display="inline",c.insertAdjacentElement("afterend",d),d.insertAdjacentElement("afterend",u),log("Badge injected successfully")}function initLinkedInJobStats(){const e=document.getElementById("monitor_stats");if(e&&!e.checked)return void(window.statsObserver&&(window.statsObserver.disconnect(),log("Observer disconnected due to unchecked checkbox")));e||injectStats(),window.statsObserver&&window.statsObserver.disconnect();let t=location.pathname+location.search;(window.statsObserver=new MutationObserver(()=>{const e=location.pathname+location.search;e!==t&&(t=e,log("URL or search changed, re-running injectStats"),injectStats())})).observe(document.body,{childList:!0,subtree:!0})}function initDirToggle(){if(!document.getElementById("hebrew-ltr-styles")){const e=document.createElement("style");e.id="hebrew-ltr-styles",e.textContent="\n :root { color-scheme: light dark; } \n .hebrew-ltr { \n cursor: crosshair !important;\n transition: background-color 0.2s;\n }\n .hebrew-ltr:not(:hover) {\n background-color: light-dark(sandybrown, darkred); \n }\n ",document.head.appendChild(e)}window._dirToggleObserver&&window._dirToggleObserver.disconnect();const e=e=>{e.currentTarget.setAttribute("dir","rtl"),e.currentTarget.firstElementChild.setAttribute("dir","rtl")},t=e=>{e.currentTarget.setAttribute("dir","ltr"),e.currentTarget.firstElementChild.setAttribute("dir","ltr")},n=()=>{const n=/[\u0590-\u05FF]/;let o='p[componentkey*="-commentary-"]';if(document.querySelectorAll(o).length>0)o=document.querySelectorAll(%60${o}:not([dir="auto"]):not(.hebrew-ltr)%60),o.forEach(o=>{o.querySelector("span")&&n.test(o.textContent)&&(o.classList.add("hebrew-ltr"),o.addEventListener("mouseenter",e),o.addEventListener("mouseleave",t))});else{document.querySelectorAll('div[dir="ltr"]:not(.hebrew-ltr)').forEach(o=>{const r=o.querySelectorAll('span[dir="ltr"]');Array.from(r).some(e=>n.test(e.textContent))&&(o.classList.add("hebrew-ltr"),o.addEventListener("mouseenter",e),o.addEventListener("mouseleave",t))})}};let o;n(),window._dirToggleObserver=new MutationObserver(()=>{clearTimeout(o),o=setTimeout(n,500)}),window._dirToggleObserver.observe(document.body,{childList:!0,subtree:!0})}function revealLinkedInTimestamps(e={}){window._linkedinDateObserver&&window._linkedinDateObserver.disconnect();const t=e.dateFormat||"iso",n=!1!==e.showTime,o=!1!==e.use24HourTime,r={postElements:'div[data-id*="urn:li:activity"], div[data-urn*="urn:li:activity"], a[href*="urn:li:activity"], .feed-shared-update-detail-viewer__right-panel',relativeTime:'.update-components-actor__sub-description span[aria-hidden="true"]',comments:"article.comments-comment-entity",commentTime:"time.comments-comment-meta__data",reposts:".update-components-mini-update-v2, .feed-shared-mini-update-v2, .feed-shared-update-v2__update-content-wrapper"},i=Object.entries(r).filter(([e,t])=>!document.querySelector(t));function l(e){if(!e)return null;try{const t=String(e).replace(/[^0-9]/g,"");if(!t)return null;const n=BigInt(t)>>22n;return Number(n.toString())}catch(e){return null}}function a(e,r){if(!e||!r)return;if(e.getAttribute("data-linkedin-date-revealed"))return;let i=e.getAttribute("data-original-text");i||(i=e.textContent.trim(),e.setAttribute("data-original-text",i));const l=%60${function(e){const r=new Date(e),i=navigator.language||"en-US";let l,a;if(l="iso"===t?r.toISOString().slice(0,10):new Intl.DateTimeFormat(i,{year:"2-digit",month:"2-digit",day:"2-digit"}).format(r),!n)return l;const c={hour:"2-digit",minute:"2-digit",hour12:!o};return a="iso"===t?r.toLocaleTimeString([],{hour:"2-digit",minute:"2-digit",hour12:!1}):r.toLocaleTimeString(i,c),%60${l}, ${a} ${r.toLocaleTimeString("en-us",{timeZoneName:"short"}).split(" ").pop()}%60}(r)} โ€ข ${i}%60;Array.from(e.childNodes).forEach(t=>{3===t.nodeType&&e.removeChild(t)}),e.insertBefore(document.createTextNode(l),e.firstChild),e.setAttribute("data-linkedin-date-revealed","true")}i.length>0&&(console.group("[LinkedInHighlighter] Selector Check"),i.forEach(([e,t])=>{console.warn(%60Missing selectors for ${e}: "${t}"%60)}),console.groupEnd());const c=()=>{try{document.querySelectorAll(r.postElements).forEach(e=>{let t=null;const n=e.getAttribute("data-id")||e.getAttribute("data-urn");if(n&&(t=n.split(":").pop().replace(/\D/g,"")),!t){const n=e.querySelector('a[href*="urn:li:activity"]');if(n&&n.href){const e=n.href.match(/urn:li:activity:(\d+)/);e&&(t=e[1])}}if(!t&&window.location.href.includes("/feed/update/urn:li:activity:")){const e=window.location.href.match(/urn:li:activity:(\d+)/);e&&(t=e[1])}if(!t)return;a(e.querySelector(r.relativeTime),l(t))}),document.querySelectorAll(r.reposts).forEach(e=>{const t=e.querySelector(r.relativeTime);if(!t)return;let n=null;const o=e.querySelector('a[href*="urn:li:activity"], a[href*="/feed/update/"]');if(o&&o.href){const e=o.href.match(/urn:li:activity:([0-9]+)/);e&&(n=e[1])}if(!n){const t=e.closest("[data-id], [data-urn]");t&&(n=t.getAttribute("data-id")||t.getAttribute("data-urn"))}if(n){a(t,l(n.split(":").pop().replace(/\D/g,"")))}}),document.querySelectorAll(r.comments).forEach(e=>{const t=e.getAttribute("data-id");if(!t)return;const n=t.match(/(\d+)[^\d]*$/);if(n){a(e.querySelector(r.commentTime),l(n[1]))}})}catch(e){}};let s;c(),window._linkedinDateObserver=new MutationObserver(()=>{clearTimeout(s),s=setTimeout(c,500)}),window._linkedinDateObserver.observe(document.body,{childList:!0,subtree:!0})}function previewLinkedinPrepare(){let e=document.getElementById("grab-wrapper");e&&e.remove(),e=document.createElement("div"),e.id="grab-wrapper",e.setAttribute("style","margin-left: 30% !important; position: relative; z-index: 9999; display: flex; align-items: center; gap: 8px;");const t=document.createElement("button");t.textContent="๐Ÿš€ Preview Tool",t.setAttribute("style","padding: 4px 12px; cursor: pointer; border-radius: 16px; border: 1px solid #0a66c2; background: white; color: #0a66c2; font-weight: 600;");const n=document.createElement("button");n.textContent="โœ•",n.setAttribute("style","background: rgba(0,0,0,0.3); border: 1px solid rgba(255,255,255,0.2); border-radius: 50%; width: 24px; height: 24px; cursor: pointer; font-weight: bold; color: white; font-size: 14px; display: flex; align-items: center; justify-content: center; text-shadow: 0px 0px 3px black; transition: background 0.2s;"),n.onmouseover=()=>n.style.background="rgba(255, 0, 0, 0.6)",n.onmouseout=()=>n.style.background="rgba(0,0,0,0.3)",n.onclick=()=>e.remove();const o=document.createElement("div");o.id="grab-content",o.style.display="none",o.style.flexDirection="row",o.style.alignItems="center",o.style.gap="8px";const r=document.createElement("textarea");r.placeholder="Paste potential post here...",r.style.cssText="height: 32px; width: 150px; border-radius: 4px; border: 1px solid #ccc; font-size: 12px; padding: 4px; background: white; color: black;";const i=document.createElement("button");i.textContent="Replace all posts/comments",i.style.cssText="background: #0a66c2; color: white; border: none; border-radius: 4px; padding: 4px 12px; cursor: pointer; font-size: 12px; height: 32px; font-weight: bold;",i.onclick=()=>previewLinkedinLaunch(r),t.onclick=()=>{const e="none"===o.style.display;o.style.display=e?"flex":"none",t.textContent=e?"Collapse":"๐Ÿš€ Preview Tool"},o.append(r,i),e.append(t,o,n);const l=document.querySelector("#global-nav");l?l.appendChild(e):document.body.prepend(e)}function previewLinkedinLaunch(e){let t='p[componentkey*="-commentary-"]',n=!1;document.querySelectorAll(t).length>0&&(n=!0);const o=document.querySelectorAll(n?%60${t}>span%60:"div>*>span[dir]");if(!e||0===o.length)return e||console.warn("Preview Source not found. Make sure the LinkedIn post modal is open."),void(0===o.length&&console.warn("Preview Target not found. Make sure the LinkedIn post modal is open."));const r=e.value.trim(),i=n?r:r.split("\n").join("<span><br></span>");o.forEach(e=>{n&&e.firstChild&&e.firstChild.nodeType===Node.TEXT_NODE?e.firstChild.textContent=i:e.innerHTML=i})}function filterPostsPre(){let e=document.getElementById("filter-wrapper");e&&e.remove(),e=document.createElement("div"),e.id="filter-wrapper",e.setAttribute("style","margin-left: 30% !important; position: relative; z-index: 9999; display: flex; align-items: center; gap: 8px;");const t=document.createElement("button");t.textContent="๐Ÿ” Filter by comments",t.setAttribute("style","padding: 4px 12px; cursor: pointer; border-radius: 16px; border: 1px solid #0a66c2; background: white; color: #0a66c2; font-weight: 600;");const n=document.createElement("button");n.textContent="โœ•",n.setAttribute("style","background: rgba(0,0,0,0.3); border: 1px solid rgba(255,255,255,0.2); border-radius: 50%; width: 24px; height: 24px; cursor: pointer; font-weight: bold; color: white; font-size: 14px; display: flex; align-items: center; justify-content: center; text-shadow: 0px 0px 3px black; transition: background 0.2s;"),n.onmouseover=()=>n.style.background="rgba(255, 0, 0, 0.6)",n.onmouseout=()=>n.style.background="rgba(0,0,0,0.3)",n.onclick=()=>{filterPostsRun("reset"),e.remove()};const o=document.createElement("div");o.id="filter-content",o.style.cssText="display: none; flex-direction: row; align-items: center; gap: 8px;";const r=document.createElement("select");[">",">=","=","<","<="].forEach(e=>{const t=document.createElement("option");t.value=e,t.textContent=e,r.appendChild(t)});const i=document.createElement("input");i.type="number",i.min="1",i.required=!0,i.value="10",i.style.cssText="width: 60px;";const l=document.createElement("input");l.type="button",l.value="Filter",l.style.cssText="background: #0a66c2; color: white; padding: 4px 12px; cursor: pointer; font-weight: bold;",l.onclick=()=>{loadAll().then(()=>{filterPostsRun(r.value,parseInt(i.value,10))})};const a=document.createElement("input");a.type="button",a.value="Unfilter",a.style.cssText="background: #666; color: white; padding: 4px 12px; cursor: pointer; font-weight: bold;",a.onclick=()=>filterPostsRun("reset"),t.onclick=()=>{const e="none"===o.style.display;o.style.display=e?"flex":"none",t.textContent=e?"Collapse filter":"๐Ÿ” Filter by comments"},o.append(r,i,l,a),e.append(t,o,n);const c=document.querySelector("#global-nav");c?c.appendChild(e):document.body.prepend(e)}function filterPostsRun(e,t){if(document.querySelectorAll('[data-filtered="true"]').forEach(e=>{e.style.display="",e.removeAttribute("data-filtered")}),"reset"===e)return;const n=document.querySelectorAll("/feed/"==location.pathname?"p":"button"),o=/^(\d+) comments/;n.forEach(n=>{const r=n.textContent.trim().match(o);if(r){const o=parseInt(r[1],10),i=n.closest("/feed/"==location.pathname?'[componentkey^="expanded"]':".artdeco-card");if(i){let n=!1;">"===e&&(n=o>t),">="===e&&(n=o>=t),"="===e&&(n=o===t),"<"===e&&(n=o<t),"<="===e&&(n=o<=t),n||(i.style.display="none",i.setAttribute("data-filtered","true"))}}})}void 0===window.dirToggleEnabled&&(window.dirToggleEnabled=!0),init();
function init() {
if (location.hostname.replace(/^www\./, '') !== 'linkedin.com') {
alert('This script only works on LinkedIn');
return;
}
if (location.pathname.startsWith('/notifications/')) {
//loadAll('section button:last-of-type', 'section:nth-of-type(2)>div>div>div>div', 'a[href*="/update/'); //'section:nth-of-type(2) a[href*="/update/');
loadAll().then(() => {
grabText('section button:last-of-type', 'section:nth-of-type(2)>div>div>div>div', 'a[href*="/update/');
});
} else if (location.pathname.startsWith('/jobs/view/') || location.search.startsWith('?currentJobId=')) {
initLinkedInJobStats();
} else if (location.pathname.startsWith('/posts/') || location.pathname.startsWith('/feed/update/') || location.pathname == '/feed/' || location.pathname == '/' || location.pathname.endsWith('/recent-activity/all/')) { // post, homepage or feed page
initDirToggle();
revealLinkedInTimestamps();
previewLinkedinPrepare();
filterPostsPre();
} else {
alert('Unsupported page');
return;
}
}
async function loadAll() {
const getScrollContainer = () => {
let maxScrollHeight = 0;
let bestContainer = document.documentElement; // Default to the global window
// Check if the global document is naturally scrollable (classic method)
const docEl = document.documentElement;
if (docEl.scrollHeight > docEl.clientHeight) {
const docOverflow = window.getComputedStyle(docEl).overflowY;
const bodyOverflow = window.getComputedStyle(document.body).overflowY;
if (docOverflow !== 'hidden' && bodyOverflow !== 'hidden') {
maxScrollHeight = docEl.scrollHeight;
}
}
// Next, scan all elements on the page to see if an inner container has a larger scroll area (modern method)
const allElements = document.body.querySelectorAll('*');
for (let i = 0; i < allElements.length; i++) {
const el = allElements[i];
// Only calculate computed styles if the element is physically overflowing, which prevents freezing
if (el.clientHeight > 0 && el.scrollHeight > el.clientHeight) {
// If it's overflowing, check if CSS actually allows it to scroll
const style = window.getComputedStyle(el);
if (style.overflowY === 'auto' || style.overflowY === 'scroll' || style.overflowY === 'overlay') {
// Get the container with largest amount of scrollable content
if (el.scrollHeight > maxScrollHeight) {
maxScrollHeight = el.scrollHeight;
bestContainer = el;
}
}
}
}
return bestContainer;
};
const scrollContainer = getScrollContainer();
// Determine if we need to use the window to scroll, or the specific element
const isGlobalScroll = (scrollContainer === document.documentElement || scrollContainer === document.body);
let currentHeight;
const startTime = Date.now();
const timeout = 10;
const delay = 2;
do {
if (Date.now() - startTime > timeout * 1000) {
alert('Timeout reached, proceeding with available content');
break;
}
currentHeight = scrollContainer.scrollHeight;
// Scroll using the correct method
if (isGlobalScroll) {
window.scrollTo(0, currentHeight);
} else {
scrollContainer.scrollTo(0, currentHeight);
}
await new Promise(resolve => setTimeout(resolve, delay * 1000));
if (currentHeight === scrollContainer.scrollHeight) {
break; // Reached the bottom
}
} while (true);
// Scroll back to top
if (isGlobalScroll) {
window.scrollTo(0, 0);
} else {
scrollContainer.scrollTo(0, 0);
}
await new Promise(resolve => setTimeout(resolve, 1));
}
function grabText(btn, parent, item) {
let highlighterParent = document.getElementById('highlighterParent');
if (!highlighterParent) {
// Create highlighterParent span
highlighterParent = document.createElement('span');
highlighterParent.id = 'highlighterParent';
// Add CSS styles with ID
const style = document.createElement('style');
style.id = 'highlightSpecialStyles';
style.textContent = '.highlightSpecial { background-color: Lavender; } \
@media (prefers-color-scheme: dark) { .highlightSpecial { background-color: DarkSlateGray; } }';
highlighterParent.appendChild(style);
highlighterElement = document.createElement('select');
highlighterElement.id = 'highlighter';
highlighterElement.onchange = () => stylize(highlighterElement, parent, item);
/*
highlighterElement.style.position = 'fixed';
highlighterElement.style.top = '10px';
highlighterElement.style.right = '10px';
highlighterElement.style.zIndex = '9999';
highlighterElement.style.padding = '5px';
highlighterElement.style.backgroundColor = 'white';
highlighterElement.style.border = '1px solid #ccc';
highlighterElement.style.borderRadius = '4px';
*/
const options = [
{ value: 'remove', text: "Keep only what's relevant" },
{ value: 'highlight', text: "Highlight only what's relevant" },
{ value: 'reset', text: 'Reset' },
{ value: 'close', text: 'Close this box' }
];
options.forEach(option => {
const optionElement = document.createElement('option');
optionElement.value = option.value;
optionElement.textContent = option.text;
highlighterElement.appendChild(optionElement);
});
highlighterParent.appendChild(highlighterElement);
document.querySelector(btn).after(highlighterParent);
highlighterElement.onchange();
}
}
function stylize(elm, parent, item) {
const triggerWords = ['replied', 'mentioned you in a', 'commented'];
let theAction = elm.value;
if (theAction == 'close') {
theAction = 'reset';
}
const parentElements = document.querySelectorAll(parent);
parentElements.forEach(parentEl => {
const itemElement = parentEl.querySelector(item);
let found = false;
if (itemElement) {
const text = itemElement.textContent.toLowerCase();
triggerWords.forEach(word => {
if (text.includes(word)) {
found = true;
switch(theAction) {
case 'highlight':
case 'remove':
parentEl.classList.add('highlightSpecial');
break;
case 'reset':
parentEl.classList.remove('highlightSpecial');
parentEl.style.display = '';
break;
}
}
});
}
if (!found) {
switch(theAction) {
case 'highlight':
parentEl.style.display = '';
break;
case 'remove':
parentEl.style.display = 'none';
break;
case 'reset':
parentEl.style.display = '';
break;
}
}
});
if (elm.value == 'close') {
document.getElementById(elm.id).remove();
}
}
function log(...args) {
const logging = true;
if (logging) {
const PREFIX = '[JobStatsExt]';
console.log(PREFIX, ...args);
}
}
// 1. Grab CSRF token from the JSESSIONID cookie
function getCsrfToken() {
const match = document.cookie.match(/JSESSIONID=(?:"([^"]+)"|([^;]+))/);
const token = match ? (match[1] || match[2]) : '';
log('CSRF token:', token || '(none found)');
return token;
}
// 2. Fetch applies/views for a given jobId
async function fetchStats(jobId, csrfToken) {
log('fetchStats called for jobId:', jobId);
try {
const resp = await fetch(`/voyager/api/jobs/jobPostings/${jobId}`, {
credentials: 'include',
headers: {
'accept': 'application/json',
'csrf-token': csrfToken,
'x-restli-protocol-version': '2.0.0'
}
});
/* A bunch of console debugging because this is a weekend project.
A better developer than me (well, any actual developer) will probably replace this with something more robust.*/
log('Fetch response status:', resp.status);
const data = await resp.json();
log('Raw JSON data:', data);
const applies = data.applies ?? data.data?.applies;
const views = data.views ?? data.data?.views;
log('Parsed applies/views:', applies, views);
if (typeof applies !== 'number' || typeof views !== 'number') {
log('Stats are not numbers, aborting');
return null;
}
return { applies, views };
} catch (e) {
log('Error fetching job stats:', e);
return null;
}
}
// 3. Finds the title, fetches stats, and injects a badge
async function injectStats() {
log('injectStats triggered, current URL path:', location.pathname);
const match = location.pathname.match(/^\/jobs\/view\/(\d+)/) || location.search.match(/currentJobId=(\d+)/);
if (!match) {
log('Not on a job view page, skipping');
return;
}
const jobId = match[1];
log('Detected jobId:', jobId);
await new Promise(resolve => setTimeout(resolve, 1));
const titleEl = document.querySelector('p > span:last-child');
if (!titleEl) {
log('No title element found');
return;
}
if (titleEl.dataset.statsInjected) {
log('Stats already injected, skipping');
return;
}
log('Title element found:', titleEl);
if (!location.search.match(/currentJobId=(\d+)/)) {
titleEl.dataset.statsInjected = '1'; // Prevent double-injection
}
const csrfToken = getCsrfToken();
const stats = await fetchStats(jobId, csrfToken);
if (!stats) {
log('No stats returned, aborting injection');
return;
}
let { applies, views } = stats;
log('Injecting badge with applies/views:', applies, views);
let badgeContainer = document.getElementById('badge_container');
if (badgeContainer) {
log('Removing existing badge');
badgeContainer.remove();
}
badgeContainer = document.createElement('span');
badgeContainer.id = 'badge_container';
const badge = document.createElement('span');
let percent = views > 0 ? Math.round((applies / views) * 100) : 0;
if (applies == 0 && views == 0) {
applies = views = percent = 'N/A';
}
badge.textContent = `${applies} applies (${percent}%) โ€ข ${views} views`;
badge.style.marginLeft = '0.5em';
badge.style.fontSize = '0.9em';
badge.style.color = '#666';
titleEl.insertAdjacentElement('afterend', badgeContainer);
badgeContainer.appendChild(badge);
// Inject a form element
const monitorCheckbox = document.createElement('input');
monitorCheckbox.type = 'checkbox';
monitorCheckbox.id = 'monitor_stats';
monitorCheckbox.checked = true;
monitorCheckbox.onclick = () => initLinkedInJobStats();
const monitorLabel = document.createElement('label');
monitorLabel.htmlFor = 'monitor_stats';
monitorLabel.textContent = 'Monitor stats';
monitorLabel.style.marginLeft = '0.5em';
monitorLabel.style.display = 'inline';
badge.insertAdjacentElement('afterend', monitorCheckbox);
monitorCheckbox.insertAdjacentElement('afterend', monitorLabel);
// monitorCheckbox.insertAdjacentText('afterend', ' Monitor stats');
log('Badge injected successfully');
}
// Main function to initialize the LinkedIn job stats extension
function initLinkedInJobStats() {
// Check if checkbox exists and is unchecked - if so, disconnect and return
const monitorCheckbox = document.getElementById('monitor_stats');
if (monitorCheckbox && !monitorCheckbox.checked) {
if (window.statsObserver) {
window.statsObserver.disconnect();
log('Observer disconnected due to unchecked checkbox');
}
return;
} else if (!monitorCheckbox) {
// Initial injection on page load
injectStats();
}
// 4. Watch for clientโ€side SPA navigations
// Disconnect existing observer if it exists
if (window.statsObserver) {
window.statsObserver.disconnect();
}
let lastUrl = location.pathname + location.search;
(window.statsObserver = new MutationObserver(() => {
const currentUrl = location.pathname + location.search;
if (currentUrl !== lastUrl) {
lastUrl = currentUrl;
log('URL or search changed, re-running injectStats');
injectStats();
}
})).observe(document.body, { childList: true, subtree: true });
}
// Initialize dirToggleEnabled only if it doesn't exist
if (typeof window.dirToggleEnabled === 'undefined') {
window.dirToggleEnabled = true;
}
function initDirToggle() {
if (!document.getElementById('hebrew-ltr-styles')) {
const style = document.createElement('style');
style.id = 'hebrew-ltr-styles';
style.textContent = `
:root { color-scheme: light dark; }
.hebrew-ltr {
cursor: crosshair !important;
transition: background-color 0.2s;
}
.hebrew-ltr:not(:hover) {
background-color: light-dark(sandybrown, darkred);
}
`;
document.head.appendChild(style);
}
if (window._dirToggleObserver) {
window._dirToggleObserver.disconnect();
}
const handleMouseEnter = (e) => {
e.currentTarget.setAttribute('dir', 'rtl');
e.currentTarget.firstElementChild.setAttribute('dir', 'rtl');
};
const handleMouseLeave = (e) => {
e.currentTarget.setAttribute('dir', 'ltr');
e.currentTarget.firstElementChild.setAttribute('dir', 'ltr');
};
const runDirToggle = () => {
const hebrewRegex = /[\u0590-\u05FF]/;
let pCandidates = 'p[componentkey*="-commentary-"]';
if (document.querySelectorAll(pCandidates).length > 0) {
pCandidates = document.querySelectorAll(
`${pCandidates}:not([dir="auto"]):not(.hebrew-ltr)`
);
pCandidates.forEach(p => {
const hasSpan = p.querySelector('span');
if (hasSpan && hebrewRegex.test(p.textContent)) {
p.classList.add('hebrew-ltr');
p.addEventListener('mouseenter', handleMouseEnter);
p.addEventListener('mouseleave', handleMouseLeave);
}
});
} else {
const divCandidates = document.querySelectorAll('div[dir="ltr"]:not(.hebrew-ltr)');
divCandidates.forEach(el => {
const spans = el.querySelectorAll('span[dir="ltr"]');
if (Array.from(spans).some(span => hebrewRegex.test(span.textContent))) {
el.classList.add('hebrew-ltr');
el.addEventListener('mouseenter', handleMouseEnter);
el.addEventListener('mouseleave', handleMouseLeave);
}
});
}
};
runDirToggle();
let timeout;
window._dirToggleObserver = new MutationObserver(() => {
clearTimeout(timeout);
timeout = setTimeout(runDirToggle, 500);
});
window._dirToggleObserver.observe(document.body, { childList: true, subtree: true });
}
function revealLinkedInTimestamps(options = {}) {
// 1. Clean up any existing observer from a previous run
if (window._linkedinDateObserver) {
window._linkedinDateObserver.disconnect();
}
// 2. Configuration
const config = {
dateFormat: options.dateFormat || "iso",
showTime: options.showTime !== false,
use24HourTime: options.use24HourTime !== false,
};
const SELECTORS = {
// Added the overlay panel class to postElements
postElements: 'div[data-id*="urn:li:activity"], div[data-urn*="urn:li:activity"], a[href*="urn:li:activity"], .feed-shared-update-detail-viewer__right-panel',
relativeTime: '.update-components-actor__sub-description span[aria-hidden="true"]',
comments: "article.comments-comment-entity",
commentTime: "time.comments-comment-meta__data",
reposts: ".update-components-mini-update-v2, .feed-shared-mini-update-v2, .feed-shared-update-v2__update-content-wrapper"
};
// Check if any selector matches are missing in the document and warn
const missingSelectors = Object.entries(SELECTORS).filter(([key, selector]) => !document.querySelector(selector));
if (missingSelectors.length > 0) {
console.group('[LinkedInHighlighter] Selector Check');
missingSelectors.forEach(([key, selector]) => {
console.warn(`Missing selectors for ${key}: "${selector}"`);
});
console.groupEnd();
}
// 3. Helpers
function getTimestampFromId(postID) {
if (!postID) return null;
try {
const cleanID = String(postID).replace(/[^0-9]/g, "");
if (!cleanID) return null;
const bigIntTime = BigInt(cleanID) >> 22n;
return Number(bigIntTime.toString());
} catch (error) {
return null;
}
}
function getFormattedDate(timestamp) {
const date = new Date(timestamp);
const locale = navigator.language || "en-US";
let dateStr;
if (config.dateFormat === "iso") {
dateStr = date.toISOString().slice(0, 10);
} else {
dateStr = new Intl.DateTimeFormat(locale, { year: "2-digit", month: "2-digit", day: "2-digit" }).format(date);
}
if (!config.showTime) return dateStr;
let timeStr;
const timeOptions = { hour: "2-digit", minute: "2-digit", hour12: !config.use24HourTime };
if (config.dateFormat === "iso") {
timeStr = date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", hour12: false });
} else {
timeStr = date.toLocaleTimeString(locale, timeOptions);
}
const tz = date.toLocaleTimeString("en-us", { timeZoneName: "short" }).split(" ").pop();
return `${dateStr}, ${timeStr} ${tz}`;
}
function updateNode(element, timestamp) {
if (!element || !timestamp) return;
if (element.getAttribute("data-linkedin-date-revealed")) return;
let original = element.getAttribute("data-original-text");
if (!original) {
original = element.textContent.trim();
element.setAttribute("data-original-text", original);
}
const newText = `${getFormattedDate(timestamp)} โ€ข ${original}`;
Array.from(element.childNodes).forEach(node => {
if (node.nodeType === 3) element.removeChild(node);
});
element.insertBefore(document.createTextNode(newText), element.firstChild);
element.setAttribute("data-linkedin-date-revealed", "true");
}
// 4. Main Processing Logic
const runUpdate = () => {
try {
// Process Posts (Feed + Overlays)
document.querySelectorAll(SELECTORS.postElements).forEach(post => {
let id = null;
// Strategy 1: Check attributes (Standard Feed)
const dataId = post.getAttribute("data-id") || post.getAttribute("data-urn");
if (dataId) {
id = dataId.split(":").pop().replace(/\D/g, "");
}
// Strategy 2: Check internal links (Overlay / Modal)
// Overlays often don't have the ID on the wrapper, but have an "Analytics" or "Share" link inside.
if (!id) {
// Look for any link containing urn:li:activity inside this container
const internalLink = post.querySelector('a[href*="urn:li:activity"]');
if (internalLink && internalLink.href) {
const match = internalLink.href.match(/urn:li:activity:(\d+)/);
if (match) id = match[1];
}
}
// Strategy 3: Check URL (Fallback for Overlays)
if (!id && window.location.href.includes("/feed/update/urn:li:activity:")) {
const match = window.location.href.match(/urn:li:activity:(\d+)/);
if (match) id = match[1];
}
if (!id) return;
const el = post.querySelector(SELECTORS.relativeTime);
updateNode(el, getTimestampFromId(id));
});
// Process Reposts
document.querySelectorAll(SELECTORS.reposts).forEach(repost => {
const el = repost.querySelector(SELECTORS.relativeTime);
if (!el) return;
let actId = null;
const link = repost.querySelector('a[href*="urn:li:activity"], a[href*="/feed/update/"]');
if (link && link.href) {
const match = link.href.match(/urn:li:activity:([0-9]+)/);
if (match) actId = match[1];
}
if (!actId) {
const cont = repost.closest("[data-id], [data-urn]");
if (cont) actId = cont.getAttribute("data-id") || cont.getAttribute("data-urn");
}
if (actId) {
const ts = getTimestampFromId(actId.split(":").pop().replace(/\D/g, ""));
updateNode(el, ts);
}
});
// Process Comments
document.querySelectorAll(SELECTORS.comments).forEach(comment => {
const dataId = comment.getAttribute("data-id");
if (!dataId) return;
const match = dataId.match(/(\d+)[^\d]*$/);
if (match) {
const el = comment.querySelector(SELECTORS.commentTime);
updateNode(el, getTimestampFromId(match[1]));
}
});
} catch (e) {
// Silent fail
}
};
// 5. Execute immediately
runUpdate();
// 6. Set up Observer for Infinite Scroll & Modals
let timeout;
window._linkedinDateObserver = new MutationObserver(() => {
clearTimeout(timeout);
timeout = setTimeout(runUpdate, 500);
});
// Observes body to catch Modals being attached to the DOM
window._linkedinDateObserver.observe(document.body, { childList: true, subtree: true });
}
function previewLinkedinPrepare() {
const whereSelector = '#global-nav';
let wrapper = document.getElementById('grab-wrapper');
if (wrapper) wrapper.remove();
wrapper = document.createElement('div');
wrapper.id = 'grab-wrapper';
wrapper.setAttribute('style', 'margin-left: 30% !important; position: relative; z-index: 9999; display: flex; align-items: center; gap: 8px;');
const toggleBtn = document.createElement('button');
toggleBtn.textContent = '\u{1F680} Preview Tool';
toggleBtn.setAttribute('style', 'padding: 4px 12px; cursor: pointer; border-radius: 16px; border: 1px solid #0a66c2; background: white; color: #0a66c2; font-weight: 600;');
const closeButton = document.createElement('button');
closeButton.textContent = '\u2715';
closeButton.setAttribute('style', 'background: rgba(0,0,0,0.3); border: 1px solid rgba(255,255,255,0.2); border-radius: 50%; width: 24px; height: 24px; cursor: pointer; font-weight: bold; color: white; font-size: 14px; display: flex; align-items: center; justify-content: center; text-shadow: 0px 0px 3px black; transition: background 0.2s;');
closeButton.onmouseover = () => closeButton.style.background = 'rgba(255, 0, 0, 0.6)';
closeButton.onmouseout = () => closeButton.style.background = 'rgba(0,0,0,0.3)';
closeButton.onclick = () => wrapper.remove();
const content = document.createElement('div');
content.id = 'grab-content';
content.style.display = 'none';
content.style.flexDirection = 'row';
content.style.alignItems = 'center';
content.style.gap = '8px';
const copyTextArea = document.createElement('textarea');
copyTextArea.placeholder = "Paste potential post here...";
copyTextArea.style.cssText = "height: 32px; width: 150px; border-radius: 4px; border: 1px solid #ccc; font-size: 12px; padding: 4px; background: white; color: black;";
const grabButton = document.createElement('button');
grabButton.textContent = 'Replace all posts/comments';
grabButton.style.cssText = "background: #0a66c2; color: white; border: none; border-radius: 4px; padding: 4px 12px; cursor: pointer; font-size: 12px; height: 32px; font-weight: bold;";
grabButton.onclick = () => previewLinkedinLaunch(copyTextArea);
toggleBtn.onclick = () => {
const isHidden = content.style.display === 'none';
content.style.display = isHidden ? 'flex' : 'none';
toggleBtn.textContent = isHidden ? 'Collapse' : '\u{1F680} Preview Tool';
};
content.append(copyTextArea, grabButton);
wrapper.append(toggleBtn, content, closeButton);
const header = document.querySelector(whereSelector);
header ? header.appendChild(wrapper) : document.body.prepend(wrapper);
}
function previewLinkedinLaunch(sourceTextArea) {
let pCandidates = 'p[componentkey*="-commentary-"]', pCandidatesExist = false;
if (document.querySelectorAll(pCandidates).length > 0) {
pCandidatesExist = true;
}
const targets = document.querySelectorAll(pCandidatesExist ? `${pCandidates}>span` : 'div>*>span[dir]');
if (!sourceTextArea || targets.length === 0) {
if (!sourceTextArea) {
console.warn('Preview Source not found. Make sure the LinkedIn post modal is open.');
}
if (targets.length === 0) {
console.warn('Preview Target not found. Make sure the LinkedIn post modal is open.');
}
return;
}
const rawText = sourceTextArea.value.trim();
const formattedText = pCandidatesExist ? rawText : rawText.split('\n').join('<span><br></span>');
targets.forEach(target => {
if (pCandidatesExist && target.firstChild && target.firstChild.nodeType === Node.TEXT_NODE) {
target.firstChild.textContent = formattedText;
} else {
target.innerHTML = formattedText;
}
});
}
function filterPostsPre() {
const whereSelector = '#global-nav';
let wrapper = document.getElementById('filter-wrapper');
if (wrapper) wrapper.remove();
wrapper = document.createElement('div');
wrapper.id = 'filter-wrapper';
wrapper.setAttribute('style', 'margin-left: 30% !important; position: relative; z-index: 9999; display: flex; align-items: center; gap: 8px;');
const toggleBtn = document.createElement('button');
toggleBtn.textContent = '๐Ÿ” Filter by comments';
toggleBtn.setAttribute('style', 'padding: 4px 12px; cursor: pointer; border-radius: 16px; border: 1px solid #0a66c2; background: white; color: #0a66c2; font-weight: 600;');
const closeButton = document.createElement('button');
closeButton.textContent = '\u2715';
closeButton.setAttribute('style', 'background: rgba(0,0,0,0.3); border: 1px solid rgba(255,255,255,0.2); border-radius: 50%; width: 24px; height: 24px; cursor: pointer; font-weight: bold; color: white; font-size: 14px; display: flex; align-items: center; justify-content: center; text-shadow: 0px 0px 3px black; transition: background 0.2s;');
closeButton.onmouseover = () => closeButton.style.background = 'rgba(255, 0, 0, 0.6)';
closeButton.onmouseout = () => closeButton.style.background = 'rgba(0,0,0,0.3)';
closeButton.onclick = () => {
filterPostsRun('reset');
wrapper.remove();
}
const content = document.createElement('div');
content.id = 'filter-content';
content.style.cssText = 'display: none; flex-direction: row; align-items: center; gap: 8px;';
const select = document.createElement('select');
['>', '>=', '=', '<', '<='].forEach(op => {
const opt = document.createElement('option');
opt.value = op;
opt.textContent = op;
select.appendChild(opt);
});
const numInput = document.createElement('input');
numInput.type = 'number';
numInput.min = '1';
numInput.required = true;
numInput.value = '10';
numInput.style.cssText = "width: 60px;";
const filterBtn = document.createElement('input');
filterBtn.type = 'button';
filterBtn.value = 'Filter';
filterBtn.style.cssText = "background: #0a66c2; color: white; padding: 4px 12px; cursor: pointer; font-weight: bold;";
filterBtn.onclick = () => {
loadAll().then(() => {
filterPostsRun(select.value, parseInt(numInput.value, 10));
});
};
const unfilterBtn = document.createElement('input');
unfilterBtn.type = 'button';
unfilterBtn.value = 'Unfilter';
unfilterBtn.style.cssText = "background: #666; color: white; padding: 4px 12px; cursor: pointer; font-weight: bold;";
unfilterBtn.onclick = () => filterPostsRun('reset');
toggleBtn.onclick = () => {
const isHidden = content.style.display === 'none';
content.style.display = isHidden ? 'flex' : 'none';
toggleBtn.textContent = isHidden ? 'Collapse filter' : '๐Ÿ” Filter by comments';
};
content.append(select, numInput, filterBtn, unfilterBtn);
wrapper.append(toggleBtn, content, closeButton);
const header = document.querySelector(whereSelector);
header ? header.appendChild(wrapper) : document.body.prepend(wrapper);
}
function filterPostsRun(operator, threshold) {
// Reset previous filtering states
document.querySelectorAll('[data-filtered="true"]').forEach(el => {
el.style.display = "";
el.removeAttribute('data-filtered');
});
if (operator === 'reset') return;
const paragraphs = document.querySelectorAll(location.pathname == '/feed/' ? "p" : 'button');
const commentRegex = /^(\d+) comments/;
paragraphs.forEach(p => {
const match = p.textContent.trim().match(commentRegex);
if (match) {
const count = parseInt(match[1], 10);
const targetParent = p.closest(location.pathname == '/feed/' ? '[componentkey^="expanded"]' : '.artdeco-card');
if (targetParent) {
let isMatch = false;
if (operator === '>') isMatch = count > threshold;
if (operator === '>=') isMatch = count >= threshold;
if (operator === '=') isMatch = count === threshold;
if (operator === '<') isMatch = count < threshold;
if (operator === '<=') isMatch = count <= threshold;
if (!isMatch) {
targetParent.style.display = "none";
targetParent.setAttribute('data-filtered', 'true');
}
}
}
});
}
init();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment