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