// post_feed.js — LinkedIn Feed Smart Comment Generator // Operates ONLY on linkedin.com/feed pages — fully independent from content.js (function () { 'use strict'; const BUTTON_CLASS = 'lja-comment-btn'; const BOX_CLASS = 'lja-comment-box'; // ─── Utility: get stored settings ──────────────────────────────────────── function getSettings() { return new Promise(resolve => { if (!chrome || !chrome.storage) { resolve({}); return; } chrome.storage.sync.get(['apiKey', 'language'], (syncData) => { if (syncData && syncData.apiKey) { resolve(syncData); } else { chrome.storage.local.get(['apiKey', 'language'], (localData) => { resolve(localData || {}); }); } }); }); } // ─── Extract post text from a feed item ────────────────────────────────── function extractPostText(postEl) { const selectors = [ '[data-testid="expandable-text-box"]', // New LinkedIn UI '.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) { // 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; } // ─── 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; } // 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; } // ─── 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"]', '[contenteditable="true"][role="textbox"]', 'div[contenteditable="true"]' ]; for (const sel of selectors) { const el = postEl.querySelector(sel); if (el) return el; } return null; } // ─── Fill comment input (handles LinkedIn's Quill editor) ──────────────── function fillCommentInput(inputEl, text) { inputEl.focus(); inputEl.innerHTML = ''; document.execCommand('insertText', false, text); 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) { 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
`; box.querySelector('.lja-cb-close').addEventListener('click', () => box.remove()); 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); }); }); box.querySelector('.lja-cb-paste').addEventListener('click', function () { const text = box.querySelector('.lja-cb-text').value; const commentTrigger = postEl.querySelector( '.comment-button, [aria-label*="comment" i], [data-control-name="comment"], svg#comment-small' ); let btnToClick = commentTrigger; if (btnToClick && btnToClick.tagName.toLowerCase() === 'svg') { btnToClick = btnToClick.closest('button') || btnToClick; } if (btnToClick) btnToClick.click(); setTimeout(() => { const inputEl = findCommentInput(postEl); if (inputEl) { fillCommentInput(inputEl, text); this.textContent = '✅ Done!'; setTimeout(() => { this.textContent = '✅ Paste into Comment'; }, 1500); } else { navigator.clipboard.writeText(text); this.textContent = '📋 Copied! Paste manually'; } }, 500); }); box.querySelector('.lja-cb-regen').addEventListener('click', async function () { box.remove(); await generateComment(postEl); }); 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 || !settings.apiKey) { alert('Please set your Gemini API key in the extension popup first.\n(Debug: Key not found in storage)'); return; } const postText = extractPostText(postEl); if (!postText) { alert('Could not read post text. The post might be an image or video.'); return; } 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 const actionBar = findActionBar(postEl); if (!actionBar) return; 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(); e.preventDefault(); generateComment(postEl); }); actionBar.appendChild(btn); } // ─── Process all posts on the page ─────────────────────────────────────── function processAllPosts() { // Check both classic LinkedIn UI and the new React/hash-class UI const posts = document.querySelectorAll( '.feed-shared-update-v2, .occludable-update, [data-urn*="activity"], [role="listitem"]' ); posts.forEach(injectButton); } // ─── MutationObserver: watch for new posts (infinite scroll) ───────────── let observerTimer; const observer = new MutationObserver(() => { // Throttle the DOM scanning to prevent heavy performance hits on React updates if (observerTimer) return; observerTimer = setTimeout(() => { processAllPosts(); observerTimer = null; }, 500); }); // ─── Initialize ────────────────────────────────────────────────────────── function init() { processAllPosts(); observer.observe(document.body, { childList: true, subtree: true }); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); setTimeout(init, 2000); // Fallback for delayed SPA renders } })();