|
|
/** * RSS/Atom Feed Preview for Links Table */
(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]; }); }
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, onSuccess, onError) { return $.ajax({ url: CORS_PROXY + url, type: 'GET', dataType: 'text', success: function (data) { var items = parseRSS(data); onSuccess(items); }, error: function () { onError(); } }); }
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; var currentRequest = null; var currentRequestId = 0; $('main table tbody').on('mouseenter mousemove mouseleave', 'tr td a', function (e) {
if (e.type === 'mouseenter') { var $link = $(this); var siteName = $link.text(); var url = $link.attr('data-feed'); if (!url) return;
currentLink = $link[0]; var requestId = ++currentRequestId;
$previewEl.html('<p>Checking for RSS/Atom feed...</p>').show(); positionPreview(e);
if (timeout) clearTimeout(timeout); timeout = setTimeout(function () { if (cache[url]) { if (currentLink === $link[0] && requestId === currentRequestId) { renderFeedItems(cache[url], siteName); positionPreview(e); } return; }
currentRequest = checkFeed( url, function (items) { if (requestId !== currentRequestId || currentLink !== $link[0]) return;
if (items && items.length) { cache[url] = items; renderFeedItems(items, siteName); } else { $previewEl.html('<p>No feed items found.</p>'); }
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') { if ($previewEl.is(':visible')) positionPreview(e); } else if (e.type === 'mouseleave') { clearTimeout(timeout); timeout = null; currentLink = null;
if (currentRequest) { currentRequest.abort(); currentRequest = 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); }})();
|