Update to Svelte 5; Use the event callback instead of the effect
This commit is contained in:
@@ -6,6 +6,7 @@ venv
|
||||
data
|
||||
*ipynb
|
||||
build
|
||||
.idea
|
||||
.vscode
|
||||
.venv
|
||||
.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",
|
||||
"@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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user