this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

more

+149 -225
+149 -225
index.html
··· 209 209 overflow: hidden; 210 210 } 211 211 212 + .feed-item-left { 213 + display: flex; 214 + align-items: center; 215 + margin-right: 10px; 216 + } 217 + 212 218 .feed-item-date { 213 219 font-family: 'JetBrains Mono', monospace; 214 220 font-size: 0.75rem; ··· 227 233 } 228 234 229 235 .feed-item-title { 230 - flex: 1; 231 236 font-size: 0.95rem; 232 237 font-weight: 400; 233 - white-space: nowrap; 234 - overflow: hidden; 235 - text-overflow: ellipsis; 236 - margin-right: 15px; 238 + margin-bottom: 3px; 237 239 } 238 240 239 241 .feed-item-title a { ··· 246 248 color: var(--accent-color); 247 249 } 248 250 249 - .feed-item-preview { 250 - flex: 2; 251 - white-space: nowrap; 251 + .feed-item-content-wrapper { 252 + flex: 1; 252 253 overflow: hidden; 253 - text-overflow: ellipsis; 254 + } 255 + 256 + .feed-item-preview { 254 257 color: var(--text-muted); 255 258 font-size: 0.85rem; 256 - margin-right: 15px; 259 + overflow: hidden; 260 + text-overflow: ellipsis; 261 + white-space: nowrap; 262 + transition: all 0.3s ease; 257 263 } 258 264 259 265 .feed-item-actions { ··· 268 274 transition: all 0.3s ease; 269 275 } 270 276 271 - .feed-item:hover, 272 - .feed-item.content-open { 277 + .feed-item:hover { 273 278 border-left-color: var(--accent-color); 274 279 background-color: rgba(77, 250, 123, 0.03); 275 280 } ··· 294 299 } 295 300 296 301 297 - .feed-item-external-links { 302 + .feed-item:hover .feed-item-preview { 303 + white-space: normal; 304 + line-height: 1.4; 305 + max-height: none; 306 + } 307 + 308 + .preview-links, 309 + .preview-references { 298 310 font-size: 0.8rem; 299 - padding: 5px 15px; 300 - border-top: 1px dashed var(--border-color); 301 311 display: none; 302 312 flex-wrap: wrap; 303 313 align-items: center; 304 - gap: 5px; 305 - background-color: rgba(77, 250, 123, 0.02); 314 + gap: 8px; 315 + margin-top: 5px; 316 + padding-top: 5px; 317 + border-top: 1px dotted var(--border-color); 318 + } 319 + 320 + .external-link-item[title*="github.com"] { 321 + background-color: rgba(77, 180, 128, 0.08); 322 + color: var(--accent-alt); 323 + } 324 + 325 + .feed-item:hover .preview-links, 326 + .feed-item:hover .preview-references { 327 + display: flex; 306 328 } 307 329 308 330 .reference-header { ··· 529 551 item.addEventListener('mouseenter', () => { 530 552 // Close all sections in previously hovered item 531 553 if (currentHoveredItem && currentHoveredItem !== item) { 532 - // Don't close manually opened content (with the button) 533 - if (!currentHoveredItem.classList.contains('content-open')) { 534 - const prevContent = currentHoveredItem.querySelector('.feed-item-content'); 535 - if (prevContent) { 536 - prevContent.style.display = 'none'; 537 - 538 - // Update button state 539 - const prevButton = currentHoveredItem.querySelector('.read-more-btn'); 540 - if (prevButton) { 541 - prevButton.textContent = '📄'; 542 - prevButton.title = 'Show content'; 543 - } 544 - } 545 - } 554 + // Remove this section - we no longer show the full content 546 555 547 - // Don't close manually opened external links 548 - if (!currentHoveredItem.classList.contains('links-open')) { 549 - const prevLinks = currentHoveredItem.querySelector('.feed-item-external-links'); 550 - if (prevLinks) { 551 - prevLinks.style.display = 'none'; 552 - 553 - // Update button state 554 - const prevButton = currentHoveredItem.querySelector('.external-links-toggle'); 555 - if (prevButton) { 556 - prevButton.textContent = '🌐'; 557 - prevButton.title = 'Show external links'; 558 - } 559 - } 560 - } 556 + // No need to close preview content now since it's controlled by CSS hover 561 557 562 - // Don't close manually opened references 563 - if (!currentHoveredItem.classList.contains('refs-to-open')) { 564 - const prevRefsTo = currentHoveredItem.querySelector('.references-to'); 565 - if (prevRefsTo) { 566 - prevRefsTo.style.display = 'none'; 567 - 568 - // Update button state 569 - const prevButton = currentHoveredItem.querySelector('.references-to-toggle'); 570 - if (prevButton) { 571 - prevButton.textContent = '➡️'; 572 - prevButton.title = 'Show references to other posts'; 573 - } 574 - } 575 - } 576 - 577 - if (!currentHoveredItem.classList.contains('refs-by-open')) { 578 - const prevRefsBy = currentHoveredItem.querySelector('.references-by'); 579 - if (prevRefsBy) { 580 - prevRefsBy.style.display = 'none'; 581 - 582 - // Update button state 583 - const prevButton = currentHoveredItem.querySelector('.references-by-toggle'); 584 - if (prevButton) { 585 - prevButton.textContent = '⬅️'; 586 - prevButton.title = 'Show posts referencing this one'; 587 - } 588 - } 589 - } 558 + // References are now controlled by CSS hover 590 559 } 591 560 592 561 // Set this as current hovered item 593 562 currentHoveredItem = item; 594 563 595 - // Show content on hover (unless manually closed) 596 - const content = item.querySelector('.feed-item-content'); 597 - if (content) { 598 - content.style.display = 'block'; 599 - 600 - // Update button state 601 - const button = item.querySelector('.read-more-btn'); 602 - if (button) { 603 - button.textContent = '📕'; 604 - button.title = 'Hide content'; 605 - } 606 - } 564 + // Remove this section - we no longer show the full content 607 565 608 - // Show external links if available 609 - const externalLinks = item.querySelector('.feed-item-external-links'); 610 - if (externalLinks) { 611 - externalLinks.style.display = 'flex'; 612 - 613 - // Update button state 614 - const externalButton = item.querySelector('.external-links-toggle'); 615 - if (externalButton) { 616 - externalButton.textContent = '🌐'; 617 - externalButton.title = 'Hide external links'; 618 - } 619 - } 566 + // Preview content is shown automatically by CSS on hover 620 567 }); 621 568 }); 622 569 } ··· 635 582 }); 636 583 } 637 584 638 - // Function to get first line of text 639 - function getFirstLine(html) { 585 + // Function to get a paragraph preview of text 586 + function getTextPreview(html, maxLength = 300) { 640 587 // Create a temporary div to parse HTML 641 588 const tempDiv = document.createElement('div'); 642 589 tempDiv.innerHTML = html; 643 590 644 - // Extract text content 591 + // Extract text content and remove extra whitespace 645 592 const text = tempDiv.textContent || ''; 593 + const cleanText = text.replace(/\s+/g, ' ').trim(); 646 594 647 - // Get first line (up to first period or newline, whichever comes first) 648 - const firstPeriod = text.indexOf('.'); 649 - const firstNewline = text.indexOf('\n'); 650 - 651 - let endIndex; 652 - if (firstPeriod !== -1 && firstNewline !== -1) { 653 - endIndex = Math.min(firstPeriod, firstNewline); 654 - } else if (firstPeriod !== -1) { 655 - endIndex = firstPeriod; 656 - } else if (firstNewline !== -1) { 657 - endIndex = firstNewline; 658 - } else { 659 - // If no period or newline, take first 80 chars 660 - endIndex = Math.min(text.length, 80); 595 + // Get a reasonable preview length (about a paragraph) 596 + if (cleanText.length <= maxLength) { 597 + return cleanText; 661 598 } 662 599 663 - return text.substring(0, endIndex + 1).trim(); 664 - } 665 - 666 - // Function to toggle content visibility 667 - function toggleContent(articleId) { 668 - const article = document.getElementById(articleId); 669 - const content = article.querySelector('.feed-item-content'); 670 - const button = article.querySelector('.read-more-btn'); 600 + // Try to find a good break point 601 + let endIndex = maxLength; 671 602 672 - if (content.style.display === 'block') { 673 - content.style.display = 'none'; 674 - button.textContent = '📄'; 675 - button.title = 'Show content'; 676 - 677 - // Remove forced hover state 678 - article.classList.remove('content-open'); 603 + // Look for the last sentence break within our limit 604 + const lastPeriod = cleanText.lastIndexOf('.', maxLength); 605 + if (lastPeriod > maxLength / 2) { 606 + endIndex = lastPeriod + 1; 679 607 } else { 680 - content.style.display = 'block'; 681 - button.textContent = '📕'; 682 - button.title = 'Hide content'; 683 - 684 - // Add forced hover state 685 - article.classList.add('content-open'); 686 - } 687 - } 688 - 689 - // Function to toggle external links visibility 690 - function toggleExternalLinks(articleId) { 691 - const article = document.getElementById(articleId); 692 - const linksContainer = article.querySelector('.feed-item-external-links'); 693 - const button = article.querySelector('.external-links-toggle'); 694 - 695 - if (!linksContainer || !button) { 696 - console.error(`External links container or button for ${articleId} not found`); 697 - return; 608 + // Look for the last space to avoid cutting words 609 + const lastSpace = cleanText.lastIndexOf(' ', maxLength); 610 + if (lastSpace > 0) { 611 + endIndex = lastSpace; 612 + } 698 613 } 699 614 700 - if (linksContainer.style.display === 'flex') { 701 - linksContainer.style.display = 'none'; 702 - button.textContent = '🌐'; 703 - button.title = 'Show external links'; 704 - article.classList.remove('links-open'); 705 - } else { 706 - linksContainer.style.display = 'flex'; 707 - button.textContent = '🌐'; 708 - button.title = 'Hide external links'; 709 - article.classList.add('links-open'); 710 - } 615 + return cleanText.substring(0, endIndex) + '...'; 711 616 } 712 617 713 - // Function to toggle references visibility 714 - function toggleReferences(articleId, type) { 715 - const article = document.getElementById(articleId); 716 - const referencesContainer = article.querySelector(`.references-${type}`); 717 - const button = article.querySelector(`.references-${type}-toggle`); 618 + // Function to get first line for preview in post listing 619 + function getFirstLine(html) { 620 + // Create a temporary div to parse HTML 621 + const tempDiv = document.createElement('div'); 622 + tempDiv.innerHTML = html; 718 623 719 - if (!referencesContainer || !button) { 720 - console.error(`References container or button for ${articleId} not found`); 721 - return; 722 - } 624 + // Extract text content 625 + const text = tempDiv.textContent || ''; 626 + const cleanText = text.replace(/\s+/g, ' ').trim(); 723 627 724 - if (referencesContainer.style.display === 'block') { 725 - referencesContainer.style.display = 'none'; 726 - button.textContent = type === 'to' ? '➡️' : '⬅️'; 727 - button.title = type === 'to' ? 'Show references to other posts' : 'Show posts referencing this one'; 728 - article.classList.remove(`refs-${type}-open`); 628 + // Get first sentence, or about 80 chars 629 + const firstPeriod = cleanText.indexOf('.'); 630 + 631 + let endIndex; 632 + if (firstPeriod !== -1 && firstPeriod < 100) { 633 + endIndex = firstPeriod; 729 634 } else { 730 - referencesContainer.style.display = 'block'; 731 - button.textContent = type === 'to' ? '➡️' : '⬅️'; 732 - button.title = type === 'to' ? 'Hide references to other posts' : 'Hide posts referencing this one'; 733 - article.classList.add(`refs-${type}-open`); 635 + // If no suitable period, take first 80 chars 636 + endIndex = Math.min(cleanText.length, 80); 637 + // Look for the last space to avoid cutting words 638 + const lastSpace = cleanText.lastIndexOf(' ', endIndex); 639 + if (lastSpace > endIndex / 2) { 640 + endIndex = lastSpace; 641 + } 734 642 } 643 + 644 + return cleanText.substring(0, endIndex + 1).trim(); 735 645 } 646 + 647 + // Function removed - we no longer toggle full content 648 + 649 + // Removed the external links toggle function as it's no longer needed 650 + 651 + // Reference toggle function removed - references are now shown with CSS on hover 736 652 737 653 try { 738 654 // Fetch the Atom feed and threads data in parallel ··· 802 718 .replace(/\n/g, '<br>'); 803 719 } 804 720 805 - // Get the first line for preview 721 + // Get the first line and paragraph preview 806 722 const firstLine = getFirstLine(contentHtml); 723 + const textPreview = getTextPreview(contentHtml); 807 724 808 725 // Store the entry data 809 726 entriesById[id] = { ··· 813 730 link, 814 731 contentHtml, 815 732 firstLine, 733 + textPreview, 816 734 published, 817 735 author, 818 736 source, ··· 926 844 entriesHTML += ` 927 845 <article id="${entry.articleId}" class="feed-item" ${dateAttr}> 928 846 <div class="feed-item-row"> 847 + <div class="feed-item-left"> 848 + <a href="${entry.link}" target="_blank" class="external-link" title="Open original post">🔗</a> 849 + </div> 929 850 <div class="feed-item-date">${formatDate(entry.published)}</div> 930 851 <div class="feed-item-author">${entry.author}</div> 931 - <div class="feed-item-title"><a href="${entry.link}" target="_blank">${entry.title}</a></div> 932 - <div class="feed-item-preview">${entry.firstLine}</div> 933 - <div class="feed-item-actions"> 934 - <button class="read-more-btn" onclick="toggleContent('${entry.articleId}')" title="Show/hide content">📄</button> 935 - <a href="${entry.link}" target="_blank" class="external-link" title="Open original post">🔗</a> 936 - ${entry.externalLinks && entry.externalLinks.length > 0 ? 937 - `<button class="external-links-toggle" onclick="toggleExternalLinks('${entry.articleId}')" title="Show/hide external links">🌐</button>` : ''} 938 - ${entry.referencesTo && entry.referencesTo.length > 0 ? 939 - `<button class="references-to-toggle references-toggle" onclick="toggleReferences('${entry.articleId}', 'to')" title="Show/hide references to other posts">➡️</button>` : ''} 940 - ${entry.referencedBy && entry.referencedBy.length > 0 ? 941 - `<button class="references-by-toggle references-toggle" onclick="toggleReferences('${entry.articleId}', 'by')" title="Show/hide posts referencing this one">⬅️</button>` : ''} 942 - </div> 943 - </div> 944 - <div class="feed-item-content">${entry.contentHtml}</div> 945 - 946 - ${entry.externalLinks && entry.externalLinks.length > 0 ? ` 947 - <div class="feed-item-external-links"> 948 - <span class="external-links-label">External links:</span> 949 - ${entry.externalLinks.map(link => `<a href="${link.url}" target="_blank" class="external-link-item" title="${link.url}">${new URL(link.url).hostname}</a>`).join(', ')} 950 - </div> 951 - ` : ''} 952 - 953 - ${entry.referencesTo && entry.referencesTo.length > 0 ? ` 954 - <div class="references-container references-to" style="display: none;"> 955 - <div class="reference-header">References to other posts:</div> 956 - ${entry.referencesTo.map(ref => ` 957 - <div class="reference-item"> 958 - <span class="reference-indicator">→</span> 959 - <a href="${ref.link}" target="_blank" class="reference-link">${ref.title}</a> 960 - <span class="reference-author"> by ${ref.author}</span> 852 + <div class="feed-item-content-wrapper"> 853 + <div class="feed-item-title"><a href="${entry.link}" target="_blank">${entry.title}</a></div> 854 + <div class="feed-item-preview">${entry.textPreview}</div> 855 + 856 + ${entry.externalLinks && entry.externalLinks.length > 0 ? ` 857 + <div class="preview-links"> 858 + <span class="external-links-label">External links:</span> 859 + ${entry.externalLinks.map(link => { 860 + const url = new URL(link.url); 861 + let displayText = url.hostname.replace('www.', ''); 862 + 863 + // Special handling for GitHub links 864 + if (url.hostname === 'github.com' || url.hostname === 'gist.github.com') { 865 + // Extract the parts from pathname (remove leading slash) 866 + const parts = url.pathname.substring(1).split('/').filter(part => part); 867 + if (parts.length >= 2) { 868 + displayText = `github:${parts[0]}/${parts[1]}`; 869 + } 870 + } 871 + 872 + // Special handling for Wikipedia links 873 + if (url.hostname === 'en.wikipedia.org' || url.hostname === 'wikipedia.org' || url.hostname.endsWith('.wikipedia.org')) { 874 + const titlePart = url.pathname.split('/').pop(); 875 + if (titlePart) { 876 + const title = decodeURIComponent(titlePart).replace(/_/g, ' '); 877 + displayText = `wikipedia:${title}`; 878 + } 879 + } 880 + 881 + return `<a href="${link.url}" target="_blank" class="external-link-item" title="${link.url}">${displayText}</a>`; 882 + }).join(', ')} 883 + </div> 884 + ` : ''} 885 + 886 + ${entry.referencesTo && entry.referencesTo.length > 0 ? ` 887 + <div class="preview-references"> 888 + <span class="external-links-label">References:</span> 889 + ${entry.referencesTo.map(ref => ` 890 + <a href="${ref.link}" target="_blank" class="external-link-item" title="${ref.title} by ${ref.author}">→ ${ref.title}</a> 891 + `).join(', ')} 961 892 </div> 962 - `).join('')} 963 - </div> 964 - ` : ''} 965 - 966 - ${entry.referencedBy && entry.referencedBy.length > 0 ? ` 967 - <div class="references-container references-by" style="display: none;"> 968 - <div class="reference-header">Posts referencing this one:</div> 969 - ${entry.referencedBy.map(ref => ` 970 - <div class="reference-item"> 971 - <span class="reference-indicator">←</span> 972 - <a href="${ref.link}" target="_blank" class="reference-link">${ref.title}</a> 973 - <span class="reference-author"> by ${ref.author}</span> 893 + ` : ''} 894 + 895 + ${entry.referencedBy && entry.referencedBy.length > 0 ? ` 896 + <div class="preview-references"> 897 + <span class="external-links-label">Referenced by:</span> 898 + ${entry.referencedBy.map(ref => ` 899 + <a href="${ref.link}" target="_blank" class="external-link-item" title="${ref.title} by ${ref.author}">← ${ref.title}</a> 900 + `).join(', ')} 974 901 </div> 975 - `).join('')} 902 + ` : ''} 903 + </div> 976 904 </div> 977 - ` : ''} 978 905 </article> 979 906 `; 980 907 ··· 986 913 // Update sources count 987 914 sourceCountElement.textContent = sources.size; 988 915 989 - // Add the toggle functions to the global scope 990 - window.toggleContent = toggleContent; 991 - window.toggleExternalLinks = toggleExternalLinks; 992 - window.toggleReferences = toggleReferences; 916 + // No toggle functions needed anymore 993 917 994 918 // Build timeline sidebar 995 919 const timelineSidebar = document.getElementById('timeline-sidebar');