Auto-deploy: 2026-05-18 01:59:28
This commit is contained in:
89
post_feed.js
89
post_feed.js
@@ -4,7 +4,6 @@
|
|||||||
(function () {
|
(function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const SERVER_URL = 'https://cv.intaleqapp.com/cv/server/generate_cv.php';
|
|
||||||
const BUTTON_CLASS = 'lja-comment-btn';
|
const BUTTON_CLASS = 'lja-comment-btn';
|
||||||
const BOX_CLASS = 'lja-comment-box';
|
const BOX_CLASS = 'lja-comment-box';
|
||||||
|
|
||||||
@@ -17,8 +16,8 @@
|
|||||||
|
|
||||||
// ─── Extract post text from a feed item ──────────────────────────────────
|
// ─── Extract post text from a feed item ──────────────────────────────────
|
||||||
function extractPostText(postEl) {
|
function extractPostText(postEl) {
|
||||||
// Try multiple selectors LinkedIn uses
|
|
||||||
const selectors = [
|
const selectors = [
|
||||||
|
'[data-testid="expandable-text-box"]', // New LinkedIn UI
|
||||||
'.feed-shared-update-v2__description .break-words span[dir]',
|
'.feed-shared-update-v2__description .break-words span[dir]',
|
||||||
'.feed-shared-text-view span[dir]',
|
'.feed-shared-text-view span[dir]',
|
||||||
'.update-components-text span[dir]',
|
'.update-components-text span[dir]',
|
||||||
@@ -27,8 +26,10 @@
|
|||||||
];
|
];
|
||||||
for (const sel of selectors) {
|
for (const sel of selectors) {
|
||||||
const el = postEl.querySelector(sel);
|
const el = postEl.querySelector(sel);
|
||||||
if (el && el.textContent.trim().length > 20) {
|
if (el) {
|
||||||
return el.textContent.trim();
|
// Strip out the "... more" text if it exists
|
||||||
|
const text = el.textContent.replace(/…\s*more/gi, '').trim();
|
||||||
|
if (text.length > 20) return text;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -45,6 +46,15 @@
|
|||||||
const el = postEl.querySelector(sel);
|
const el = postEl.querySelector(sel);
|
||||||
if (el) return el;
|
if (el) return el;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// New UI fallback: find the comment button SVG and get its parent container
|
||||||
|
const commentSvg = postEl.querySelector('svg#comment-small, svg[data-test-icon="comment-small"]');
|
||||||
|
if (commentSvg) {
|
||||||
|
const btn = commentSvg.closest('button');
|
||||||
|
if (btn && btn.parentNode) {
|
||||||
|
return btn.parentNode; // This is the action bar container
|
||||||
|
}
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,10 +64,11 @@
|
|||||||
'.comments-comment-box__form-container .ql-editor',
|
'.comments-comment-box__form-container .ql-editor',
|
||||||
'.comments-comment-texteditor .ql-editor',
|
'.comments-comment-texteditor .ql-editor',
|
||||||
'.ql-editor[contenteditable="true"]',
|
'.ql-editor[contenteditable="true"]',
|
||||||
|
'[contenteditable="true"][role="textbox"]',
|
||||||
|
'div[contenteditable="true"]'
|
||||||
];
|
];
|
||||||
// Search inside the post first, then fall back to document
|
|
||||||
for (const sel of selectors) {
|
for (const sel of selectors) {
|
||||||
const el = postEl.querySelector(sel) || document.querySelector(sel);
|
const el = postEl.querySelector(sel);
|
||||||
if (el) return el;
|
if (el) return el;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -66,18 +77,14 @@
|
|||||||
// ─── Fill comment input (handles LinkedIn's Quill editor) ────────────────
|
// ─── Fill comment input (handles LinkedIn's Quill editor) ────────────────
|
||||||
function fillCommentInput(inputEl, text) {
|
function fillCommentInput(inputEl, text) {
|
||||||
inputEl.focus();
|
inputEl.focus();
|
||||||
// Clear existing content
|
|
||||||
inputEl.innerHTML = '';
|
inputEl.innerHTML = '';
|
||||||
// Use execCommand for rich text editors (most reliable cross-browser)
|
|
||||||
document.execCommand('insertText', false, text);
|
document.execCommand('insertText', false, text);
|
||||||
// Fallback: dispatch input event for React to pick up
|
|
||||||
inputEl.dispatchEvent(new InputEvent('input', { bubbles: true, data: text }));
|
inputEl.dispatchEvent(new InputEvent('input', { bubbles: true, data: text }));
|
||||||
inputEl.dispatchEvent(new Event('change', { bubbles: true }));
|
inputEl.dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Create the comment suggestion box ───────────────────────────────────
|
// ─── Create the comment suggestion box ───────────────────────────────────
|
||||||
function createCommentBox(postEl, commentText) {
|
function createCommentBox(postEl, commentText) {
|
||||||
// Remove any existing box on this post
|
|
||||||
const existing = postEl.querySelector('.' + BOX_CLASS);
|
const existing = postEl.querySelector('.' + BOX_CLASS);
|
||||||
if (existing) existing.remove();
|
if (existing) existing.remove();
|
||||||
|
|
||||||
@@ -103,10 +110,8 @@
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Close
|
|
||||||
box.querySelector('.lja-cb-close').addEventListener('click', () => box.remove());
|
box.querySelector('.lja-cb-close').addEventListener('click', () => box.remove());
|
||||||
|
|
||||||
// Copy to clipboard
|
|
||||||
box.querySelector('.lja-cb-copy').addEventListener('click', function () {
|
box.querySelector('.lja-cb-copy').addEventListener('click', function () {
|
||||||
const text = box.querySelector('.lja-cb-text').value;
|
const text = box.querySelector('.lja-cb-text').value;
|
||||||
navigator.clipboard.writeText(text).then(() => {
|
navigator.clipboard.writeText(text).then(() => {
|
||||||
@@ -115,15 +120,17 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Paste into LinkedIn comment field
|
|
||||||
box.querySelector('.lja-cb-paste').addEventListener('click', function () {
|
box.querySelector('.lja-cb-paste').addEventListener('click', function () {
|
||||||
const text = box.querySelector('.lja-cb-text').value;
|
const text = box.querySelector('.lja-cb-text').value;
|
||||||
|
|
||||||
// First click the LinkedIn comment button to open the field if not open
|
|
||||||
const commentTrigger = postEl.querySelector(
|
const commentTrigger = postEl.querySelector(
|
||||||
'.comment-button, [aria-label*="comment" i], [data-control-name="comment"]'
|
'.comment-button, [aria-label*="comment" i], [data-control-name="comment"], svg#comment-small'
|
||||||
);
|
);
|
||||||
if (commentTrigger) commentTrigger.click();
|
let btnToClick = commentTrigger;
|
||||||
|
if (btnToClick && btnToClick.tagName.toLowerCase() === 'svg') {
|
||||||
|
btnToClick = btnToClick.closest('button') || btnToClick;
|
||||||
|
}
|
||||||
|
if (btnToClick) btnToClick.click();
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const inputEl = findCommentInput(postEl);
|
const inputEl = findCommentInput(postEl);
|
||||||
@@ -132,20 +139,17 @@
|
|||||||
this.textContent = '✅ Done!';
|
this.textContent = '✅ Done!';
|
||||||
setTimeout(() => { this.textContent = '✅ Paste into Comment'; }, 1500);
|
setTimeout(() => { this.textContent = '✅ Paste into Comment'; }, 1500);
|
||||||
} else {
|
} else {
|
||||||
// Fallback: copy to clipboard and tell user
|
|
||||||
navigator.clipboard.writeText(text);
|
navigator.clipboard.writeText(text);
|
||||||
this.textContent = '📋 Copied! Paste manually';
|
this.textContent = '📋 Copied! Paste manually';
|
||||||
}
|
}
|
||||||
}, 300);
|
}, 500);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Regenerate
|
|
||||||
box.querySelector('.lja-cb-regen').addEventListener('click', async function () {
|
box.querySelector('.lja-cb-regen').addEventListener('click', async function () {
|
||||||
box.remove();
|
box.remove();
|
||||||
await generateComment(postEl);
|
await generateComment(postEl);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Insert before the action bar
|
|
||||||
const actionBar = findActionBar(postEl);
|
const actionBar = findActionBar(postEl);
|
||||||
if (actionBar) {
|
if (actionBar) {
|
||||||
actionBar.parentNode.insertBefore(box, actionBar.nextSibling);
|
actionBar.parentNode.insertBefore(box, actionBar.nextSibling);
|
||||||
@@ -168,7 +172,6 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show spinner on the button
|
|
||||||
const btn = postEl.querySelector('.' + BUTTON_CLASS);
|
const btn = postEl.querySelector('.' + BUTTON_CLASS);
|
||||||
if (btn) {
|
if (btn) {
|
||||||
btn.disabled = true;
|
btn.disabled = true;
|
||||||
@@ -206,6 +209,10 @@
|
|||||||
// ─── Inject button into a single post ────────────────────────────────────
|
// ─── Inject button into a single post ────────────────────────────────────
|
||||||
function injectButton(postEl) {
|
function injectButton(postEl) {
|
||||||
if (postEl.querySelector('.' + BUTTON_CLASS)) return; // Already injected
|
if (postEl.querySelector('.' + BUTTON_CLASS)) return; // Already injected
|
||||||
|
|
||||||
|
const actionBar = findActionBar(postEl);
|
||||||
|
if (!actionBar) return;
|
||||||
|
|
||||||
if (!extractPostText(postEl)) return; // Skip non-text posts silently
|
if (!extractPostText(postEl)) return; // Skip non-text posts silently
|
||||||
|
|
||||||
const btn = document.createElement('button');
|
const btn = document.createElement('button');
|
||||||
@@ -215,43 +222,31 @@
|
|||||||
|
|
||||||
btn.addEventListener('click', (e) => {
|
btn.addEventListener('click', (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
generateComment(postEl);
|
generateComment(postEl);
|
||||||
});
|
});
|
||||||
|
|
||||||
const actionBar = findActionBar(postEl);
|
actionBar.appendChild(btn);
|
||||||
if (actionBar) {
|
|
||||||
actionBar.appendChild(btn);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Process all posts on the page ───────────────────────────────────────
|
// ─── Process all posts on the page ───────────────────────────────────────
|
||||||
function processAllPosts() {
|
function processAllPosts() {
|
||||||
|
// Check both classic LinkedIn UI and the new React/hash-class UI
|
||||||
const posts = document.querySelectorAll(
|
const posts = document.querySelectorAll(
|
||||||
'.feed-shared-update-v2, .occludable-update, [data-urn*="activity"]'
|
'.feed-shared-update-v2, .occludable-update, [data-urn*="activity"], [role="listitem"]'
|
||||||
);
|
);
|
||||||
posts.forEach(injectButton);
|
posts.forEach(injectButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── MutationObserver: watch for new posts (infinite scroll) ─────────────
|
// ─── MutationObserver: watch for new posts (infinite scroll) ─────────────
|
||||||
const observer = new MutationObserver((mutations) => {
|
let observerTimer;
|
||||||
for (const mutation of mutations) {
|
const observer = new MutationObserver(() => {
|
||||||
for (const node of mutation.addedNodes) {
|
// Throttle the DOM scanning to prevent heavy performance hits on React updates
|
||||||
if (node.nodeType !== Node.ELEMENT_NODE) continue;
|
if (observerTimer) return;
|
||||||
// Check if the node itself is a post
|
observerTimer = setTimeout(() => {
|
||||||
if (
|
processAllPosts();
|
||||||
node.classList?.contains('feed-shared-update-v2') ||
|
observerTimer = null;
|
||||||
node.classList?.contains('occludable-update') ||
|
}, 500);
|
||||||
node.dataset?.urn?.includes('activity')
|
|
||||||
) {
|
|
||||||
injectButton(node);
|
|
||||||
}
|
|
||||||
// Or if it contains posts
|
|
||||||
const innerPosts = node.querySelectorAll?.(
|
|
||||||
'.feed-shared-update-v2, .occludable-update, [data-urn*="activity"]'
|
|
||||||
);
|
|
||||||
innerPosts?.forEach(injectButton);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// ─── Initialize ──────────────────────────────────────────────────────────
|
// ─── Initialize ──────────────────────────────────────────────────────────
|
||||||
@@ -260,12 +255,10 @@
|
|||||||
observer.observe(document.body, { childList: true, subtree: true });
|
observer.observe(document.body, { childList: true, subtree: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for DOM to be ready
|
|
||||||
if (document.readyState === 'loading') {
|
if (document.readyState === 'loading') {
|
||||||
document.addEventListener('DOMContentLoaded', init);
|
document.addEventListener('DOMContentLoaded', init);
|
||||||
} else {
|
} else {
|
||||||
// Run immediately, then again after a short delay for LinkedIn's SPA
|
|
||||||
init();
|
init();
|
||||||
setTimeout(init, 2000);
|
setTimeout(init, 2000); // Fallback for delayed SPA renders
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|||||||
Reference in New Issue
Block a user