mirror of
				https://github.com/Mabbs/mabbs.github.io
				synced 2025-10-31 12:52:08 +08:00 
			
		
		
		
	Update 5 files
- /.github/PULL_REQUEST_TEMPLATE.yml - /assets/js/rss-feed-preview.js - /links.md - /sitemap.xsl - /.github/PULL_REQUEST_TEMPLATE.md
This commit is contained in:
		| @@ -2,235 +2,196 @@ | ||||
|  * RSS/Atom Feed Preview for Links Table | ||||
|  */ | ||||
|  | ||||
| (function() { | ||||
|     const existingPreviews = document.querySelectorAll('#rss-feed-preview'); | ||||
|     existingPreviews.forEach(el => el.remove()); | ||||
|    | ||||
|     const CORS_PROXY = 'https://cors-anywhere.mayx.eu.org/?'; | ||||
|    | ||||
|     const createPreviewElement = () => { | ||||
|       const existingPreview = document.getElementById('rss-feed-preview'); | ||||
|       if (existingPreview) { | ||||
|         return existingPreview; | ||||
|       } | ||||
|    | ||||
|       const previewEl = document.createElement('div'); | ||||
|       previewEl.id = 'rss-feed-preview'; | ||||
|       previewEl.style.cssText = ` | ||||
|         position: fixed; | ||||
|         display: none; | ||||
|         width: 300px; | ||||
|         max-height: 400px; | ||||
|         overflow-y: auto; | ||||
|         background-color: white; | ||||
|         border: 1px solid #ccc; | ||||
|         border-radius: 5px; | ||||
|         padding: 10px; | ||||
|         box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); | ||||
|         z-index: 1000; | ||||
|         font-size: 14px; | ||||
|         line-height: 1.4; | ||||
|       `; | ||||
|       document.body.appendChild(previewEl); | ||||
|       return previewEl; | ||||
|     }; | ||||
|    | ||||
|     const parseRSS = (xmlText) => { | ||||
|       const parser = new DOMParser(); | ||||
|       const xml = parser.parseFromString(xmlText, 'text/xml'); | ||||
|    | ||||
|       const rssItems = xml.querySelectorAll('item'); | ||||
|       if (rssItems.length > 0) { | ||||
|         return Array.from(rssItems).slice(0, 5).map(item => { | ||||
|           return { | ||||
|             title: item.querySelector('title')?.textContent || 'No title', | ||||
|             date: item.querySelector('pubDate')?.textContent || 'No date', | ||||
|           }; | ||||
|         }); | ||||
|       } | ||||
|    | ||||
|       const atomItems = xml.querySelectorAll('entry'); | ||||
|       if (atomItems.length > 0) { | ||||
|         return Array.from(atomItems).slice(0, 5).map(item => { | ||||
|           return { | ||||
|             title: item.querySelector('title')?.textContent || 'No title', | ||||
|             date: item.querySelector('updated')?.textContent || 'No date', | ||||
|           }; | ||||
|         }); | ||||
|       } | ||||
|    | ||||
|       return null; | ||||
|     }; | ||||
|    | ||||
|     const checkFeed = async (url) => { | ||||
|       try { | ||||
|         const response = await fetch(CORS_PROXY + url); | ||||
|         if (!response.ok) { | ||||
|           return null; | ||||
|         } | ||||
|    | ||||
|         const text = await response.text(); | ||||
|         return parseRSS(text); | ||||
|       } catch (error) { | ||||
|         return null; | ||||
|       } | ||||
|     }; | ||||
|    | ||||
|     const findFeedUrl = async (siteUrl, linkElement) => { | ||||
|       if (linkElement && linkElement.hasAttribute('data-feed')) { | ||||
|         const dataFeedUrl = linkElement.getAttribute('data-feed'); | ||||
|         if (dataFeedUrl) { | ||||
|           const feedItems = await checkFeed(dataFeedUrl); | ||||
|           if (feedItems) { | ||||
|             return { url: dataFeedUrl, items: feedItems }; | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|    | ||||
|       return null; | ||||
|     }; | ||||
|    | ||||
|     const escapeHTML = (str) => { | ||||
|       return String(str).replace(/[&<>"'/]/g, (c) => ({ | ||||
| (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 { | ||||
|         '&': '&', | ||||
|         '<': '<', | ||||
|         '>': '>', | ||||
|         '"': '"', | ||||
|         "'": ''', | ||||
|         '/': '/' | ||||
|       }[c])); | ||||
|     }; | ||||
|         "'": ''' | ||||
|       }[c]; | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|     const renderFeedItems = (previewEl, items, siteName) => { | ||||
|       if (!items || items.length === 0) { | ||||
|         previewEl.innerHTML = '<p>No feed items found.</p>'; | ||||
|         return; | ||||
|       } | ||||
|    | ||||
|       let html = `<h3>Latest from ${siteName}</h3><ul style="list-style: none; padding: 0; margin: 0;">`; | ||||
|    | ||||
|       items.forEach(item => { | ||||
|         const safeTitle = escapeHTML(item.title); | ||||
|         const safeDate = escapeHTML(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;"> | ||||
|               ${safeTitle} | ||||
|             </div> | ||||
|             <div style="color: #586069; font-size: 12px; margin: 3px 0;"> | ||||
|               ${safeDate} | ||||
|             </div> | ||||
|           </li> | ||||
|         `; | ||||
|       }); | ||||
|    | ||||
|       html += '</ul>'; | ||||
|       previewEl.innerHTML = html; | ||||
|     }; | ||||
|    | ||||
|     const positionPreview = (previewEl, event) => { | ||||
|       const viewportWidth = window.innerWidth; | ||||
|       const viewportHeight = window.innerHeight; | ||||
|    | ||||
|       let left = event.clientX + 20; | ||||
|       let top = event.clientY + 20; | ||||
|    | ||||
|       const rect = previewEl.getBoundingClientRect(); | ||||
|    | ||||
|       if (left + rect.width > viewportWidth) { | ||||
|         left = event.clientX - rect.width - 20; | ||||
|       } | ||||
|    | ||||
|       if (top + rect.height > viewportHeight) { | ||||
|         top = event.clientY - rect.height - 20; | ||||
|       } | ||||
|    | ||||
|       left = Math.max(10, left); | ||||
|       top = Math.max(10, top); | ||||
|    | ||||
|       previewEl.style.left = `${left}px`; | ||||
|       previewEl.style.top = `${top}px`; | ||||
|     }; | ||||
|    | ||||
|     const initFeedPreview = () => { | ||||
|       const previewEl = createPreviewElement(); | ||||
|    | ||||
|       const tableLinks = document.querySelectorAll('main table tbody tr td a'); | ||||
|    | ||||
|       const feedCache = {}; | ||||
|    | ||||
|       let currentLink = null; | ||||
|       let loadingTimeout = null; | ||||
|    | ||||
|       tableLinks.forEach(link => { | ||||
|         link.addEventListener('mouseenter', async (event) => { | ||||
|           currentLink = link; | ||||
|           const url = link.getAttribute('href'); | ||||
|           const siteName = link.textContent; | ||||
|    | ||||
|           previewEl.innerHTML = '<p>Checking for RSS/Atom feed...</p>'; | ||||
|           previewEl.style.display = 'block'; | ||||
|           positionPreview(previewEl, event); | ||||
|    | ||||
|           if (loadingTimeout) { | ||||
|             clearTimeout(loadingTimeout); | ||||
|           } | ||||
|    | ||||
|           loadingTimeout = setTimeout(async () => { | ||||
|             if (feedCache[url]) { | ||||
|               renderFeedItems(previewEl, feedCache[url].items, siteName); | ||||
|               positionPreview(previewEl, event); // Reposition after content is loaded | ||||
|               return; | ||||
|             } | ||||
|    | ||||
|             const feedData = await findFeedUrl(url, link); | ||||
|    | ||||
|             if (currentLink === link) { | ||||
|               if (feedData) { | ||||
|                 feedCache[url] = feedData; | ||||
|                 renderFeedItems(previewEl, feedData.items, siteName); | ||||
|                 positionPreview(previewEl, event); // Reposition after content is loaded | ||||
|               } else { | ||||
|                 previewEl.style.display = 'none'; | ||||
|               } | ||||
|             } | ||||
|           }, 300); | ||||
|         }); | ||||
|    | ||||
|         link.addEventListener('mousemove', (event) => { | ||||
|           if (previewEl.style.display === 'block') { | ||||
|             window.requestAnimationFrame(() => { | ||||
|               positionPreview(previewEl, event); | ||||
|             }); | ||||
|           } | ||||
|         }); | ||||
|    | ||||
|         link.addEventListener('mouseleave', () => { | ||||
|           if (loadingTimeout) { | ||||
|             clearTimeout(loadingTimeout); | ||||
|             loadingTimeout = null; | ||||
|           } | ||||
|    | ||||
|           currentLink = null; | ||||
|           previewEl.style.display = 'none'; | ||||
|         }); | ||||
|       }); | ||||
|    | ||||
|       document.addEventListener('click', (event) => { | ||||
|         if (!previewEl.contains(event.target)) { | ||||
|           previewEl.style.display = 'none'; | ||||
|         } | ||||
|       }); | ||||
|     }; | ||||
|    | ||||
|     if (!window.rssFeedPreviewInitialized) { | ||||
|       window.rssFeedPreviewInitialized = true; | ||||
|    | ||||
|       if (document.readyState === 'loading') { | ||||
|         document.addEventListener('DOMContentLoaded', initFeedPreview); | ||||
|       } else { | ||||
|         initFeedPreview(); | ||||
|       } | ||||
|   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' | ||||
|       }); | ||||
|     }); | ||||
|  | ||||
|     return result; | ||||
|   } | ||||
|  | ||||
|   function checkFeed(url, callback) { | ||||
|     $.ajax({ | ||||
|       url: CORS_PROXY + url, | ||||
|       type: 'GET', | ||||
|       dataType: 'text', | ||||
|       success: function (data) { | ||||
|         var items = parseRSS(data); | ||||
|         callback(items); | ||||
|       }, | ||||
|       error: function () { | ||||
|         callback(null); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   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; | ||||
|  | ||||
|     $('main table tbody tr td a').each(function () { | ||||
|       var $link = $(this); | ||||
|  | ||||
|       $link.on('mouseenter', function (e) { | ||||
|         currentLink = this; | ||||
|         var siteName = $link.text(); | ||||
|         var url = $link.attr('data-feed'); | ||||
|         if (!url) | ||||
|           return; | ||||
|  | ||||
|         $previewEl.html('<p>Checking for RSS/Atom feed...</p>').show(); | ||||
|         positionPreview(e); | ||||
|  | ||||
|         if (timeout) | ||||
|           clearTimeout(timeout); | ||||
|         timeout = setTimeout(function () { | ||||
|           if (cache[url]) { | ||||
|             renderFeedItems(cache[url], siteName); | ||||
|             positionPreview(e); | ||||
|             return; | ||||
|           } | ||||
|  | ||||
|           if (url) { | ||||
|             checkFeed(url, function (items) { | ||||
|               if (currentLink === $link[0] && items) { | ||||
|                 cache[url] = items; | ||||
|                 renderFeedItems(items, siteName); | ||||
|                 positionPreview(e); | ||||
|               } else { | ||||
|                 $previewEl.hide(); | ||||
|               } | ||||
|             }); | ||||
|           } else { | ||||
|             $previewEl.hide(); | ||||
|           } | ||||
|         }, 300); | ||||
|       }); | ||||
|  | ||||
|       $link.on('mousemove', function (e) { | ||||
|         if ($previewEl.is(':visible')) | ||||
|           positionPreview(e); | ||||
|       }); | ||||
|  | ||||
|       $link.on('mouseleave', function () { | ||||
|         clearTimeout(timeout); | ||||
|         timeout = null; | ||||
|         currentLink = null; | ||||
|         $previewEl.hide(); | ||||
|       }); | ||||
|     }); | ||||
|  | ||||
|     $(document).on('click', function (e) { | ||||
|       if (!$(e.target).closest('#rss-feed-preview').length) { | ||||
|         $previewEl.hide(); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   if (document.readyState === 'complete' || document.readyState === 'interactive') { | ||||
|     init(); | ||||
|   } else { | ||||
|     $(document).ready(init); | ||||
|   } | ||||
| })(); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user