Skip to content

Commit 7da3679

Browse files
committed
Add blog JavaScript functionality
1 parent d7b876c commit 7da3679

File tree

1 file changed

+380
-0
lines changed

1 file changed

+380
-0
lines changed

docs/blog/blog-script.js

Lines changed: 380 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,380 @@
1+
// Blog functionality for MCP Portal
2+
3+
document.addEventListener('DOMContentLoaded', function() {
4+
initBlogFunctionality();
5+
});
6+
7+
function initBlogFunctionality() {
8+
setupCategoryFiltering();
9+
setupNewsletterForm();
10+
setupSearchFunctionality();
11+
setupSocialSharing();
12+
trackPageViews();
13+
}
14+
15+
// Category filtering functionality
16+
function setupCategoryFiltering() {
17+
const categoryLinks = document.querySelectorAll('.category-list a');
18+
const blogPosts = document.querySelectorAll('.blog-post');
19+
20+
categoryLinks.forEach(link => {
21+
link.addEventListener('click', function(e) {
22+
e.preventDefault();
23+
const category = this.dataset.category;
24+
filterPostsByCategory(category);
25+
26+
// Update active state
27+
categoryLinks.forEach(l => l.classList.remove('active'));
28+
this.classList.add('active');
29+
});
30+
});
31+
}
32+
33+
function filterPostsByCategory(category) {
34+
const posts = document.querySelectorAll('.blog-post');
35+
36+
posts.forEach(post => {
37+
const postCategory = post.querySelector('.category');
38+
if (!category || category === 'all') {
39+
post.style.display = 'block';
40+
} else {
41+
const categoryText = postCategory ? postCategory.textContent.toLowerCase().replace(/\s+/g, '-') : '';
42+
if (categoryText === category) {
43+
post.style.display = 'block';
44+
} else {
45+
post.style.display = 'none';
46+
}
47+
}
48+
});
49+
}
50+
51+
// Newsletter signup functionality
52+
function setupNewsletterForm() {
53+
const forms = document.querySelectorAll('.newsletter-form');
54+
55+
forms.forEach(form => {
56+
form.addEventListener('submit', function(e) {
57+
e.preventDefault();
58+
const email = this.querySelector('input[type="email"]').value;
59+
60+
if (isValidEmail(email)) {
61+
subscribeToNewsletter(email);
62+
showSuccessMessage(this);
63+
} else {
64+
showErrorMessage(this, 'Please enter a valid email address');
65+
}
66+
});
67+
});
68+
}
69+
70+
function isValidEmail(email) {
71+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
72+
return emailRegex.test(email);
73+
}
74+
75+
function subscribeToNewsletter(email) {
76+
// Simulate newsletter subscription
77+
console.log('Subscribing email:', email);
78+
79+
// In a real implementation, this would make an API call
80+
// fetch('/api/newsletter/subscribe', {
81+
// method: 'POST',
82+
// headers: { 'Content-Type': 'application/json' },
83+
// body: JSON.stringify({ email })
84+
// });
85+
86+
// Store locally for demo purposes
87+
const subscribers = JSON.parse(localStorage.getItem('newsletter_subscribers') || '[]');
88+
if (!subscribers.includes(email)) {
89+
subscribers.push(email);
90+
localStorage.setItem('newsletter_subscribers', JSON.stringify(subscribers));
91+
}
92+
}
93+
94+
function showSuccessMessage(form) {
95+
const button = form.querySelector('button');
96+
const originalText = button.textContent;
97+
98+
button.textContent = '✓ Subscribed!';
99+
button.style.background = '#48bb78';
100+
101+
setTimeout(() => {
102+
button.textContent = originalText;
103+
button.style.background = '';
104+
form.reset();
105+
}, 3000);
106+
}
107+
108+
function showErrorMessage(form, message) {
109+
const existingError = form.querySelector('.error-message');
110+
if (existingError) {
111+
existingError.remove();
112+
}
113+
114+
const errorDiv = document.createElement('div');
115+
errorDiv.className = 'error-message';
116+
errorDiv.style.color = '#e53e3e';
117+
errorDiv.style.fontSize = '0.9em';
118+
errorDiv.style.marginTop = '5px';
119+
errorDiv.textContent = message;
120+
121+
form.appendChild(errorDiv);
122+
123+
setTimeout(() => {
124+
errorDiv.remove();
125+
}, 5000);
126+
}
127+
128+
// Search functionality (placeholder)
129+
function setupSearchFunctionality() {
130+
// This would be implemented with a search API
131+
// For now, just adding placeholder functionality
132+
const searchInputs = document.querySelectorAll('.search-bar');
133+
134+
searchInputs.forEach(input => {
135+
input.addEventListener('input', function() {
136+
const query = this.value.toLowerCase();
137+
searchPosts(query);
138+
});
139+
});
140+
}
141+
142+
function searchPosts(query) {
143+
if (!query) {
144+
document.querySelectorAll('.blog-post').forEach(post => {
145+
post.style.display = 'block';
146+
});
147+
return;
148+
}
149+
150+
document.querySelectorAll('.blog-post').forEach(post => {
151+
const title = post.querySelector('h3').textContent.toLowerCase();
152+
const content = post.querySelector('p').textContent.toLowerCase();
153+
const tags = Array.from(post.querySelectorAll('.tag')).map(tag => tag.textContent.toLowerCase());
154+
155+
const searchText = `${title} ${content} ${tags.join(' ')}`;
156+
157+
if (searchText.includes(query)) {
158+
post.style.display = 'block';
159+
} else {
160+
post.style.display = 'none';
161+
}
162+
});
163+
}
164+
165+
// Social sharing functionality
166+
function setupSocialSharing() {
167+
const shareButtons = document.querySelectorAll('.share-button');
168+
169+
shareButtons.forEach(button => {
170+
button.addEventListener('click', function(e) {
171+
e.preventDefault();
172+
const platform = this.dataset.platform;
173+
const url = encodeURIComponent(window.location.href);
174+
const title = encodeURIComponent(document.title);
175+
176+
let shareUrl = '';
177+
178+
switch (platform) {
179+
case 'twitter':
180+
shareUrl = `https://twitter.com/intent/tweet?url=${url}&text=${title}`;
181+
break;
182+
case 'linkedin':
183+
shareUrl = `https://www.linkedin.com/sharing/share-offsite/?url=${url}`;
184+
break;
185+
case 'facebook':
186+
shareUrl = `https://www.facebook.com/sharer/sharer.php?u=${url}`;
187+
break;
188+
case 'reddit':
189+
shareUrl = `https://reddit.com/submit?url=${url}&title=${title}`;
190+
break;
191+
}
192+
193+
if (shareUrl) {
194+
window.open(shareUrl, '_blank', 'width=600,height=400');
195+
}
196+
});
197+
});
198+
}
199+
200+
// Analytics and tracking
201+
function trackPageViews() {
202+
// Track page view
203+
const pageData = {
204+
page: window.location.pathname,
205+
title: document.title,
206+
timestamp: new Date().toISOString(),
207+
referrer: document.referrer
208+
};
209+
210+
// Store page view data
211+
const pageViews = JSON.parse(localStorage.getItem('page_views') || '[]');
212+
pageViews.push(pageData);
213+
214+
// Keep only last 100 page views
215+
if (pageViews.length > 100) {
216+
pageViews.splice(0, pageViews.length - 100);
217+
}
218+
219+
localStorage.setItem('page_views', JSON.stringify(pageViews));
220+
221+
// Send to analytics if available
222+
if (typeof gtag !== 'undefined') {
223+
gtag('event', 'page_view', {
224+
page_title: document.title,
225+
page_location: window.location.href
226+
});
227+
}
228+
}
229+
230+
// Reading time calculation
231+
function calculateReadingTime() {
232+
const posts = document.querySelectorAll('.blog-post');
233+
234+
posts.forEach(post => {
235+
const content = post.querySelector('p');
236+
if (content) {
237+
const wordCount = content.textContent.split(' ').length;
238+
const readingTime = Math.ceil(wordCount / 200); // 200 words per minute
239+
240+
// Add reading time indicator
241+
const readingTimeElement = document.createElement('span');
242+
readingTimeElement.className = 'reading-time';
243+
readingTimeElement.textContent = `${readingTime} min read`;
244+
readingTimeElement.style.color = '#718096';
245+
readingTimeElement.style.fontSize = '0.8em';
246+
247+
const postMeta = post.querySelector('.post-meta');
248+
if (postMeta) {
249+
postMeta.appendChild(readingTimeElement);
250+
}
251+
}
252+
});
253+
}
254+
255+
// Tag cloud functionality
256+
function setupTagCloud() {
257+
const tags = document.querySelectorAll('.tag-cloud .tag');
258+
259+
tags.forEach(tag => {
260+
tag.addEventListener('click', function(e) {
261+
e.preventDefault();
262+
const tagText = this.textContent.toLowerCase();
263+
filterPostsByTag(tagText);
264+
});
265+
});
266+
}
267+
268+
function filterPostsByTag(tagText) {
269+
const posts = document.querySelectorAll('.blog-post');
270+
271+
posts.forEach(post => {
272+
const postTags = Array.from(post.querySelectorAll('.tag')).map(tag =>
273+
tag.textContent.toLowerCase()
274+
);
275+
276+
if (postTags.includes(tagText)) {
277+
post.style.display = 'block';
278+
} else {
279+
post.style.display = 'none';
280+
}
281+
});
282+
}
283+
284+
// Lazy loading for images (when we add them)
285+
function setupLazyLoading() {
286+
const images = document.querySelectorAll('img[data-src]');
287+
288+
const imageObserver = new IntersectionObserver((entries, observer) => {
289+
entries.forEach(entry => {
290+
if (entry.isIntersecting) {
291+
const img = entry.target;
292+
img.src = img.dataset.src;
293+
img.classList.remove('lazy');
294+
observer.unobserve(img);
295+
}
296+
});
297+
});
298+
299+
images.forEach(img => imageObserver.observe(img));
300+
}
301+
302+
// Initialize additional features when page loads
303+
document.addEventListener('DOMContentLoaded', function() {
304+
calculateReadingTime();
305+
setupTagCloud();
306+
setupLazyLoading();
307+
});
308+
309+
// Smooth scrolling for anchor links
310+
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
311+
anchor.addEventListener('click', function (e) {
312+
e.preventDefault();
313+
const target = document.querySelector(this.getAttribute('href'));
314+
if (target) {
315+
target.scrollIntoView({
316+
behavior: 'smooth',
317+
block: 'start'
318+
});
319+
}
320+
});
321+
});
322+
323+
// Add copy code functionality for code blocks (when we add them)
324+
function setupCodeCopy() {
325+
const codeBlocks = document.querySelectorAll('pre code');
326+
327+
codeBlocks.forEach(block => {
328+
const copyButton = document.createElement('button');
329+
copyButton.className = 'copy-code-btn';
330+
copyButton.textContent = 'Copy';
331+
copyButton.style.position = 'absolute';
332+
copyButton.style.top = '5px';
333+
copyButton.style.right = '5px';
334+
copyButton.style.background = '#667eea';
335+
copyButton.style.color = 'white';
336+
copyButton.style.border = 'none';
337+
copyButton.style.padding = '5px 10px';
338+
copyButton.style.borderRadius = '4px';
339+
copyButton.style.cursor = 'pointer';
340+
341+
copyButton.addEventListener('click', function() {
342+
navigator.clipboard.writeText(block.textContent).then(() => {
343+
copyButton.textContent = 'Copied!';
344+
setTimeout(() => {
345+
copyButton.textContent = 'Copy';
346+
}, 2000);
347+
});
348+
});
349+
350+
block.parentElement.style.position = 'relative';
351+
block.parentElement.appendChild(copyButton);
352+
});
353+
}
354+
355+
// Theme toggle functionality (for future dark mode)
356+
function setupThemeToggle() {
357+
const themeToggle = document.querySelector('.theme-toggle');
358+
359+
if (themeToggle) {
360+
themeToggle.addEventListener('click', function() {
361+
document.body.classList.toggle('dark-theme');
362+
const isDark = document.body.classList.contains('dark-theme');
363+
localStorage.setItem('theme', isDark ? 'dark' : 'light');
364+
});
365+
366+
// Load saved theme
367+
const savedTheme = localStorage.getItem('theme');
368+
if (savedTheme === 'dark') {
369+
document.body.classList.add('dark-theme');
370+
}
371+
}
372+
}
373+
374+
// Export functions for potential external use
375+
window.MCPBlog = {
376+
filterPostsByCategory,
377+
filterPostsByTag,
378+
subscribeToNewsletter,
379+
trackPageViews
380+
};

0 commit comments

Comments
 (0)