diff options
-rw-r--r-- | .github/PULL_REQUEST_TEMPLATE.md (renamed from .github/PULL_REQUEST_TEMPLATE.yml) | 0 | ||||
-rw-r--r-- | assets/js/rss-feed-preview.js | 397 | ||||
-rw-r--r-- | links.md | 4 | ||||
-rw-r--r-- | sitemap.xsl | 2 |
4 files changed, 181 insertions, 222 deletions
diff --git a/.github/PULL_REQUEST_TEMPLATE.yml b/.github/PULL_REQUEST_TEMPLATE.md index f595bc4..f595bc4 100644 --- a/.github/PULL_REQUEST_TEMPLATE.yml +++ b/.github/PULL_REQUEST_TEMPLATE.md diff --git a/assets/js/rss-feed-preview.js b/assets/js/rss-feed-preview.js index 2929622..30af324 100644 --- a/assets/js/rss-feed-preview.js +++ b/assets/js/rss-feed-preview.js @@ -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])); - }; - - 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> - `; + "'": ''' + }[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' }); - - 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; + }); + + 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); } - - 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); + }); + } + + 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; } - - 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 + + if (url) { + checkFeed(url, function (items) { + if (currentLink === $link[0] && items) { + cache[url] = items; + renderFeedItems(items, siteName); + positionPreview(e); } else { - previewEl.style.display = 'none'; + $previewEl.hide(); } - } - }, 300); - }); - - link.addEventListener('mousemove', (event) => { - if (previewEl.style.display === 'block') { - window.requestAnimationFrame(() => { - positionPreview(previewEl, event); }); + } else { + $previewEl.hide(); } - }); - - link.addEventListener('mouseleave', () => { - if (loadingTimeout) { - clearTimeout(loadingTimeout); - loadingTimeout = null; - } - - currentLink = null; - previewEl.style.display = 'none'; - }); + }, 300); }); - - document.addEventListener('click', (event) => { - if (!previewEl.contains(event.target)) { - previewEl.style.display = 'none'; - } + + $link.on('mousemove', function (e) { + if ($previewEl.is(':visible')) + positionPreview(e); }); - }; - - if (!window.rssFeedPreviewInitialized) { - window.rssFeedPreviewInitialized = true; - - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', initFeedPreview); - } else { - initFeedPreview(); + + $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(); } - } - })(); - \ No newline at end of file + }); + } + + if (document.readyState === 'complete' || document.readyState === 'interactive') { + init(); + } else { + $(document).ready(init); + } +})(); diff --git a/links.md b/links.md index 6a9c5d3..8d07260 100644 --- a/links.md +++ b/links.md @@ -29,6 +29,4 @@ tags: [links] 头像:<https://avatars0.githubusercontent.com/u/17966333> Logo:<https://mabbs.github.io/favicon.ico> -<!--[if !IE]> --> -<script src="/assets/js/rss-feed-preview.js"></script> -<!-- <![endif]--> \ No newline at end of file +<script src="/assets/js/rss-feed-preview.js"></script> \ No newline at end of file diff --git a/sitemap.xsl b/sitemap.xsl index 256704b..4485c3b 100644 --- a/sitemap.xsl +++ b/sitemap.xsl @@ -11,7 +11,7 @@ title: Sitemap <li> <a> <xsl:attribute name="href"><xsl:value-of select="sm:loc" /></xsl:attribute> - <xsl:value-of select="sm:loc" /> + <xsl:value-of select="sm:loc" /> </a> </li> </xsl:for-each> |