Update to Svelte 5; Use the event callback instead of the effect

This commit is contained in:
DanikVitek
2025-04-28 20:28:43 +03:00
parent 669bf1fb97
commit b6fb019b34
6 changed files with 1188 additions and 956 deletions
+1
View File
@@ -6,6 +6,7 @@ venv
data data
*ipynb *ipynb
build build
.idea
.vscode .vscode
.venv .venv
.env .env
+6
View File
@@ -0,0 +1,6 @@
{
"compilerOptions": {
"checkJs": true
},
"exclude": ["node_modules", "**/node_modules/*"]
}
+1055 -863
View File
File diff suppressed because it is too large Load Diff
+5 -5
View File
@@ -11,19 +11,19 @@
"@neoconfetti/svelte": "^1.0.0", "@neoconfetti/svelte": "^1.0.0",
"@sveltejs/adapter-auto": "^3.0.0", "@sveltejs/adapter-auto": "^3.0.0",
"@sveltejs/adapter-static": "^3.0.1", "@sveltejs/adapter-static": "^3.0.1",
"@sveltejs/kit": "^2.0.0", "@sveltejs/kit": "^2.20.7",
"@sveltejs/vite-plugin-svelte": "^3.0.0", "@sveltejs/vite-plugin-svelte": "^5.0.3",
"@tailwindcss/typography": "^0.5.10", "@tailwindcss/typography": "^0.5.10",
"autoprefixer": "^10.4.16", "autoprefixer": "^10.4.16",
"daisyui": "^4.4.20", "daisyui": "^4.4.20",
"postcss": "^8.4.32", "postcss": "^8.4.32",
"postcss-load-config": "^5.0.2", "postcss-load-config": "^5.0.2",
"svelte": "^4.2.7", "svelte": "^5.28.2",
"tailwindcss": "^3.3.6", "tailwindcss": "^3.3.6",
"vite": "^5.0.3" "vite": "^6.3.3"
}, },
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"svelte-preprocess": "^5.1.3" "svelte-preprocess": "^6.0.3"
} }
} }
+8 -1
View File
@@ -1,5 +1,12 @@
<script> <script>
import "../app.pcss"; import "../app.pcss";
/**
* @typedef {Object} Props
* @property {import('svelte').Snippet} [children]
*/
/** @type {Props} */
let { children } = $props();
</script> </script>
<svelte:head> <svelte:head>
@@ -10,7 +17,7 @@
</style> </style>
</svelte:head> </svelte:head>
<slot /> {@render children?.()}
<footer <footer
class="footer footer-center p-4 bg-base-300 text-base-content bottom-0 fixed" class="footer footer-center p-4 bg-base-300 text-base-content bottom-0 fixed"
+113 -87
View File
@@ -1,85 +1,114 @@
<script> <script>
let download_images = false; let downloadImages = $state(false);
let download_as_pdf = false; // 0 = epub, 1 = pdf let downloadAsPdf = $state(false); // 0 = epub, 1 = pdf
let is_paid_story = false; let isPaidStory = $state(false);
let invalid_url = false; let invalidUrl = $state(false);
let after_download_page = false; let afterDownloadPage = $state(false);
let credentials = { let credentials = $state({
username: "", username: "",
password: "", password: "",
}; });
let download_id = ""; let downloadId = $state("");
let mode = ""; /** @type {"story" | "part" | ""} */
let input_url = ""; let mode = $state("");
let inputUrl = $state("");
let button_disabled = false; let buttonDisabled = $derived(
$: button_disabled = !inputUrl ||
!input_url || (isPaidStory && !(credentials.username && credentials.password))
(is_paid_story && !(credentials.username && credentials.password)); );
$: url = let url = $derived(
`/download/` + `/download/` +
download_id + downloadId +
`?om=1` + `?om=1` +
(download_images ? "&download_images=true" : "") + (downloadImages ? "&download_images=true" : "") +
(is_paid_story (isPaidStory
? `&username=${encodeURIComponent(credentials.username)}&password=${encodeURIComponent(credentials.password)}` ? `&username=${encodeURIComponent(credentials.username)}&password=${encodeURIComponent(credentials.password)}`
: "") + : "") +
`&mode=${mode}` + `&mode=${mode}` +
(download_as_pdf ? "&format=pdf" : "&format=epub"); (downloadAsPdf ? "&format=pdf" : "&format=epub")
);
$: { /** @type {HTMLDialogElement} */
if (input_url.length) { let storyURLTutorialModal = $state.raw(undefined);
input_url = input_url.toLowerCase();
invalid_url = false; /**
* @param {string} input
* @param {HTMLInputElement} [inputElement]
*/
const setValid = (input, inputElement) => {
invalidUrl = false;
inputUrl = input;
downloadId = input;
if (inputElement) inputElement.value = input;
};
if (/^\d+$/.test(input_url)) { /**
// All numbers * @param {string} input
download_id = input_url; * @param {HTMLInputElement} inputElement
mode = "story"; */
} else if (input_url.includes("wattpad.com/")) { const setInvalid = (input, inputElement) => {
// Is a string and contains contain wattpad.com/ invalidUrl = true;
inputUrl = input;
downloadId = input;
inputElement.value = input;
};
if (input_url.includes("/story/")) { /** @type {import("svelte/elements").FormEventHandler<HTMLInputElement>} */
// https://wattpad.com/story/237369078-wattpad-books-presents const onInputUrl = (e) => {
input_url = input_url.split("-")[0].split("?")[0].split("/story/")[1]; // removes tracking fields and title let input = e.currentTarget.value.toLowerCase();
download_id = input_url;
mode = "story";
} else if (input_url.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";
} 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
mode = "part";
} else {
invalid_url = true;
input_url = "";
download_id = "";
}
}
} else {
invalid_url = true;
}
input_url = input_url.match(/\d+/g)?.join("") || ""; if (!input) {
download_id = input_url; setValid("");
return;
// 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 = "";
} }
if (/^\d+$/.test(input)) {
// All numbers
mode = "story";
setValid(input, e.currentTarget);
return;
}
if (!input.includes("wattpad.com/")) {
setInvalid(
input.match(/\d+/g)?.join("") ?? "",
e.currentTarget,
);
return;
}
// Is a string and contains wattpad.com/
if (input.includes("/story/")) {
// https://wattpad.com/story/237369078-wattpad-books-presents
mode = "story";
setValid(
input.split("-", 1)[0].split("?", 1)[0].split("/story/")[1], // removes tracking fields and title
e.currentTarget,
);
} else if (input.includes("/stories/")) {
// https://www.wattpad.com/api/v3/stories/237369078?fields=...
mode = "story";
setValid(
input.split("?", 1)[0].split("/stories/")[1], // removes params
e.currentTarget,
);
} else {
// https://www.wattpad.com/939051741-wattpad-books-presents-the-qb-bad-boy-and-me
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";
setValid(input, e.currentTarget);
} else {
setInvalid("", e.currentTarget);
}
}
// 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).
} }
</script> </script>
@@ -88,13 +117,12 @@
<div <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 lg:flex-row-reverse bg-base-100/50 lg:p-16 py-32 rounded shadow-sm"
> >
{#if !after_download_page} {#if !afterDownloadPage}
<div class="text-center lg:text-left lg:p-10"> <div class="text-center lg:text-left lg:p-10">
<h1 <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="font-extrabold text-transparent text-5xl bg-clip-text bg-gradient-to-r to-pink-600 via-yellow-600 from-red-700"
>Wattpad Downloader</h1
> >
Wattpad Downloader
</h1>
<div <div
role="alert" role="alert"
class="alert bg-green-200 mt-10 break-words max-w-md" class="alert bg-green-200 mt-10 break-words max-w-md"
@@ -169,17 +197,17 @@
type="text" type="text"
placeholder="Story URL" placeholder="Story URL"
class="input input-bordered" class="input input-bordered"
class:input-warning={invalid_url} class:input-warning={invalidUrl}
bind:value={input_url} oninput={onInputUrl}
required required
name="input_url" name="input_url"
/> />
<label class="label" for="input_url"> <label class="label" for="input_url">
{#if invalid_url} {#if invalidUrl}
<p class=" text-red-500"> <p class=" text-red-500">
Refer to (<button Refer to (<button
class="link font-semibold" class="link font-semibold"
onclick="StoryURLTutorialModal.showModal()" onclick={() => storyURLTutorialModal.showModal()}
data-umami-event="Part StoryURLTutorialModal Open" data-umami-event="Part StoryURLTutorialModal Open"
>How to get a Story URL</button >How to get a Story URL</button
>). >).
@@ -187,7 +215,7 @@
{:else} {:else}
<button <button
class="label-text link font-semibold" class="label-text link font-semibold"
onclick="StoryURLTutorialModal.showModal()" onclick={() => storyURLTutorialModal.showModal()}
data-umami-event="StoryURLTutorialModal Open" data-umami-event="StoryURLTutorialModal Open"
>How to get a Story URL</button >How to get a Story URL</button
> >
@@ -201,10 +229,10 @@
<input <input
type="checkbox" type="checkbox"
class="checkbox checkbox-warning shadow-md" class="checkbox checkbox-warning shadow-md"
bind:checked={is_paid_story} bind:checked={isPaidStory}
/> />
</label> </label>
{#if is_paid_story} {#if isPaidStory}
<label class="input input-bordered flex items-center gap-2"> <label class="input input-bordered flex items-center gap-2">
Username Username
<input <input
@@ -233,16 +261,16 @@
<div class="form-control mt-6"> <div class="form-control mt-6">
<a <a
class="btn rounded-l-none" class="btn rounded-l-none"
class:btn-primary={!download_as_pdf} class:btn-primary={!downloadAsPdf}
class:btn-secondary={download_as_pdf} class:btn-secondary={downloadAsPdf}
class:btn-disabled={button_disabled} class:btn-disabled={buttonDisabled}
data-umami-event="Download" data-umami-event="Download"
href={url} href={url}
on:click={() => (after_download_page = true)}>Download</a on:click={() => (afterDownloadPage = true)}>Download</a
> >
<!-- <label class="swap w-fit label mt-2"> <!-- <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"> <div class="swap-on">
Downloading as <span class=" underline text-bold">PDF</span> (Click) Downloading as <span class=" underline text-bold">PDF</span> (Click)
</div> </div>
@@ -258,7 +286,7 @@
<input <input
type="checkbox" type="checkbox"
class="checkbox checkbox-warning shadow-md" class="checkbox checkbox-warning shadow-md"
bind:checked={download_images} bind:checked={downloadImages}
/> />
</label> </label>
</div> </div>
@@ -299,8 +327,8 @@
> >
<button <button
on:click={() => { on:click={() => {
after_download_page = false; afterDownloadPage = false;
input_url = ""; inputUrl = "";
}} }}
class="btn btn-outline btn-lg">Download More</button class="btn btn-outline btn-lg">Download More</button
> >
@@ -311,9 +339,7 @@
</div> </div>
</div> </div>
<!-- Open the modal using ID.showModal() method --> <dialog class="modal" bind:this={storyURLTutorialModal}>
<dialog id="StoryURLTutorialModal" class="modal">
<div class="modal-box"> <div class="modal-box">
<form method="dialog"> <form method="dialog">
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" <button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"