/** * 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 = $('
', { 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('

No feed items found.

'); return; } var html = '

Latest from ' + escapeHTML(siteName) + '

'; $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('

Checking for RSS/Atom feed...

').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('

No feed items found.

'); } positionPreview(e); }, function () { if (requestId !== currentRequestId || currentLink !== $link[0]) return; $previewEl.html('

Failed to load feed.

'); 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); } })();