SmartHeal's picture
Upload 19 files
a19173c verified
// Professional Newsletter App JavaScript
class NewsletterApp {
constructor() {
this.charts = {};
this.analytics = {
pageViews: 0,
timeOnPage: Date.now(),
interactions: 0
};
this.init();
}
init() {
this.trackAnalytics();
this.setupChartInteractions();
this.setupScrollTracking();
this.setupExportFunctionality();
this.setupSearchFunctionality();
}
// Analytics Tracking
trackAnalytics() {
this.analytics.pageViews++;
// Track time on page
window.addEventListener('beforeunload', () => {
const timeSpent = Date.now() - this.analytics.timeOnPage;
this.logAnalytics('timeOnPage', timeSpent);
});
// Track interactions
document.addEventListener('click', (e) => {
if (e.target.matches('a, button, .chart-container, .metric-card')) {
this.analytics.interactions++;
this.logAnalytics('interaction', e.target.tagName + ':' + (e.target.className || 'no-class'));
}
});
}
logAnalytics(event, data) {
// In production, this would send to analytics service
console.log('📊 Analytics:', { event, data, timestamp: new Date().toISOString() });
}
// Chart Interactions
setupChartInteractions() {
// Add hover effects and click interactions to charts
document.addEventListener('DOMContentLoaded', () => {
const chartContainers = document.querySelectorAll('.chart-container');
chartContainers.forEach((container, index) => {
this.enhanceChartContainer(container, index);
});
});
}
enhanceChartContainer(container, index) {
// Add download button for charts
const downloadBtn = document.createElement('button');
downloadBtn.className = 'btn btn-secondary chart-download';
downloadBtn.innerHTML = '📥 Download Chart';
downloadBtn.style.cssText = 'position: absolute; top: 10px; right: 10px; z-index: 1000; font-size: 0.8em; padding: 5px 10px;';
downloadBtn.addEventListener('click', () => {
this.downloadChart(index);
});
container.style.position = 'relative';
container.appendChild(downloadBtn);
// Add fullscreen option
const fullscreenBtn = document.createElement('button');
fullscreenBtn.className = 'btn btn-secondary chart-fullscreen';
fullscreenBtn.innerHTML = '🔍 Expand';
fullscreenBtn.style.cssText = 'position: absolute; top: 10px; right: 120px; z-index: 1000; font-size: 0.8em; padding: 5px 10px;';
fullscreenBtn.addEventListener('click', () => {
this.expandChart(container);
});
container.appendChild(fullscreenBtn);
}
downloadChart(index) {
const canvas = document.getElementById(`chart-${index}`);
if (canvas) {
const link = document.createElement('a');
link.download = `newsletter-chart-${index}-${Date.now()}.png`;
link.href = canvas.toDataURL();
link.click();
this.logAnalytics('chartDownload', index);
}
}
expandChart(container) {
container.classList.toggle('chart-expanded');
if (container.classList.contains('chart-expanded')) {
container.style.cssText += `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 90vw;
height: 90vh;
background: white;
z-index: 10000;
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
border-radius: 10px;
padding: 20px;
`;
// Add close button
const closeBtn = document.createElement('button');
closeBtn.innerHTML = '✕';
closeBtn.style.cssText = 'position: absolute; top: 10px; right: 10px; background: #dc3545; color: white; border: none; border-radius: 50%; width: 30px; height: 30px; cursor: pointer; z-index: 10001;';
closeBtn.addEventListener('click', () => {
this.expandChart(container); // Toggle back
});
container.appendChild(closeBtn);
// Add overlay
const overlay = document.createElement('div');
overlay.className = 'chart-overlay';
overlay.style.cssText = 'position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 9999;';
overlay.addEventListener('click', () => {
this.expandChart(container); // Toggle back
});
document.body.appendChild(overlay);
} else {
container.style.cssText = '';
const overlay = document.querySelector('.chart-overlay');
if (overlay) overlay.remove();
const closeBtn = container.querySelector('button[style*="position: absolute; top: 10px; right: 10px"]');
if (closeBtn) closeBtn.remove();
}
this.logAnalytics('chartExpand', container.classList.contains('chart-expanded'));
}
// Scroll Tracking for Reading Progress
setupScrollTracking() {
let maxScroll = 0;
window.addEventListener('scroll', () => {
const scrollPercent = (window.scrollY / (document.body.scrollHeight - window.innerHeight)) * 100;
maxScroll = Math.max(maxScroll, scrollPercent);
this.updateReadingProgress(scrollPercent);
});
window.addEventListener('beforeunload', () => {
this.logAnalytics('maxScrollPercent', maxScroll);
});
}
updateReadingProgress(percent) {
// Create or update reading progress bar
let progressBar = document.getElementById('reading-progress');
if (!progressBar) {
progressBar = document.createElement('div');
progressBar.id = 'reading-progress';
progressBar.style.cssText = `
position: fixed;
top: 0;
left: 0;
height: 3px;
background: linear-gradient(90deg, #667eea, #764ba2);
z-index: 9999;
transition: width 0.1s ease;
`;
document.body.appendChild(progressBar);
}
progressBar.style.width = percent + '%';
}
// Export Functionality
setupExportFunctionality() {
// Add export buttons to newsletter
const header = document.querySelector('.header');
if (header) {
const exportContainer = document.createElement('div');
exportContainer.className = 'export-buttons';
exportContainer.style.cssText = 'margin-top: 20px; display: flex; gap: 10px; justify-content: center;';
const exportPDFBtn = this.createExportButton('📄 Export PDF', 'pdf');
const exportEmailBtn = this.createExportButton('📧 Email Newsletter', 'email');
const printBtn = this.createExportButton('🖨️ Print', 'print');
exportContainer.appendChild(exportPDFBtn);
exportContainer.appendChild(exportEmailBtn);
exportContainer.appendChild(printBtn);
header.appendChild(exportContainer);
}
}
createExportButton(text, type) {
const btn = document.createElement('button');
btn.className = 'btn btn-secondary';
btn.innerHTML = text;
btn.style.fontSize = '0.9em';
btn.addEventListener('click', () => {
this.handleExport(type);
});
return btn;
}
handleExport(type) {
switch (type) {
case 'pdf':
this.exportToPDF();
break;
case 'email':
this.shareViaEmail();
break;
case 'print':
window.print();
break;
}
this.logAnalytics('export', type);
}
exportToPDF() {
// Using browser's print functionality for PDF export
const originalTitle = document.title;
document.title = 'Professional Newsletter - ' + new Date().toLocaleDateString();
// Temporarily hide export buttons
const exportButtons = document.querySelector('.export-buttons');
if (exportButtons) exportButtons.style.display = 'none';
window.print();
// Restore
document.title = originalTitle;
if (exportButtons) exportButtons.style.display = 'flex';
}
shareViaEmail() {
const subject = encodeURIComponent(document.title);
const body = encodeURIComponent(`Check out this professional newsletter: ${window.location.href}`);
window.open(`mailto:?subject=${subject}&body=${body}`);
}
// Search Functionality
setupSearchFunctionality() {
// Add search box to newsletter
const content = document.querySelector('.content');
if (content) {
const searchContainer = document.createElement('div');
searchContainer.className = 'search-container';
searchContainer.style.cssText = 'margin-bottom: 30px; padding: 20px; background: #f8f9fa; border-radius: 8px;';
const searchInput = document.createElement('input');
searchInput.type = 'text';
searchInput.placeholder = '🔍 Search newsletter content...';
searchInput.style.cssText = 'width: 100%; padding: 12px; border: 1px solid #ddd; border-radius: 5px; font-size: 1em;';
searchInput.addEventListener('input', (e) => {
this.searchContent(e.target.value);
});
searchContainer.appendChild(searchInput);
content.insertBefore(searchContainer, content.firstChild);
}
}
searchContent(query) {
// Remove previous highlights
this.clearHighlights();
if (query.length < 3) return;
const textNodes = this.getTextNodes(document.querySelector('.content'));
let matchCount = 0;
textNodes.forEach(node => {
const text = node.textContent;
const regex = new RegExp(`(${query})`, 'gi');
if (regex.test(text)) {
const highlightedText = text.replace(regex, '<mark class="search-highlight">$1</mark>');
const wrapper = document.createElement('span');
wrapper.innerHTML = highlightedText;
node.parentNode.replaceChild(wrapper, node);
matchCount++;
}
});
this.showSearchResults(matchCount, query);
this.logAnalytics('search', { query, matches: matchCount });
}
getTextNodes(element) {
const textNodes = [];
const walker = document.createTreeWalker(
element,
NodeFilter.SHOW_TEXT,
null,
false
);
let node;
while (node = walker.nextNode()) {
if (node.textContent.trim()) {
textNodes.push(node);
}
}
return textNodes;
}
clearHighlights() {
const highlights = document.querySelectorAll('.search-highlight');
highlights.forEach(highlight => {
const parent = highlight.parentNode;
parent.replaceChild(document.createTextNode(highlight.textContent), highlight);
parent.normalize();
});
}
showSearchResults(count, query) {
let resultsDiv = document.getElementById('search-results');
if (!resultsDiv) {
resultsDiv = document.createElement('div');
resultsDiv.id = 'search-results';
resultsDiv.style.cssText = 'margin-top: 10px; padding: 10px; background: #e3f2fd; border-radius: 5px; font-size: 0.9em;';
document.querySelector('.search-container').appendChild(resultsDiv);
}
if (count > 0) {
resultsDiv.innerHTML = `✅ Found ${count} matches for "${query}"`;
resultsDiv.style.background = '#e8f5e8';
} else {
resultsDiv.innerHTML = `❌ No matches found for "${query}"`;
resultsDiv.style.background = '#ffebee';
}
}
// Utility Functions
debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// Initialize tooltips for data points
initializeTooltips() {
const metrics = document.querySelectorAll('.metric-card, .chart-container');
metrics.forEach(element => {
element.addEventListener('mouseenter', (e) => {
this.showTooltip(e, element);
});
element.addEventListener('mouseleave', () => {
this.hideTooltip();
});
});
}
showTooltip(event, element) {
const tooltip = document.createElement('div');
tooltip.className = 'tooltip';
tooltip.style.cssText = `
position: absolute;
background: #333;
color: white;
padding: 8px 12px;
border-radius: 4px;
font-size: 0.8em;
z-index: 10000;
pointer-events: none;
max-width: 200px;
`;
// Set tooltip content based on element type
if (element.classList.contains('metric-card')) {
tooltip.textContent = 'Click for detailed analysis';
} else if (element.classList.contains('chart-container')) {
tooltip.textContent = 'Interactive chart - hover for details';
}
document.body.appendChild(tooltip);
// Position tooltip
const rect = element.getBoundingClientRect();
tooltip.style.left = rect.left + 'px';
tooltip.style.top = (rect.top - tooltip.offsetHeight - 5) + 'px';
}
hideTooltip() {
const tooltip = document.querySelector('.tooltip');
if (tooltip) {
tooltip.remove();
}
}
}
// Initialize app when DOM is loaded
document.addEventListener('DOMContentLoaded', () => {
window.newsletterApp = new NewsletterApp();
});
// CSS for search highlights
const searchStyles = document.createElement('style');
searchStyles.textContent = `
.search-highlight {
background: #ffeb3b;
padding: 2px 4px;
border-radius: 3px;
font-weight: bold;
}
.chart-download, .chart-fullscreen {
opacity: 0;
transition: opacity 0.3s ease;
}
.chart-container:hover .chart-download,
.chart-container:hover .chart-fullscreen {
opacity: 1;
}
@media print {
.export-buttons, .search-container, #reading-progress {
display: none !important;
}
}
`;
document.head.appendChild(searchStyles);