Five Thoughts After Sunday’s Patriots Loss in Tennessee: Maye’s Got the “It” Factor
The Titans outlasted the Patriots' defense on Sunday during New England's 20-17 overtime loss.
(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) + '
HOME
>
Patriots Blog
>
Patriots Commentary
Heading into Sunday’s game, the New England Patriots certainly seemed like they were in a position to beat a woeful 1-6 Titans team and make it two-straight wins for the first time all year. Instead, New England ended up continuing its streak of making mistakes in key moments, which ultimately allowed Tennessee to walk away with a 20-17 overtime win at home, handing the Patriots their 7th loss of the season. Here are some thoughts coming off of yesterday: 1) Maye’s got the “it” this team has needed: If anyone thought Maye would be more conservative after ending up in the concussion protocol last week, the rookie certainly didn’t show any hesitation yesterday as he repeatedly took matters into his own hands. Maye racked up 95 yards on 8 rushes on the ground, with the rookie taking off each time he saw no one was open to throw to. He showed zero hesitation and definitely didn’t shy away from any contact, including one play where he dropped his shoulder to pick up whatever additional yards he could muster. As a result, it was obvious that if there was any thought about dialing it back, that didn’t appear to be an option. After the game reporters asked Maye if there was any hesitation to run despite the hit he took against the Jets. Maye immediately brushed off the question and shot down any notion of concern. “Yeah, never,” said Maye. “I should have probably broke that first one. Had one-on-one with the safety. I got to make somebody miss, man. So that’s on me.” The fact Maye actually believes he should have been able to slip the coverage and head off to the races was eye-opening, showing off just how competitive the rookie really is. Maye also had a fairly solid game throwing the football, finishing Sunday’s game 29-of-41 (70.7%) for 206 yards along with a touchdown and two interceptions. The first interception happened when he was hit as he was letting go of the football on a pass across the middle of the field. The second was obviously the game-ending pick he threw on a deep ball to Kayshon Boutte that he threw into the wind and didn’t quite get enough on. The one encouraging factor was that Maye made the right read. Boutte had gotten behind the defender and had Maye been able to get more on the throw to allow Boutte to stay vertical, it might have potentially been a game-winning touchdown. Instead, the wind knocked the pass down, and the Titans ended up with the turnover to end the game. Still, Maye admitted that given the fact it was a first down play, it was a risk he shouldn’t have taken. “First down and see the safety. I think we’re throwing into the wind. I got to put some more on it,” said Maye. “Just a dumb decision. Something you’d like to have back, especially in that situation. We’re going to go at least tie it up. We’re on, like I said, our own 40. Sometimes the best players throw it away. It’s a bad decision on my part.” When it comes to whether or not Maye should have been more careful when he did run, the rookie explained that it’s a tough balance but when it comes to how he’s going to play, he’s not going to change who he is. “I tried to slide head first a little bit. Sometimes I think that’s just as dangerous diving at people’s legs,” said Maye. “But I’m not going to change the player that I am. When I’m past the protocol and cleared, I’m going to be the player. If they’re dropping out guys and there’s some rush lanes up front, I’m going to make them pay. That’s my mindset. Maybe I’ll make some better throws and maybe I’ll miss some guys, but at the same time, I’m going to play how I’m going to play.” The “I’m going to make them pay” comment is exactly what you want to hear from your starting quarterback. One final quote that stood out is the fact that Maye admitted he’s still a work in progress, and he’s already shown more humility and self-awareness than former Patriots QB, Mac Jones. When asked whether or not he’s getting more comfortable as a player, Maye admitted that he’s making progress but that he’s still got some work to do. “Yeah, no doubt,” said Maye. “I think sometimes I get a little hoppy back there in the pocket but I feel like I’ve always felt pressure well and moved well, and that’s not really something that… I feel like I did that well in college, so just trying to keep on doing the same.” For now, the one bright spot, despite how things have gone so far, is they seem to have finally found the right guy to lead them moving forward. Maye’s a kid who has that killer instinct, and he went for the jugular on Sunday on that final throw. Unfortunately, that gust from the Gods caused this one to go the other way. But if his trajectory continues where it’s at, the offense might soon get to the point where the Patriots may no longer find themselves looking for a last-gasp effort. With 9 games left, Maye has plenty of time to grow and judging by what we’ve already seen, it feels likely that he’ll at least help his football team potentially finish on a high note. 2) The running game is stuck in neutral: One thing that was clearly an issue on Sunday was the fact the Patriots ground game wasn’t able to do much of anything against the Titans’ defense. It was obvious that Tennessee came into this game with the intent of shutting down the run and forcing Maye to beat them. While the rookie played relatively well and gave them a shot at the end, the fact things were as bad as they were in the run game was staggering. Rhamondre Stevenson finished the contest carrying 10 times for just 16 yards (1.6 avg), along with a rushing touchdown and a receiving touchdown. But overall, he wasn’t able to find any daylight and gained more than 2 yards just three times the entire contest. To make matters worse, the playcalling certainly didn’t help. The Titans made it apparent that they knew the Patriots were a team that liked to run the football on first down, with five of Rhamondre Stevenson’s six first-half rushes all coming on first down for just 8-yards (1.6 avg). Overall, in the first half, they held Stevenson to six carries for just 7 yards, while JaMycal Hasty carried once for no gain. In the second half, they finally started mixing things up, with Stevenson carrying just twice on first down for 5 yards, while Antonio Gibson had one rush that went for -1 yards. From there, Stevenson carried twice for four yards on second down, including a touchdown. But overall, of the 7 total rushing first downs the club had for the entire game, Maye was responsible for 5 of them, while Stevenson had two – one of which was his touchdown (the NFL counts a touchdown as a first down conversion on the stat sheet). “You never really want your quarterback to be the leading rusher, and that’s in all facets,” said Jerod Mayo after the game. “We got to be able to move guys off the line.” They didn’t do that on Sunday, and it definitely played a factor for an offense that battled all afternoon. 3) Untimely penalties continue to be an issue: The Patriots continue to kill themselves with untimely penalties, which ended up being the case again on Sunday. New England finished Sunday’s game with 8 penalties for 58 yards, several of which put them in long yardage and made things tough for an offense that already doesn’t have much margin for error. The most glaring example came early in the third quarter on their first offensive possession of the second half. Ben Brown was called for a holding penalty on a 3rd-and-5, which came after Maye took off and picked up 5 yards to seemingly move the chains. Instead, the play was called back, and they faced a 3rd-and-15. Fortunately, Maye willed his way to a 22-yard run that again moved the sticks, bailing them out of what seemed to be a tough situation. However, Jalen Polk was then flagged on the next play for an illegal shift, which pushed them back, facing 1st-and-15. But Maye came through again on the next play, hitting Demario Douglas on a pass that went for 21 yards, getting New England into Titans territory. But again, the Patriots were flagged on the next play, with Demontrey Jacobs being called for a false start, putting them in another 1st-and-15. After a one-yard loss on the next play by Gibson that set up 2nd-and-16, Maye took off for 13 yards on second down, and was aided by the Titans committing a defensive holding penalty that got them the first down. The drive ultimately ended in Stevenson’s touchdown run, but it was an example of this team making things far harder on itself than it needed to. Another holding call on a 3rd-and-7 in the fourth quarter following Tennessee’s touchdown – which put them up 17-10 with 4:27 remaining in the game – was brutal. After Maye hit Kendrick Bourne for a 22-yard completion that would have put them at the Titans’ 35 yard line, Brown was again called for offensive holding. That wiped out Bourne’s reception and instead set up a 3rd-and-17 back at their own 33. Maye couldn’t pull off any magic for that one, as he faced pressure and overthrew Douglas, which forced a punt and ended the drive. Fortunately, the defense came up with a stop on the next possession and Maye ultimately pulled off his game-tying touchdown pass after an 11 play, 50 yard drive that ended in his unbelievable throw to Rhamondre Stevenson, which saw the rookie buy himself 12 seconds as he scrambled and finally connected with the running back to force overtime. Still, had it not been for Maye’s heroics, this game might not have ended up being as close as it was and it’s a trend that absolutely cannot continue. 4) Marcus Jones continues to shine: While Jones had a couple of shaky moments on defense, he still made a couple of nice plays but more importantly, he was a huge force on special teams Sunday. Jones had a 44-yard punt return early in the first quarter, along with a 25-yard punt return ahead of New England’s game-tying drive at the end of regulation. He’s been outstanding in recent weeks, with Mayo admitting that special teams needed to step up, and he’s disappointed that they weren’t able to take advantage of Jones’ efforts. “They had to make plays, and we had the big returns by Marcus [Jones], and those guys out there are blocking for them,” said Mayo. “Too bad we let this one slip away.” 5) Defense needs to take itself “to the hill” after yesterday: One thing that was obvious down the stretch was the fact it ended up being the Titans – not the Patriots -who were the better conditioned team late in this one. Tennessee had its way with New England’s defense for most of the game, but they really took it to that group in the second half. They were clearly the tougher team down the stretch, and it’s an indictment on a unit that, despite the injuries, has seemingly dropped off in an area they had taken a lot of pride in coming off that win over the Bengals in Week 1. By the time overtime came around, that unit was clearly gassed as the Titans put together a 13 play, 72 yard drive that lasted over seven minutes. Had Mason Rudolph not fumbled the snap on a 3rd-and-2 from the Patriots’ 7-yard line, it’s entirely possible that the game would have been over because the Titans were running the ball at will and likely would have taken it in for the touchdown. Instead, they caught a break and former Patriots kicker Nick Folk hit the field goal, giving New England’s offense one final shot before Maye’s game-ending interception. However, they were poor against the run all game after allowing 167 yards rushing, 128 yards of which came at the hands of Tony Pollard, who gashed them all afternoon. They were pushed around by Tennessee’s offensive line, which Mayo talked about after the game. “Defensively, again, leaky run defense,” said Mayo. “There were times where it goes back to fundamentals. We spent a lot of time talking about the Xs and O’s, but I thought the tackling still needs to improve.” Granted, they did make a timely play after Jahlani Tavai stopped a would-be touchdown for the Titans with an interception in the end zone early in the second quarter. That turnover was huge, because it would have put New England down 14-3 had they scored on that possession. But for a group that talked itself up as being the better-conditioned team, they’ve fallen off in a big way in recent weeks. It’s obviously an area that they need to improve on, especially with another challenging road game ahead next Sunday out in Chicago.
From our archive - this week all-time:
Join 2,000+ fans getting exclusive stats, analysis, and insights delivered straight to their inbox every week. Never miss a play.
(Google data usually lags by 48 hours). Striking Distance
';
html += '
';
html += 'Suggested Addition:Five Thoughts After Sunday’s Patriots Loss in Tennessee: Maye’s Got the “It” Factor
The Titans outlasted the Patriots' defense on Sunday during New England's 20-17 overtime loss.




More Patriots News Headlines:

Several Remaining Patriots Free Agents Still Seeking Homes
By: Ian Logue
Patriots free agents Jahlani Tavai, Antonio Gibson, and several others are still seeking homes for the 2026 season. Two former Pats found new teams.
ESPN Insider on Patriots A.J. Brown Trade: ‘I Think He Knows Where His Future is Headed’
By: Ian Logue
Adam Schefter is confident an A.J. Brown trade to the Patriots is still on track, expecting a June 1st deal involving a future first-round pick.
Former Patriots Staffer Reveals Surprising Person Behind Two Key Player Cornerstone Additions in 2021
By: Ian Logue
In a surprising twist, former Patriots staffer Joe Kim credits Matt Patricia for the 2021 draft selections of Rhamondre Stevenson and Christian Barmore.
Patriots News 05-03, A.J. Brown Concerns, Vrabel’s Saga
By: Steve Balestrieri
Analyzing Patriots news, including real concerns about a potential A.J. Brown trade, Mike Vrabel's locker room support, and rookie updates.
MORSE: Clearing the Notebook from the Patriots Draft
By: Mark Morse
Patriots draft analysis covers PFF grades for Caleb Lomu, Gabe Jacas, and Eli Raridon, plus rookie camp invites and Marshall Oium's departure.
📅 This Week in Patriots History:
April 21 - May 6 (Through 26yrs)BE THE FIRST TO KNOW