177 lines
5.7 KiB
HTML
177 lines
5.7 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<title>Article Summaries</title>
|
||
|
||
<!-- Tiny bit of styling so it already looks decent -->
|
||
<style>
|
||
/* ─────────── Global title bar ─────────── */
|
||
.navbar{
|
||
position:fixed;
|
||
top:0; left:0; right:0;
|
||
height:3rem;
|
||
background:#0d47a1;
|
||
color:#fff;
|
||
display:flex;
|
||
align-items:center;
|
||
justify-content:space-between;
|
||
padding:0 1rem;
|
||
box-shadow:0 1px 4px rgba(0,0,0,.15);
|
||
z-index:100;
|
||
}
|
||
.navbar .brand{
|
||
font-size:1.15rem;
|
||
font-weight:700;
|
||
color:#fff;
|
||
text-decoration:none;
|
||
}
|
||
.navbar .nav-link{
|
||
color:#fff;
|
||
text-decoration:none;
|
||
margin-left:1rem;
|
||
font-size:.95rem;
|
||
}
|
||
.navbar .nav-link:hover{
|
||
text-decoration:underline;
|
||
}
|
||
|
||
body{
|
||
font-family: Arial, sans-serif;
|
||
margin: 0;
|
||
padding: 4rem 1rem 2rem;
|
||
background:#f6f8fa;
|
||
}
|
||
h1{color:#333;text-align:center;margin-top:1.5rem;}
|
||
#articles{
|
||
display:flex;
|
||
flex-direction:column;
|
||
gap:1rem;
|
||
max-width:800px;
|
||
margin:2rem auto;
|
||
}
|
||
.article-card{
|
||
background:#fff;
|
||
border-radius:6px;
|
||
padding:1rem 1.5rem;
|
||
box-shadow:0 1px 3px rgba(0,0,0,.08);
|
||
}
|
||
.article-card h2{
|
||
margin:.2rem 0 .6rem;
|
||
font-size:1.2rem;
|
||
}
|
||
.article-card p{
|
||
color:#444;
|
||
margin:0;
|
||
}
|
||
.article-link .article-card{
|
||
transition: transform .15s ease, box-shadow .15s ease;
|
||
}
|
||
.article-link:hover .article-card,
|
||
.article-link:focus-visible .article-card{
|
||
transform: translateY(-4px) scale(1.02);
|
||
box-shadow: 0 6px 14px rgba(0,0,0,.16);
|
||
}
|
||
|
||
.article-link,
|
||
.article-link:visited,
|
||
.article-link:hover,
|
||
.article-link:active,
|
||
.article-link:focus {
|
||
text-decoration: none;
|
||
color: inherit; /* keep the original text color */
|
||
}
|
||
.error{
|
||
color:#c00;
|
||
text-align:center;
|
||
margin-top:2rem;
|
||
}
|
||
</style>
|
||
</head>
|
||
|
||
<header class="navbar">
|
||
<a href="/news/" class="brand">Newsulizer</a>
|
||
<nav>
|
||
<a href="/news/" class="nav-link">Home</a>
|
||
<a href="/news/browse" class="nav-link">Browse</a>
|
||
<a href="/news/search" class="nav-link">Search</a>
|
||
</nav>
|
||
</header>
|
||
|
||
<body>
|
||
<div style="display: flex; justify-content: center;">
|
||
<h1>Newsulizer</h1>
|
||
</div>
|
||
<div style="display: flex; justify-content: center;">
|
||
<h2>Latest Article Summaries</h2>
|
||
</div>
|
||
|
||
<section id="articles" aria-live="polite"></section>
|
||
<p id="error" class="error" hidden>Unable to load articles. Please try again later.</p>
|
||
|
||
<script type="text/javascript">
|
||
(function loadArticles() {
|
||
const container = document.getElementById('articles');
|
||
const errorEl = document.getElementById('error');
|
||
|
||
// Change this to the full path of your API if it differs
|
||
const ENDPOINT = '/news/api/articles?count=25';
|
||
|
||
fetch(ENDPOINT)
|
||
.then(res => {
|
||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||
return res.json();
|
||
})
|
||
.then(data => {
|
||
if (!Array.isArray(data) || data.length === 0) {
|
||
throw new Error('No data returned');
|
||
}
|
||
|
||
data.forEach(item => {
|
||
const [url, meta] = Object.entries(item)[0];
|
||
|
||
/* Build <a><article>… inside …</article></a> so
|
||
the whole card is a single coherent link */
|
||
const link = document.createElement('a');
|
||
link.className = 'article-link';
|
||
link.href = '/news/view?url=' + encodeURIComponent(url);
|
||
|
||
const card = document.createElement('article');
|
||
card.className = 'article-card';
|
||
|
||
const h2 = document.createElement('h2');
|
||
h2.textContent = meta.title || url;
|
||
|
||
const p = document.createElement('p');
|
||
const txt = meta.processed_text;
|
||
let reg = txt.replace(/(\(.*[^\w:_; '.,’"\s]+.*\))/g, '')
|
||
reg = reg.replace(/(\[.*])/g, '')
|
||
reg = reg.replace(/([^\w:_; '.,’"\s/]+)/g, '')
|
||
const words = reg.split(/\s/g)
|
||
reg = words.slice(0, Math.min(60, words.length)).join(' ').trim();
|
||
if (reg.endsWith('.'))
|
||
reg += "..";
|
||
else
|
||
reg += "...";
|
||
p.textContent = reg;
|
||
|
||
card.appendChild(h2);
|
||
card.appendChild(p);
|
||
link.appendChild(card);
|
||
container.appendChild(link);
|
||
});
|
||
})
|
||
.catch(err => {
|
||
console.error(err);
|
||
errorEl.hidden = false;
|
||
});
|
||
|
||
// Helper to shorten long text without breaking words mid-sentence
|
||
function truncate(str, maxLen) {
|
||
if (typeof str !== 'string') return '';
|
||
return str.length > maxLen ? str.slice(0, maxLen).trimEnd() + '…' : str;
|
||
}
|
||
})();
|
||
</script>
|
||
</body>
|
||
</html> |