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
+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",
"@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",
"svelte": "^5.28.2",
"tailwindcss": "^3.3.6",
"vite": "^5.0.3"
"vite": "^6.3.3"
},
"type": "module",
"dependencies": {
"svelte-preprocess": "^5.1.3"
"svelte-preprocess": "^6.0.3"
}
}
+8 -1
View File
@@ -1,5 +1,12 @@
<script>
import "../app.pcss";
/**
* @typedef {Object} Props
* @property {import('svelte').Snippet} [children]
*/
/** @type {Props} */
let { children } = $props();
</script>
<svelte:head>
@@ -10,7 +17,7 @@
</style>
</svelte:head>
<slot />
{@render children?.()}
<footer
class="footer footer-center p-4 bg-base-300 text-base-content bottom-0 fixed"
+113 -87
View File
@@ -1,85 +1,114 @@
<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 = $state.raw(undefined);
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
download_id = input_url;
mode = "story";
} else if (input_url.includes("wattpad.com/")) {
// Is a string and contains contain wattpad.com/
/**
* @param {string} input
* @param {HTMLInputElement} inputElement
*/
const setInvalid = (input, inputElement) => {
invalidUrl = true;
inputUrl = input;
downloadId = input;
inputElement.value = input;
};
if (input_url.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/")) {
// 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;
}
/** @type {import("svelte/elements").FormEventHandler<HTMLInputElement>} */
const onInputUrl = (e) => {
let input = e.currentTarget.value.toLowerCase();
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 = "";
if (!input) {
setValid("");
return;
}
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>
@@ -88,13 +117,12 @@
<div
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">
<h1
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
role="alert"
class="alert bg-green-200 mt-10 break-words max-w-md"
@@ -169,17 +197,17 @@
type="text"
placeholder="Story URL"
class="input input-bordered"
class:input-warning={invalid_url}
bind:value={input_url}
class:input-warning={invalidUrl}
oninput={onInputUrl}
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
>).
@@ -187,7 +215,7 @@
{:else}
<button
class="label-text link font-semibold"
onclick="StoryURLTutorialModal.showModal()"
onclick={() => storyURLTutorialModal.showModal()}
data-umami-event="StoryURLTutorialModal Open"
>How to get a Story URL</button
>
@@ -201,10 +229,10 @@
<input
type="checkbox"
class="checkbox checkbox-warning shadow-md"
bind:checked={is_paid_story}
bind:checked={isPaidStory}
/>
</label>
{#if is_paid_story}
{#if isPaidStory}
<label class="input input-bordered flex items-center gap-2">
Username
<input
@@ -233,16 +261,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
on:click={() => (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>
@@ -258,7 +286,7 @@
<input
type="checkbox"
class="checkbox checkbox-warning shadow-md"
bind:checked={download_images}
bind:checked={downloadImages}
/>
</label>
</div>
@@ -299,8 +327,8 @@
>
<button
on:click={() => {
after_download_page = false;
input_url = "";
afterDownloadPage = false;
inputUrl = "";
}}
class="btn btn-outline btn-lg">Download More</button
>
@@ -311,9 +339,7 @@
</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"