// post_feed.js — LinkedIn Feed Smart Comment Generator // Operates ONLY on linkedin.com/feed pages — fully independent from content.js (function () { 'use strict'; const SERVER_URL = 'https://cv.intaleqapp.com/cv/server/generate_cv.php'; const BUTTON_CLASS = 'lja-comment-btn'; const BOX_CLASS = 'lja-comment-box'; // ─── Utility: get stored settings ──────────────────────────────────────── function getSettings() { return new Promise(resolve => chrome.storage.local.get(['apiKey', 'language'], resolve) ); } // ─── Extract post text from a feed item ────────────────────────────────── function extractPostText(postEl) { // Try multiple selectors LinkedIn uses const selectors = [ '.feed-shared-update-v2__description .break-words span[dir]', '.feed-shared-text-view span[dir]', '.update-components-text span[dir]', '.feed-shared-update-v2__description', '.feed-shared-text', ]; for (const sel of selectors) { const el = postEl.querySelector(sel); if (el && el.textContent.trim().length > 20) { return el.textContent.trim(); } } return null; } // ─── Find the action bar in a post ─────────────────────────────────────── function findActionBar(postEl) { const selectors = [ '.feed-shared-social-action-bar', '.social-actions-bar', '[data-urn] .feed-shared-social-action-bar', ]; for (const sel of selectors) { const el = postEl.querySelector(sel); if (el) return el; } return null; } // ─── Find comment input field ───────────────────────────────────────────── function findCommentInput(postEl) { const selectors = [ '.comments-comment-box__form-container .ql-editor', '.comments-comment-texteditor .ql-editor', '.ql-editor[contenteditable="true"]', ]; // Search inside the post first, then fall back to document for (const sel of selectors) { const el = postEl.querySelector(sel) || document.querySelector(sel); if (el) return el; } return null; } // ─── Fill comment input (handles LinkedIn's Quill editor) ──────────────── function fillCommentInput(inputEl, text) { inputEl.focus(); // Clear existing content inputEl.innerHTML = ''; // Use execCommand for rich text editors (most reliable cross-browser) 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 Event('change', { bubbles: true })); } // ─── Create the comment suggestion box ─────────────────────────────────── function createCommentBox(postEl, commentText) { // Remove any existing box on this post const existing = postEl.querySelector('.' + BOX_CLASS); if (existing) existing.remove(); const isRTL = /[\u0600-\u06FF]/.test(commentText); const box = document.createElement('div'); box.className = BOX_CLASS; box.innerHTML = `
🤖 Smart Comment
`; // Close box.querySelector('.lja-cb-close').addEventListener('click', () => box.remove()); // Copy to clipboard box.querySelector('.lja-cb-copy').addEventListener('click', function () { const text = box.querySelector('.lja-cb-text').value; navigator.clipboard.writeText(text).then(() => { this.textContent = '✅ Copied!'; setTimeout(() => { this.textContent = '📋 Copy'; }, 1500); }); }); // Paste into LinkedIn comment field box.querySelector('.lja-cb-paste').addEventListener('click', function () { 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( '.comment-button, [aria-label*="comment" i], [data-control-name="comment"]' ); if (commentTrigger) commentTrigger.click(); setTimeout(() => { const inputEl = findCommentInput(postEl); if (inputEl) { fillCommentInput(inputEl, text); this.textContent = '✅ Done!'; setTimeout(() => { this.textContent = '✅ Paste into Comment'; }, 1500); } else { // Fallback: copy to clipboard and tell user navigator.clipboard.writeText(text); this.textContent = '📋 Copied! Paste manually'; } }, 300); }); // Regenerate box.querySelector('.lja-cb-regen').addEventListener('click', async function () { box.remove(); await generateComment(postEl); }); // Insert before the action bar const actionBar = findActionBar(postEl); if (actionBar) { actionBar.parentNode.insertBefore(box, actionBar.nextSibling); } else { postEl.appendChild(box); } } // ─── Core: call server and generate comment ─────────────────────────────── async function generateComment(postEl) { const settings = await getSettings(); if (!settings.apiKey) { alert('Please set your Gemini API key in the extension popup first.'); return; } const postText = extractPostText(postEl); if (!postText) { alert('Could not read post text. The post might be an image or video.'); return; } // Show spinner on the button const btn = postEl.querySelector('.' + BUTTON_CLASS); if (btn) { btn.disabled = true; btn.innerHTML = ' Thinking...'; } try { const response = await chrome.runtime.sendMessage({ type: 'GEMINI_REQUEST', payload: { apiKey: settings.apiKey, action: 'generateComment', postText: postText } }); if (!response.success) { throw new Error(response.error || 'Unknown error'); } const commentText = response.data.comment || response.data; createCommentBox(postEl, commentText); } catch (e) { console.error('[LJA Feed]', e); alert('Comment generation failed: ' + e.message); } finally { if (btn) { btn.disabled = false; btn.innerHTML = '💬 Smart Comment'; } } } // ─── Inject button into a single post ──────────────────────────────────── function injectButton(postEl) { if (postEl.querySelector('.' + BUTTON_CLASS)) return; // Already injected if (!extractPostText(postEl)) return; // Skip non-text posts silently const btn = document.createElement('button'); btn.className = BUTTON_CLASS; btn.innerHTML = '💬 Smart Comment'; btn.title = 'Generate an AI-powered comment for this post'; btn.addEventListener('click', (e) => { e.stopPropagation(); generateComment(postEl); }); const actionBar = findActionBar(postEl); if (actionBar) { actionBar.appendChild(btn); } } // ─── Process all posts on the page ─────────────────────────────────────── function processAllPosts() { const posts = document.querySelectorAll( '.feed-shared-update-v2, .occludable-update, [data-urn*="activity"]' ); posts.forEach(injectButton); } // ─── MutationObserver: watch for new posts (infinite scroll) ───────────── const observer = new MutationObserver((mutations) => { for (const mutation of mutations) { for (const node of mutation.addedNodes) { if (node.nodeType !== Node.ELEMENT_NODE) continue; // Check if the node itself is a post if ( node.classList?.contains('feed-shared-update-v2') || node.classList?.contains('occludable-update') || 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 ────────────────────────────────────────────────────────── function init() { processAllPosts(); observer.observe(document.body, { childList: true, subtree: true }); } // Wait for DOM to be ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { // Run immediately, then again after a short delay for LinkedIn's SPA init(); setTimeout(init, 2000); } })();