Spaces:
Sleeping
Sleeping
| // 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); | |