This repository has no description
0

Configure Feed

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

nanopub / src / landing-page.ts
6.1 kB 102 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>Nanopub — AT Protocol Nanopublication Index</title> 7 <style> 8 :root { --bg: #0a0a0a; --surface: #141414; --border: #222; --text: #e0e0e0; --dim: #888; --accent: #6d9bc0; --accent2: #8fb8d4; --green: #6dc08b; } 9 * { margin: 0; padding: 0; box-sizing: border-box; } 10 body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; background: var(--bg); color: var(--text); line-height: 1.6; } 11 a { color: var(--accent); text-decoration: none; } 12 a:hover { color: var(--accent2); text-decoration: underline; } 13 .container { max-width: 960px; margin: 0 auto; padding: 2rem 1.5rem; } 14 header { margin-bottom: 2rem; } 15 header h1 { font-size: 1.8rem; font-weight: 600; margin-bottom: 0.3rem; } 16 header p { color: var(--dim); font-size: 0.95rem; } 17 .stats { display: flex; gap: 1.5rem; margin-bottom: 2rem; flex-wrap: wrap; } 18 .stat { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 1rem 1.5rem; min-width: 120px; } 19 .stat .num { font-size: 1.6rem; font-weight: 700; color: var(--accent); } 20 .stat .label { font-size: 0.8rem; color: var(--dim); text-transform: uppercase; letter-spacing: 0.05em; } 21 .nanopub-list { list-style: none; } 22 .nanopub-item { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 1rem 1.25rem; margin-bottom: 0.75rem; transition: border-color 0.15s; } 23 .nanopub-item:hover { border-color: var(--accent); } 24 .nanopub-label { font-weight: 500; margin-bottom: 0.25rem; } 25 .nanopub-meta { font-size: 0.8rem; color: var(--dim); display: flex; gap: 1rem; flex-wrap: wrap; align-items: center; } 26 .nanopub-type { display: inline-block; background: var(--accent); color: var(--bg); font-size: 0.7rem; font-weight: 600; padding: 0.15rem 0.5rem; border-radius: 4px; text-transform: uppercase; letter-spacing: 0.05em; } 27 .nanopub-type.retraction { background: #c06d6d; } 28 .nanopub-type.new-version { background: #6dc08b; } 29 .island-link { display: inline-block; background: var(--green); color: var(--bg); font-size: 0.7rem; font-weight: 600; padding: 0.15rem 0.5rem; border-radius: 4px; text-transform: uppercase; letter-spacing: 0.05em; } 30 .island-link:hover { text-decoration: none; opacity: 0.85; } 31 .empty { color: var(--dim); font-style: italic; padding: 2rem; text-align: center; } 32 .loading { color: var(--dim); padding: 2rem; text-align: center; } 33 footer { margin-top: 3rem; padding-top: 1.5rem; border-top: 1px solid var(--border); color: var(--dim); font-size: 0.8rem; } 34 </style> 35</head> 36<body> 37 <div class="container"> 38 <header> 39 <h1>Nanopub</h1> 40 <p>AT Protocol nanopublication index — the smallest unit of publishable scientific knowledge</p> 41 </header> 42 <div class="stats" id="stats"></div> 43 <ul class="nanopub-list" id="list"><li class="loading">Loading...</li></ul> 44 <footer> 45 <p>Nanopub appview powered by <a href="https://github.com/nandithebull">Contrail</a> on AT Protocol</p> 46 </footer> 47 </div> 48 <script> 49 (async function() { 50 const statsEl = document.getElementById('stats'); 51 const listEl = document.getElementById('list'); 52 53 try { 54 const res = await fetch('/api/nanopubs?limit=100'); 55 const { nanopubs } = await res.json(); 56 57 // Stats 58 const total = nanopubs.length; 59 const assertions = nanopubs.filter(n => !n.nanopubType || n.nanopubType.includes('assertion')).length; 60 const retractions = nanopubs.filter(n => n.nanopubType && n.nanopubType.includes('retraction')).length; 61 const newVersions = nanopubs.filter(n => n.nanopubType && n.nanopubType.includes('newVersion')).length; 62 const withIsland = nanopubs.filter(n => n.islandUrl).length; 63 statsEl.innerHTML = [ 64 { num: total, label: 'Total' }, 65 { num: assertions, label: 'Assertions' }, 66 { num: withIsland, label: 'Islands' }, 67 { num: retractions, label: 'Retractions' }, 68 ].map(s => '<div class="stat"><div class="num">' + s.num + '</div><div class="label">' + s.label + '</div></div>').join(''); 69 70 if (!nanopubs.length) { 71 listEl.innerHTML = '<li class="empty">No nanopublications indexed yet</li>'; 72 return; 73 } 74 75 listEl.innerHTML = nanopubs.map(n => { 76 const type = n.nanopubType ? n.nanopubType.split('#').pop() : 'assertion'; 77 const typeClass = type === 'retraction' ? ' retraction' : type === 'newVersion' ? ' new-version' : ''; 78 const label = n.assertion?.label || n.assertion?.subjectIri || 'Untitled'; 79 const creators = (n.pubInfo?.creators || []).map(c => { 80 const m = c.match(/orcid\\.org\\/([\\d-]+)/); 81 return m ? m[1] : c.split('/').pop(); 82 }).join(', '); 83 const published = n.pubInfo?.createdOn || n.createdAt || ''; 84 return '<li class="nanopub-item">' 85 + '<a class="nanopub-label" href="/nanopub/' + n.rkey + '">' + escHtml(label) + '</a>' 86 + '<span class="nanopub-type' + typeClass + '">' + type + '</span>' 87 + (n.islandUrl ? ' <a class="island-link" href="' + escHtml(n.islandUrl) + '">island</a>' : '') 88 + '<div class="nanopub-meta">' 89 + (creators ? '<span>' + escHtml(creators) + '</span>' : '') 90 + (published ? '<span>' + published.substring(0, 10) + '</span>' : '') 91 + (n.sourceNanopubUri ? '<span><a href="' + escHtml(n.sourceNanopubUri) + '">source</a></span>' : '') 92 + '</div></li>'; 93 }).join(''); 94 } catch (err) { 95 listEl.innerHTML = '<li class="empty">Failed to load: ' + escHtml(err.message) + '</li>'; 96 } 97 98 function escHtml(s) { if (!s) return ''; const d = document.createElement('div'); d.textContent = s; return d.innerHTML; } 99 })(); 100 </script> 101</body> 102</html>`;