(function() {
document.addEventListener('DOMContentLoaded', function() {
var mediaBtn = document.getElementById('pfeai-media-button');
if (mediaBtn) {
mediaBtn.addEventListener('click', function(e) {
e.preventDefault();
if (window.PFEditorialAI) {
PFEditorialAI.togglePanel();
}
});
}
});
if (typeof tinymce === 'undefined') {
return;
}
tinymce.create('tinymce.plugins.PFEditorialAI', {
init: function(editor, url) {
editor.on('init', function() {
PFEditorialAI.createPanel(editor);
PFEditorialAI.bindTooltipEvents();
PFEditorialAI.preloadSavedData();
});
},
getInfo: function() {
return { longname: 'PF Editorial AI', author: 'PatsFans + AI', version: '5.2.7' };
}
});
tinymce.PluginManager.add('pfeai_plugin', tinymce.plugins.PFEditorialAI);
window.PFEditorialAI = {
panelCreated: false,
panelEl: null,
loadingEl: null,
analysisEl: null,
titlesEl: null,
ideasEl: null,
transcriptEl: null,
socialEl: null,
savedEl: null,
factsEl: null,
linksEl: null,
isMinimized: false,
// Cache window tracking
cacheWindowStart: null,
cacheWindowTimer: null,
cacheWindowDuration: 4 * 60 * 1000, // 4 minutes (conservative estimate)
tasksInWindow: 0,
/**
* SECURITY: Escape HTML to prevent XSS
* This function must be used for ALL user/API data inserted into the DOM
*/
escapeHtml: function(str) {
if (str === null || str === undefined) return '';
return String(str)
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
},
/**
* SECURITY: Sanitize for textarea/input values
*/
escapeAttr: function(str) {
if (str === null || str === undefined) return '';
return String(str)
.replace(/&/g, '&')
.replace(/"/g, '"')
.replace(/'/g, ''')
.replace(//g, '>');
},
/**
* Cache Window Indicator - shows when implicit caching is active
*/
startCacheWindow: function(fromCache) {
var self = this;
// If result was from app cache, don't start/extend the API cache window
if (fromCache) return;
this.tasksInWindow++;
// Start or extend the cache window
this.cacheWindowStart = Date.now();
// Clear existing timer
if (this.cacheWindowTimer) {
clearInterval(this.cacheWindowTimer);
}
// Update indicator immediately
this.updateCacheIndicator();
// Update every second
this.cacheWindowTimer = setInterval(function() {
self.updateCacheIndicator();
}, 1000);
},
updateCacheIndicator: function() {
var indicator = document.getElementById('pfeai-cache-indicator');
if (!indicator) return;
if (!this.cacheWindowStart) {
indicator.style.display = 'none';
return;
}
var elapsed = Date.now() - this.cacheWindowStart;
var remaining = this.cacheWindowDuration - elapsed;
if (remaining <= 0) {
// Cache window expired
indicator.style.display = 'none';
this.cacheWindowStart = null;
this.tasksInWindow = 0;
if (this.cacheWindowTimer) {
clearInterval(this.cacheWindowTimer);
this.cacheWindowTimer = null;
}
return;
}
// Show indicator
indicator.style.display = 'block';
var minutes = Math.floor(remaining / 60000);
var seconds = Math.floor((remaining % 60000) / 1000);
var timeStr = minutes + ':' + (seconds < 10 ? '0' : '') + seconds;
var savingsText = this.tasksInWindow > 1
? ' · ' + (this.tasksInWindow - 1) + ' task' + (this.tasksInWindow > 2 ? 's' : '') + ' cached'
: '';
indicator.innerHTML = '⚡ Cache active: ' + timeStr + ' remaining' + savingsText + '
Run more tasks now to save tokens';
},
getProvider: function() {
var select = document.getElementById('pfeai-provider-select');
return select ? select.value : 'gemini';
},
getSafeContent: function(editor) {
try {
if (editor && !editor.isHidden()) return editor.getContent({ format: 'raw' });
var textArea = document.getElementById('content');
return textArea ? textArea.value : '';
} catch (e) { return ''; }
},
getSafeText: function(editor) {
try {
if (editor && !editor.isHidden()) return editor.getContent({ format: 'text' });
var textArea = document.getElementById('content');
return textArea ? textArea.value : '';
} catch (e) { return ''; }
},
createPanel: function(editor) {
if (this.panelCreated) return;
// FIX: Inject Layout Push Styles directly to ensure they work
var style = document.createElement('style');
style.innerHTML = 'body.pfeai-panel-open #wpbody-content { margin-right: 360px !important; transition: margin-right 0.3s ease; } body.pfeai-panel-open .pfeai-panel { right: 0; box-shadow: -2px 0 5px rgba(0,0,0,0.1); }';
document.head.appendChild(style);
var panel = document.createElement('div');
panel.id = 'pfeai-panel';
var html = '';
html += '
Error: ' + self.escapeHtml(json.message) + '
'; } }).catch(function(e) { self.setLoading(false); console.error(e); }); }, renderSocialPack: function(data) { var self = this; this.socialEl.innerHTML = 'Error: ' + self.escapeHtml(json.message) + '
'; } }).catch(function(e) { self.setLoading(false); console.error(e); }); }, runTitles: function(editor, forceRefresh) { var self = this; var contentHtml = this.getSafeContent(editor); var title = (document.getElementById('title') || {}).value || ''; this.showResults('titles'); this.setLoading(true); fetch(PFEAI.rest_url + 'seo-titles?_t=' + Date.now(), { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': PFEAI.nonce }, body: JSON.stringify({ content: contentHtml, title: title, post_id: PFEAI.post_id, provider: this.getProvider(), force_refresh: forceRefresh || false }) }) .then(function(r){return r.json()}).then(function(json){ self.setLoading(false); if(json.titles) { self.renderTitles(json); var timestamp = json.generated_at || Date.now()/1000; self.setCachedState('btn-titles', 'View Titles', 'titles', timestamp, json.from_cache); self.startCacheWindow(json.from_cache); } else if (json.code) { self.titlesEl.innerHTML = 'Error: ' + self.escapeHtml(json.message) + '
'; } }); }, runTranscriptScan: function(editor, forceRefresh) { var self = this; var contentText = this.getSafeText(editor); if (contentText.length < 50) { alert('Please paste transcript.'); return; } this.showResults('transcript'); this.setLoading(true); fetch(PFEAI.rest_url + 'transcript-scan?_t=' + Date.now(), { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': PFEAI.nonce }, body: JSON.stringify({ content: contentText, post_id: PFEAI.post_id, provider: this.getProvider(), force_refresh: forceRefresh || false }) }) .then(function(r){return r.json()}) .then(function(json){ self.setLoading(false); console.log('Transcript scan response:', json); // Handle the response - could be {suggestions: [...]} or a single object or bad cache var suggestions = null; if (json.suggestions && Array.isArray(json.suggestions)) { // Correct format: {suggestions: [{...}, {...}]} suggestions = json.suggestions; } else if (json.title && json.context) { // Bad format: single object at root (legacy cache issue) // Wrap it in an array suggestions = [json]; console.warn('Transcript response was single object, wrapped in array. Consider clearing cache.'); } if (suggestions && suggestions.length > 0) { self.renderTranscriptSuggestions(suggestions, contentText); var timestamp = json.generated_at || Date.now()/1000; self.setCachedState('btn-transcript', 'View Transcript Results', 'transcript', timestamp, json.from_cache); self.startCacheWindow(json.from_cache); } else if (json.code) { self.transcriptEl.innerHTML = 'Error: ' + self.escapeHtml(json.message) + '
'; } else { console.error('Unexpected transcript response:', json); self.transcriptEl.innerHTML = 'Unexpected response format. Please click "Rescan" to refresh.
'; } }) .catch(function(err) { self.setLoading(false); console.error('Transcript scan error:', err); self.transcriptEl.innerHTML = 'Request failed: ' + self.escapeHtml(err.message) + '
'; }); }, /** * IMPROVED: Added better error handling and timeout management for transcript outline */ runTranscriptOutline: function(title, content, clickedCard) { var self = this; var resDiv = document.getElementById('pfeai-transcript-result'); // Show loading state with timeout warning resDiv.innerHTML = 'Generating outline...
This may take up to 30 seconds for longer transcripts.
'; // Validate inputs if (!title) { resDiv.innerHTML = 'Error: No headline selected. Please click a headline above.
'; return; } if (!content || content.length < 50) { resDiv.innerHTML = 'Error: Transcript content is too short or missing.
'; return; } // Create abort controller for timeout var controller = new AbortController(); var timeoutId = setTimeout(function() { controller.abort(); }, 180000); // 3 minute timeout (matching server timeout) fetch(PFEAI.rest_url + 'transcript-outline?_t=' + Date.now(), { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': PFEAI.nonce }, body: JSON.stringify({ selected_title: title, content: content, provider: this.getProvider() }), signal: controller.signal }) .then(function(r) { clearTimeout(timeoutId); if (!r.ok) { throw new Error('Server returned status ' + r.status); } return r.json(); }) .then(function(json) { console.log('Transcript outline response:', json); // Check for error response if (json.code) { resDiv.innerHTML = 'Error: ' + self.escapeHtml(json.message) + '
Try selecting a different headline or click Rescan.
'; return; } // Check for required field (blurb) if (json.blurb) { self.renderTranscriptOutlineResult(json, title); } else { console.error('Missing blurb in response:', json); resDiv.innerHTML = 'AI response was incomplete. Please try again.
Response received but missing required content.
'; } }) .catch(function(err) { clearTimeout(timeoutId); console.error('Transcript outline error:', err); var errorMsg = 'Error generating outline.'; if (err.name === 'AbortError') { errorMsg = 'Request timed out. The transcript may be too long.'; } else if (err.message) { errorMsg = err.message; } resDiv.innerHTML = '' + self.escapeHtml(errorMsg) + '
Try again or select a different headline.
'; }); }, runArticleIdeas: function(editor, forceRefresh) { var self = this; var title = (document.getElementById('title') || {}).value || ''; var contentText = this.getSafeText(editor).trim(); // Only block if not forcing refresh AND there's content if (!forceRefresh && (title.length > 0 || contentText.length > 5)) { alert('To generate fresh Topic Inspiration, please clear the Title and Content editor first.'); return; } this.showResults('ideas'); this.setLoading(true); // Function to fetch new ideas var fetchIdeas = function() { fetch(PFEAI.rest_url + 'article-ideas?_t=' + Date.now(), { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': PFEAI.nonce }, body: JSON.stringify({ title: '', post_id: PFEAI.post_id, provider: self.getProvider(), force_refresh: forceRefresh || false }) }) .then(function(r){return r.json()}).then(function(json){ self.setLoading(false); if(json.ideas) { self.renderIdeasInteractive(json); self.setCachedState('btn-ideas', 'View Topics', 'ideas', Date.now()/1000); } else if (json.code) { self.ideasEl.innerHTML = 'Error: ' + self.escapeHtml(json.message) + '
'; } }).catch(function(e) { self.setLoading(false); console.error(e); }); }; // If forcing refresh, clear cache first, then fetch new ideas if (forceRefresh) { fetch(PFEAI.rest_url + 'clear-ideas-cache?_t=' + Date.now(), { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': PFEAI.nonce }, body: JSON.stringify({ post_id: PFEAI.post_id }) }) .then(function() { fetchIdeas(); }) .catch(function() { fetchIdeas(); }); // Still try to fetch even if clear fails } else { fetchIdeas(); } }, /** * IMPROVED: Added better error handling for idea outline */ runIdeaOutline: function(title, context) { var self = this; var resDiv = document.getElementById('pfeai-idea-result'); if(!resDiv) return; resDiv.innerHTML = 'Drafting outline...
This may take up to 30 seconds.
'; // Create abort controller for timeout var controller = new AbortController(); var timeoutId = setTimeout(function() { controller.abort(); }, 180000); // 3 minute timeout fetch(PFEAI.rest_url + 'idea-outline?_t=' + Date.now(), { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': PFEAI.nonce }, body: JSON.stringify({ idea_title: title, context: context, provider: this.getProvider() }), signal: controller.signal }) .then(function(r) { clearTimeout(timeoutId); if (!r.ok) { throw new Error('Server returned status ' + r.status); } return r.json(); }) .then(function(json) { console.log('Idea outline response:', json); if (json.code) { resDiv.innerHTML = 'Error: ' + self.escapeHtml(json.message) + '
'; return; } if(json.blurb) { self.renderIdeaOutlineResult(json, title); } else { resDiv.innerHTML = 'AI response was incomplete. Please try again.
'; } }) .catch(function(err) { clearTimeout(timeoutId); console.error('Idea outline error:', err); var errorMsg = 'Error generating outline.'; if (err.name === 'AbortError') { errorMsg = 'Request timed out. Please try again.'; } else if (err.message) { errorMsg = err.message; } resDiv.innerHTML = '' + self.escapeHtml(errorMsg) + '
'; }); }, runTagline: function(editor) { var self = this; var c = this.getSafeContent(editor); var t = (document.getElementById('title') || {}).value || ''; if (!t) { alert('Enter title first'); return; } // Debug logging console.log('PFEAI Tagline: Content length = ' + c.length); // Show loading state on button var btn = this.panelEl.querySelector('.pfeai-tagline-btn'); var originalText = btn ? btn.textContent : ''; if (btn) { btn.disabled = true; btn.innerHTML = ' Thinking...'; } fetch(PFEAI.rest_url + 'generate-tagline?_t=' + Date.now(), { method: 'POST', headers: {'Content-Type':'application/json','X-WP-Nonce':PFEAI.nonce}, body:JSON.stringify({content:c, title:t, provider: this.getProvider()}) }) .then(function(r){ console.log('PFEAI Tagline: Response status = ' + r.status); return r.json(); }) .then(function(j){ console.log('PFEAI Tagline: Response = ', j); // Restore button state if (btn) { btn.disabled = false; btn.textContent = originalText; } if(j.tagline) { document.getElementById('pftwt_tagline').value = j.tagline; self.startCacheWindow(false); // API call was made } else if (j.code) { alert('Error: ' + j.message); } else { console.warn('PFEAI Tagline: No tagline in response', j); alert('No tagline was generated. Please try again.'); } }) .catch(function(e) { console.error('PFEAI Tagline: Error = ', e); // Restore button state on error if (btn) { btn.disabled = false; btn.textContent = originalText; } alert('Error: ' + e.message); }); }, runTagSuggestions: function(editor) { var self = this; var c = this.getSafeContent(editor); var t = (document.getElementById('title') || {}).value || ''; // Debug: log content length console.log('PFEAI Tags: Content length = ' + c.length + ', Title = ' + t); if (!c || c.length < 50) { alert('Please add more content before generating tags.'); return; } // Show loading state on button var btn = this.panelEl.querySelector('.pfeai-tags-btn'); var originalText = btn ? btn.textContent : ''; if (btn) { btn.disabled = true; btn.innerHTML = ' Thinking...'; } fetch(PFEAI.rest_url + 'suggest-tags?_t=' + Date.now(), { method: 'POST', headers: {'Content-Type':'application/json','X-WP-Nonce':PFEAI.nonce}, body:JSON.stringify({content:c, title:t, provider: this.getProvider()}) }) .then(function(r){ console.log('PFEAI Tags: Response status = ' + r.status); return r.json(); }) .then(function(j){ console.log('PFEAI Tags: Response = ', j); // Restore button state if (btn) { btn.disabled = false; btn.textContent = originalText; } // Check for error response if (j.code) { alert('Error generating tags: ' + (j.message || j.code)); return; } if(j.tags && j.tags.length > 0 && jQuery) { var $ = jQuery; var tagsAdded = 0; var tagBox = $('#post_tag'); // Method 1: Use WordPress tagBox API if available if (window.tagBox && typeof window.tagBox.flushTags === 'function') { var $input = $('#new-tag-post_tag'); j.tags.forEach(function(tag) { $input.val(tag); // Trigger the tag addition window.tagBox.flushTags(tagBox, false, 1); tagsAdded++; }); } else { // Method 2: Manually add tags and trigger events var $input = $('#new-tag-post_tag'); var $addBtn = $('#post_tag .tagadd'); j.tags.forEach(function(tag, index) { // Small delay between additions to let WordPress process each one setTimeout(function() { $input.val(tag); $addBtn.trigger('click'); // Also try triggering the keypress enter event as backup var e = $.Event('keypress'); e.which = 13; // Enter key $input.trigger(e); tagsAdded++; // On last tag, show success message if (index === j.tags.length - 1) { setTimeout(function() { // Force refresh the tag cloud display if (window.tagBox && typeof window.tagBox.get === 'function') { window.tagBox.get('post_tag'); } console.log('PFEAI Tags: Added ' + j.tags.length + ' tags'); }, 100); } }, index * 50); // 50ms delay between each tag }); } self.startCacheWindow(false); // API call was made // Show feedback to user if (btn) { btn.textContent = 'Added ' + j.tags.length + ' tags!'; setTimeout(function() { btn.textContent = originalText; }, 2000); } console.log('PFEAI Tags: Added ' + j.tags.length + ' tags'); } else { console.warn('PFEAI Tags: No tags returned or empty array', j); alert('No tags were generated. Please try again.'); } }) .catch(function(e) { console.error('PFEAI Tags: Error = ', e); // Restore button state on error if (btn) { btn.disabled = false; btn.textContent = originalText; } alert('Error: ' + e.message); }); }, /** * Run Meta Description from the main menu button * Shows loading on the menu button and navigates to SEO tab with result */ runMetaDescFromMenu: function(editor) { var self = this; var title = (document.getElementById('title') || {}).value || ''; var content = this.getSafeContent(editor); if (!title && !content) { alert('Please add a title or content first.'); return; } // Show loading state on the menu button var btn = this.panelEl.querySelector('.pfeai-meta-btn'); var originalText = btn ? btn.textContent : ''; if (btn) { btn.disabled = true; btn.innerHTML = ' Thinking...'; } fetch(PFEAI.rest_url + 'generate-meta-desc?_t=' + Date.now(), { method: 'POST', headers: {'Content-Type':'application/json','X-WP-Nonce':PFEAI.nonce}, body: JSON.stringify({content: content, title: title, provider: this.getProvider()}) }) .then(function(r){return r.json()}) .then(function(j){ // Restore button state if (btn) { btn.disabled = false; btn.textContent = originalText; } if(j.meta_description) { // Navigate to SEO tab and show result self.showResults('seo'); // Fill in the meta description textarea var textarea = document.getElementById('pfeai-meta-description'); if (textarea) { textarea.value = j.meta_description; self.updateSerpPreview(); } self.startCacheWindow(false); // API call was made // Show success feedback on button briefly if (btn) { btn.textContent = 'Generated!'; setTimeout(function() { btn.textContent = originalText; }, 2000); } } else if (j.code) { alert('Error: ' + j.message); } else { alert('No meta description was generated. Please try again.'); } }) .catch(function(e) { // Restore button state on error if (btn) { btn.disabled = false; btn.textContent = originalText; } console.error('PFEAI Meta Desc: Error = ', e); alert('Error: ' + e.message); }); }, runMetaDescription: function() { var self = this; var title = (document.getElementById('title') || {}).value || ''; var content = this.getSafeContent(tinymce.activeEditor); if (!title && !content) { alert('Please add a title or content first.'); return; } var btn = document.getElementById('pfeai-generate-meta-btn'); if (btn) { btn.textContent = 'Generating...'; btn.disabled = true; } fetch(PFEAI.rest_url + 'generate-meta-desc?_t=' + Date.now(), { method: 'POST', headers: {'Content-Type':'application/json','X-WP-Nonce':PFEAI.nonce}, body: JSON.stringify({content: content, title: title, provider: this.getProvider()}) }) .then(function(r){return r.json()}) .then(function(j){ if (btn) { btn.innerHTML = '✨ Generate New Meta Description'; btn.disabled = false; } if(j.meta_description) { var textarea = document.getElementById('pfeai-meta-description'); if (textarea) { textarea.value = j.meta_description; self.updateSerpPreview(); } self.startCacheWindow(false); // API call was made } else if (j.code) { alert('Error: ' + j.message); } }) .catch(function(e) { if (btn) { btn.innerHTML = '✨ Generate New Meta Description'; btn.disabled = false; } console.error(e); }); }, runFAQ: function(editor) { var self = this; var c = this.getSafeContent(editor); var t = (document.getElementById('title') || {}).value || ''; if (!c || c.length < 100) { alert('Please write or paste the article content first so the AI can generate accurate FAQs.'); return; } this.showResults('faq'); this.setLoading(true); fetch(PFEAI.rest_url + 'generate-faq?_t=' + Date.now(), { method: 'POST', headers: {'Content-Type':'application/json','X-WP-Nonce':PFEAI.nonce}, body: JSON.stringify({content: c, title: t, provider: this.getProvider()}) }) .then(function(r){ return r.json(); }) .then(function(j){ self.setLoading(false); if(j.faq && j.faq.length > 0) { self.renderFAQ(j.faq, editor); self.startCacheWindow(false); } else if (j.code) { self.faqEl.innerHTML = 'Error: ' + self.escapeHtml(j.message) + '
'; } else { self.faqEl.innerHTML = 'Failed to generate FAQ. Please try again.
'; } }) .catch(function(e) { self.setLoading(false); self.faqEl.innerHTML = 'Error: ' + self.escapeHtml(e.message) + '
'; }); }, renderFAQ: function(faqData, editor) { var self = this; if (!this.faqEl) return; this.faqEl.innerHTML = ''; var header = document.createElement('h4'); header.style.margin = '0 0 10px 0'; header.textContent = 'Review & Edit FAQ'; this.faqEl.appendChild(header); var instruction = document.createElement('p'); instruction.style.fontSize = '11px'; instruction.style.color = '#666'; instruction.textContent = 'Review your FAQs below. Clicking the button will send them down to the "Article FAQ & Schema" box at the bottom of your post screen. Remember to click the WordPress Update/Publish button to save them!'; this.faqEl.appendChild(instruction); var faqContainer = document.createElement('div'); faqContainer.id = 'pfeai-faq-inputs-container'; // Build editable inputs for each Q&A in the sidebar faqData.forEach(function(item, index) { var box = document.createElement('div'); box.style.background = '#fff'; box.style.padding = '10px'; box.style.marginBottom = '10px'; box.style.border = '1px solid #ddd'; box.style.borderRadius = '4px'; var qLabel = document.createElement('strong'); qLabel.style.fontSize = '11px'; qLabel.textContent = 'Question ' + (index + 1); box.appendChild(qLabel); var qInput = document.createElement('input'); qInput.type = 'text'; qInput.className = 'pfeai-faq-q'; qInput.style.width = '100%'; qInput.style.marginBottom = '8px'; qInput.value = item.question || ''; box.appendChild(qInput); var aLabel = document.createElement('strong'); aLabel.style.fontSize = '11px'; aLabel.textContent = 'Answer ' + (index + 1); box.appendChild(aLabel); var aInput = document.createElement('textarea'); aInput.className = 'pfeai-faq-a'; aInput.style.width = '100%'; aInput.style.height = '50px'; aInput.style.fontSize = '12px'; aInput.value = item.answer || ''; box.appendChild(aInput); faqContainer.appendChild(box); }); this.faqEl.appendChild(faqContainer); // The Button to transfer text to the Meta Box var insertBtn = document.createElement('button'); insertBtn.type = 'button'; insertBtn.className = 'button button-primary'; insertBtn.style.width = '100%'; insertBtn.innerHTML = '↓ Send to FAQ Meta Box'; insertBtn.onclick = function() { var qInputs = faqContainer.querySelectorAll('.pfeai-faq-q'); var aInputs = faqContainer.querySelectorAll('.pfeai-faq-a'); var transferCount = 0; for (var i = 0; i < qInputs.length; i++) { var qText = qInputs[i].value.trim(); var aText = aInputs[i].value.trim(); // Locate the PHP Meta Box inputs on the main post screen var metaQ = document.getElementById('pfeai_meta_q_' + i); var metaA = document.getElementById('pfeai_meta_a_' + i); if (metaQ && metaA) { metaQ.value = qText; metaA.value = aText; if (qText && aText) transferCount++; } } if (transferCount === 0) { alert('Could not locate the FAQ Meta Box on the screen. Please ensure "Article FAQ & Schema" is checked in your Screen Options at the top right.'); return; } insertBtn.innerHTML = '✓ Sent to Meta Box!'; setTimeout(function() { insertBtn.innerHTML = '↓ Send to FAQ Meta Box'; }, 2000); }; this.faqEl.appendChild(insertBtn); }, updateSerpPreview: function(selectedTitle) { var titleEl = document.getElementById('pfeai-serp-title'); var descEl = document.getElementById('pfeai-serp-desc'); var charCount = document.getElementById('pfeai-meta-char-count'); var titleCharNote = document.getElementById('pfeai-title-char-note'); var metaTextarea = document.getElementById('pfeai-meta-description'); // Update title if provided if (selectedTitle && titleEl) { titleEl.textContent = selectedTitle; // Truncate if too long (Google truncates around 60 chars) if (selectedTitle.length > 60) { titleEl.textContent = selectedTitle.substring(0, 57) + '...'; } } // Update title character count if (titleCharNote && titleEl) { var currentTitle = selectedTitle || titleEl.textContent; if (currentTitle === '(Select a title above)') currentTitle = ''; var titleLen = currentTitle.length; var titleColor = titleLen <= 60 ? '#2e7d32' : '#c62828'; titleCharNote.innerHTML = 'Title: ' + titleLen + '/60 chars ' + (titleLen <= 60 ? '✓' : '(may be truncated)'); } // Update description if (metaTextarea && descEl) { var desc = metaTextarea.value; if (desc) { descEl.textContent = desc.length > 155 ? desc.substring(0, 152) + '...' : desc; } else { descEl.textContent = '(Enter a meta description above)'; } } // Update character count with color coding if (metaTextarea && charCount) { var len = metaTextarea.value.length; var color = '#666'; var status = ''; if (len === 0) { color = '#666'; } else if (len < 120) { color = '#e65100'; // Orange - too short status = ' (too short)'; } else if (len >= 120 && len <= 155) { color = '#2e7d32'; // Green - optimal status = ' ✓ optimal'; } else if (len > 155 && len <= 160) { color = '#e65100'; // Orange - slightly long status = ' (slightly long)'; } else { color = '#c62828'; // Red - too long status = ' (will be truncated)'; } charCount.innerHTML = '' + len + ' / 155 characters' + status + ''; } }, saveToClipboard: function(title, html, redirectToNew) { fetch(PFEAI.rest_url + 'save-clipboard?_t=' + Date.now(), { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': PFEAI.nonce }, body: JSON.stringify({ title: title, content: html }) }) .then(function(){ if (redirectToNew) { // Redirect to new post page window.open('/patriots/blog/wp-admin/post-new.php', '_blank'); } else { alert('Saved! You can now access this draft in the "Saved" tab on any new post.'); location.reload(); } }); }, clearClipboard: function() { fetch(PFEAI.rest_url + 'clear-clipboard?_t=' + Date.now(), { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': PFEAI.nonce } }) .then(function(r) { return r.json(); }) .then(function(json) { if (json.success) { alert('Saved draft cleared.'); location.reload(); } }) .catch(function(e) { console.error(e); }); }, clearIdeasCache: function() { fetch(PFEAI.rest_url + 'clear-ideas-cache?_t=' + Date.now(), { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': PFEAI.nonce }, body: JSON.stringify({ post_id: PFEAI.post_id }) }) .then(function(r) { return r.json(); }) .then(function(json) { if (json.success) { alert('Topics cache cleared.'); location.reload(); } }) .catch(function(e) { console.error(e); }); }, togglePanel: function(forceState) { if (!this.panelEl) return; var open = this.panelEl.classList.contains('pfeai-open'); var shouldOpen = (typeof forceState === 'boolean') ? forceState : !open; if (shouldOpen) { this.panelEl.classList.add('pfeai-open'); document.body.classList.add('pfeai-panel-open'); this.updateTranscriptVisibility(); if (this.isMinimized) document.body.classList.add('pfeai-panel-minimized'); else document.body.classList.remove('pfeai-panel-minimized'); } else { this.panelEl.classList.remove('pfeai-open'); document.body.classList.remove('pfeai-panel-open'); } }, showMenu: function() { this.panelEl.classList.remove('pfeai-show-results'); }, showResults: function(activeTab) { this.panelEl.classList.add('pfeai-show-results'); if (activeTab) this.switchTab(activeTab); }, updateTranscriptVisibility: function() { var isTranscript = false; var labels = document.querySelectorAll('.categorychecklist label'); for (var i = 0; i < labels.length; i++) { if (labels[i].innerText.indexOf('Patriots Transcripts') !== -1) { var input = labels[i].querySelector('input[type="checkbox"]'); if (input && input.checked) isTranscript = true; } } var btn = document.getElementById('btn-transcript'); var tab = document.getElementById('tab-transcript'); if (isTranscript) { if (btn) btn.style.display = 'block'; if (tab) tab.style.display = 'inline-block'; } else { if (btn) btn.style.display = 'none'; if (tab) tab.style.display = 'none'; } }, preloadSavedData: function() { if (!PFEAI.saved_data) return; if (PFEAI.saved_data.transcript && PFEAI.saved_data.transcript.suggestions) { this.renderTranscriptSuggestions(PFEAI.saved_data.transcript.suggestions, ''); this.setCachedState('btn-transcript', 'View Transcript Results', 'transcript', PFEAI.saved_data.transcript.generated_at); } if (PFEAI.saved_data.analysis && PFEAI.saved_data.analysis.highlights) { this.renderAnalysis(PFEAI.saved_data.analysis); this.setCachedState('btn-scan', 'View Analysis Results', 'highlights', PFEAI.saved_data.analysis.generated_at); } if (PFEAI.saved_data.titles && PFEAI.saved_data.titles.titles) { this.renderTitles(PFEAI.saved_data.titles); this.setCachedState('btn-titles', 'View Titles', 'titles', PFEAI.saved_data.titles.generated_at); } if (PFEAI.saved_data.ideas && PFEAI.saved_data.ideas.ideas) { this.renderIdeasInteractive(PFEAI.saved_data.ideas); this.setCachedState('btn-ideas', 'View Topics', 'ideas', PFEAI.saved_data.ideas.generated_at); } if (PFEAI.saved_data.social && PFEAI.saved_data.social.tweets) { this.renderSocialPack(PFEAI.saved_data.social); this.setCachedState('btn-social', 'View Social Posts', 'social', PFEAI.saved_data.social.generated_at); } if (PFEAI.saved_data.clipboard && this.savedEl) { this.renderSavedDraft(PFEAI.saved_data.clipboard); } if (PFEAI.saved_data.fact_check && PFEAI.saved_data.fact_check.factChecks) { this.renderFacts(PFEAI.saved_data.fact_check.factChecks); this.setCachedState('btn-fact-check', 'View Fact Check', 'facts', PFEAI.saved_data.fact_check.generated_at); } if (PFEAI.saved_data.links && PFEAI.saved_data.links.links) { this.renderInternalLinks(PFEAI.saved_data.links, null); this.setCachedState('btn-internal-links', '\uD83D\uDD17 View Links', 'links', PFEAI.saved_data.links.generated_at); } // Always render the SEO tab this.renderSeoTab(); // Render the Search Insights data this.renderGSCPanel(); }, setCachedState: function(btnId, newText, tabName, timestamp) { var btn = document.getElementById(btnId); if (btn) { if (btnId === 'btn-transcript' || btnId === 'btn-ideas' || btnId === 'btn-social') { btn.innerHTML = '' + this.escapeHtml(newText) + '' + self.escapeHtml(p) + '
'; }).join('\n'); tinymce.activeEditor.insertContent(html); insertDraftsBtn.textContent = 'Inserted!'; setTimeout(function() { insertDraftsBtn.textContent = 'Insert All Drafts into Editor'; }, 1500); } }; draftsDiv.appendChild(insertDraftsBtn); // Copy all drafts button var copyDraftsBtn = document.createElement('button'); copyDraftsBtn.type = 'button'; copyDraftsBtn.className = 'button button-small'; copyDraftsBtn.style.marginTop = '8px'; copyDraftsBtn.textContent = 'Copy All Drafts'; copyDraftsBtn.onclick = function() { var allDrafts = (data.draft_paragraphs || []).join('\n\n'); navigator.clipboard.writeText(allDrafts); copyDraftsBtn.textContent = 'Copied!'; setTimeout(function() { copyDraftsBtn.textContent = 'Copy All Drafts'; }, 1500); }; draftsDiv.appendChild(copyDraftsBtn); container.appendChild(draftsDiv); } var kpLabel = document.createElement('strong'); kpLabel.textContent = 'Key Points:'; container.appendChild(kpLabel); var kpList = document.createElement('ul'); kpList.className = 'pfeai-list'; (data.key_points || []).forEach(function(pt) { var li = document.createElement('li'); li.textContent = pt; // SECURITY: textContent escapes HTML kpList.appendChild(li); }); container.appendChild(kpList); var sparkDiv = document.createElement('div'); sparkDiv.style.marginTop = '10px'; sparkDiv.style.borderTop = '1px solid #eee'; sparkDiv.style.paddingTop = '10px'; var sparkLabel = document.createElement('strong'); sparkLabel.textContent = 'Spark Sentences:'; sparkDiv.appendChild(sparkLabel); var sparkText = (data.spark_sentences || []).join('\n\n'); var textarea = document.createElement('textarea'); textarea.id = 'pfeai-spark-text'; textarea.style.width = '100%'; textarea.style.height = '100px'; textarea.style.fontSize = '12px'; textarea.style.marginTop = '5px'; textarea.value = sparkText; // SECURITY: .value doesn't execute HTML sparkDiv.appendChild(textarea); var btnDiv = document.createElement('div'); btnDiv.style.marginTop = '8px'; btnDiv.style.display = 'flex'; btnDiv.style.gap = '10px'; var insertBtn = document.createElement('button'); insertBtn.type = 'button'; insertBtn.className = 'button button-small button-primary'; insertBtn.id = 'pfeai-insert-spark'; insertBtn.textContent = 'Insert into Editor'; btnDiv.appendChild(insertBtn); var saveBtn = document.createElement('button'); saveBtn.type = 'button'; saveBtn.className = 'button button-small'; saveBtn.style.background = '#e8f5e9'; saveBtn.style.color = '#2e7d32'; saveBtn.style.borderColor = '#66bb6a'; saveBtn.id = 'pfeai-save-clipboard'; saveBtn.innerHTML = '📝 Start New Post →'; btnDiv.appendChild(saveBtn); sparkDiv.appendChild(btnDiv); container.appendChild(sparkDiv); resDiv.innerHTML = ''; resDiv.appendChild(container); resDiv.scrollIntoView({behavior:'smooth'}); // Bind events insertBtn.onclick = function(){ var wt = document.getElementById('title'); if(wt) { wt.value = title; wt.focus(); var pt = document.getElementById('title-prompt-text'); if(pt) pt.classList.add('screen-reader-text'); } if(typeof tinymce !== 'undefined' && tinymce.activeEditor) { var t = document.getElementById('pfeai-spark-text').value; tinymce.activeEditor.insertContent('' + self.escapeHtml(t).replace(/\n\n/g, '
') + '
'); } }; saveBtn.onclick = function(){ var fullHTML = ''; if(data.blurb) fullHTML += 'Blurb: ' + self.escapeHtml(data.blurb) + '
'; // Include key quotes in saved draft if(data.key_quotes && data.key_quotes.length) { fullHTML += 'Key Quotes:
Draft Paragraphs:
'; data.draft_paragraphs.forEach(function(p){ fullHTML += '' + self.escapeHtml(p) + '
'; }); } if(data.key_points && data.key_points.length) { fullHTML += 'Key Points:
' + self.escapeHtml(sparkVal).replace(/\n\n/g, '
') + '
'; // Save and redirect to new post self.saveToClipboard(title, fullHTML, true); }; }, /** * SECURITY FIX: renderIdeasInteractive now uses proper escaping */ renderIdeasInteractive: function(data) { var self = this; this.ideasEl.innerHTML = ''; var h3 = document.createElement('h3'); h3.textContent = 'Topic Inspiration'; this.ideasEl.appendChild(h3); var p = document.createElement('p'); p.style.fontSize = '11px'; p.style.marginBottom = '10px'; p.textContent = 'Click a topic to generate an outline.'; this.ideasEl.appendChild(p); (data.ideas || []).forEach(function(i) { var c = document.createElement('div'); c.className = 'pfeai-card pfeai-clickable'; c.style.cursor = 'pointer'; c.style.borderLeftColor = '#8e44ad'; var h = document.createElement('h4'); h.style.margin = '0 0 4px 0'; h.style.color = '#8e44ad'; h.textContent = i.title; // SECURITY: textContent escapes HTML c.appendChild(h); var pEl = document.createElement('p'); pEl.style.fontSize = '12px'; pEl.textContent = i.context || i.angle || ''; // SECURITY: textContent escapes HTML c.appendChild(pEl); var sr = document.createElement('div'); sr.className = 'pfeai-score-row'; sr.style.marginTop = '6px'; sr.style.borderTop = '1px solid #eee'; sr.style.paddingTop = '4px'; sr.innerHTML = 'Trend: ' + parseInt(i.trend_score || i.ctr_score || 0, 10) + 'Disc: ' + parseInt(i.discover_score || 0, 10) + ''; c.appendChild(sr); (function(title, context) { c.addEventListener('click', function() { self.runIdeaOutline(title, context); }); })(i.title, i.context); self.ideasEl.appendChild(c); }); var rd = document.createElement('div'); rd.id = 'pfeai-idea-result'; rd.style.marginTop = '15px'; rd.style.borderTop = '1px solid #ccc'; rd.style.paddingTop = '10px'; this.ideasEl.appendChild(rd); }, /** * SECURITY FIX: renderIdeaOutlineResult now uses proper escaping */ renderIdeaOutlineResult: function(data, title) { var self = this; var resDiv = document.getElementById('pfeai-idea-result'); // Build DOM elements instead of innerHTML for safety var container = document.createElement('div'); container.style.background = '#fff'; container.style.padding = '10px'; container.style.border = '1px solid #ddd'; container.style.marginTop = '10px'; var h3 = document.createElement('h3'); h3.style.marginTop = '0'; h3.style.color = '#8e44ad'; h3.textContent = title; // SECURITY: textContent escapes HTML container.appendChild(h3); var blurbLabel = document.createElement('strong'); blurbLabel.textContent = 'Blurb:'; container.appendChild(blurbLabel); var blurbP = document.createElement('p'); blurbP.textContent = data.blurb || ''; // SECURITY: textContent escapes HTML container.appendChild(blurbP); // DRAFT PARAGRAPHS SECTION if (data.draft_paragraphs && data.draft_paragraphs.length > 0) { var draftsDiv = document.createElement('div'); draftsDiv.style.marginTop = '12px'; draftsDiv.style.marginBottom = '12px'; draftsDiv.style.padding = '10px'; draftsDiv.style.background = '#f3e5f5'; draftsDiv.style.borderLeft = '4px solid #9c27b0'; draftsDiv.style.borderRadius = '4px'; var draftsLabel = document.createElement('strong'); draftsLabel.style.display = 'block'; draftsLabel.style.marginBottom = '8px'; draftsLabel.style.color = '#7b1fa2'; draftsLabel.innerHTML = '✍ Draft Paragraphs:'; draftsDiv.appendChild(draftsLabel); var paragraphLabels = ['Opening', 'Middle/Analysis', 'Context/Closing']; (data.draft_paragraphs || []).forEach(function(para, idx) { var paraContainer = document.createElement('div'); paraContainer.style.marginBottom = '12px'; paraContainer.style.padding = '8px'; paraContainer.style.background = '#fff'; paraContainer.style.borderRadius = '4px'; paraContainer.style.border = '1px solid #e1bee7'; var paraLabel = document.createElement('div'); paraLabel.style.fontSize = '10px'; paraLabel.style.color = '#8e44ad'; paraLabel.style.marginBottom = '4px'; paraLabel.style.fontWeight = 'bold'; paraLabel.textContent = (paragraphLabels[idx] || 'Paragraph ' + (idx + 1)) + ':'; paraContainer.appendChild(paraLabel); var paraText = document.createElement('p'); paraText.style.margin = '0'; paraText.style.fontSize = '12px'; paraText.style.lineHeight = '1.5'; paraText.style.color = '#333'; paraText.textContent = para; paraContainer.appendChild(paraText); // Copy button for individual paragraph var copyParaBtn = document.createElement('button'); copyParaBtn.type = 'button'; copyParaBtn.className = 'button button-small'; copyParaBtn.style.marginTop = '6px'; copyParaBtn.style.fontSize = '10px'; copyParaBtn.textContent = 'Copy'; (function(text, btn) { btn.onclick = function() { navigator.clipboard.writeText(text); btn.textContent = 'Copied!'; setTimeout(function() { btn.textContent = 'Copy'; }, 1500); }; })(para, copyParaBtn); paraContainer.appendChild(copyParaBtn); draftsDiv.appendChild(paraContainer); }); // Insert all drafts button var insertDraftsBtn = document.createElement('button'); insertDraftsBtn.type = 'button'; insertDraftsBtn.className = 'button button-small button-primary'; insertDraftsBtn.style.marginTop = '8px'; insertDraftsBtn.style.marginRight = '8px'; insertDraftsBtn.textContent = 'Insert All Drafts into Editor'; insertDraftsBtn.onclick = function() { if(typeof tinymce !== 'undefined' && tinymce.activeEditor) { var html = (data.draft_paragraphs || []).map(function(p) { return '' + self.escapeHtml(p) + '
'; }).join('\n'); tinymce.activeEditor.insertContent(html); insertDraftsBtn.textContent = 'Inserted!'; setTimeout(function() { insertDraftsBtn.textContent = 'Insert All Drafts into Editor'; }, 1500); } }; draftsDiv.appendChild(insertDraftsBtn); // Copy all drafts button var copyDraftsBtn = document.createElement('button'); copyDraftsBtn.type = 'button'; copyDraftsBtn.className = 'button button-small'; copyDraftsBtn.style.marginTop = '8px'; copyDraftsBtn.textContent = 'Copy All Drafts'; copyDraftsBtn.onclick = function() { var allDrafts = (data.draft_paragraphs || []).join('\n\n'); navigator.clipboard.writeText(allDrafts); copyDraftsBtn.textContent = 'Copied!'; setTimeout(function() { copyDraftsBtn.textContent = 'Copy All Drafts'; }, 1500); }; draftsDiv.appendChild(copyDraftsBtn); container.appendChild(draftsDiv); } var kpLabel = document.createElement('strong'); kpLabel.textContent = 'Key Points:'; container.appendChild(kpLabel); var kpList = document.createElement('ul'); kpList.className = 'pfeai-list'; (data.key_points || []).forEach(function(pt) { var li = document.createElement('li'); li.textContent = pt; // SECURITY: textContent escapes HTML kpList.appendChild(li); }); container.appendChild(kpList); var sparkDiv = document.createElement('div'); sparkDiv.style.marginTop = '10px'; sparkDiv.style.borderTop = '1px solid #eee'; sparkDiv.style.paddingTop = '10px'; var sparkLabel = document.createElement('strong'); sparkLabel.textContent = 'Spark Sentences:'; sparkDiv.appendChild(sparkLabel); var sparkText = (data.spark_sentences || []).join('\n\n'); var textarea = document.createElement('textarea'); textarea.id = 'pfeai-idea-spark-text'; textarea.style.width = '100%'; textarea.style.height = '100px'; textarea.style.fontSize = '12px'; textarea.style.marginTop = '5px'; textarea.value = sparkText; // SECURITY: .value doesn't execute HTML sparkDiv.appendChild(textarea); var btnDiv = document.createElement('div'); btnDiv.style.marginTop = '8px'; btnDiv.style.display = 'flex'; btnDiv.style.gap = '10px'; var insertBtn = document.createElement('button'); insertBtn.type = 'button'; insertBtn.className = 'button button-small button-primary'; insertBtn.id = 'pfeai-insert-idea-spark'; insertBtn.textContent = 'Insert into Editor'; btnDiv.appendChild(insertBtn); var saveBtn = document.createElement('button'); saveBtn.type = 'button'; saveBtn.className = 'button button-small'; saveBtn.style.background = '#e8f5e9'; saveBtn.style.color = '#2e7d32'; saveBtn.style.borderColor = '#66bb6a'; saveBtn.id = 'pfeai-save-idea-clipboard'; saveBtn.innerHTML = '📝 Start New Post →'; btnDiv.appendChild(saveBtn); sparkDiv.appendChild(btnDiv); container.appendChild(sparkDiv); resDiv.innerHTML = ''; resDiv.appendChild(container); resDiv.scrollIntoView({behavior:'smooth'}); // Bind events insertBtn.onclick = function(){ var wt = document.getElementById('title'); if(wt) { wt.value = title; wt.focus(); var pt = document.getElementById('title-prompt-text'); if(pt) pt.classList.add('screen-reader-text'); } if(typeof tinymce !== 'undefined' && tinymce.activeEditor) { var t = document.getElementById('pfeai-idea-spark-text').value; tinymce.activeEditor.insertContent('' + self.escapeHtml(t).replace(/\n\n/g, '
') + '
'); } }; saveBtn.onclick = function(){ var fullHTML = ''; if(data.blurb) fullHTML += 'Blurb: ' + self.escapeHtml(data.blurb) + '
'; // Include draft paragraphs in saved draft if(data.draft_paragraphs && data.draft_paragraphs.length) { fullHTML += 'Draft Paragraphs:
'; data.draft_paragraphs.forEach(function(p){ fullHTML += '' + self.escapeHtml(p) + '
'; }); } if(data.key_points && data.key_points.length) { fullHTML += 'Key Points:
' + self.escapeHtml(sparkVal).replace(/\n\n/g, '
') + '
'; // Save and redirect to new post self.saveToClipboard(title, fullHTML, true); }; }, /** * SECURITY FIX: renderSavedDraft now uses proper escaping */ renderSavedDraft: function(clip) { var self = this; if(!this.savedEl) return; var cleanText = (clip.content || '') .replace(//g, '').replace(/<\/p>/g, '\n\n')
.replace(//g, '').replace(/<\/strong>/g, '')
.trim();
// Build DOM elements instead of innerHTML
var container = document.createElement('div');
container.style.background = '#e8f5e9';
container.style.padding = '10px';
container.style.border = '1px solid #c8e6c9';
var h3 = document.createElement('h3');
h3.style.marginTop = '0';
h3.style.color = '#2e7d32';
h3.textContent = 'Saved Draft';
container.appendChild(h3);
// Content first
var contentDiv = document.createElement('div');
var contentLabel = document.createElement('strong');
contentLabel.textContent = 'Content:';
contentDiv.appendChild(contentLabel);
contentDiv.appendChild(document.createElement('br'));
var contentTextarea = document.createElement('textarea');
contentTextarea.id = 'pfeai-saved-content';
contentTextarea.style.width = '100%';
contentTextarea.style.height = '300px';
contentTextarea.style.fontSize = '12px';
contentTextarea.value = cleanText; // SECURITY: .value doesn't execute HTML
contentDiv.appendChild(contentTextarea);
container.appendChild(contentDiv);
// Title second
var titleDiv = document.createElement('div');
titleDiv.style.marginTop = '10px';
titleDiv.style.marginBottom = '10px';
var titleLabel = document.createElement('strong');
titleLabel.textContent = 'Title:';
titleDiv.appendChild(titleLabel);
titleDiv.appendChild(document.createElement('br'));
var titleInput = document.createElement('input');
titleInput.type = 'text';
titleInput.id = 'pfeai-saved-title';
titleInput.value = clip.title || ''; // SECURITY: .value doesn't execute HTML
titleInput.style.width = '100%';
titleInput.style.fontSize = '12px';
titleDiv.appendChild(titleInput);
container.appendChild(titleDiv);
// Button container
var btnContainer = document.createElement('div');
btnContainer.style.marginTop = '10px';
btnContainer.style.display = 'flex';
btnContainer.style.gap = '10px';
var insertBtn = document.createElement('button');
insertBtn.type = 'button';
insertBtn.className = 'button button-primary';
insertBtn.id = 'pfeai-insert-saved';
insertBtn.textContent = 'Insert into Editor';
btnContainer.appendChild(insertBtn);
var clearBtn = document.createElement('button');
clearBtn.type = 'button';
clearBtn.className = 'button';
clearBtn.style.background = '#ffebee';
clearBtn.style.color = '#c62828';
clearBtn.style.borderColor = '#ef9a9a';
clearBtn.textContent = 'Clear Saved Draft';
btnContainer.appendChild(clearBtn);
container.appendChild(btnContainer);
this.savedEl.innerHTML = '';
this.savedEl.appendChild(container);
insertBtn.onclick = function() {
var t = document.getElementById('pfeai-saved-title').value;
var c = clip.content; // Use original HTML content (already sanitized by server)
var wt = document.getElementById('title');
if(wt) {
wt.value = t;
wt.focus();
var pt = document.getElementById('title-prompt-text');
if(pt) pt.classList.add('screen-reader-text');
}
if(typeof tinymce !== 'undefined' && tinymce.activeEditor){
tinymce.activeEditor.setContent(c);
}
};
clearBtn.onclick = function() {
if (confirm('Are you sure you want to clear your saved draft? This cannot be undone.')) {
self.clearClipboard();
}
};
},
toggleMinimize: function() {
if (!this.panelEl) return;
this.isMinimized = !this.isMinimized;
this.panelEl.classList.toggle('pfeai-minimized', this.isMinimized);
document.body.classList.toggle('pfeai-panel-minimized', this.isMinimized);
},
setLoading: function(isLoading) { if(this.loadingEl) this.loadingEl.style.display = isLoading ? 'block' : 'none'; },
switchTab: function(tabName) {
if (!this.panelEl) return;
this.panelEl.querySelectorAll('.pfeai-tab').forEach(function(b) {
b.classList.toggle('pfeai-tab-active', b.getAttribute('data-tab') === tabName);
});
this.panelEl.querySelectorAll('.pfeai-tab-content').forEach(function(c) {
c.style.display = (c.getAttribute('data-tab') === tabName) ? 'block' : 'none';
});
// Refresh SEO tab to get current title when switching to it
if (tabName === 'seo') {
this.renderSeoTab();
}
},
/**
* Internal Link Suggester
* Fetches candidate posts from WP via the REST endpoint, sends to LLM for ranking,
* then renders cards with one-click TinyMCE link insertion.
*/
runInternalLinks: function(editor, forceRefresh) {
var self = this;
var title = (document.getElementById('title') || {}).value || '';
var contentHtml = this.getSafeContent(editor);
if (!contentHtml || contentHtml.length < 100) {
alert('Please write or paste article content first.');
return;
}
if (!PFEAI.post_id) {
alert('Please save the post as a draft first. WordPress needs a Post ID to match categories and tags for link candidates.');
return;
}
this.showResults('links');
this.setLoading(true);
fetch(PFEAI.rest_url + 'internal-links?_t=' + Date.now(), {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': PFEAI.nonce },
body: JSON.stringify({
content: contentHtml,
title: title,
post_id: PFEAI.post_id,
provider: this.getProvider(),
force_refresh: forceRefresh || false
})
})
.then(function(r) { return r.json(); })
.then(function(json) {
self.setLoading(false);
if (json.links && json.links.length > 0) {
self.renderInternalLinks(json, editor);
var timestamp = json.generated_at || Date.now()/1000;
self.setCachedState('btn-internal-links', '\uD83D\uDD17 View Links', 'links', timestamp, json.from_cache);
self.startCacheWindow(json.from_cache);
} else if (json.code) {
self.linksEl.innerHTML = ' Error: ' + self.escapeHtml(json.message) + ' No strong internal link opportunities found for this article. Error: ' + self.escapeHtml(e.message) + ' No "striking distance" keywords found for this URL yet. Keywords ranking #7-#20. Push these to Page 1! ' + self.escapeHtml(json.suggestion) + ' ' + self.escapeHtml(json.suggestion) + ' The New England Patriots<\/strong><\/a> take on the Pittsburgh Steelers in the NFL 2015 season opener on NBC. Al Michaels and Cris Collinsworth will be on the call with Michelle Tafoya handling sideline duties. Kickoff is slated for 8:30 p.m.<\/p>\n The Patriots (12-4) won the AFC East and defeated Baltimore in the divisional round of the playoffs, Indianapolis in the AFC Championship and Seattle in the Super Bowl for their fourth title in the Belichick reign.<\/p>\n The Steelers (11-5) won the tough AFC North title but lost in the Wild Card round to Baltimore. With one of the more potent offenses in the league, the Steelers will be looking to make a deep playoff run.<\/p>\n We here at\u00a0PatsFans.com<\/a>\u00a0are continuing our \u00a0\u201cRazor\u2019s Edge\u201d<\/strong>\u00a0 column in 2015 to give a quick break down on some of the key matchups of the game and what you can look for in how the game plays out.<\/p>\n Series History:<\/strong> Here is a look at some of the key matchups and who holds the Razor\u2019s Edge.<\/p>\n Who gets the nod on offense, defense and special teams in the Patriots – Steelers game on Thursday night? Read on and check it out.<\/p>\n First up is the Patriots offense:<\/p>\n Patriots RBs vs Steelers Front Seven – <\/strong>Razor\u2019s Edge Pittsburgh LaGarrette Blount is suspended for the opener, James Develin is on IR and Jonas Gray is on the Dolphins practice squad. The only big back is Brandon Bolden who is mainly a STs core player. That leaves change of pace backs James White, Dion Lewis and Travaris Cadet.<\/p>\n This is a game where the Patriots probably won\u2019t have 20 carries.<\/p>\n Patriots WRs vs Steelers Secondary – <\/strong>Razor\u2019s Edge Patriots The Patriots should have a big, big edge here provided they protect Tom Brady in the game. Pittsburgh was 27th<\/sup> against the pass in 2014 and are going through wholesale changes with a new defensive coordinator and with many new faces on defense, they are going to take awhile to get their act together.<\/p>\n Rob Gronkowski is the player to watch on the Patriots offense; he\u2019s a matchup nightmare regardless of the opponent. But facing the Steelers, he has a decided advantage against either safety. Look for him in motion or split wide a lot in this one as Brady and the offense create space underneath for Julian Edelman and Scott Chandler<\/p>\n Next up the Pittsburgh offense:<\/p>\n Steelers RBs vs NE Front Seven –\u00a0<\/strong>Razor\u2019s Edge Patriots The Patriots will be without Vince Wilfork this season but Alan Branch, Sealver Siliga as well as rookie Malcom Brown should be up to the task inside.<\/p>\n Steelers WRs vs NE Secondary- <\/strong>Razor\u2019s Edge Pittsburgh The Patriots secondary is still a question mark with no Revis, Browner and Arrington. Malcolm Butler and Tarell Brown will attempt to hold down the fort. The front seven pass rush this season will get its first test against a good, solid offensive line. Jabaal Sheard will be the guy to watch for New England.<\/p>\n Special Teams- <\/strong>Razor\u2019s Edge Patriots The Patriots have returning kickers Stephen Gostkowski and Ryan Allen who are very dependable. Julian Edelman, if healthy is a dangerous punter returner and the Patriots coverage units are always solid.<\/p>\n Prediction: The last meeting had nearly 1150 yards of offense between the two teams; this one should be no different. This should be a high-scoring, entertaining affair with both teams being able to move the football.<\/p>\n In the end Brady and the Patriots STs at home will have a tad more production going against a Pittsburgh defense that probably won\u2019t find its way until mid-way thru the season. Patriots 35-31<\/strong><\/p>\n Follow me on Twitter\u00a0<\/em>@SteveB7SFG<\/em><\/a>\u00a0or email me at\u00a0<\/em>stevebpatsfans@gmail.com<\/em><\/a><\/p>\n Listen to our\u00a0<\/em>Patriots 4th<\/sup>\u00a0and 2<\/em><\/a>\u00a0podcast on blog talk radio as the writers Russ Goldman, Derek Havens and I from\u00a0<\/em>PatsFans.com<\/em><\/a>\u00a0discuss the latest Patriots news Wednesdays at 12 noon.<\/em><\/p>\n","protected":false},"excerpt":{"rendered":" Who gets the nod on offense, defense and special teams in the Patriots – Steelers game on Thursday night? Read on and check it out.<\/p>\n","protected":false},"author":9,"featured_media":974,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[11,10],"tags":[185,206,205,34,230,231,211,209,17,207,229,214,44,70,8,38,201,117,36,234,13,213],"class_list":["post-1047","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-patriots-commentary","category-patriots-news","tag-2015-nfl-season","tag-antonio-brown","tag-ben-roethlisberger","tag-bill-belichick","tag-deangelo-williams","tag-dri-archer","tag-heath-miller","tag-james-harrison","tag-julian-edelman","tag-leveon-bell","tag-martavis-bryant","tag-mike-mitchell","tag-new-england-patriots","tag-nfl","tag-patriots","tag-patriots-edge-rushers","tag-pittsburgh-steelers","tag-rob-gronkowski","tag-rob-ninkovich","tag-tarell-brown","tag-tom-brady","tag-will-allen"],"yoast_head":"\n
(Google data usually lags by 48 hours). Striking Distance
';
html += '
';
html += 'Suggested Addition:
\nThe Patriots and Steelers have met 27 times with the Steelers holding an all-time 15-12 edge including a 3-0 record in Overtime. The teams are 4-4 in games at New England and have split the last four meetings. The last time they met in November 2013, the Patriots blew open a close game in the 4th<\/sup> quarter for a 55-31 victory. The teams combined for over 1130 yards of offense in that game.<\/p>\n
\n<\/em>While the Steelers defense is going through changes, the one thing that hasn\u2019t changed is that the Steelers run defense remains one of the best in the league. Pittsburgh was 6th<\/sup> against the run in the NFL in 2014 playing in the tough, physical AFC North.<\/p>\n
\n<\/em>Spread ’em out and air it out….<\/p>\n
\n<\/em>The Steelers don\u2019t have Le\u2019Veon Bell who is suspended for the first three games of the season but in DeAngelo Williams and Dri Archer, they have both a big back and a change of pace runner that can be effective. Missing is C Maurkice Pouncey who had surgery last week and would be a big key to the push inside.<\/p>\n
\n<\/em>Ben Roethlisberger had a fantastic 2014 and averaged over 300 yards a game passing. Antonio Brown is his go-to guy and will be a handful for whoever covers him. With speedster Martavis Bryant suspended, Pittsburgh will used rookie Sammie Coates who can fly as well as Markus Wheaton and Darrius Heyward-Bey. Old hand Heath Miller remains a solid safety valve at tight end.<\/p>\n
\n<\/em>The Steelers lost two kickers due to injury in the preseason and now have Josh Scobee who came over from Jacksonville. Jordan Berry is the new punter this season. Return men Brown, Wheaton and Archer are good but not spectacular.<\/p>\n
\n<\/strong>The stadium will be rocking and the atmosphere electric on Thursday night in Foxboro. Between the unfurling of the Super Bowl banner and having Tom Brady available after his suspension was vacated by Judge Berman in court.<\/p>\n