377 lines
10 KiB
HTML
377 lines
10 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>Send to E-Reader — Web Articles to EPUB</title>
|
|
|
|
<!-- Primary Meta Tags -->
|
|
<meta
|
|
name="description"
|
|
content="Convert web articles to EPUB and email them straight to your e-reader (Kindle, PocketBook, or any device that accepts emailed files). No ads, no tracking, open source."
|
|
/>
|
|
<meta
|
|
name="keywords"
|
|
content="send to ereader, send to kindle, send to pocketbook, article to epub, web to ereader, web article epub"
|
|
/>
|
|
<meta name="author" content="Stefan Atanasov" />
|
|
|
|
<!-- Open Graph / Facebook -->
|
|
<meta property="og:type" content="website" />
|
|
<meta property="og:url" content="https://read.atanasov.fi" />
|
|
<meta
|
|
property="og:title"
|
|
content="Send to E-Reader — Web Articles to EPUB"
|
|
/>
|
|
<meta
|
|
property="og:description"
|
|
content="Convert web articles to EPUB and email them to your e-reader in one click."
|
|
/>
|
|
|
|
<!-- Twitter -->
|
|
<meta name="twitter:card" content="summary_large_image" />
|
|
<meta name="twitter:url" content="https://read.atanasov.fi" />
|
|
<meta
|
|
name="twitter:title"
|
|
content="Send to E-Reader — Web Articles to EPUB"
|
|
/>
|
|
<meta
|
|
name="twitter:description"
|
|
content="Convert web articles to EPUB and email them to your e-reader in one click."
|
|
/>
|
|
|
|
<!-- Canonical URL -->
|
|
<link rel="canonical" href="https://read.atanasov.fi" />
|
|
|
|
<style>
|
|
body {
|
|
font-family: "Courier New", Courier, monospace;
|
|
line-height: 1.4;
|
|
max-width: 800px;
|
|
margin: 8px auto;
|
|
padding: 8px;
|
|
background: #f6f6ef;
|
|
}
|
|
|
|
.container {
|
|
background: #f6f6ef;
|
|
}
|
|
|
|
h1 {
|
|
color: #000;
|
|
margin: 0 0 16px 0;
|
|
font-size: 16px;
|
|
}
|
|
|
|
.setup-info {
|
|
border: 1px solid #828282;
|
|
padding: 8px;
|
|
margin-bottom: 16px;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.setup-info code {
|
|
background: #fff;
|
|
padding: 2px 4px;
|
|
}
|
|
|
|
.setup-info table {
|
|
border-collapse: collapse;
|
|
margin: 8px 0;
|
|
font-size: 13px;
|
|
}
|
|
|
|
.setup-info table th,
|
|
.setup-info table td {
|
|
text-align: left;
|
|
padding: 2px 8px 2px 0;
|
|
}
|
|
|
|
.input-group {
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.input-group label {
|
|
display: block;
|
|
margin-bottom: 4px;
|
|
font-size: 14px;
|
|
}
|
|
|
|
input[type="url"],
|
|
input[type="email"] {
|
|
width: 100%;
|
|
padding: 4px;
|
|
border: 1px solid #828282;
|
|
font-family: "Courier New", Courier, monospace;
|
|
font-size: 14px;
|
|
background: #fff;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
input[type="url"]:focus,
|
|
input[type="email"]:focus {
|
|
outline: none;
|
|
border-color: #000;
|
|
}
|
|
|
|
button {
|
|
background: #1ce783;
|
|
color: #000;
|
|
border: 1px solid #828282;
|
|
padding: 4px 8px;
|
|
font-family: "Courier New", Courier, monospace;
|
|
font-size: 14px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
button:hover {
|
|
background: #00b45a;
|
|
}
|
|
|
|
button:disabled {
|
|
background: #cccccc;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.message {
|
|
margin-top: 12px;
|
|
padding: 8px;
|
|
font-size: 14px;
|
|
border: 1px solid #828282;
|
|
display: none;
|
|
}
|
|
|
|
.success {
|
|
background: #dfd;
|
|
display: block;
|
|
}
|
|
|
|
.error {
|
|
background: #fdd;
|
|
display: block;
|
|
}
|
|
|
|
.loading {
|
|
background: #ffffd1;
|
|
display: block;
|
|
}
|
|
|
|
#credits {
|
|
font-size: 12px;
|
|
margin-top: 16px;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<header>
|
|
<h1>Send to E-Reader</h1>
|
|
</header>
|
|
|
|
<main>
|
|
<article class="container">
|
|
<section class="setup-info">
|
|
<h2>guide</h2>
|
|
<ol>
|
|
<li>
|
|
whitelist <code>send@read.atanasov.fi</code> on your device — one-time setup:
|
|
<ul>
|
|
<li>
|
|
<strong>Kindle</strong>: add it in
|
|
<a
|
|
href="https://www.amazon.com/hz/mycd/myx#/home/settings/payment"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>Amazon → Manage Your Content and Devices → Preferences →
|
|
Personal Document Settings → Approved Personal Document E-mail List</a
|
|
>
|
|
before you send the first article.
|
|
</li>
|
|
<li>
|
|
<strong>PocketBook</strong>: no dashboard step needed. Send the
|
|
first article — PocketBook will email you a confirmation with
|
|
an "add to whitelist" link. Click it once, and every future
|
|
article will be delivered automatically.
|
|
</li>
|
|
</ul>
|
|
</li>
|
|
<li>enter your e-reader's email below</li>
|
|
<li>paste the article URL and hit send</li>
|
|
</ol>
|
|
|
|
<h3>e-reader email formats</h3>
|
|
<table>
|
|
<tr>
|
|
<th>device</th>
|
|
<th>email pattern</th>
|
|
</tr>
|
|
<tr>
|
|
<td>Amazon Kindle</td>
|
|
<td><code>*@kindle.com</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td>PocketBook</td>
|
|
<td><code>*@pbsync.com</code></td>
|
|
</tr>
|
|
</table>
|
|
|
|
<h3>notes:</h3>
|
|
<ul>
|
|
<li>any email works — domain isn't checked, use whatever your device gave you</li>
|
|
<li>article should be publicly accessible (no paywalls / login required)</li>
|
|
<li>EPUB is generated server-side and deleted immediately after sending</li>
|
|
<li>
|
|
your e-reader email is saved in your browser
|
|
<code>localStorage</code> for future use; nothing is stored on the server
|
|
</li>
|
|
</ul>
|
|
</section>
|
|
|
|
<form
|
|
id="readerForm"
|
|
class="reader-form"
|
|
aria-label="Article to e-reader conversion form"
|
|
>
|
|
<div class="input-group">
|
|
<label for="readerEmail">E-Reader Email:</label>
|
|
<input
|
|
type="email"
|
|
id="readerEmail"
|
|
name="readerEmail"
|
|
placeholder="your-device@kindle.com"
|
|
autocomplete="email"
|
|
required
|
|
aria-required="true"
|
|
/>
|
|
</div>
|
|
|
|
<div class="input-group">
|
|
<label for="articleUrl">Article URL:</label>
|
|
<input
|
|
type="url"
|
|
id="articleUrl"
|
|
name="articleUrl"
|
|
placeholder="https://example.com/article"
|
|
autocomplete="off"
|
|
required
|
|
aria-required="true"
|
|
/>
|
|
</div>
|
|
|
|
<button type="submit" id="sendButton">Send</button>
|
|
<div
|
|
id="message"
|
|
class="message"
|
|
role="alert"
|
|
aria-live="polite"
|
|
></div>
|
|
</form>
|
|
</article>
|
|
</main>
|
|
|
|
<footer>
|
|
<p id="credits">
|
|
v 0.1.0 •
|
|
<a
|
|
href="https://atanasov.fi"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>atanasov.fi</a
|
|
>
|
|
</p>
|
|
</footer>
|
|
|
|
<script>
|
|
const urlInput = document.getElementById("articleUrl");
|
|
const emailInput = document.getElementById("readerEmail");
|
|
const sendButton = document.getElementById("sendButton");
|
|
const messageDiv = document.getElementById("message");
|
|
|
|
// Migrate any previously-saved key from the old version
|
|
const legacy = localStorage.getItem("kindleEmail");
|
|
if (legacy && !localStorage.getItem("readerEmail")) {
|
|
localStorage.setItem("readerEmail", legacy);
|
|
localStorage.removeItem("kindleEmail");
|
|
}
|
|
|
|
if (localStorage.getItem("readerEmail")) {
|
|
emailInput.value = localStorage.getItem("readerEmail");
|
|
}
|
|
|
|
function showMessage(text, type) {
|
|
messageDiv.textContent = text;
|
|
messageDiv.className = `message ${type}`;
|
|
}
|
|
|
|
async function sendArticle(event) {
|
|
if (event) event.preventDefault();
|
|
|
|
const url = urlInput.value.trim();
|
|
const email = emailInput.value.trim();
|
|
|
|
if (!url) {
|
|
showMessage("please enter a valid url", "error");
|
|
return;
|
|
}
|
|
|
|
if (!email) {
|
|
showMessage("please enter your e-reader email", "error");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
sendButton.disabled = true;
|
|
showMessage("processing article...", "loading");
|
|
|
|
localStorage.setItem("readerEmail", email);
|
|
|
|
const response = await fetch("/send-article", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({ url, readerEmail: email }),
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
showMessage(`success! "${data.title}" sent to your e-reader`, "success");
|
|
urlInput.value = "";
|
|
} else {
|
|
throw new Error(data.error || data.message || "failed to send article");
|
|
}
|
|
} catch (error) {
|
|
showMessage(error.message || "error sending article", "error");
|
|
} finally {
|
|
sendButton.disabled = false;
|
|
}
|
|
}
|
|
|
|
document
|
|
.getElementById("readerForm")
|
|
.addEventListener("submit", sendArticle);
|
|
</script>
|
|
|
|
<script type="application/ld+json">
|
|
{
|
|
"@context": "https://schema.org",
|
|
"@type": "WebApplication",
|
|
"name": "Send to E-Reader",
|
|
"applicationCategory": "UtilityApplication",
|
|
"offers": {
|
|
"@type": "Offer",
|
|
"price": "0",
|
|
"priceCurrency": "EUR"
|
|
},
|
|
"creator": {
|
|
"@type": "Person",
|
|
"name": "Stefan Atanasov",
|
|
"url": "https://atanasov.fi"
|
|
},
|
|
"description": "Convert web articles to EPUB and email them to your e-reader.",
|
|
"operatingSystem": "All",
|
|
"browserRequirements": "Requires JavaScript"
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|