Mayx's Home Page https://mabbs.github.io
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

222 lines
6.4 KiB

  1. /**
  2. * RSS/Atom Feed Preview for Links Table
  3. */
  4. (function() {
  5. const existingPreviews = document.querySelectorAll('#rss-feed-preview');
  6. existingPreviews.forEach(el => el.remove());
  7. const CORS_PROXY = 'https://cors-anywhere.mayx.eu.org/?';
  8. const createPreviewElement = () => {
  9. const existingPreview = document.getElementById('rss-feed-preview');
  10. if (existingPreview) {
  11. return existingPreview;
  12. }
  13. const previewEl = document.createElement('div');
  14. previewEl.id = 'rss-feed-preview';
  15. previewEl.style.cssText = `
  16. position: fixed;
  17. display: none;
  18. width: 300px;
  19. max-height: 400px;
  20. overflow-y: auto;
  21. background-color: white;
  22. border: 1px solid #ccc;
  23. border-radius: 5px;
  24. padding: 10px;
  25. box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
  26. z-index: 1000;
  27. font-size: 14px;
  28. line-height: 1.4;
  29. `;
  30. document.body.appendChild(previewEl);
  31. return previewEl;
  32. };
  33. const parseRSS = (xmlText) => {
  34. const parser = new DOMParser();
  35. const xml = parser.parseFromString(xmlText, 'text/xml');
  36. const rssItems = xml.querySelectorAll('item');
  37. if (rssItems.length > 0) {
  38. return Array.from(rssItems).slice(0, 5).map(item => {
  39. return {
  40. title: item.querySelector('title')?.textContent || 'No title',
  41. date: item.querySelector('pubDate')?.textContent || 'No date',
  42. };
  43. });
  44. }
  45. const atomItems = xml.querySelectorAll('entry');
  46. if (atomItems.length > 0) {
  47. return Array.from(atomItems).slice(0, 5).map(item => {
  48. return {
  49. title: item.querySelector('title')?.textContent || 'No title',
  50. date: item.querySelector('updated')?.textContent || 'No date',
  51. };
  52. });
  53. }
  54. return null;
  55. };
  56. const checkFeed = async (url) => {
  57. try {
  58. const response = await fetch(CORS_PROXY + url);
  59. if (!response.ok) {
  60. return null;
  61. }
  62. const text = await response.text();
  63. return parseRSS(text);
  64. } catch (error) {
  65. return null;
  66. }
  67. };
  68. const findFeedUrl = async (siteUrl, linkElement) => {
  69. if (linkElement && linkElement.hasAttribute('data-feed')) {
  70. const dataFeedUrl = linkElement.getAttribute('data-feed');
  71. if (dataFeedUrl) {
  72. const feedItems = await checkFeed(dataFeedUrl);
  73. if (feedItems) {
  74. return { url: dataFeedUrl, items: feedItems };
  75. }
  76. }
  77. }
  78. return null;
  79. };
  80. const renderFeedItems = (previewEl, items, siteName) => {
  81. if (!items || items.length === 0) {
  82. previewEl.innerHTML = '<p>No feed items found.</p>';
  83. return;
  84. }
  85. let html = `<h3>Latest from ${siteName}</h3><ul style="list-style: none; padding: 0; margin: 0;">`;
  86. items.forEach(item => {
  87. html += `
  88. <li style="margin-bottom: 10px; padding-bottom: 10px; border-bottom: 1px solid #eee;">
  89. <div style="color: #24292e; font-weight: bold;">
  90. ${item.title}
  91. </div>
  92. <div style="color: #586069; font-size: 12px; margin: 3px 0;">
  93. ${new Date(item.date).toLocaleDateString()}
  94. </div>
  95. </li>
  96. `;
  97. });
  98. html += '</ul>';
  99. previewEl.innerHTML = html;
  100. };
  101. const positionPreview = (previewEl, event) => {
  102. const viewportWidth = window.innerWidth;
  103. const viewportHeight = window.innerHeight;
  104. let left = event.clientX + 20;
  105. let top = event.clientY + 20;
  106. const rect = previewEl.getBoundingClientRect();
  107. if (left + rect.width > viewportWidth) {
  108. left = event.clientX - rect.width - 20;
  109. }
  110. if (top + rect.height > viewportHeight) {
  111. top = event.clientY - rect.height - 20;
  112. }
  113. left = Math.max(10, left);
  114. top = Math.max(10, top);
  115. previewEl.style.left = `${left}px`;
  116. previewEl.style.top = `${top}px`;
  117. };
  118. const initFeedPreview = () => {
  119. const previewEl = createPreviewElement();
  120. const tableLinks = document.querySelectorAll('main table tbody tr td a');
  121. const feedCache = {};
  122. let currentLink = null;
  123. let loadingTimeout = null;
  124. tableLinks.forEach(link => {
  125. link.addEventListener('mouseenter', async (event) => {
  126. currentLink = link;
  127. const url = link.getAttribute('href');
  128. const siteName = link.textContent;
  129. previewEl.innerHTML = '<p>Checking for RSS/Atom feed...</p>';
  130. previewEl.style.display = 'block';
  131. positionPreview(previewEl, event);
  132. if (loadingTimeout) {
  133. clearTimeout(loadingTimeout);
  134. }
  135. loadingTimeout = setTimeout(async () => {
  136. if (feedCache[url]) {
  137. renderFeedItems(previewEl, feedCache[url].items, siteName);
  138. positionPreview(previewEl, event); // Reposition after content is loaded
  139. return;
  140. }
  141. const feedData = await findFeedUrl(url, link);
  142. if (currentLink === link) {
  143. if (feedData) {
  144. feedCache[url] = feedData;
  145. renderFeedItems(previewEl, feedData.items, siteName);
  146. positionPreview(previewEl, event); // Reposition after content is loaded
  147. } else {
  148. previewEl.style.display = 'none';
  149. }
  150. }
  151. }, 300);
  152. });
  153. link.addEventListener('mousemove', (event) => {
  154. if (previewEl.style.display === 'block') {
  155. window.requestAnimationFrame(() => {
  156. positionPreview(previewEl, event);
  157. });
  158. }
  159. });
  160. link.addEventListener('mouseleave', () => {
  161. if (loadingTimeout) {
  162. clearTimeout(loadingTimeout);
  163. loadingTimeout = null;
  164. }
  165. currentLink = null;
  166. previewEl.style.display = 'none';
  167. });
  168. });
  169. document.addEventListener('click', (event) => {
  170. if (!previewEl.contains(event.target)) {
  171. previewEl.style.display = 'none';
  172. }
  173. });
  174. };
  175. if (!window.rssFeedPreviewInitialized) {
  176. window.rssFeedPreviewInitialized = true;
  177. if (document.readyState === 'loading') {
  178. document.addEventListener('DOMContentLoaded', initFeedPreview);
  179. } else {
  180. initFeedPreview();
  181. }
  182. }
  183. })();

Powered by TurnKey Linux.