Update to Svelte 5; Use the event callback instead of the effect
This commit is contained in:
@@ -6,6 +6,7 @@ venv
|
|||||||
data
|
data
|
||||||
*ipynb
|
*ipynb
|
||||||
build
|
build
|
||||||
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
.venv
|
.venv
|
||||||
.env
|
.env
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"checkJs": true
|
||||||
|
},
|
||||||
|
"exclude": ["node_modules", "**/node_modules/*"]
|
||||||
|
}
|
||||||
Generated
+1055
-863
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user