| 
									
										
										
										
											2025-04-07 20:27:45 +08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * RSS/Atom Feed Preview for Links Table | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-15 11:43:20 +08:00
										 |  |  | (function () { | 
					
						
							|  |  |  |   if (window.rssFeedPreviewInitialized) | 
					
						
							|  |  |  |     return; | 
					
						
							|  |  |  |   window.rssFeedPreviewInitialized = true; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   var CORS_PROXY = 'https://cors-anywhere.mayx.eu.org/?'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   var $previewEl = $('<div>', { | 
					
						
							|  |  |  |     id: 'rss-feed-preview' | 
					
						
							|  |  |  |   }).css({ | 
					
						
							|  |  |  |     position: 'fixed', | 
					
						
							|  |  |  |     display: 'none', | 
					
						
							|  |  |  |     width: '300px', | 
					
						
							|  |  |  |     maxHeight: '400px', | 
					
						
							|  |  |  |     overflowY: 'auto', | 
					
						
							|  |  |  |     backgroundColor: 'white', | 
					
						
							|  |  |  |     border: '1px solid #ccc', | 
					
						
							|  |  |  |     borderRadius: '5px', | 
					
						
							|  |  |  |     padding: '10px', | 
					
						
							|  |  |  |     fontSize: '14px', | 
					
						
							|  |  |  |     lineHeight: '1.4', | 
					
						
							|  |  |  |     zIndex: 1000, | 
					
						
							|  |  |  |     boxShadow: '0 2px 10px rgba(0,0,0,0.1)' | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   $('body').append($previewEl); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   function escapeHTML(str) { | 
					
						
							|  |  |  |     return String(str).replace(/[&<>"']/g, function (c) { | 
					
						
							|  |  |  |       return { | 
					
						
							| 
									
										
										
										
											2025-04-08 22:12:54 +08:00
										 |  |  |         '&': '&', | 
					
						
							|  |  |  |         '<': '<', | 
					
						
							|  |  |  |         '>': '>', | 
					
						
							|  |  |  |         '"': '"', | 
					
						
							| 
									
										
										
										
											2025-07-15 11:43:20 +08:00
										 |  |  |         "'": ''' | 
					
						
							|  |  |  |       }[c]; | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   function parseRSS(xmlText) { | 
					
						
							|  |  |  |     var xml; | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |       xml = $.parseXML(xmlText); | 
					
						
							|  |  |  |     } catch (e) { | 
					
						
							|  |  |  |       return []; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     var $xml = $(xml); | 
					
						
							|  |  |  |     var $items = $xml.find('item'); | 
					
						
							|  |  |  |     if (!$items.length) | 
					
						
							|  |  |  |       $items = $xml.find('entry'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     var result = []; | 
					
						
							|  |  |  |     $items.slice(0, 5).each(function () { | 
					
						
							|  |  |  |       var $el = $(this); | 
					
						
							|  |  |  |       result.push({ | 
					
						
							|  |  |  |         title: $el.find('title').text() || 'No title', | 
					
						
							|  |  |  |         date: $el.find('pubDate, updated').text() || 'No date' | 
					
						
							| 
									
										
										
										
											2025-04-07 20:27:45 +08:00
										 |  |  |       }); | 
					
						
							| 
									
										
										
										
											2025-07-15 11:43:20 +08:00
										 |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return result; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-04 13:19:53 +08:00
										 |  |  |   function checkFeed(url, onSuccess, onError) { | 
					
						
							|  |  |  |     return $.ajax({ | 
					
						
							| 
									
										
										
										
											2025-07-15 11:43:20 +08:00
										 |  |  |       url: CORS_PROXY + url, | 
					
						
							|  |  |  |       type: 'GET', | 
					
						
							|  |  |  |       dataType: 'text', | 
					
						
							|  |  |  |       success: function (data) { | 
					
						
							|  |  |  |         var items = parseRSS(data); | 
					
						
							| 
									
										
										
										
											2025-08-04 13:19:53 +08:00
										 |  |  |         onSuccess(items); | 
					
						
							| 
									
										
										
										
											2025-07-15 11:43:20 +08:00
										 |  |  |       }, | 
					
						
							|  |  |  |       error: function () { | 
					
						
							| 
									
										
										
										
											2025-08-04 13:19:53 +08:00
										 |  |  |         onError(); | 
					
						
							| 
									
										
										
										
											2025-04-07 20:27:45 +08:00
										 |  |  |       } | 
					
						
							| 
									
										
										
										
											2025-07-15 11:43:20 +08:00
										 |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   function renderFeedItems(items, siteName) { | 
					
						
							|  |  |  |     if (!items || !items.length) { | 
					
						
							|  |  |  |       $previewEl.html('<p>No feed items found.</p>'); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     var html = '<h3>Latest from ' + escapeHTML(siteName) + '</h3><ul style="list-style:none; padding:0; margin:0;">'; | 
					
						
							|  |  |  |     for (var i = 0; i < items.length; i++) { | 
					
						
							|  |  |  |       var item = items[i]; | 
					
						
							|  |  |  |       var dateStr = new Date(item.date).toLocaleDateString(); | 
					
						
							|  |  |  |       html += '<li style="margin-bottom:10px; padding-bottom:10px; border-bottom:1px solid #eee;">' + | 
					
						
							|  |  |  |         '<div style="color:#24292e; font-weight:bold;">' + escapeHTML(item.title) + '</div>' + | 
					
						
							|  |  |  |         '<div style="color:#586069; font-size:12px; margin:3px 0;">' + escapeHTML(dateStr) + '</div>' + | 
					
						
							|  |  |  |         '</li>'; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     html += '</ul>'; | 
					
						
							|  |  |  |     $previewEl.html(html); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   function positionPreview(e) { | 
					
						
							|  |  |  |     e = e || window.event; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     var x = e.clientX; | 
					
						
							|  |  |  |     var y = e.clientY; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     var offsetWidth = $previewEl.outerWidth(); | 
					
						
							|  |  |  |     var offsetHeight = $previewEl.outerHeight(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     var left = x + 20; | 
					
						
							|  |  |  |     var top = y + 20; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (left + offsetWidth > $(window).width()) { | 
					
						
							|  |  |  |       left = x - offsetWidth - 20; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (top + offsetHeight > $(window).height()) { | 
					
						
							|  |  |  |       top = y - offsetHeight - 20; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     $previewEl.css({ | 
					
						
							|  |  |  |       left: Math.max(10, left), | 
					
						
							|  |  |  |       top: Math.max(10, top) | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   function init() { | 
					
						
							|  |  |  |     var cache = {}; | 
					
						
							|  |  |  |     var currentLink = null; | 
					
						
							|  |  |  |     var timeout = null; | 
					
						
							| 
									
										
										
										
											2025-08-04 13:19:53 +08:00
										 |  |  |     var currentRequest = null; | 
					
						
							|  |  |  |     var currentRequestId = 0; | 
					
						
							|  |  |  |     $('main table tbody').on('mouseenter mousemove mouseleave', 'tr td a', function (e) { | 
					
						
							| 
									
										
										
										
											2025-07-15 11:43:20 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-04 13:19:53 +08:00
										 |  |  |       if (e.type === 'mouseenter') { | 
					
						
							|  |  |  |         var $link = $(this); | 
					
						
							| 
									
										
										
										
											2025-07-15 11:43:20 +08:00
										 |  |  |         var siteName = $link.text(); | 
					
						
							|  |  |  |         var url = $link.attr('data-feed'); | 
					
						
							|  |  |  |         if (!url) | 
					
						
							|  |  |  |           return; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-04 13:19:53 +08:00
										 |  |  |         currentLink = $link[0]; | 
					
						
							|  |  |  |         var requestId = ++currentRequestId; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-15 11:43:20 +08:00
										 |  |  |         $previewEl.html('<p>Checking for RSS/Atom feed...</p>').show(); | 
					
						
							|  |  |  |         positionPreview(e); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (timeout) | 
					
						
							|  |  |  |           clearTimeout(timeout); | 
					
						
							|  |  |  |         timeout = setTimeout(function () { | 
					
						
							|  |  |  |           if (cache[url]) { | 
					
						
							| 
									
										
										
										
											2025-08-04 13:19:53 +08:00
										 |  |  |             if (currentLink === $link[0] && requestId === currentRequestId) { | 
					
						
							|  |  |  |               renderFeedItems(cache[url], siteName); | 
					
						
							|  |  |  |               positionPreview(e); | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2025-07-15 11:43:20 +08:00
										 |  |  |             return; | 
					
						
							| 
									
										
										
										
											2025-04-07 20:27:45 +08:00
										 |  |  |           } | 
					
						
							| 
									
										
										
										
											2025-07-15 11:43:20 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-04 13:19:53 +08:00
										 |  |  |           currentRequest = checkFeed( | 
					
						
							|  |  |  |             url, | 
					
						
							|  |  |  |             function (items) { | 
					
						
							|  |  |  |               if (requestId !== currentRequestId || currentLink !== $link[0]) | 
					
						
							|  |  |  |                 return; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |               if (items && items.length) { | 
					
						
							| 
									
										
										
										
											2025-07-15 11:43:20 +08:00
										 |  |  |                 cache[url] = items; | 
					
						
							|  |  |  |                 renderFeedItems(items, siteName); | 
					
						
							| 
									
										
										
										
											2025-04-07 20:27:45 +08:00
										 |  |  |               } else { | 
					
						
							| 
									
										
										
										
											2025-08-04 13:19:53 +08:00
										 |  |  |                 $previewEl.html('<p>No feed items found.</p>'); | 
					
						
							| 
									
										
										
										
											2025-04-07 20:27:45 +08:00
										 |  |  |               } | 
					
						
							| 
									
										
										
										
											2025-07-15 11:43:20 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-04 13:19:53 +08:00
										 |  |  |               positionPreview(e); | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  |             function () { | 
					
						
							|  |  |  |               if (requestId !== currentRequestId || currentLink !== $link[0]) | 
					
						
							|  |  |  |                 return; | 
					
						
							|  |  |  |               $previewEl.html('<p>Failed to load feed.</p>'); | 
					
						
							|  |  |  |               positionPreview(e); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |           ); | 
					
						
							|  |  |  |         }, 300); | 
					
						
							|  |  |  |       } else if (e.type === 'mousemove') { | 
					
						
							| 
									
										
										
										
											2025-07-15 11:43:20 +08:00
										 |  |  |         if ($previewEl.is(':visible')) | 
					
						
							|  |  |  |           positionPreview(e); | 
					
						
							| 
									
										
										
										
											2025-08-04 13:19:53 +08:00
										 |  |  |       } else if (e.type === 'mouseleave') { | 
					
						
							| 
									
										
										
										
											2025-07-15 11:43:20 +08:00
										 |  |  |         clearTimeout(timeout); | 
					
						
							|  |  |  |         timeout = null; | 
					
						
							|  |  |  |         currentLink = null; | 
					
						
							| 
									
										
										
										
											2025-08-04 13:19:53 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |         if (currentRequest) { | 
					
						
							|  |  |  |           currentRequest.abort(); | 
					
						
							|  |  |  |           currentRequest = null; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-15 11:43:20 +08:00
										 |  |  |         $previewEl.hide(); | 
					
						
							| 
									
										
										
										
											2025-08-04 13:19:53 +08:00
										 |  |  |       } | 
					
						
							| 
									
										
										
										
											2025-07-15 11:43:20 +08:00
										 |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     $(document).on('click', function (e) { | 
					
						
							|  |  |  |       if (!$(e.target).closest('#rss-feed-preview').length) { | 
					
						
							|  |  |  |         $previewEl.hide(); | 
					
						
							| 
									
										
										
										
											2025-04-07 20:27:45 +08:00
										 |  |  |       } | 
					
						
							| 
									
										
										
										
											2025-07-15 11:43:20 +08:00
										 |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-04 13:19:53 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-15 11:43:20 +08:00
										 |  |  |   if (document.readyState === 'complete' || document.readyState === 'interactive') { | 
					
						
							|  |  |  |     init(); | 
					
						
							|  |  |  |   } else { | 
					
						
							|  |  |  |     $(document).ready(init); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | })(); |