Merge pull request #54 from @DanikVitek

feat(frontend): Update to Svelte 5; Code cleanup
This commit is contained in:
Dhanush R
2025-05-27 18:13:51 +05:30
committed by GitHub
16 changed files with 1444 additions and 1069 deletions
+1
View File
@@ -6,6 +6,7 @@ venv
data
*ipynb
build
.idea
.vscode
.venv
.env
+3
View File
@@ -0,0 +1,3 @@
# Ignore artifacts:
build
coverage
+18
View File
@@ -0,0 +1,18 @@
{
"trailingComma": "all",
"endOfLine": "lf",
"semi": true,
"tabWidth": 2,
"htmlWhitespaceSensitivity": "strict",
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
"overrides": [
{
"files": "*.svelte",
"options": {
"parser": "svelte"
}
}
],
"tailwindStylesheet": "./src/app.css",
"tailwindConfig": "./tailwind.config.js"
}
+7
View File
@@ -0,0 +1,7 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"checkJs": true
},
"exclude": ["node_modules", "**/node_modules/*"]
}
+1191 -872
View File
File diff suppressed because it is too large Load Diff
+11 -9
View File
@@ -4,26 +4,28 @@
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview"
"preview": "vite preview",
"format": "prettier . --write"
},
"devDependencies": {
"@fontsource/fira-mono": "^4.5.10",
"@neoconfetti/svelte": "^1.0.0",
"@sveltejs/adapter-auto": "^3.0.0",
"@sveltejs/adapter-static": "^3.0.1",
"@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^3.0.0",
"@sveltejs/kit": "^2.20.7",
"@sveltejs/vite-plugin-svelte": "^5.0.3",
"@tailwindcss/typography": "^0.5.10",
"autoprefixer": "^10.4.16",
"daisyui": "^4.4.20",
"postcss": "^8.4.32",
"postcss-load-config": "^5.0.2",
"svelte": "^4.2.7",
"prettier": "^3.5.3",
"prettier-plugin-svelte": "^3.3.3",
"prettier-plugin-tailwindcss": "^0.6.11",
"svelte": "^5.28.2",
"svelte-preprocess": "^6.0.3",
"tailwindcss": "^3.3.6",
"vite": "^5.0.3"
"vite": "^6.3.3"
},
"type": "module",
"dependencies": {
"svelte-preprocess": "^5.1.3"
}
"type": "module"
}
@@ -1,13 +1,12 @@
const tailwindcss = require("tailwindcss");
const autoprefixer = require("autoprefixer");
import tailwindcss from "tailwindcss";
import autoprefixer from "autoprefixer";
const config = {
/** @type {import('postcss-load-config').Config} */
export default {
plugins: [
//Some plugins, like tailwindcss/nesting, need to run before Tailwind,
tailwindcss(),
tailwindcss,
//But others, like autoprefixer, need to run after,
autoprefixer,
],
};
module.exports = config;
+13 -4
View File
@@ -1,5 +1,5 @@
<!doctype html>
<html lang="en" data-theme="nord">
<html lang="en" data-theme="bumblebee">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.svg" />
@@ -7,20 +7,29 @@
<title>Wattpad Downloader</title>
<meta name="title" content="Wattpad Downloader" />
<meta name="description" content="Read your way, download Wattpad Books as PDFs or EPUBs in seconds. Have an Ad-Free experience with Unlimited Offline Reading. Try it now!" />
<meta
name="description"
content="Read your way, download Wattpad Books as PDFs or EPUBs in seconds. Have an Ad-Free experience with Unlimited Offline Reading. Try it now!"
/>
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website" />
<meta property="og:url" content="https://wpd.rambhat.la/" />
<meta property="og:title" content="Wattpad Downloader" />
<meta property="og:description" content="Read your way, download Wattpad Books as PDFs or EPUBs in seconds. Have an Ad-Free experience with Unlimited Offline Reading. Try it now!" />
<meta
property="og:description"
content="Read your way, download Wattpad Books as PDFs or EPUBs in seconds. Have an Ad-Free experience with Unlimited Offline Reading. Try it now!"
/>
<meta property="og:image" content="https://wpd.rambhat.la/embed.png" />
<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content="https://wpd.rambhat.la/" />
<meta property="twitter:title" content="Wattpad Downloader" />
<meta property="twitter:description" content="Read your way, download Wattpad Books as PDFs or EPUBs in seconds. Have an Ad-Free experience with Unlimited Offline Reading. Try it now!" />
<meta
property="twitter:description"
content="Read your way, download Wattpad Books as PDFs or EPUBs in seconds. Have an Ad-Free experience with Unlimited Offline Reading. Try it now!"
/>
<meta property="twitter:image" content="https://wpd.rambhat.la/embed.png" />
<!-- Meta Tags Generated with https://metatags.io -->
+3 -3
View File
@@ -1,9 +1,9 @@
<div class="flex">
<div class="hero min-h-screen">
<div class="hero-content text-center">
<div class="bg-base-200 p-16 max-w-lg rounded-md">
<div class="max-w-lg rounded-md bg-base-200 p-16">
<h1 class="text-5xl font-bold">There was an error.</h1>
<div class="py-6 join">
<div class="join py-6">
<a class="btn btn-primary btn-lg" href="/">Home</a>
</div>
</div>
@@ -11,7 +11,7 @@
</div>
<footer
class="footer footer-center p-4 bg-base-300 text-base-content bottom-0 fixed"
class="footer footer-center fixed bottom-0 bg-base-300 p-4 text-base-content"
>
<aside class="text-2xl">
<p>
+11 -4
View File
@@ -1,5 +1,12 @@
<script>
import "../app.pcss";
import "../app.css";
/**
* @typedef {Object} Props
* @property {import('svelte').Snippet} [children]
*/
/** @type {Props} */
let { children } = $props();
</script>
<svelte:head>
@@ -10,13 +17,13 @@
</style>
</svelte:head>
<slot />
{@render children?.()}
<footer
class="footer footer-center p-4 bg-base-300 text-base-content bottom-0 fixed"
class="footer footer-center fixed bottom-0 bg-base-300 p-4 text-base-content"
>
<aside>
<div class="flex flex-row max-w-lg w-full">
<div class="flex w-full max-w-lg flex-row">
<a
href="/donate"
target="_blank"
+117 -104
View File
@@ -1,103 +1,118 @@
<script>
let download_images = false;
let download_as_pdf = false; // 0 = epub, 1 = pdf
let is_paid_story = false;
let invalid_url = false;
let after_download_page = false;
let credentials = {
let downloadImages = $state(false);
let downloadAsPdf = $state(false); // 0 = epub, 1 = pdf
let isPaidStory = $state(false);
let invalidUrl = $state(false);
let afterDownloadPage = $state(false);
let credentials = $state({
username: "",
password: "",
};
let download_id = "";
let mode = "";
let input_url = "";
});
let downloadId = $state("");
/** @type {"story" | "part" | ""} */
let mode = $state("");
let inputUrl = $state("");
let button_disabled = false;
$: button_disabled =
!input_url ||
(is_paid_story && !(credentials.username && credentials.password));
let buttonDisabled = $derived(
!inputUrl ||
(isPaidStory && !(credentials.username && credentials.password)),
);
$: url =
let url = $derived(
`/download/` +
download_id +
downloadId +
`?om=1` +
(download_images ? "&download_images=true" : "") +
(is_paid_story
(downloadImages ? "&download_images=true" : "") +
(isPaidStory
? `&username=${encodeURIComponent(credentials.username)}&password=${encodeURIComponent(credentials.password)}`
: "") +
`&mode=${mode}` +
(download_as_pdf ? "&format=pdf" : "&format=epub");
(downloadAsPdf ? "&format=pdf" : "&format=epub"),
);
$: {
if (input_url.length) {
input_url = input_url.toLowerCase();
/** @type {HTMLDialogElement} */
let storyURLTutorialModal;
invalid_url = false;
/** @param {string} input */
const setInputAsValid = (input) => {
invalidUrl = false;
inputUrl = input;
downloadId = input;
};
if (/^\d+$/.test(input_url)) {
/** @param {string} input */
const setInputAsInvalid = (input) => {
invalidUrl = true;
inputUrl = input;
downloadId = input;
};
/** @param {string} input */
const setInputUrl = (input) => {
input = input.toLowerCase();
if (!input) {
setInputAsValid("");
return;
}
if (/^\d+$/.test(input)) {
// All numbers
download_id = input_url;
mode = "story";
} else if (input_url.includes("wattpad.com/")) {
// Is a string and contains contain wattpad.com/
setInputAsValid(input);
return;
}
if (input_url.includes("/story/")) {
if (!input.includes("wattpad.com/")) {
setInputAsInvalid(input.match(/\d+/g)?.join("") ?? "");
return;
}
// Is a string and contains wattpad.com/
if (input.includes("/story/")) {
// https://wattpad.com/story/237369078-wattpad-books-presents
input_url = input_url.split("-")[0].split("?")[0].split("/story/")[1]; // removes tracking fields and title
download_id = input_url;
mode = "story";
} else if (input_url.includes("/stories/")) {
setInputAsValid(
input.split("-", 1)[0].split("?", 1)[0].split("/story/")[1], // removes tracking fields and title
);
} else if (input.includes("/stories/")) {
// https://www.wattpad.com/api/v3/stories/237369078?fields=...
input_url = input_url.split("?")[0].split("/stories/")[1]; // removes params
download_id = input_url;
mode = "story";
setInputAsValid(
input.split("?", 1)[0].split("/stories/")[1], // removes params
);
} else {
// https://www.wattpad.com/939051741-wattpad-books-presents-the-qb-bad-boy-and-me
input_url = input_url
.split("-")[0]
.split("?")[0]
.split("wattpad.com/")[1]; // removes tracking fields and title
download_id = input_url;
if (/^\d+$/.test(download_id)) {
// If "wattpad.com/{download_id}" contains only numbers
input = input.split("-", 1)[0].split("?", 1)[0].split("wattpad.com/")[1]; // removes tracking fields and title
if (/^\d+$/.test(input)) {
// If "wattpad.com/{downloadId}" contains only numbers
mode = "part";
setInputAsValid(input);
} else {
invalid_url = true;
input_url = "";
download_id = "";
setInputAsInvalid("");
}
}
} else {
invalid_url = true;
}
input_url = input_url.match(/\d+/g)?.join("") || "";
download_id = input_url;
// Originally, I was going to call the Wattpad API (wattpad.com/api/v3/stories/${story_id}), but Wattpad kept blocking those requests. I suspect it has something to do with the Origin header, I wasn't able to remove it.
// In the future, if this is considered, it would be cool if we could derive the Story ID from a pasted Part URL. Refer to @AaronBenDaniel's https://github.com/AaronBenDaniel/WattpadDownloader/blob/49b29b245188149f2d24c0b1c59e4c7f90f289a9/src/api/src/create_book.py#L156 (https://www.wattpad.com/api/v3/story_parts/{part_id}?fields=url).
} else {
invalid_url = false;
download_id = "";
}
}
};
</script>
<div>
<div class="hero min-h-screen">
<div
class="hero-content flex-col lg:flex-row-reverse bg-base-100/50 lg:p-16 py-32 rounded shadow-sm"
class="hero-content flex-col rounded bg-base-100/50 py-32 shadow-sm lg:flex-row-reverse lg:p-16"
>
{#if !after_download_page}
<div class="text-center lg:text-left lg:p-10">
{#if !afterDownloadPage}
<div class="text-center lg:p-10 lg:text-left">
<h1
class="font-extrabold text-transparent text-5xl bg-clip-text bg-gradient-to-r to-pink-600 via-yellow-600 from-red-700"
class="bg-gradient-to-r from-red-700 via-yellow-600 to-pink-600 bg-clip-text text-5xl font-extrabold text-transparent"
>Wattpad Downloader</h1
>
Wattpad Downloader
</h1>
<div
role="alert"
class="alert bg-green-200 mt-10 break-words max-w-md"
class="alert mt-10 max-w-md break-words bg-green-200"
>
<svg
xmlns="http://www.w3.org/2000/svg"
@@ -137,11 +152,11 @@
</svg>
<span class="text-lg">Please Donate</span>
</div> -->
<p class="pt-6 text-lg max-w-md">
Download your favourite books with a single click. Have a great new
year!
</p>
<ul class="pt-4 list list-inside text-xl">
<p class="max-w-md pt-6 text-lg"
>Download your favourite books with a single click. Have a great new
year!</p
>
<ul class="list list-inside pt-4 text-xl">
<!-- TODO: 'max-lg: hidden' to hide on screen sizes smaller than lg. I'll do this when I figure out how to make this show up _below_ the card on smaller screen sizes. -->
<!-- <li>12/24 - ⚡ Super-fast Downloads!</li>
<li>12/24 - 📑 PDF Downloads!</li> -->
@@ -162,49 +177,49 @@
<li>06/24 - 🖼️ Image Downloading!</li>
</ul>
</div>
<div class="card shrink-0 w-full max-w-sm shadow-2xl bg-base-100">
<div class="card w-full max-w-sm shrink-0 bg-base-100 shadow-2xl">
<form class="card-body">
<div class="form-control">
<input
type="text"
placeholder="Story URL"
class="input input-bordered"
class:input-warning={invalid_url}
bind:value={input_url}
class:input-warning={invalidUrl}
bind:value={() => inputUrl, setInputUrl}
required
name="input_url"
/>
<label class="label" for="input_url">
{#if invalid_url}
{#if invalidUrl}
<p class=" text-red-500">
Refer to (<button
class="link font-semibold"
onclick="StoryURLTutorialModal.showModal()"
onclick={() => storyURLTutorialModal.showModal()}
data-umami-event="Part StoryURLTutorialModal Open"
>How to get a Story URL</button
>).
</p>
{:else}
<button
class="label-text link font-semibold"
onclick="StoryURLTutorialModal.showModal()"
class="link label-text font-semibold"
onclick={() => storyURLTutorialModal.showModal()}
data-umami-event="StoryURLTutorialModal Open"
>How to get a Story URL</button
>
{/if}
</label>
<label class="cursor-pointer label">
<label class="label cursor-pointer">
<span class="label-text"
>This is a Paid Story, and I've purchased it</span
>
<input
type="checkbox"
class="checkbox checkbox-warning shadow-md"
bind:checked={is_paid_story}
class="checkbox-warning checkbox shadow-md"
bind:checked={isPaidStory}
/>
</label>
{#if is_paid_story}
{#if isPaidStory}
<label class="input input-bordered flex items-center gap-2">
Username
<input
@@ -233,16 +248,16 @@
<div class="form-control mt-6">
<a
class="btn rounded-l-none"
class:btn-primary={!download_as_pdf}
class:btn-secondary={download_as_pdf}
class:btn-disabled={button_disabled}
class:btn-primary={!downloadAsPdf}
class:btn-secondary={downloadAsPdf}
class:btn-disabled={buttonDisabled}
data-umami-event="Download"
href={url}
on:click={() => (after_download_page = true)}>Download</a
onclick={() => (afterDownloadPage = true)}>Download</a
>
<!-- <label class="swap w-fit label mt-2">
<input type="checkbox" bind:checked={download_as_pdf} />
<input type="checkbox" bind:checked={downloadAsPdf} />
<div class="swap-on">
Downloading as <span class=" underline text-bold">PDF</span> (Click)
</div>
@@ -251,28 +266,28 @@
</div>
</label> -->
<label class="cursor-pointer label">
<label class="label cursor-pointer">
<span class="label-text"
>Include Images (<strong>Slower Download</strong>)</span
>
<input
type="checkbox"
class="checkbox checkbox-warning shadow-md"
bind:checked={download_images}
class="checkbox-warning checkbox shadow-md"
bind:checked={downloadImages}
/>
</label>
</div>
</form>
</div>
{:else}
<div class="text-center max-w-4xl">
<h1 class="font-bold text-3xl">
<div class="max-w-4xl text-center">
<h1 class="text-3xl font-bold">
Your download has <span
class="text-transparent bg-clip-text bg-gradient-to-r to-pink-600 via-yellow-600 from-red-700"
class="bg-gradient-to-r from-red-700 via-yellow-600 to-pink-600 bg-clip-text text-transparent"
>Started</span
>
</h1>
<div class="py-4 space-y-2">
<div class="space-y-2 py-4">
<p class="text-2xl">
If you found this site useful, please consider <a
href="https://github.com/TheOnlyWayUp/WattpadDownloader"
@@ -281,7 +296,7 @@
data-umami-event="Star">starring the project</a
> to support WattpadDownloader.
</p>
<p class="text-lg pt-2">
<p class="pt-2 text-lg">
You can also join us on <a
href="https://discord.gg/P9RHC4KCwd"
target="_blank"
@@ -290,17 +305,17 @@
>, where we release features early and discuss updates.
</p>
</div>
<div class="grid justify-center grid-rows-2 gap-y-10">
<div class="grid grid-rows-2 justify-center gap-y-10">
<a
href="/donate"
target="_blank"
class="btn bg-cyan-200 btn-lg mt-10 hover:bg-green-200"
class="btn btn-lg mt-10 bg-cyan-200 hover:bg-green-200"
>Buy me a Coffee! 🍵</a
>
<button
on:click={() => {
after_download_page = false;
input_url = "";
onclick={() => {
afterDownloadPage = false;
inputUrl = "";
}}
class="btn btn-outline btn-lg">Download More</button
>
@@ -311,29 +326,27 @@
</div>
</div>
<!-- Open the modal using ID.showModal() method -->
<dialog id="StoryURLTutorialModal" class="modal">
<dialog class="modal" bind:this={storyURLTutorialModal}>
<div class="modal-box">
<form method="dialog">
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
<button class="btn btn-circle btn-ghost btn-sm absolute right-2 top-2"
></button
>
</form>
<h3 class="font-bold text-lg">Finding the Story URL</h3>
<ol class="list list-disc list-inside py-4 space-y-4">
<h3 class="text-lg font-bold">Finding the Story URL</h3>
<ol class="list list-inside list-disc space-y-4 py-4">
<li>
Copy the URL from the Website, or hit share and copy the URL on the App.
</li>
<li>
For example,
<span class="font-mono bg-slate-100 p-1"
>wattpad.com/<span class="bg-amber-200 rounded-sm">story</span
<span class="bg-slate-100 p-1 font-mono"
>wattpad.com/<span class="rounded-sm bg-amber-200">story</span
>/237369078-wattpad-books-presents</span
>.
</li>
<li>
<span class="font-mono bg-slate-100 p-1"
<span class="bg-slate-100 p-1 font-mono"
>https://www.wattpad.com/939103774-given</span
> is okay too.
</li>
+4 -3
View File
@@ -2,7 +2,7 @@ import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
import adapter from "@sveltejs/adapter-static";
/** @type {import('@sveltejs/kit').Config} */
const config = {
export default {
kit: {
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
// If your environment is not supported or you settled on a specific environment, switch out the adapter.
@@ -11,6 +11,7 @@ const config = {
},
preprocess: [vitePreprocess({})],
vitePlugin: {
inspector: true,
},
};
export default config;
-21
View File
@@ -1,21 +0,0 @@
const daisyui = require("daisyui");
const typography = require("@tailwindcss/typography");
/** @type {import('tailwindcss').Config}*/
const config = {
content: ["./src/**/*.{html,js,svelte,ts}"],
theme: {
extend: {},
},
plugins: [typography, daisyui],
daisyui: {
themes: [
"bumblebee"
],
},
};
module.exports = config;
+17
View File
@@ -0,0 +1,17 @@
import daisyui from "daisyui";
import typography from "@tailwindcss/typography";
/** @type {import('tailwindcss').Config} */
export default {
content: ["./src/**/*.{html,js,svelte,ts}"],
theme: {
extend: {},
},
plugins: [typography, daisyui],
daisyui: {
themes: ["bumblebee"],
},
};
+3 -3
View File
@@ -1,6 +1,6 @@
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
import { sveltekit } from "@sveltejs/kit/vite";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [sveltekit()]
plugins: [sveltekit()],
});