93 lines
4.1 KiB
HTML
93 lines
4.1 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Read it later</title>
|
|
<style>
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
|
|
background: #fff;
|
|
color: #1a1a1a;
|
|
max-width: 640px;
|
|
margin: 0 auto;
|
|
padding: 48px 24px;
|
|
}
|
|
h1 { font-size: 20px; font-weight: 600; margin-bottom: 32px; }
|
|
.input-row { display: flex; gap: 8px; margin-bottom: 12px; }
|
|
input[type="url"] {
|
|
flex: 1; padding: 10px 14px; border: 1px solid #ddd;
|
|
border-radius: 8px; font-size: 14px; outline: none;
|
|
}
|
|
input[type="url"]:focus { border-color: #999; }
|
|
button {
|
|
padding: 10px 20px; background: #1a1a1a; color: #fff;
|
|
border: none; border-radius: 8px; font-size: 14px; cursor: pointer;
|
|
}
|
|
button:hover { background: #333; }
|
|
button:disabled { background: #999; cursor: not-allowed; }
|
|
.error { color: #c00; font-size: 13px; margin-bottom: 24px; min-height: 18px; }
|
|
.articles { display: flex; flex-direction: column; gap: 24px; margin-top: 24px; }
|
|
.article { padding-bottom: 24px; border-bottom: 1px solid #eee; }
|
|
.article:last-child { border-bottom: none; }
|
|
.article-url { font-size: 12px; color: #999; word-break: break-all; margin-bottom: 4px; }
|
|
.article-title { font-size: 16px; font-weight: 600; margin-bottom: 8px; }
|
|
.article-summary { font-size: 14px; color: #555; line-height: 1.6; }
|
|
.article-date { font-size: 12px; color: #bbb; margin-top: 8px; }
|
|
.empty { color: #bbb; font-size: 14px; text-align: center; padding: 48px 0; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1>Read it later</h1>
|
|
<form class="input-row" id="form">
|
|
<input type="url" id="url" placeholder="Paste article URL..." required>
|
|
<button type="submit" id="btn">Save</button>
|
|
</form>
|
|
<div id="error" class="error"></div>
|
|
<div class="articles" id="articles"></div>
|
|
<script>
|
|
const API = '/api';
|
|
|
|
async function load() {
|
|
try {
|
|
const r = await fetch(API + '/articles');
|
|
const data = await r.json();
|
|
const el = document.getElementById('articles');
|
|
if (!data.length) { el.innerHTML = '<div class="empty">No articles yet</div>'; return; }
|
|
el.innerHTML = data.map(a => `
|
|
<div class="article">
|
|
<div class="article-url">${a.url}</div>
|
|
<div class="article-title">${a.title || 'Untitled'}</div>
|
|
<div class="article-summary">${a.summary || 'Processing...'}</div>
|
|
<div class="article-date">${new Date(a.created_at).toLocaleDateString()}</div>
|
|
</div>`).join('');
|
|
} catch (e) {
|
|
document.getElementById('articles').innerHTML = '<div class="empty">Failed to load</div>';
|
|
}
|
|
}
|
|
|
|
document.getElementById('form').addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
const btn = document.getElementById('btn');
|
|
const inp = document.getElementById('url');
|
|
const err = document.getElementById('error');
|
|
btn.disabled = true; btn.textContent = '...'; err.textContent = '';
|
|
try {
|
|
const r = await fetch(API + '/articles', {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify({url: inp.value})
|
|
});
|
|
if (!r.ok) throw new Error((await r.json()).detail || 'Error');
|
|
inp.value = '';
|
|
await load();
|
|
} catch (e) { err.textContent = e.message; }
|
|
finally { btn.disabled = false; btn.textContent = 'Save'; }
|
|
});
|
|
|
|
load();
|
|
</script>
|
|
</body>
|
|
</html>
|