This repository has no description
0

Configure Feed

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

papers / src / landing-page.ts
4.9 kB 138 lines
1export const LANDING_PAGE = `<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="utf-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1"> 6 <title>Papers — Research Paper Summaries</title> 7 <style> 8 :root { 9 --bg: #0f0f0f; 10 --surface: #1a1a1a; 11 --text: #e0e0e0; 12 --dim: #888; 13 --accent: #6d9eeb; 14 --border: #333; 15 --green: #4caf50; 16 --mauve: #ce93d8; 17 } 18 * { box-sizing: border-box; margin: 0; padding: 0; } 19 body { 20 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; 21 background: var(--bg); 22 color: var(--text); 23 line-height: 1.6; 24 padding: 2rem; 25 max-width: 960px; 26 margin: 0 auto; 27 } 28 header { margin-bottom: 2rem; } 29 header h1 { font-size: 1.75rem; font-weight: 700; } 30 header p { color: var(--dim); margin-top: 0.25rem; } 31 .actions { margin-bottom: 1.5rem; display: flex; gap: 0.75rem; } 32 .actions button { 33 padding: 0.5rem 1rem; 34 background: var(--accent); 35 color: var(--bg); 36 border: none; 37 border-radius: 6px; 38 font-weight: 600; 39 cursor: pointer; 40 } 41 .actions button:hover { opacity: 0.9; } 42 .actions button.secondary { 43 background: var(--surface); 44 color: var(--text); 45 border: 1px solid var(--border); 46 } 47 .paper-list { display: flex; flex-direction: column; gap: 0.75rem; } 48 .paper-card { 49 background: var(--surface); 50 border: 1px solid var(--border); 51 border-radius: 8px; 52 padding: 1rem 1.25rem; 53 cursor: pointer; 54 transition: border-color 0.15s; 55 text-decoration: none; 56 color: inherit; 57 display: block; 58 } 59 .paper-card:hover { border-color: var(--accent); } 60 .paper-card .title { font-weight: 600; margin-bottom: 0.25rem; } 61 .paper-card .meta { font-size: 0.8125rem; color: var(--dim); } 62 .paper-card .summary { font-size: 0.875rem; margin-top: 0.5rem; color: var(--text); } 63 .paper-card .domains { margin-top: 0.5rem; display: flex; gap: 0.375rem; flex-wrap: wrap; } 64 .paper-card .domain { 65 font-size: 0.6875rem; 66 padding: 0.125rem 0.375rem; 67 border-radius: 4px; 68 background: var(--border); 69 color: var(--dim); 70 } 71 .paper-card .venue-badge { 72 font-size: 0.6875rem; 73 padding: 0.125rem 0.375rem; 74 border-radius: 4px; 75 background: var(--mauve); 76 color: var(--bg); 77 font-weight: 600; 78 } 79 .empty { color: var(--dim); text-align: center; padding: 3rem; } 80 #poll-status { font-size: 0.8125rem; color: var(--dim); margin-left: 0.75rem; } 81 </style> 82</head> 83<body> 84 <header> 85 <h1>Papers</h1> 86 <p>Research paper summaries from the Paper Skygest feed</p> 87 </header> 88 <div class="actions"> 89 <button onclick="pollFeed()">Poll Feed</button> 90 <span id="poll-status"></span> 91 </div> 92 <div id="papers" class="paper-list"></div> 93 <script> 94 const summaries = window.__SUMMARIES__ || []; 95 const container = document.getElementById('papers'); 96 const pollStatus = document.getElementById('poll-status'); 97 98 function render(summaries) { 99 if (summaries.length === 0) { 100 container.innerHTML = '<div class="empty">No papers indexed yet. Click "Poll Feed" to start.</div>'; 101 return; 102 } 103 container.innerHTML = summaries.map(s => { 104 const domains = (s.domains || []).map(d => '<span class="domain">' + d + '</span>').join(''); 105 const venue = s.venue ? '<span class="venue-badge">' + s.venue + '</span> ' : ''; 106 const year = s.year ? ' (' + s.year + ')' : ''; 107 const authors = (s.authors || []).length > 0 108 ? s.authors.length <= 3 ? s.authors.join(', ') : s.authors[0] + ' et al.' 109 : ''; 110 return '<a class="paper-card" href="/paper/' + s.rkey + '">' 111 + '<div class="title">' + venue + escHtml(s.title) + year + '</div>' 112 + '<div class="meta">' + (authors ? escHtml(authors) + ' · ' : '') + escHtml(s.posterHandle) + ' · ' + s.paperUrl + '</div>' 113 + '<div class="summary">' + escHtml(s.summary).substring(0, 200) + '</div>' 114 + (domains ? '<div class="domains">' + domains + '</div>' : '') 115 + '</a>'; 116 }).join(''); 117 } 118 119 function escHtml(s) { 120 return (s || '').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); 121 } 122 123 async function pollFeed() { 124 pollStatus.textContent = 'Polling...'; 125 try { 126 const res = await fetch('/api/poll', { method: 'POST' }); 127 const data = await res.json(); 128 pollStatus.textContent = 'Polled ' + data.polled + ', new: ' + data.new + ', written: ' + data.written + (data.errors ? ', errors: ' + data.errors : ''); 129 if (data.written > 0) setTimeout(() => location.reload(), 1500); 130 } catch (err) { 131 pollStatus.textContent = 'Error: ' + err.message; 132 } 133 } 134 135 render(summaries); 136 </script> 137</body> 138</html>`;