diff options
115 files changed, 1701 insertions, 1056 deletions
| Binary files differ diff --git a/package.json b/package.json index 6b3e1f53..9d3ea106 100644 --- a/package.json +++ b/package.json @@ -17,49 +17,50 @@ "devDependencies": { "@iconify/svelte": "^3.1.6", "@sveltejs/adapter-vercel": "next", - "@sveltejs/kit": "^1.20.4", + "@sveltejs/kit": "^2.7.3", + "@sveltejs/vite-plugin-svelte": "^4.0.0", "@types/fast-levenshtein": "^0.0.4", - "@types/jsdom": "^21.1.6", + "@types/jsdom": "^21.1.7", "@types/string-similarity": "^4.0.2", - "@types/web-push": "^3.6.3", - "@typescript-eslint/eslint-plugin": "^5.45.0", - "@typescript-eslint/parser": "^5.45.0", - "autoprefixer": "^10.4.16", - "eslint": "^8.28.0", - "eslint-config-prettier": "^8.5.0", - "eslint-plugin-svelte": "^2.30.0", - "prettier": "^2.8.0", - "prettier-plugin-svelte": "^2.10.1", - "sass": "^1.69.7", - "svelte": "^4.0.5", - "svelte-check": "^3.4.3", - "svelte-preprocess": "^5.1.3", - "sveltekit-graphql": "^0.4.3", - "sveltekit-rate-limiter": "^0.4.2", - "tslib": "^2.4.1", - "typescript": "^5.0.0", - "vite": "^4.4.2" + "@types/web-push": "^3.6.4", + "@typescript-eslint/eslint-plugin": "^5.62.0", + "@typescript-eslint/parser": "^5.62.0", + "autoprefixer": "^10.4.20", + "eslint": "^8.57.1", + "eslint-config-prettier": "^8.10.0", + "eslint-plugin-svelte": "^2.46.0", + "prettier": "^3.3.3", + "prettier-plugin-svelte": "^3.2.7", + "sass": "^1.80.4", + "svelte": "^5.1.3", + "svelte-check": "^4.0.5", + "svelte-preprocess": "^6.0.3", + "sveltekit-graphql": "https://github.com/Fuwn/sveltekit-graphql", + "sveltekit-rate-limiter": "^0.4.3", + "tslib": "^2.8.0", + "typescript": "^5.6.3", + "vite": "^5.4.10" }, "type": "module", "dependencies": { - "@supabase/supabase-js": "^2.39.3", + "@supabase/supabase-js": "^2.45.6", "@trigger.dev/sdk": "3.0.0-beta.51", "@trigger.dev/sveltekit": "3.0.0-beta.51", - "@vercel/speed-insights": "^1.0.9", - "caniuse-lite": "^1.0.30001655", - "dexie": "^4.0.1-alpha.25", - "jsdom": "^23.0.1", + "@vercel/speed-insights": "^1.0.14", + "caniuse-lite": "^1.0.30001673", + "dexie": "^4.0.9", + "jsdom": "^23.2.0", "jszip": "^3.10.1", "lz-string": "^1.5.0", - "modern-screenshot": "^4.4.33", + "modern-screenshot": "^4.4.39", "rss-parser": "^3.13.0", - "sortablejs": "^1.15.2", + "sortablejs": "^1.15.3", "string-similarity": "^4.0.4", - "svelte-i18n": "^4.0.0", + "svelte-i18n": "^4.0.1", "svelte-markdown": "^0.4.1", "svelte-notifications": "^0.9.98", "uuid": "^10.0.0", "wanakana": "^5.3.1", "web-push": "^3.6.7" } -} +}
\ No newline at end of file diff --git a/src/lib/Announcement.svelte b/src/lib/Announcement.svelte index 7bcc9df7..0dc330c7 100644 --- a/src/lib/Announcement.svelte +++ b/src/lib/Announcement.svelte @@ -40,9 +40,9 @@ {line}<br /> {/each} - <p /> + <p></p> - <button on:click={dismiss} class="dismiss">{dismissButton || 'Dismiss'}</button> + <button onclick={dismiss} class="dismiss">{dismissButton || 'Dismiss'}</button> </Popup> {/if} diff --git a/src/lib/Error/AnimeRateLimited.svelte b/src/lib/Error/AnimeRateLimited.svelte index 96df3ad5..4671d45b 100644 --- a/src/lib/Error/AnimeRateLimited.svelte +++ b/src/lib/Error/AnimeRateLimited.svelte @@ -1,15 +1,22 @@ <script> import Popup from '$lib/Layout/Popup.svelte'; + /** + * @typedef {Object} Props + * @property {import('svelte').Snippet} [children] + */ + + /** @type {Props} */ + let { children } = $props(); </script> <Popup locked fullscreen> - <p><slot /></p> + <p>{@render children?.()}</p> <span>It is likely that you have been rate-limited by AniList. Please try again later.</span> {#await fetch('https://api.waifu.pics/sfw/cry') then response} {#await response.json() then json} - <p /> + <p></p> <a href={`https://trace.moe/?url=${encodeURIComponent(json.url)}`} target="_blank"> <img src={json.url} alt="" style="width: 30vw;" /> diff --git a/src/lib/Error/DotDotDot.svelte b/src/lib/Error/DotDotDot.svelte index 73261eba..22cd895c 100644 --- a/src/lib/Error/DotDotDot.svelte +++ b/src/lib/Error/DotDotDot.svelte @@ -1,11 +1,15 @@ <script lang="ts"> import { onDestroy, onMount } from 'svelte'; - export let max: number | undefined = undefined; - export let perMs = 1000; - export let start = ''; + interface Props { + max?: number | undefined; + perMs?: number; + start?: string; + } - let dots = start; + let { max = undefined, perMs = 1000, start = '' }: Props = $props(); + + let dots = $state(start); let interval: NodeJS.Timeout; onMount(() => { diff --git a/src/lib/Error/LogInRestricted.svelte b/src/lib/Error/LogInRestricted.svelte index 07a9adec..d19dfd3d 100644 --- a/src/lib/Error/LogInRestricted.svelte +++ b/src/lib/Error/LogInRestricted.svelte @@ -7,7 +7,7 @@ <div class="message"> Please <a href={`https://anilist.co/api/v2/oauth/authorize?client_id=${env.PUBLIC_ANILIST_CLIENT_ID}&redirect_uri=${env.PUBLIC_ANILIST_REDIRECT_URI}&response_type=code`} - on:click={() => { + onclick={() => { localStorage.setItem( 'redirect', window.location.origin + window.location.pathname + window.location.search diff --git a/src/lib/Error/Notice.svelte b/src/lib/Error/Notice.svelte index d4cf96c5..6bdd7327 100644 --- a/src/lib/Error/Notice.svelte +++ b/src/lib/Error/Notice.svelte @@ -1,2 +1,12 @@ +<script> + /** + * @typedef {Object} Props + * @property {import('svelte').Snippet} [children] + */ + + /** @type {Props} */ + let { children } = $props(); +</script> + <b>Notice:</b> -<slot /> +{@render children?.()} diff --git a/src/lib/Error/RateLimited.svelte b/src/lib/Error/RateLimited.svelte index 973d75d9..15e779b3 100644 --- a/src/lib/Error/RateLimited.svelte +++ b/src/lib/Error/RateLimited.svelte @@ -1,10 +1,23 @@ <script lang="ts"> - export let type = 'Media'; - export let loginSessionError = true; - export let contact = true; - export let list = true; - export let card = false; - export let might = true; + interface Props { + type?: string; + loginSessionError?: boolean; + contact?: boolean; + list?: boolean; + card?: boolean; + might?: boolean; + children?: import('svelte').Snippet; + } + + let { + type = 'Media', + loginSessionError = true, + contact = true, + list = true, + card = false, + might = true, + children + }: Props = $props(); </script> <div class:card> @@ -24,10 +37,10 @@ </p> {/if} - <slot /> + {@render children?.()} {#if contact} - <p /> + <p></p> If the problem persists, please contact <a href="https://anilist.co/user/fuwn" target="_blank">@fuwn</a> on AniList. @@ -48,10 +61,10 @@ </p> {/if} - <slot /> + {@render children?.()} {#if contact} - <p /> + <p></p> If the problem persists, please contact <a href="https://anilist.co/user/fuwn" target="_blank">@fuwn</a> on AniList. diff --git a/src/lib/Events/Event.svelte b/src/lib/Events/Event.svelte index a2b1d2b5..f0a4f9f7 100644 --- a/src/lib/Events/Event.svelte +++ b/src/lib/Events/Event.svelte @@ -7,39 +7,39 @@ export let avatar = false; </script> -<a href={event.anilist_url} target="_blank"> - <div - class="card" - id="user-grid" - style={`background-image: ${event.banner ? `url(${event.banner})` : 'none'}; padding: 0;`} - > - {#if event} +<div + class="card" + id="user-grid" + style={`background-image: ${event.banner ? `url(${event.banner})` : 'none'}; padding: 0;`} +> + {#if event} + <a href={event.anilist_url} target="_blank"> <img src={event.banner} alt="" id="cover-image" /> - {/if} + </a> + {/if} - <div class="card" id="user-grid-content"> - {#if avatar} - <div id="user-grid-avatar"> - <a href={root(`/events/group/${event.group.anilist_username}`)}> - <img src={event.group.avatar} alt="" width="100vw" id="avatar" /> - </a> - </div> - {/if} + <div class="card" id="user-grid-content"> + {#if avatar} + <div id="user-grid-avatar"> + <a href={root(`/events/group/${event.group.anilist_username}`)}> + <img src={event.group.avatar} alt="" width="100vw" id="avatar" /> + </a> + </div> + {/if} - <div id="user-grid-content-text"> - <p> - <a href={event.anilist_url} target="_blank" class="title-text"> - {event.title} - </a> - <br /> - {$locale().dateFormatter(new Date(event.created_at))} - </p> + <div id="user-grid-content-text"> + <p> + <a href={event.anilist_url} target="_blank" class="title-text"> + {event.title} + </a> + <br /> + {$locale().dateFormatter(new Date(event.created_at))} + </p> - <p>{event.description}</p> - </div> + <p>{event.description}</p> </div> </div> -</a> +</div> <style> #user-grid-content { diff --git a/src/lib/Events/Group.svelte b/src/lib/Events/Group.svelte index a3d24807..e88d9b2a 100644 --- a/src/lib/Events/Group.svelte +++ b/src/lib/Events/Group.svelte @@ -2,7 +2,11 @@ import type { Group } from '$lib/Database/SB/groups'; import tooltip from '$lib/Tooltip/tooltip'; - export let group: Group; + interface Props { + group: Group; + } + + let { group }: Props = $props(); </script> <div diff --git a/src/lib/Hololive/Lives.svelte b/src/lib/Hololive/Lives.svelte index 9e762df9..0f566df4 100644 --- a/src/lib/Hololive/Lives.svelte +++ b/src/lib/Hololive/Lives.svelte @@ -4,10 +4,19 @@ import type { Live, ParseResult } from './hololive'; import Stream from './Stream.svelte'; - export let schedule: ParseResult; - export let pinnedStreams: string[]; - export let getPinnedStreams: () => void; - export let filter: string | undefined; + interface Props { + schedule: ParseResult; + pinnedStreams: string[]; + getPinnedStreams: () => void; + filter: string | undefined; + } + + let { + schedule, + pinnedStreams, + getPinnedStreams, + filter + }: Props = $props(); const pinStream = (streamer: string) => fetch(root(`/api/preferences/pin?stream=${encodeURIComponent(streamer)}`), { @@ -17,7 +26,7 @@ } }).then(getPinnedStreams); - $: categorisedStreams = schedule.lives + let categorisedStreams = $derived(schedule.lives .filter((live) => (filter ? live.streamer === filter : true)) .sort((a, b) => new Date(a.time).getTime() - new Date(b.time).getTime()) .sort((a, b) => { @@ -55,7 +64,7 @@ return acc; }, { live: [], upcoming: [], ended: [] } - ); + )); </script> {#if schedule.lives.length === 0} @@ -68,7 +77,7 @@ {/each} </div> -<p /> +<p></p> <div class="container"> {#each categorisedStreams.upcoming as live} @@ -76,7 +85,7 @@ {/each} </div> -<p /> +<p></p> <div class="container"> {#each categorisedStreams.ended as live} diff --git a/src/lib/Hololive/Stream.svelte b/src/lib/Hololive/Stream.svelte index 12681acb..9f2b5e7b 100644 --- a/src/lib/Hololive/Stream.svelte +++ b/src/lib/Hololive/Stream.svelte @@ -5,10 +5,19 @@ import locale from '$stores/locale'; import Icon from '@iconify/svelte'; - export let live: any; - export let pinStream: (streamer: string) => void; - export let pinnedStreams: string[]; - export let icon: string; + interface Props { + live: any; + pinStream: (streamer: string) => void; + pinnedStreams: string[]; + icon: string; + } + + let { + live, + pinStream, + pinnedStreams, + icon + }: Props = $props(); </script> <div class="stream card"> @@ -16,7 +25,7 @@ <div class="pin-icon"> <a href={'#'} - on:click={(e) => { + onclick={(e) => { e.preventDefault(); pinStream(live.streamer); }} diff --git a/src/lib/Home/HeadTitle.svelte b/src/lib/Home/HeadTitle.svelte index 39a65ccd..a87da69a 100644 --- a/src/lib/Home/HeadTitle.svelte +++ b/src/lib/Home/HeadTitle.svelte @@ -1,6 +1,10 @@ <script lang="ts"> - export let route: string | undefined = undefined; - export let path = '/'; + interface Props { + route?: string | undefined; + path?: string; + } + + let { route = undefined, path = '/' }: Props = $props(); const title = (route ? `${route} • ` : '') + 'due.moe'; </script> diff --git a/src/lib/Home/LastActivity.svelte b/src/lib/Home/LastActivity.svelte index 000c7f7b..cdbf4d44 100644 --- a/src/lib/Home/LastActivity.svelte +++ b/src/lib/Home/LastActivity.svelte @@ -5,9 +5,13 @@ import { lastActivityDate } from '../Data/AniList/activity'; import settings from '$stores/settings'; - export let user: AniListAuthorisation; + interface Props { + user: AniListAuthorisation; + } - let lastActivityWasToday = true; + let { user }: Props = $props(); + + let lastActivityWasToday = $state(true); onMount(async () => { if (user !== undefined && !$settings.displayDisableLastActivityWarning) { diff --git a/src/lib/Home/Root.svelte b/src/lib/Home/Root.svelte index bc1bdea9..e17f9a77 100644 --- a/src/lib/Home/Root.svelte +++ b/src/lib/Home/Root.svelte @@ -2,14 +2,19 @@ import settings from '$stores/settings'; import { fly } from 'svelte/transition'; - export let data: any; - export let way: number; + interface Props { + data: any; + way: number; + children?: import('svelte').Snippet; + } + + let { data, way, children }: Props = $props(); const animationDelay = 100; </script> {#if $settings.displayDisableAnimations} - <slot /> + {@render children?.()} {:else} {#key data.url} <div @@ -20,7 +25,7 @@ }} out:fly={{ x: -way, duration: animationDelay }} > - <slot /> + {@render children?.()} </div> {/key} {/if} diff --git a/src/lib/Image/FallbackImage.svelte b/src/lib/Image/FallbackImage.svelte index 2029cf0e..1eafdd4c 100644 --- a/src/lib/Image/FallbackImage.svelte +++ b/src/lib/Image/FallbackImage.svelte @@ -1,14 +1,27 @@ <script lang="ts"> - export let source: string | undefined | null; - export let alternative: string | undefined | null; - export let fallback: string | undefined | null; - export let maxReplaceCount = 1; - export let replaceDelay = 1000; - export let error = 'https://i2.kym-cdn.com/photos/images/newsfeed/000/290/992/0aa.jpg'; - export let hideOnError = false; - export let style = ''; + interface Props { + source: string | undefined | null; + alternative: string | undefined | null; + fallback: string | undefined | null; + maxReplaceCount?: number; + replaceDelay?: number; + error?: string; + hideOnError?: boolean; + style?: string; + } + + let { + source, + alternative, + fallback, + maxReplaceCount = 1, + replaceDelay = 1000, + error = 'https://i2.kym-cdn.com/photos/images/newsfeed/000/290/992/0aa.jpg', + hideOnError = false, + style = '' + }: Props = $props(); - let replaceCount = 0; + let replaceCount = $state(0); const delayedReplace = (event: Event, image: string | undefined | null) => { if (replaceCount >= maxReplaceCount) return; @@ -27,7 +40,7 @@ alt={alternative} loading="lazy" class="badge" - on:error={(e) => delayedReplace(e, fallback)} + onerror={(e) => delayedReplace(e, fallback)} {style} /> {:else if !hideOnError} diff --git a/src/lib/Image/ParallaxImage.svelte b/src/lib/Image/ParallaxImage.svelte index 48565ce1..390b9c92 100644 --- a/src/lib/Image/ParallaxImage.svelte +++ b/src/lib/Image/ParallaxImage.svelte @@ -2,17 +2,31 @@ import { cubicOut } from 'svelte/easing'; import { tweened } from 'svelte/motion'; - export let source: string; - export let duration = 300 * 1.75; - export let easing = cubicOut; - export let alternativeText: string; - export let classList = ''; - export let style = ''; - export let factor = 1.25; - export let limit = 300 * 1.75; - export let borderRadius = '8px'; - - let badgeReference: HTMLImageElement; + interface Props { + source: string; + duration?: any; + easing?: any; + alternativeText: string; + classList?: string; + style?: string; + factor?: number; + limit?: any; + borderRadius?: string; + } + + let { + source, + duration = 300 * 1.75, + easing = cubicOut, + alternativeText, + classList = '', + style = '', + factor = 1.25, + limit = 300 * 1.75, + borderRadius = '8px' + }: Props = $props(); + + let badgeReference: HTMLImageElement = $state(); const mouse = tweened({ x: 0, y: 0 }, { duration, easing }); const handleMouseMove = (event: MouseEvent) => { @@ -40,7 +54,7 @@ -$mouse.x / 10 }deg); border-radius: ${borderRadius}; ${style}`} alt={alternativeText} - on:mousemove={handleMouseMove} - on:mouseleave={handleMouseLeave} + onmousemove={handleMouseMove} + onmouseleave={handleMouseLeave} class={classList} /> diff --git a/src/lib/Landing.svelte b/src/lib/Landing.svelte index 29a341f1..bfe69de9 100644 --- a/src/lib/Landing.svelte +++ b/src/lib/Landing.svelte @@ -39,7 +39,7 @@ </div> </div> -<p /> +<p></p> <div class="example-item card"> <div class="card item-description"> @@ -65,7 +65,7 @@ </div> </div> -<p /> +<p></p> <div class="example-item card"> <div class="item-content"> @@ -105,7 +105,7 @@ <span class="big-text"> <a href={`https://anilist.co/api/v2/oauth/authorize?client_id=${env.PUBLIC_ANILIST_CLIENT_ID}&redirect_uri=${env.PUBLIC_ANILIST_REDIRECT_URI}&response_type=code`} - on:click={() => { + onclick={() => { localStorage.setItem( 'redirect', window.location.origin + window.location.pathname + window.location.search diff --git a/src/lib/Layout/Dropdown.svelte b/src/lib/Layout/Dropdown.svelte index fb270dfe..aca94975 100644 --- a/src/lib/Layout/Dropdown.svelte +++ b/src/lib/Layout/Dropdown.svelte @@ -6,12 +6,14 @@ preventDefault?: boolean; } - export let items: Item[] = []; - export let title: string | undefined = undefined; - export let header = true; - export let center = false; + let { + items = [] as Item[], + title = undefined as string | undefined, + header = true, + center = false + } = $props(); - let open = false; + let open = $state(false); const handleClickOutside = (event: any) => { if (!event.target.closest('.dropdown')) open = false; @@ -30,8 +32,8 @@ <span class={`${header ? 'header-item' : ''} dropdown-toggle`} id="dropdown-toggle" - on:click|preventDefault={() => (open = !open)} - on:keydown={() => {}} + onclick={() => (open = !open)} + onkeydown={() => {}} role="button" tabindex="0" > @@ -47,7 +49,7 @@ <a href={item.url} class="header-item" - on:click={(e) => { + onclick={(e) => { if (item.preventDefault) e.preventDefault(); if (item.onClick) item.onClick(); }} @@ -89,7 +91,10 @@ transform: translateY(-20px); visibility: hidden; $delay: 0.25s; - transition: opacity $delay ease, transform $delay ease, visibility 0s linear $delay; + transition: + opacity $delay ease, + transform $delay ease, + visibility 0s linear $delay; left: var(--dropdown-left); transform: var(--dropdown-transform); z-index: 1; diff --git a/src/lib/Layout/NumberTicker.svelte b/src/lib/Layout/NumberTicker.svelte index b5e2f49c..3bd7e6c8 100644 --- a/src/lib/Layout/NumberTicker.svelte +++ b/src/lib/Layout/NumberTicker.svelte @@ -1,11 +1,24 @@ <script> + import { run } from 'svelte/legacy'; + import { tweened } from 'svelte/motion'; import { cubicOut } from 'svelte/easing'; - export let start = 0; - export let end = 100; - export let duration = 2000; - export let delay = 0; + /** + * @typedef {Object} Props + * @property {number} [start] + * @property {number} [end] + * @property {number} [duration] + * @property {number} [delay] + */ + + /** @type {Props} */ + let { + start = 0, + end = 100, + duration = 2000, + delay = 0 + } = $props(); const count = tweened(start, { duration: duration, @@ -13,9 +26,9 @@ delay: delay }); - $: { + run(() => { count.set(end); - } + }); </script> <span class="counter" class:visible={$count !== start}> diff --git a/src/lib/Layout/Popup.svelte b/src/lib/Layout/Popup.svelte index b82e86c2..45b719f8 100644 --- a/src/lib/Layout/Popup.svelte +++ b/src/lib/Layout/Popup.svelte @@ -1,16 +1,32 @@ <script lang="ts"> + import { run } from 'svelte/legacy'; + import { browser } from '$app/environment'; import { onMount } from 'svelte'; - export let onLeave = () => { + interface Props { + onLeave?: any; + card?: boolean; + smallCard?: boolean; + fullscreen?: boolean; + show?: boolean; + locked?: boolean; + center?: boolean; + children?: import('svelte').Snippet; + } + + let { + onLeave = () => { return; - }; - export let card = true; - export let smallCard = false; - export let fullscreen = false; - export let show = true; - export let locked = false; - export let center = false; + }, + card = true, + smallCard = false, + fullscreen = false, + show = $bindable(true), + locked = false, + center = false, + children + }: Props = $props(); const handleClickOutside = (event: any) => { if (!locked && event.target.classList.contains('popup')) { @@ -24,24 +40,24 @@ if (browser) document.body.style.overflow = 'auto'; }); - $: { + run(() => { if (browser) { document.body.style.overflow = 'auto'; if (show) document.body.style.overflow = 'hidden'; else document.body.style.overflow = 'auto'; } - } + }); </script> -<svelte:window on:click={handleClickOutside} /> +<svelte:window onclick={handleClickOutside} /> {#if show} <div class={`popup ${fullscreen ? 'popup-fullscreen' : ''}`}> <span class={`${card ? `card ${smallCard ? 'card-small' : ''}` : ''} ${center ? 'centered' : ''}`} > - <slot /> + {@render children?.()} </span> </div> {/if} diff --git a/src/lib/Layout/TextTransition.svelte b/src/lib/Layout/TextTransition.svelte index 35cfe1ea..5f5bd036 100644 --- a/src/lib/Layout/TextTransition.svelte +++ b/src/lib/Layout/TextTransition.svelte @@ -1,24 +1,37 @@ <script> + import { run } from 'svelte/legacy'; + import { tweened } from 'svelte/motion'; import { cubicOut } from 'svelte/easing'; - export let text = ''; - export let opacityTransitionDuration = 50; - export let blurTransitionDuration = opacityTransitionDuration; - export let easing = cubicOut; + /** + * @typedef {Object} Props + * @property {string} [text] + * @property {number} [opacityTransitionDuration] + * @property {any} [blurTransitionDuration] + * @property {any} [easing] + */ + + /** @type {Props} */ + let { + text = '', + opacityTransitionDuration = 50, + blurTransitionDuration = opacityTransitionDuration, + easing = cubicOut + } = $props(); - let previousValue = ''; + let previousValue = $state(''); let opacity = tweened(1, { duration: opacityTransitionDuration, easing }); let blur = tweened(0, { duration: blurTransitionDuration, easing }); - $: { + run(() => { if (text !== previousValue) Promise.all([opacity.set(0), blur.set(10)]).then(() => { previousValue = text; Promise.all([opacity.set(1), blur.set(0)]); }); - } + }); </script> <span diff --git a/src/lib/Layout/Username.svelte b/src/lib/Layout/Username.svelte index 8b89b708..82be59d0 100644 --- a/src/lib/Layout/Username.svelte +++ b/src/lib/Layout/Username.svelte @@ -1,5 +1,9 @@ <script lang="ts"> - export let username: string; + interface Props { + username: string; + } + + let { username }: Props = $props(); </script> <a href={`https://anilist.co/user/${username}/`}>@{username}</a> diff --git a/src/lib/Lazy.svelte b/src/lib/Lazy.svelte index da5b09a9..8167711d 100644 --- a/src/lib/Lazy.svelte +++ b/src/lib/Lazy.svelte @@ -1,17 +1,31 @@ <script lang="ts"> import { onMount, onDestroy } from 'svelte'; - export let top = 0; - export let bottom = 0; - export let left = 0; - export let right = 0; - export let steps = 100; - export let threshold = 0.01; - export let once = false; - - let element: Element; - let percent: number = 0; - let visible = false; + interface Props { + top?: number; + bottom?: number; + left?: number; + right?: number; + steps?: number; + threshold?: number; + once?: boolean; + children?: import('svelte').Snippet<[any]>; + } + + let { + top = 0, + bottom = 0, + left = 0, + right = 0, + steps = 100, + threshold = 0.01, + once = false, + children + }: Props = $props(); + + let element: Element = $state(); + let percent: number = $state(0); + let visible = $state(false); let observer: IntersectionObserver; const intersectPercent = ( @@ -45,5 +59,5 @@ </script> <div bind:this={element}> - <slot {visible} {percent} /> + {@render children?.({ visible, percent, })} </div> diff --git a/src/lib/List/Anime/AnimeListTemplate.svelte b/src/lib/List/Anime/AnimeListTemplate.svelte index 08583d7c..8b105df5 100644 --- a/src/lib/List/Anime/AnimeListTemplate.svelte +++ b/src/lib/List/Anime/AnimeListTemplate.svelte @@ -14,26 +14,41 @@ import subsPlease from '$stores/subsPlease'; import identity from '$stores/identity'; - export let endTime: number; - export let cleanMedia: ( + interface Props { + endTime: number; + cleanMedia: ( media: Media[], displayUnresolved: boolean, subsPlease: SubsPlease | null, plannedOnly?: boolean ) => Media[]; - export let animeLists: Promise<Media[]>; - export let user: AniListAuthorisation; - export let title: any; - export let completed = false; - export let plannedOnly = false; - export let upcoming = false; - export let notYetReleased = false; - export let dummy = false; + animeLists: Promise<Media[]>; + user: AniListAuthorisation; + title: any; + completed?: boolean; + plannedOnly?: boolean; + upcoming?: boolean; + notYetReleased?: boolean; + dummy?: boolean; + } - let lastUpdatedMedia = -1; - let previousAnimeList: Media[]; - let pendingUpdate: number | null = null; - let lastListSize = 8; + let { + endTime, + cleanMedia, + animeLists = $bindable(), + user, + title, + completed = false, + plannedOnly = false, + upcoming = false, + notYetReleased = false, + dummy = false + }: Props = $props(); + + let lastUpdatedMedia = $state(-1); + let previousAnimeList: Media[] = $state(); + let pendingUpdate: number | null = $state(null); + let lastListSize = $state(8); onMount(() => { if (browser) { diff --git a/src/lib/List/Anime/CleanAnimeList.svelte b/src/lib/List/Anime/CleanAnimeList.svelte index 0220a366..b088964e 100644 --- a/src/lib/List/Anime/CleanAnimeList.svelte +++ b/src/lib/List/Anime/CleanAnimeList.svelte @@ -16,18 +16,35 @@ import CleanGrid from '$lib/List/CleanGrid.svelte'; import CleanList from '../CleanList.svelte'; - export let media: Media[]; - export let title: any; - export let animeLists: Promise<Media[]>; - export let user: AniListAuthorisation; - export let endTime: number; - export let lastUpdatedMedia: number; - export let completed = false; - export let previousAnimeList: Media[]; - export let pendingUpdate: number | null; - export let upcoming = false; - export let notYetReleased = false; - export let dummy = false; + interface Props { + media: Media[]; + title: any; + animeLists: Promise<Media[]>; + user: AniListAuthorisation; + endTime: number; + lastUpdatedMedia: number; + completed?: boolean; + previousAnimeList: Media[]; + pendingUpdate: number | null; + upcoming?: boolean; + notYetReleased?: boolean; + dummy?: boolean; + } + + let { + media = $bindable(), + title, + animeLists = $bindable(), + user, + endTime, + lastUpdatedMedia = $bindable(), + completed = false, + previousAnimeList = $bindable(), + pendingUpdate = $bindable(), + upcoming = false, + notYetReleased = false, + dummy = false + }: Props = $props(); let keyCacher: NodeJS.Timeout; let totalEpisodeDueCount = media @@ -109,60 +126,64 @@ /> {#if media.length === 0} - No anime to display. <button on:click={() => (animeLists = cleanCache(user, $identity))}> + No anime to display. <button onclick={() => (animeLists = cleanCache(user, $identity))}> Force refresh </button> {/if} {#if $settings.displayCoverModeAnime} <CleanGrid {media} {dummy} type="anime" {upcoming} {notYetReleased}> - <div slot="title" let:title={anime} let:progress> - {#if !upcoming && !notYetReleased} - {pendingUpdate === anime.id ? progress + 1 : progress}{@html totalEpisodes(anime)} - <button - class={`button-square button-action ${pendingUpdate === anime.id ? 'opaque' : ''}`} - style={pendingUpdate === anime.id ? 'pointer-events: none;' : ''} - on:click={() => increment(anime, progress)}>+</button - > - {#if !completed || dummy} - [{anime.nextAiringEpisode?.episode === -1 - ? '?' - : (anime.nextAiringEpisode?.episode || 1) - 1}] - <br /> - <AiringTime originalAnime={anime} /> + {#snippet title({ title: anime, progress })} + <div > + {#if !upcoming && !notYetReleased} + {pendingUpdate === anime.id ? progress + 1 : progress}{@html totalEpisodes(anime)} + <button + class={`button-square button-action ${pendingUpdate === anime.id ? 'opaque' : ''}`} + style={pendingUpdate === anime.id ? 'pointer-events: none;' : ''} + onclick={() => increment(anime, progress)}>+</button + > + {#if !completed || dummy} + [{anime.nextAiringEpisode?.episode === -1 + ? '?' + : (anime.nextAiringEpisode?.episode || 1) - 1}] + <br /> + <AiringTime originalAnime={anime} /> + {/if} + {:else} + <AiringTime originalAnime={anime} upcoming={true} /> {/if} - {:else} - <AiringTime originalAnime={anime} upcoming={true} /> - {/if} - </div> + </div> + {/snippet} </CleanGrid> {:else} <CleanList {media} type="anime" {upcoming} {notYetReleased} {lastUpdatedMedia}> - <span slot="information" let:title={anime} let:progress> - {#if !upcoming || notYetReleased || !$settings.displayCountdownRightAligned} - <span class="opaque">|</span> - {/if} - {#if !upcoming || notYetReleased} - <!-- {anime.mediaListEntry?.progress || 0}{@html totalEpisodes(anime)} --> - {pendingUpdate === anime.id ? progress + 1 : progress}{@html totalEpisodes(anime)} - <button - class={`button-square button-action ${pendingUpdate === anime.id ? 'opaque' : ''}`} - style={pendingUpdate === anime.id ? 'pointer-events: none;' : ''} - on:click={() => increment(anime, progress)}>+</button - > - {#if !completed} - [{anime.nextAiringEpisode?.episode === -1 - ? '?' - : (anime.nextAiringEpisode?.episode || 1) - 1}] + {#snippet information({ title: anime, progress })} + <span > + {#if !upcoming || notYetReleased || !$settings.displayCountdownRightAligned} + <span class="opaque">|</span> + {/if} + {#if !upcoming || notYetReleased} + <!-- {anime.mediaListEntry?.progress || 0}{@html totalEpisodes(anime)} --> + {pendingUpdate === anime.id ? progress + 1 : progress}{@html totalEpisodes(anime)} + <button + class={`button-square button-action ${pendingUpdate === anime.id ? 'opaque' : ''}`} + style={pendingUpdate === anime.id ? 'pointer-events: none;' : ''} + onclick={() => increment(anime, progress)}>+</button + > + {#if !completed} + [{anime.nextAiringEpisode?.episode === -1 + ? '?' + : (anime.nextAiringEpisode?.episode || 1) - 1}] + <span class:countdown={$settings.displayCountdownRightAligned}> + <AiringTime originalAnime={anime} /> + </span> + {/if} + {:else} <span class:countdown={$settings.displayCountdownRightAligned}> - <AiringTime originalAnime={anime} /> + <AiringTime originalAnime={anime} upcoming={true} /> </span> {/if} - {:else} - <span class:countdown={$settings.displayCountdownRightAligned}> - <AiringTime originalAnime={anime} upcoming={true} /> - </span> - {/if} - </span> + </span> + {/snippet} </CleanList> {/if} diff --git a/src/lib/List/Anime/CompletedAnimeList.svelte b/src/lib/List/Anime/CompletedAnimeList.svelte index 76ec7207..a8cb6fd1 100644 --- a/src/lib/List/Anime/CompletedAnimeList.svelte +++ b/src/lib/List/Anime/CompletedAnimeList.svelte @@ -11,18 +11,22 @@ import identity from '$stores/identity'; import sampleAnime from '$lib/Data/Static/SampleMedia/anime.json'; - export let user: AniListAuthorisation = { + interface Props { + user?: AniListAuthorisation; + dummy?: boolean; + } + + let { user = { accessToken: '', refreshToken: '', expiresIn: 0, tokenType: '' - }; - export let dummy = false; + }, dummy = false }: Props = $props(); const { addNotification } = getNotificationsContext(); - let animeLists: Promise<Media[]>; + let animeLists: Promise<Media[]> = $state(); let startTime: number; - let endTime: number; + let endTime: number = $state(); onMount(async () => { startTime = performance.now(); diff --git a/src/lib/List/Anime/DueAnimeList.svelte b/src/lib/List/Anime/DueAnimeList.svelte index 1453baeb..bbae319a 100644 --- a/src/lib/List/Anime/DueAnimeList.svelte +++ b/src/lib/List/Anime/DueAnimeList.svelte @@ -12,12 +12,16 @@ import locale from '$stores/locale'; import identity from '$stores/identity'; - export let user: AniListAuthorisation; + interface Props { + user: AniListAuthorisation; + } + + let { user }: Props = $props(); const { addNotification } = getNotificationsContext(); - let animeLists: Promise<Media[]>; + let animeLists: Promise<Media[]> = $state(); let startTime: number; - let endTime: number; + let endTime: number = $state(); const keyCacher = setInterval(() => { startTime = performance.now(); diff --git a/src/lib/List/Anime/DueIndexColumn.svelte b/src/lib/List/Anime/DueIndexColumn.svelte index 61f2a178..0f20a2a1 100644 --- a/src/lib/List/Anime/DueIndexColumn.svelte +++ b/src/lib/List/Anime/DueIndexColumn.svelte @@ -6,8 +6,12 @@ import ListTitle from '../ListTitle.svelte'; import AnimeList from '$lib/List/Anime/DueAnimeList.svelte'; - export let userIdentity: { id: number }; - export let user: AniListAuthorisation; + interface Props { + userIdentity: { id: number }; + user: AniListAuthorisation; + } + + let { userIdentity, user }: Props = $props(); </script> <details open={!$settings.displayAnimeCollapsed} class="list list-due"> diff --git a/src/lib/List/Anime/PlaceholderList.svelte b/src/lib/List/Anime/PlaceholderList.svelte index 1f701d79..2c9baab5 100644 --- a/src/lib/List/Anime/PlaceholderList.svelte +++ b/src/lib/List/Anime/PlaceholderList.svelte @@ -4,8 +4,12 @@ import ListTitle from '../ListTitle.svelte'; import type { Title } from '../mediaTitle'; - export let title: Title; - export let count = 8; + interface Props { + title: Title; + count?: number; + } + + let { title, count = 8 }: Props = $props(); </script> <ListTitle {title} /> diff --git a/src/lib/List/Anime/UpcomingAnimeList.svelte b/src/lib/List/Anime/UpcomingAnimeList.svelte index 7b01af86..4957cd45 100644 --- a/src/lib/List/Anime/UpcomingAnimeList.svelte +++ b/src/lib/List/Anime/UpcomingAnimeList.svelte @@ -1,4 +1,6 @@ <script lang="ts"> + import { run } from 'svelte/legacy'; + import { mediaListCollection, Type, type Media } from '$lib/Data/AniList/media'; import type { AniListAuthorisation } from '$lib/Data/AniList/identity'; import { onMount } from 'svelte'; @@ -13,12 +15,16 @@ import { injectAiringTime } from '$lib/Media/Anime/Airing/Subtitled/match'; import revalidateAnime from '$stores/revalidateAnime'; - export let user: AniListAuthorisation; + interface Props { + user: AniListAuthorisation; + } + + let { user }: Props = $props(); const { addNotification } = getNotificationsContext(); - let animeLists: Promise<Media[]>; + let animeLists: Promise<Media[]> = $state(); let startTime: number; - let endTime: number; + let endTime: number = $state(); onMount(async () => { startTime = performance.now(); @@ -74,7 +80,7 @@ return upcomingAnime; }; - $: { + run(() => { if ($revalidateAnime) { $revalidateAnime = false; $lastPruneTimes.anime = -1; @@ -83,7 +89,7 @@ notificationType: 'Upcoming Episodes' }); } - } + }); </script> <AnimeList @@ -96,7 +102,7 @@ /> {#if $settings.displayPlannedAnime} - <p /> + <p></p> <AnimeList {endTime} diff --git a/src/lib/List/CleanGrid.svelte b/src/lib/List/CleanGrid.svelte index ec93a685..774303aa 100644 --- a/src/lib/List/CleanGrid.svelte +++ b/src/lib/List/CleanGrid.svelte @@ -7,11 +7,13 @@ import { mediaTitle } from './mediaTitle'; import './covers.css'; - export let media: Media[]; - export let dummy = false; - export let type: 'anime' | 'manga'; - export let upcoming = false; - export let notYetReleased = false; + let { + media = [] as Media[], + dummy = false, + type = 'anime' as 'anime' | 'manga', + upcoming = false, + notYetReleased = false + } = $props(); let uniqueID = new Date().getTime(); </script> diff --git a/src/lib/List/CleanList.svelte b/src/lib/List/CleanList.svelte index 47811932..46946973 100644 --- a/src/lib/List/CleanList.svelte +++ b/src/lib/List/CleanList.svelte @@ -5,11 +5,23 @@ import LinkedTooltip from '$lib/Tooltip/LinkedTooltip.svelte'; import settings from '$stores/settings'; - export let media: Media[]; - export let type: 'anime' | 'manga'; - export let upcoming = false; - export let notYetReleased = false; - export let lastUpdatedMedia: number; + interface Props { + media: Media[]; + type: 'anime' | 'manga'; + upcoming?: boolean; + notYetReleased?: boolean; + lastUpdatedMedia: number; + information?: import('svelte').Snippet<[any]>; + } + + let { + media, + type, + upcoming = false, + notYetReleased = false, + lastUpdatedMedia, + information + }: Props = $props(); </script> <ul> @@ -32,7 +44,7 @@ href={$settings.displayCopyMediaTitleNotLink ? '#' : outboundLink(title, type, $settings.displayOutboundLinksTo)} - on:click={(e) => { + onclick={(e) => { if ($settings.displayCopyMediaTitleNotLink) { e.preventDefault(); @@ -54,7 +66,7 @@ [<a href={`https://anilist.co/${type}/${title.id}/social`} target="_blank">S</a>] {/if} - <slot name="information" {progress} {title} /> + {@render information?.({ progress, title, })} </span> </li> {/if} diff --git a/src/lib/List/ListTitle.svelte b/src/lib/List/ListTitle.svelte index 21013b52..cccc5731 100644 --- a/src/lib/List/ListTitle.svelte +++ b/src/lib/List/ListTitle.svelte @@ -2,15 +2,28 @@ import tooltip from '$lib/Tooltip/tooltip'; import type { Title } from './mediaTitle'; - export let time: number | undefined = undefined; - export let count = -1337; - export let title: Title = { + interface Props { + time?: number | undefined; + count?: any; + title?: Title; + progress?: undefined | number; + hideTime?: boolean; + hideCount?: boolean; + children?: import('svelte').Snippet; + } + + let { + time = undefined, + count = -1337, + title = { title: 'Media List', hint: 'This is a media list.' - }; - export let progress: undefined | number = undefined; - export let hideTime = false; - export let hideCount = false; + }, + progress = undefined, + hideTime = false, + hideCount = false, + children + }: Props = $props(); </script> <summary> @@ -23,7 +36,7 @@ {#if !hideTime} <small class="opaque">{time ? time.toFixed(3) : '...'}s</small> {/if} - <slot /> + {@render children?.()} {#if progress !== undefined} <button class="badge unclickable-button button-badge badge-info"> {progress.toFixed(0)}% diff --git a/src/lib/List/Manga/CleanMangaList.svelte b/src/lib/List/Manga/CleanMangaList.svelte index dfaa187c..ec1a3d83 100644 --- a/src/lib/List/Manga/CleanMangaList.svelte +++ b/src/lib/List/Manga/CleanMangaList.svelte @@ -14,22 +14,37 @@ import CleanGrid from '../CleanGrid.svelte'; import CleanList from '../CleanList.svelte'; - export let media: Media[]; - export let cleanCache: () => void; - export let endTime: number; - export let lastUpdatedMedia: number; - export let updateMedia: ( + interface Props { + media: Media[]; + cleanCache: () => void; + endTime: number; + lastUpdatedMedia: number; + updateMedia: ( id: number, progress: number | undefined, media: Media[] ) => Promise<void>; - export let pendingUpdate: number | null; - export let due: boolean; - export let rateLimited: boolean; - export let authorised: boolean; - export let dummy = false; + pendingUpdate: number | null; + due: boolean; + rateLimited: boolean; + authorised: boolean; + dummy?: boolean; + } - let serviceStatusResponse: Promise<Response>; + let { + media, + cleanCache, + endTime, + lastUpdatedMedia, + updateMedia, + pendingUpdate, + due, + rateLimited, + authorised, + dummy = false + }: Props = $props(); + + let serviceStatusResponse: Promise<Response> = $state(); onMount(() => { serviceStatusResponse = fetch(proxy('https://api.mangadex.org/ping')); @@ -58,7 +73,7 @@ <button class="small-button" title="Force a full refresh" - on:click={cleanCache} + onclick={cleanCache} data-umami-event="Force Refresh Manga">Refresh</button > {/if} @@ -90,18 +105,18 @@ {#if media.length === 0 && !rateLimited} {#if rateLimited} - <p /> + <p></p> {/if} <p> - No manga to display. <button on:click={cleanCache} data-umami-event="Force Refresh No Manga" + No manga to display. <button onclick={cleanCache} data-umami-event="Force Refresh No Manga" >Force refresh</button > </p> <span> Don't read manga? <button - on:click={() => ($settings.disableManga = true)} + onclick={() => ($settings.disableManga = true)} data-umami-event="Disable No Manga">Hide the manga panel</button > You can re-enable it later in the <a href={root('/settings')}>Settings</a>. @@ -110,57 +125,61 @@ {#if $settings.displayCoverModeManga || dummy} <CleanGrid {media} {dummy} type="manga"> - <div slot="title" let:title={manga} let:progress> - {pendingUpdate === manga.id ? progress + 1 : progress}{#if !due} - <span class="opaque">/{manga.chapters || '?'}</span> - {/if} - <button - class={`button-square button-action ${pendingUpdate === manga.id ? 'opaque' : ''}`} - style={pendingUpdate === manga.id ? 'pointer-events: none;' : ''} - on:click={() => increment(manga)} - > - + - </button> - {#if due || Math.floor(manga.episodes) < manga.chapters} - [{manga.episodes || '?'}] - {#await volumeCount(manga) then volumes} - {@const volumeProgress = manga.mediaListEntry?.progressVolumes} + {#snippet title({ title: manga, progress })} + <div > + {pendingUpdate === manga.id ? progress + 1 : progress}{#if !due} + <span class="opaque">/{manga.chapters || '?'}</span> + {/if} + <button + class={`button-square button-action ${pendingUpdate === manga.id ? 'opaque' : ''}`} + style={pendingUpdate === manga.id ? 'pointer-events: none;' : ''} + onclick={() => increment(manga)} + > + + + </button> + {#if due || Math.floor(manga.episodes) < manga.chapters} + [{manga.episodes || '?'}] + {#await volumeCount(manga) then volumes} + {@const volumeProgress = manga.mediaListEntry?.progressVolumes} - {#if volumes !== null && (volumeProgress || 0) < volumes} - <span style="color: lightcoral;"> - Vol. {volumeProgress} → {volumes} - </span> - {/if} - {/await} - {/if} - </div> + {#if volumes !== null && (volumeProgress || 0) < volumes} + <span style="color: lightcoral;"> + Vol. {volumeProgress} → {volumes} + </span> + {/if} + {/await} + {/if} + </div> + {/snippet} </CleanGrid> {:else} <CleanList {media} type="manga" {lastUpdatedMedia}> - <span slot="information" let:title={manga} let:progress> - <span class="opaque">|</span> - {pendingUpdate === manga.id ? progress + 1 : progress}{#if !due} - <span class="opaque">/{manga.chapters || '?'}</span> - {/if} - <button - class={`button-square button-action ${pendingUpdate === manga.id ? 'opaque' : ''}`} - style={pendingUpdate === manga.id ? 'pointer-events: none;' : ''} - on:click={() => increment(manga)} - > - + - </button> - {#if due || Math.floor(manga.episodes) < manga.chapters} - [{manga.episodes || '?'}] - {#await volumeCount(manga) then volumes} - {@const volumeProgress = manga.mediaListEntry?.progressVolumes} + {#snippet information({ title: manga, progress })} + <span > + <span class="opaque">|</span> + {pendingUpdate === manga.id ? progress + 1 : progress}{#if !due} + <span class="opaque">/{manga.chapters || '?'}</span> + {/if} + <button + class={`button-square button-action ${pendingUpdate === manga.id ? 'opaque' : ''}`} + style={pendingUpdate === manga.id ? 'pointer-events: none;' : ''} + onclick={() => increment(manga)} + > + + + </button> + {#if due || Math.floor(manga.episodes) < manga.chapters} + [{manga.episodes || '?'}] + {#await volumeCount(manga) then volumes} + {@const volumeProgress = manga.mediaListEntry?.progressVolumes} - {#if volumes !== null && (volumeProgress || 0) < volumes} - <span style="color: lightcoral;"> - Vol. {volumeProgress} → {volumes} - </span> - {/if} - {/await} - {/if} - </span> + {#if volumes !== null && (volumeProgress || 0) < volumes} + <span style="color: lightcoral;"> + Vol. {volumeProgress} → {volumes} + </span> + {/if} + {/await} + {/if} + </span> + {/snippet} </CleanList> {/if} diff --git a/src/lib/List/Manga/MangaListTemplate.svelte b/src/lib/List/Manga/MangaListTemplate.svelte index 1303419f..107ff47d 100644 --- a/src/lib/List/Manga/MangaListTemplate.svelte +++ b/src/lib/List/Manga/MangaListTemplate.svelte @@ -21,28 +21,37 @@ import { browser } from '$app/environment'; import identity from '$stores/identity'; - export let user: AniListAuthorisation = { + interface Props { + user?: AniListAuthorisation; + displayUnresolved: boolean; + due: boolean; + dummy?: any; + } + + let { + user = { accessToken: '', refreshToken: '', expiresIn: 0, tokenType: '' - }; - export let displayUnresolved: boolean; - export let due: boolean; - export let dummy = $settings.debugDummyLists || false; + }, + displayUnresolved, + due, + dummy = $settings.debugDummyLists || false + }: Props = $props(); const { addNotification } = getNotificationsContext(); const authorised = authorisedJson.includes($identity.id); - let mangaLists: Promise<Media[]>; + let mangaLists: Promise<Media[]> = $state(); let startTime: number; - let endTime: number; - let lastUpdatedMedia = -1; - let previousMangaList: Media[]; - let pendingUpdate: number | null = null; - let progress = 0; - let rateLimited = false; - let forceFlag = false; - let lastListSize = 5; + let endTime: number = $state(); + let lastUpdatedMedia = $state(-1); + let previousMangaList: Media[] = $state(); + let pendingUpdate: number | null = $state(null); + let progress = $state(0); + let rateLimited = $state(false); + let forceFlag = $state(false); + let lastListSize = $state(5); const keyCacher = setInterval(() => { startTime = performance.now(); @@ -270,7 +279,7 @@ <button data-umami-event="Force Refresh Manga" title="Force a full refresh" - on:click={() => { + onclick={() => { cleanCache(); forceFlag = true; @@ -317,7 +326,7 @@ <button data-umami-event="Force Refresh Manga" title="Force a full refresh" - on:click={() => { + onclick={() => { cleanCache(); forceFlag = true; @@ -349,7 +358,7 @@ <button data-umami-event="Force Refresh Manga" title="Force a full refresh" - on:click={() => { + onclick={() => { cleanCache(); forceFlag = true; diff --git a/src/lib/List/MediaTitleDisplay.svelte b/src/lib/List/MediaTitleDisplay.svelte index 6a886704..0fd7cf17 100644 --- a/src/lib/List/MediaTitleDisplay.svelte +++ b/src/lib/List/MediaTitleDisplay.svelte @@ -6,10 +6,19 @@ import LZString from 'lz-string'; import * as wanakana from 'wanakana'; - export let title: MediaTitle; - export let abbreviate = false; - export let abbreviateTo = 20; - export let tooltip = false; + interface Props { + title: MediaTitle; + abbreviate?: boolean; + abbreviateTo?: number; + tooltip?: boolean; + } + + let { + title, + abbreviate = false, + abbreviateTo = 20, + tooltip = false + }: Props = $props(); const compressToBase64 = (string: string) => LZString.compressToBase64(string); </script> diff --git a/src/lib/Loading/Ellipsis.svelte b/src/lib/Loading/Ellipsis.svelte index ba1f30b8..7ee9dcfb 100644 --- a/src/lib/Loading/Ellipsis.svelte +++ b/src/lib/Loading/Ellipsis.svelte @@ -1,10 +1,14 @@ <script lang="ts"> - export let colour = 'var(--fg)'; + interface Props { + colour?: string; + } + + let { colour = 'var(--fg)' }: Props = $props(); </script> <div class="ellipsis" style={`--loader-colour: ${colour};`}> {#each Array.from({ length: 4 }) as _} - <div /> + <div></div> {/each} </div> diff --git a/src/lib/Loading/Grid.svelte b/src/lib/Loading/Grid.svelte index 1a64b3e0..107b985b 100644 --- a/src/lib/Loading/Grid.svelte +++ b/src/lib/Loading/Grid.svelte @@ -1,10 +1,14 @@ <script lang="ts"> - export let colour = 'var(--fg)'; + interface Props { + colour?: string; + } + + let { colour = 'var(--fg)' }: Props = $props(); </script> <div class="grid" style={`--loader-colour: ${colour};`}> {#each Array.from({ length: 9 }) as _} - <div /> + <div></div> {/each} </div> diff --git a/src/lib/Loading/Message.svelte b/src/lib/Loading/Message.svelte index 5aabec9c..c940a452 100644 --- a/src/lib/Loading/Message.svelte +++ b/src/lib/Loading/Message.svelte @@ -4,12 +4,25 @@ import Grid from './Grid.svelte'; import Popup from '$lib/Layout/Popup.svelte'; - export let message: string | undefined = undefined; - export let loader: 'ellipsis' | 'ripple' | 'grid' = 'ellipsis'; - export let colour = 'var(--fg)'; - export let slot = false; - export let withReload = false; - export let fullscreen = true; + interface Props { + message?: string | undefined; + loader?: 'ellipsis' | 'ripple' | 'grid'; + colour?: string; + slot?: boolean; + withReload?: boolean; + fullscreen?: boolean; + children?: import('svelte').Snippet; + } + + let { + message = undefined, + loader = 'ellipsis', + colour = 'var(--fg)', + slot = false, + withReload = false, + fullscreen = true, + children + }: Props = $props(); </script> <Popup {fullscreen} locked> @@ -29,10 +42,10 @@ {:else if slot} <br /> - <slot /> + {@render children?.()} {#if withReload} - Please <a href={'#'} on:click={() => location.reload()}>try again</a> later. + Please <a href={'#'} onclick={() => location.reload()}>try again</a> later. {/if} {/if} </div> diff --git a/src/lib/Loading/Ripple.svelte b/src/lib/Loading/Ripple.svelte index 05d62bb5..20b1447a 100644 --- a/src/lib/Loading/Ripple.svelte +++ b/src/lib/Loading/Ripple.svelte @@ -1,10 +1,14 @@ <script lang="ts"> - export let colour = 'var(--fg)'; + interface Props { + colour?: string; + } + + let { colour = 'var(--fg)' }: Props = $props(); </script> <div class="ripple" style={`--loader-colour: ${colour};`}> - <div /> - <div /> + <div></div> + <div></div> </div> <style lang="scss"> diff --git a/src/lib/Loading/Skeleton.svelte b/src/lib/Loading/Skeleton.svelte index 3f39beec..5ca3789a 100644 --- a/src/lib/Loading/Skeleton.svelte +++ b/src/lib/Loading/Skeleton.svelte @@ -1,12 +1,25 @@ <script lang="ts"> - export let count = 3; - export let width = '100%'; - export let height = '100px'; - export let card = true; - export let grid = false; - export let list = false; - export let pad = false; - export let bigCard = false; + interface Props { + count?: number; + width?: string; + height?: string; + card?: boolean; + grid?: boolean; + list?: boolean; + pad?: boolean; + bigCard?: boolean; + } + + let { + count = 3, + width = '100%', + height = '100px', + card = true, + grid = false, + list = false, + pad = false, + bigCard = false + }: Props = $props(); </script> {#if grid} @@ -14,7 +27,7 @@ {#each Array(count) as _, i} <div class={card ? `${bigCard ? 'card' : ''} card-small` : ''} style={`width: ${width};`}> <div class="skeleton-container" style={`--i: ${i};`}> - <div class="skeleton" style={`width: ${width}; height: ${height};`} /> + <div class="skeleton" style={`width: ${width}; height: ${height};`}></div> </div> </div> {/each} @@ -26,12 +39,12 @@ style={`width: ${width}; ${list ? 'padding-top: .75em;' : ''}; --i: ${i};`} > <div class="skeleton-container"> - <div class="skeleton" style={`width: ${width}; height: ${height};`} /> + <div class="skeleton" style={`width: ${width}; height: ${height};`}></div> </div> </div> {#if !list && i < count - 1} - <p /> + <p></p> {/if} {/each} {/if} diff --git a/src/lib/MarkdownLink.svelte b/src/lib/MarkdownLink.svelte index 731eb263..a4c62112 100644 --- a/src/lib/MarkdownLink.svelte +++ b/src/lib/MarkdownLink.svelte @@ -1,6 +1,10 @@ <script lang="ts"> - export let href: string; - export let text: string; + interface Props { + href: string; + text: string; + } + + let { href = $bindable(), text }: Props = $props(); try { let url = new URL(href); diff --git a/src/lib/Media/Anime/Airing/AiringTime.svelte b/src/lib/Media/Anime/Airing/AiringTime.svelte index 53a39c39..b9b224d7 100644 --- a/src/lib/Media/Anime/Airing/AiringTime.svelte +++ b/src/lib/Media/Anime/Airing/AiringTime.svelte @@ -6,16 +6,20 @@ import tooltip from '$lib/Tooltip/tooltip'; import locale from '$stores/locale'; - export let originalAnime: Media; - export let upcoming = false; + interface Props { + originalAnime: Media; + upcoming?: boolean; + } + + let { originalAnime, upcoming = false }: Props = $props(); const anime = originalAnime; - let opacity = 100; - let timeFrame = ''; - let time = ''; + let opacity = $state(100); + let timeFrame = $state(''); + let time = $state(''); let nextEpisode = anime.nextAiringEpisode?.episode || 0; - let few = true; - let dateString = ''; + let few = $state(true); + let dateString = $state(''); let updateInterval: NodeJS.Timeout; onMount(() => { diff --git a/src/lib/Media/Cover/HoverCover.svelte b/src/lib/Media/Cover/HoverCover.svelte index 51cbf5d2..f27be993 100644 --- a/src/lib/Media/Cover/HoverCover.svelte +++ b/src/lib/Media/Cover/HoverCover.svelte @@ -2,8 +2,12 @@ import settings from '$stores/settings'; import type { HoverCoverResponse } from './hoverCover'; - export let options: HoverCoverResponse; - export let width = 250; + interface Props { + options: HoverCoverResponse; + width?: number; + } + + let { options, width = 250 }: Props = $props(); </script> {#if options.hovering} diff --git a/src/lib/Notification/Notification.svelte b/src/lib/Notification/Notification.svelte index 6764f46e..f2b6ac44 100644 --- a/src/lib/Notification/Notification.svelte +++ b/src/lib/Notification/Notification.svelte @@ -1,12 +1,18 @@ <script lang="ts"> + import { preventDefault } from 'svelte/legacy'; + import settings from '$stores/settings'; import { onMount } from 'svelte'; - export let notification: { [key: string]: any } = {}; - export let onRemove: () => void = () => { + interface Props { + notification?: { [key: string]: any }; + onRemove?: () => void; + removed?: boolean; + } + + let { notification = {}, onRemove = () => { return; - }; - export let removed = false; + }, removed = $bindable(false) }: Props = $props(); onMount(() => setTimeout(remove, notification.duration)); @@ -21,20 +27,20 @@ <div id="notification-container" class={removed ? 'fade-out' : 'fade-in'} - on:click={remove} - on:keydown={() => { + onclick={remove} + onkeydown={() => { return; }} role="button" tabindex="0" > {#if notification.description} - <!-- svelte-ignore a11y-no-noninteractive-element-interactions --> + <!-- svelte-ignore a11y_no_noninteractive_element_interactions --> <details open id="notification" - on:click|preventDefault={(event) => event.preventDefault()} - on:keydown={() => { + onclick={preventDefault((event) => event.preventDefault())} + onkeydown={() => { return; }} > diff --git a/src/lib/Reader/Chapters/MangaDex.svelte b/src/lib/Reader/Chapters/MangaDex.svelte index 44c0d8f6..4be69d02 100644 --- a/src/lib/Reader/Chapters/MangaDex.svelte +++ b/src/lib/Reader/Chapters/MangaDex.svelte @@ -1,5 +1,9 @@ <script lang="ts"> - export let data: any; + interface Props { + data: any; + } + + let { data }: Props = $props(); </script> <ul> diff --git a/src/lib/Reader/Chapters/Rawkuma.svelte b/src/lib/Reader/Chapters/Rawkuma.svelte index 720c65d5..671a0572 100644 --- a/src/lib/Reader/Chapters/Rawkuma.svelte +++ b/src/lib/Reader/Chapters/Rawkuma.svelte @@ -2,7 +2,11 @@ import { getChaptersFromText } from '$lib/Data/Manga/raw'; import { onMount } from 'svelte'; - export let data: string; + interface Props { + data: string; + } + + let { data }: Props = $props(); onMount(() => { console.log(); diff --git a/src/lib/Schedule/CoverBypass.svelte b/src/lib/Schedule/CoverBypass.svelte index e94ddd40..72b84eb8 100644 --- a/src/lib/Schedule/CoverBypass.svelte +++ b/src/lib/Schedule/CoverBypass.svelte @@ -8,10 +8,19 @@ import { abbreviate } from '$lib/Utility/string'; import settings from '$stores/settings'; - export let media: Media | null; - export let entry: SubsPleaseEpisode; - export let cover = true; - export let showTooltip = true; + interface Props { + media: Media | null; + entry: SubsPleaseEpisode; + cover?: boolean; + showTooltip?: boolean; + } + + let { + media, + entry, + cover = true, + showTooltip = true + }: Props = $props(); const abbreviateTo = 40; diff --git a/src/lib/Schedule/Crunchyroll.svelte b/src/lib/Schedule/Crunchyroll.svelte index d22cff42..8fc0e95a 100644 --- a/src/lib/Schedule/Crunchyroll.svelte +++ b/src/lib/Schedule/Crunchyroll.svelte @@ -29,7 +29,7 @@ (media) => media.day === 'soon' ); - $: columnCount = Math.ceil(Object.keys(days).length / 2); + let columnCount = $derived(Math.ceil(Object.keys(days).length / 2)); const ordinalSuffix = (i: number) => { const j = i % 10; @@ -61,7 +61,7 @@ </details> </div> - <p /> + <p></p> {/each} <div class="card day"> @@ -76,7 +76,7 @@ </details> </div> - <p /> + <p></p> <div class="card day"> <details open class="details-unstyled"> diff --git a/src/lib/Schedule/Days.svelte b/src/lib/Schedule/Days.svelte index f7a49029..51558240 100644 --- a/src/lib/Schedule/Days.svelte +++ b/src/lib/Schedule/Days.svelte @@ -18,15 +18,24 @@ import Skeleton from '$lib/Loading/Skeleton.svelte'; import Error from '$lib/Error/RateLimited.svelte'; - export let subsPlease: SubsPlease; - export let scheduledMedia: Partial<Media[]>; - export let forceListMode = false; - export let user; + interface Props { + subsPlease: SubsPlease; + scheduledMedia: Partial<Media[]>; + forceListMode?: boolean; + user: any; + } + + let { + subsPlease, + scheduledMedia, + forceListMode = false, + user + }: Props = $props(); const urlParameters = browser ? new URLSearchParams(window.location.search) : null; let day: string | null = parseOrDefault(urlParameters, 'day', null); - let mediaListPromise: Promise<Media[]>; + let mediaListPromise: Promise<Media[]> = $state(); onMount(async () => { if (user === undefined || $identity.id === -2) mediaListPromise = Promise.resolve([]); diff --git a/src/lib/Settings/Categories/Attributions.svelte b/src/lib/Settings/Categories/Attributions.svelte index 250b08c3..28f69a74 100644 --- a/src/lib/Settings/Categories/Attributions.svelte +++ b/src/lib/Settings/Categories/Attributions.svelte @@ -34,7 +34,7 @@ </li> --> </ul> -<p /> +<p></p> <details open class="card-clear"> <summary>Outbound Link Disclaimer</summary> diff --git a/src/lib/Settings/Categories/Cache.svelte b/src/lib/Settings/Categories/Cache.svelte index e65b8081..df0165d5 100644 --- a/src/lib/Settings/Categories/Cache.svelte +++ b/src/lib/Settings/Categories/Cache.svelte @@ -11,7 +11,7 @@ <a href="https://due.moe">due.moe</a>'s site data will clear these caches too. </small> -<p /> +<p></p> Re-cache AniList media lists every <input @@ -22,7 +22,7 @@ Re-cache AniList media lists every max="60" placeholder="30" size="1" - on:change={() => + onchange={() => ($settings.cacheMinutes < 1 && ($settings.cacheMinutes = 1)) || ($settings.cacheMinutes > 60 && ($settings.cacheMinutes = 60))} /> @@ -39,7 +39,7 @@ Re-cache manga data every max="1440" placeholder="120" size="2" - on:change={() => + onchange={() => ($settings.cacheMangaMinutes < 5 && ($settings.cacheMangaMinutes = 5)) || ($settings.cacheMangaMinutes > 1440 && ($settings.cacheMangaMinutes = 1440))} /> diff --git a/src/lib/Settings/Categories/Calculation.svelte b/src/lib/Settings/Categories/Calculation.svelte index 4a202fff..1f6eeaa1 100644 --- a/src/lib/Settings/Categories/Calculation.svelte +++ b/src/lib/Settings/Categories/Calculation.svelte @@ -75,7 +75,7 @@ {#if !$settings.calculateGuessingDisabled} <br /> - <select bind:value={$settings.calculateGuessMethod} on:change={pruneAllManga}> + <select bind:value={$settings.calculateGuessMethod} onchange={pruneAllManga}> <option value="mode">Mode (fast, moderate to low accuracy)</option> <option value="median">Median (moderate speed, high accuracy, recommended)</option> <option value="iqr_median">Interquartile Range with Median (slower, high accuracy)</option> diff --git a/src/lib/Settings/Categories/Debug.svelte b/src/lib/Settings/Categories/Debug.svelte index 79e18c03..00c5852e 100644 --- a/src/lib/Settings/Categories/Debug.svelte +++ b/src/lib/Settings/Categories/Debug.svelte @@ -12,7 +12,7 @@ <SettingCheckboxToggle setting="debugDummyLists" text={$locale().debug.dummyLists} /> <button - on:click={() => { + onclick={() => { localStorage.removeItem('anime'); localStorage.removeItem('manga'); addNotification( @@ -23,10 +23,10 @@ }}>{$locale().debug.clearCaches}</button > -<p /> +<p></p> <button - on:click={() => { + onclick={() => { settings.reset(); addNotification( options({ @@ -41,10 +41,10 @@ {$locale().debug.resetAllSettings.hint} </SettingHint> -<p /> +<p></p> <button - on:click={() => { + onclick={() => { localStorage.clear(); addNotification( options({ diff --git a/src/lib/Settings/Categories/Display.svelte b/src/lib/Settings/Categories/Display.svelte index 3b572b7b..a83157fb 100644 --- a/src/lib/Settings/Categories/Display.svelte +++ b/src/lib/Settings/Categories/Display.svelte @@ -85,7 +85,7 @@ <SettingHint lineBreak> Media where either the next episode's release date is unknown or the chapter count could not be resolved is considered unresolved. - <p /> + <p></p> <span> Additionally, you hard exclude specific media from <a href={root('/')}>due.moe</a> on AniList. To exclude any media from being included in <b>any</b> <a href={root('/')}>due.moe</a> @@ -107,7 +107,7 @@ </span> </SettingHint> -<p /> +<p></p> <b>{$locale().settings.display.categories.hidePanels}</b><br /> <SettingCheckboxToggle @@ -160,7 +160,7 @@ }} /> -<p /> +<p></p> <b>{$locale().settings.display.categories.collapsePanelsByDefault}</b><br /> <SettingCheckboxToggle @@ -175,7 +175,7 @@ /> <SettingCheckboxToggle setting="displayMangaCollapsed" text={$locale().settings.media.manga} /> -<p /> +<p></p> <b>{$locale().settings.display.categories.motionAndAccessibility.title}</b><br /> <SettingCheckboxToggle @@ -215,7 +215,7 @@ </button> {/if} -<p /> +<p></p> <SettingCheckboxToggle setting="displayAniListNotifications" @@ -242,7 +242,7 @@ </SettingHint> </SettingCheckboxToggle> -<p /> +<p></p> <b>{$locale().settings.display.categories.dateAndTime.title}</b><br /> <SettingCheckboxToggle @@ -262,7 +262,7 @@ text={$locale().settings.display.categories.dateAndTime.fields.abbreviateCountdown} /> -<p /> +<p></p> <SettingCheckboxToggle setting="displayDisableLastActivityWarning" @@ -277,7 +277,7 @@ </SettingHint> </SettingCheckboxToggle> -<p /> +<p></p> <b>Show lists with media covers instead of text</b><br /> <SettingCheckboxToggle setting="displayCoverModeAnime" text="Anime" lineBreak={false} /> @@ -297,7 +297,7 @@ min="50" placeholder="116.609" size="3" - on:change={() => { + onchange={() => { if ($settings.displayCoverWidth === null) { $settings.displayCoverWidth = 116.609; @@ -313,7 +313,7 @@ <br /> {/if} -<p /> +<p></p> <b>{$locale().settings.display.categories.media.title}</b><br /> <SettingCheckboxToggle @@ -357,7 +357,7 @@ <br /> {/if} -<p /> +<p></p> <select bind:value={$settings.displayOutboundLinksTo}> <option value="anilist">AniList</option> @@ -390,7 +390,7 @@ <br /> -<select bind:value={$settings.displayAoButa} on:change={onHelperChange}> +<select bind:value={$settings.displayAoButa} onchange={onHelperChange}> <option value="mai">{$locale().settings.display.categories.helper.options.mai}</option> <option value="mai_2">{$locale().settings.display.categories.helper.options.mai} #2</option> <option value="nodoka">{$locale().settings.display.categories.helper.options.nodoka}</option> diff --git a/src/lib/Settings/Categories/RSSFeeds.svelte b/src/lib/Settings/Categories/RSSFeeds.svelte index a225b137..9ced917b 100644 --- a/src/lib/Settings/Categories/RSSFeeds.svelte +++ b/src/lib/Settings/Categories/RSSFeeds.svelte @@ -6,13 +6,17 @@ import SettingHint from '../SettingHint.svelte'; import tooltip from '$lib/Tooltip/tooltip'; - export let user: any; + interface Props { + user: any; + } + + let { user }: Props = $props(); const { addNotification } = getNotificationsContext(); </script> <button - on:click={() => { + onclick={() => { addNotification( options({ heading: 'RSS feed URL copied to clipboard' @@ -31,7 +35,7 @@ Your AniList notifications RSS feed URL <SettingHint lineBreak> This <a href={'#'} - on:click={(e) => e.preventDefault()} + onclick={(e) => e.preventDefault()} target="_blank" title={$locale().settings.rssFeeds.tooltips.rss} use:tooltip diff --git a/src/lib/Settings/Categories/SettingSync.svelte b/src/lib/Settings/Categories/SettingSync.svelte index 39e62954..24a35c79 100644 --- a/src/lib/Settings/Categories/SettingSync.svelte +++ b/src/lib/Settings/Categories/SettingSync.svelte @@ -13,7 +13,7 @@ {#if !$settings.settingsSync} <button - on:click={() => { + onclick={() => { $settings.settingsSync = true; fetch(root(`/api/configuration?id=${$identity.id}`)).then((response) => { @@ -50,9 +50,9 @@ <SettingHint lineBreak> {$locale().settings.settingsSync.buttons.pull.hint} </SettingHint> - <p /> + <p></p> <button - on:click={() => { + onclick={() => { $settings.settingsSync = true; fetch(root(`/api/configuration`), { @@ -76,7 +76,7 @@ </SettingHint> {:else} <button - on:click={() => { + onclick={() => { $settings.settingsSync = false; addNotification( @@ -89,7 +89,7 @@ {$locale().settings.settingsSync.buttons.disable} </button> <button - on:click={() => { + onclick={() => { fetch(root(`/api/configuration?id=${$identity.id}`), { method: 'DELETE' }).then((response) => { @@ -108,7 +108,7 @@ {$locale().settings.settingsSync.buttons.delete} </button> - <p /> + <p></p> <b>Last Push</b>: {$locale().dateFormatter($settingsSyncTimes.lastPush)} <br /> diff --git a/src/lib/Settings/Category.svelte b/src/lib/Settings/Category.svelte index a5b3e211..aef86d77 100644 --- a/src/lib/Settings/Category.svelte +++ b/src/lib/Settings/Category.svelte @@ -1,8 +1,19 @@ <script lang="ts"> - export let title = ''; - export let id = title.toLowerCase(); - export let open = true; - export let newLine = true; + interface Props { + title?: string; + id?: any; + open?: boolean; + newLine?: boolean; + children?: import('svelte').Snippet; + } + + let { + title = '', + id = title.toLowerCase(), + open = true, + newLine = true, + children + }: Props = $props(); </script> <details {open} {id}> @@ -10,9 +21,9 @@ <summary>{title}</summary> {/if} - <slot /> + {@render children?.()} </details> {#if newLine} - <p /> + <p></p> {/if} diff --git a/src/lib/Settings/SettingCheckboxToggle.svelte b/src/lib/Settings/SettingCheckboxToggle.svelte index 6a16edec..85dbfcdc 100644 --- a/src/lib/Settings/SettingCheckboxToggle.svelte +++ b/src/lib/Settings/SettingCheckboxToggle.svelte @@ -1,4 +1,6 @@ <script lang="ts"> + import { run } from 'svelte/legacy'; + import settings, { type Settings } from '$stores/settings'; type BooleanSettingsKeys<T> = { @@ -6,19 +8,37 @@ }; type SettingsBooleanKeys = BooleanSettingsKeys<Settings>; - export let sectionBreak = false; - export let disabled = false; - export let text: string | (() => string); - export let setting: SettingsBooleanKeys[keyof SettingsBooleanKeys]; - export let lineBreak = true; - export let onChange: () => void = () => { - return; - }; - export let invert = false; - export let id: string | null = null; - - $: checked = setting ? (invert ? !$settings[setting] : $settings[setting]) : false; - $: field = text instanceof Function ? text() : text; + interface Props { + sectionBreak?: boolean; + disabled?: boolean; + text: string | (() => string); + setting: SettingsBooleanKeys[keyof SettingsBooleanKeys]; + lineBreak?: boolean; + onChange?: () => void; + invert?: boolean; + id?: string | null; + children?: import('svelte').Snippet; + } + + let { + sectionBreak = false, + disabled = false, + text, + setting, + lineBreak = true, + onChange = () => { + return; + }, + invert = false, + id = null, + children + }: Props = $props(); + + let checked = $state(false); + run(() => { + checked = setting ? (invert ? !$settings[setting] : $settings[setting]) : false; + }); + let field = $derived(text instanceof Function ? text() : text); // const toggler = (key: keyof Settings) => [ // () => @@ -53,11 +73,11 @@ }; </script> -<input type="checkbox" on:change={check} bind:checked {id} /> +<input type="checkbox" onchange={check} bind:checked {id} /> <span - on:click={flip} - on:keydown={() => { + onclick={flip} + onkeydown={() => { return; }} role="button" @@ -72,12 +92,12 @@ {/if} </span> -<slot /> +{@render children?.()} {#if lineBreak} <br /> {/if} {#if sectionBreak} - <p /> + <p></p> {/if} diff --git a/src/lib/Settings/SettingHint.svelte b/src/lib/Settings/SettingHint.svelte index f82f061c..c5a9072f 100644 --- a/src/lib/Settings/SettingHint.svelte +++ b/src/lib/Settings/SettingHint.svelte @@ -1,5 +1,10 @@ <script lang="ts"> - export let lineBreak = false; + interface Props { + lineBreak?: boolean; + children?: import('svelte').Snippet; + } + + let { lineBreak = false, children }: Props = $props(); </script> {#if lineBreak} @@ -7,5 +12,5 @@ {/if} <small style="opacity: 80%;"> - <slot /> + {@render children?.()} </small> diff --git a/src/lib/Settings/SettingToggle.svelte b/src/lib/Settings/SettingToggle.svelte index 0d177b50..0d3b893a 100644 --- a/src/lib/Settings/SettingToggle.svelte +++ b/src/lib/Settings/SettingToggle.svelte @@ -1,16 +1,28 @@ <script lang="ts"> import settings, { type Settings } from '$stores/settings'; - export let setting: keyof Settings; - export let on = ''; - export let off = ''; - export let sectionBreak = false; - export let disabled = false; + interface Props { + setting: keyof Settings; + on?: string; + off?: string; + sectionBreak?: boolean; + disabled?: boolean; + children?: import('svelte').Snippet; + } + + let { + setting, + on = '', + off = '', + sectionBreak = false, + disabled = false, + children + }: Props = $props(); </script> <a href={'#'} - on:click={() => + onclick={() => disabled ? {} : $settings[setting] @@ -20,16 +32,16 @@ {#if disabled} <strike> {$settings[setting] ? on : off} - <slot /> + {@render children?.()} </strike> {:else} {$settings[setting] ? on : off} - <slot /> + {@render children?.()} {/if} </a> <br /> {#if sectionBreak} - <p /> + <p></p> {/if} diff --git a/src/lib/Tools/ActivityHistory/Grid.svelte b/src/lib/Tools/ActivityHistory/Grid.svelte index db9f3839..8789a786 100644 --- a/src/lib/Tools/ActivityHistory/Grid.svelte +++ b/src/lib/Tools/ActivityHistory/Grid.svelte @@ -12,12 +12,16 @@ import tooltip from '$lib/Tooltip/tooltip'; import LogInRestricted from '$lib/Error/LogInRestricted.svelte'; - export let user: AniListAuthorisation; - export let activityData: ActivityHistoryEntry[] | null = null; - export let currentYear = new Date().getFullYear(); + interface Props { + user: AniListAuthorisation; + activityData?: ActivityHistoryEntry[] | null; + currentYear?: any; + } + + let { user, activityData = null, currentYear = new Date().getFullYear() }: Props = $props(); - let activityHistoryData: ActivityHistoryEntry[]; - let baseHue = Math.floor(Math.random() * 360); + let activityHistoryData: ActivityHistoryEntry[] = $state(); + let baseHue = $state(Math.floor(Math.random() * 360)); onMount(async () => { clearAllParameters(); @@ -45,8 +49,8 @@ <div class="grid-item" style="background-color: {gradientColour(activity.amount, highestActivity, baseHue)}" - on:click={() => (baseHue = Math.floor(Math.random() * 360))} - on:keydown={() => { + onclick={() => (baseHue = Math.floor(Math.random() * 360))} + onkeydown={() => { return; }} role="button" @@ -55,7 +59,7 @@ title={`Date: ${new Date(activity.date * 1000).toLocaleDateString()}\nAmount: ${ activity.amount }`} - /> +></div> {/each} </div> {/if} diff --git a/src/lib/Tools/ActivityHistory/Tool.svelte b/src/lib/Tools/ActivityHistory/Tool.svelte index b6e66a5e..07b2096a 100644 --- a/src/lib/Tools/ActivityHistory/Tool.svelte +++ b/src/lib/Tools/ActivityHistory/Tool.svelte @@ -14,10 +14,14 @@ import Skeleton from '$lib/Loading/Skeleton.svelte'; import LogInRestricted from '$lib/Error/LogInRestricted.svelte'; - export let user: AniListAuthorisation; + interface Props { + user: AniListAuthorisation; + } - let activityHistoryData: Promise<ActivityHistoryEntry[]>; - let generated = false; + let { user }: Props = $props(); + + let activityHistoryData: Promise<ActivityHistoryEntry[]> = $state(); + let generated = $state(false); onMount(async () => { clearAllParameters(); @@ -79,18 +83,18 @@ <div class="card"> <ActivityHistoryGrid {user} /> - <p /> + <p></p> - <div id="grid-final" /> + <div id="grid-final"></div> {#if generated} - <p /> + <p></p> {/if} - <button on:click={screenshot}>Generate grid image</button> + <button onclick={screenshot}>Generate grid image</button> </div> - <p /> + <p></p> <details open> <summary>Days in risk of developing an activity history hole</summary> diff --git a/src/lib/Tools/Birthdays.svelte b/src/lib/Tools/Birthdays.svelte index 97ff40d8..17c91709 100644 --- a/src/lib/Tools/Birthdays.svelte +++ b/src/lib/Tools/Birthdays.svelte @@ -1,4 +1,6 @@ <script lang="ts"> + import { run } from 'svelte/legacy'; + import { browser } from '$app/environment'; import { page } from '$app/stores'; import { ACDBBirthdays, type ACDBBirthday } from '$lib/Data/Birthday/secondary'; @@ -18,12 +20,12 @@ const urlParameters = browser ? new URLSearchParams(window.location.search) : null; let date = new Date(); - let month = parseOrDefault(urlParameters, 'month', date.getMonth() + 1); - let day = parseOrDefault(urlParameters, 'day', date.getDate()); - let anisearchBirthdays: Promise<aniSearchBirthday[]>; - let acdbBirthdays: Promise<ACDBBirthday[]>; + let month = $state(parseOrDefault(urlParameters, 'month', date.getMonth() + 1)); + let day = $state(parseOrDefault(urlParameters, 'day', date.getDate())); + let anisearchBirthdays: Promise<aniSearchBirthday[]> = $state(); + let acdbBirthdays: Promise<ACDBBirthday[]> = $state(); - $: { + run(() => { month = Math.min(month, 12); month = Math.max(month, 1); day = Math.min(day, new Date(new Date().getFullYear(), month, 0).getDate()); @@ -39,7 +41,7 @@ clearAllParameters(['month', 'day']); history.replaceState(null, '', `?${$page.url.searchParams.toString()}`); } - } + }); onMount(() => clearAllParameters(['month', 'day'])); diff --git a/src/lib/Tools/DumpProfile.svelte b/src/lib/Tools/DumpProfile.svelte index 45d4ffc9..ac0184ae 100644 --- a/src/lib/Tools/DumpProfile.svelte +++ b/src/lib/Tools/DumpProfile.svelte @@ -5,7 +5,7 @@ import InputTemplate from './InputTemplate.svelte'; import LZString from 'lz-string'; - let submission = ''; + let submission = $state(''); // Credit: @hoh const decodeJSON = (about: string): JSON | null => { @@ -26,7 +26,7 @@ }; </script> -<!-- svelte-ignore missing-declaration --> +<!-- svelte-ignore missing_declaration --> <InputTemplate field="Username" bind:submission event="Dump User" submitText="Dump"> {#await dumpUser(submission)} <Skeleton card={false} count={1} height="500px" /> @@ -36,7 +36,7 @@ <pre>{JSON.stringify(dump, null, 2)}</pre> {#if decoded && (dump.about || '').includes('[](json')} - <p /> + <p></p> <pre>{JSON.stringify(decoded, null, 2).replaceAll(/\\n/g, '\n')}</pre> {/if} diff --git a/src/lib/Tools/EpisodeDiscussionCollector.svelte b/src/lib/Tools/EpisodeDiscussionCollector.svelte index 4c61f3cf..746dff22 100644 --- a/src/lib/Tools/EpisodeDiscussionCollector.svelte +++ b/src/lib/Tools/EpisodeDiscussionCollector.svelte @@ -6,7 +6,7 @@ import InputTemplate from './InputTemplate.svelte'; import tooltip from '$lib/Tooltip/tooltip'; - let submission = ''; + let submission = $state(''); onMount(clearAllParameters); </script> @@ -54,7 +54,7 @@ </p> {/await} {:else} - <p /> + <p></p> Enter a username to search for to continue. {/if} diff --git a/src/lib/Tools/FollowFix.svelte b/src/lib/Tools/FollowFix.svelte index b9fddeff..411b1a92 100644 --- a/src/lib/Tools/FollowFix.svelte +++ b/src/lib/Tools/FollowFix.svelte @@ -3,23 +3,27 @@ import type { AniListAuthorisation } from '$lib/Data/AniList/identity'; import LogInRestricted from '$lib/Error/LogInRestricted.svelte'; - export let user: AniListAuthorisation; + interface Props { + user: AniListAuthorisation; + } - let input = ''; - let submit = ''; + let { user }: Props = $props(); + + let input = $state(''); + let submit = $state(''); </script> {#if user === undefined} <LogInRestricted /> {:else} <p> - <!-- svelte-ignore missing-declaration --> + <!-- svelte-ignore missing_declaration --> <input type="text" minlength="1" placeholder="Username" bind:value={input} - on:keypress={(e) => { + onkeypress={(e) => { if (e.key === 'Enter') { submit = input; @@ -28,7 +32,7 @@ } }} /> - <a href={'#'} on:click={() => (submit = input)}> + <a href={'#'} onclick={() => (submit = input)}> Toggle follow for {input.length === 0 ? '...' : input} </a> </p> diff --git a/src/lib/Tools/Hayai.svelte b/src/lib/Tools/Hayai.svelte index 1790af53..127c07b9 100644 --- a/src/lib/Tools/Hayai.svelte +++ b/src/lib/Tools/Hayai.svelte @@ -90,13 +90,13 @@ )} </small> - <p /> + <p></p> {@html applyBionicReadingToString( `After selecting an EPUB file, 早い will apply a bionic reading filter over any and all words, and return the newly created "bionic" EPUB file.` )} - <p /> + <p></p> - <input type="file" id="epub-file" accept=".epub" on:change={handleFileUpload} /> + <input type="file" id="epub-file" accept=".epub" onchange={handleFileUpload} /> </div> diff --git a/src/lib/Tools/HololiveBirthdays.svelte b/src/lib/Tools/HololiveBirthdays.svelte index 68a591de..769f5d6f 100644 --- a/src/lib/Tools/HololiveBirthdays.svelte +++ b/src/lib/Tools/HololiveBirthdays.svelte @@ -1,4 +1,6 @@ <script lang="ts"> + import { run } from 'svelte/legacy'; + import { browser } from '$app/environment'; import { page } from '$app/stores'; import { onMount } from 'svelte'; @@ -9,14 +11,14 @@ const urlParameters = browser ? new URLSearchParams(window.location.search) : null; let date = new Date(); - let month = parseOrDefault(urlParameters, 'month', date.getMonth() + 1); - let day = parseOrDefault(urlParameters, 'day', date.getDate()); + let month = $state(parseOrDefault(urlParameters, 'month', date.getMonth() + 1)); + let day = $state(parseOrDefault(urlParameters, 'day', date.getDate())); - $: todaysBirthdays = birthdays.filter( + let todaysBirthdays = $derived(birthdays.filter( (birthday) => birthday.month === month && birthday.day === day - ); + )); - $: { + run(() => { month = Math.min(month, 12); month = Math.max(month, 1); day = Math.min(day, new Date(new Date().getFullYear(), month, 0).getDate()); @@ -28,7 +30,7 @@ clearAllParameters(['month', 'day']); history.replaceState(null, '', `?${$page.url.searchParams.toString()}`); } - } + }); onMount(() => clearAllParameters(['month', 'day'])); </script> diff --git a/src/lib/Tools/InputTemplate.svelte b/src/lib/Tools/InputTemplate.svelte index 72e2f807..52b9fd46 100644 --- a/src/lib/Tools/InputTemplate.svelte +++ b/src/lib/Tools/InputTemplate.svelte @@ -3,33 +3,49 @@ import { onMount } from 'svelte'; import SettingHint from '$lib/Settings/SettingHint.svelte'; - export let field: string; - export let submission: string; - export let event: string | undefined = undefined; - export let submitText: string; - export let saveParameters: string[] = []; - export let onSubmit = () => { + interface Props { + field: string; + submission: string; + event?: string | undefined; + submitText: string; + saveParameters?: string[]; + onSubmit?: any; + preserveCase?: boolean; + prompt?: any; + hint?: string | undefined; + children?: import('svelte').Snippet; + } + + let { + field, + submission = $bindable(), + event = undefined, + submitText, + saveParameters = [], + onSubmit = () => { return; - }; - export let preserveCase = false; - export let prompt = `Enter a ${ + }, + preserveCase = false, + prompt = `Enter a ${ preserveCase ? field : field.toLowerCase() - } to search for to continue.`; - export let hint: string | undefined = undefined; + } to search for to continue.`, + hint = undefined, + children + }: Props = $props(); - let input = ''; + let input = $state(''); onMount(() => clearAllParameters(saveParameters)); </script> <div class="card"> <p> - <!-- svelte-ignore missing-declaration --> + <!-- svelte-ignore missing_declaration --> <input type="text" placeholder={field} bind:value={input} - on:keypress={(e) => { + onkeypress={(e) => { if (e.key === 'Enter') { submission = input; @@ -42,7 +58,7 @@ /> <button class="button-lined" - on:click={() => { + onclick={() => { submission = input; onSubmit(); @@ -62,9 +78,9 @@ </p> {#if submission !== ''} - <slot /> + {@render children?.()} {:else} - <p /> + <p></p> {prompt} {/if} diff --git a/src/lib/Tools/Likes.svelte b/src/lib/Tools/Likes.svelte index 7b626c94..d86c5e12 100644 --- a/src/lib/Tools/Likes.svelte +++ b/src/lib/Tools/Likes.svelte @@ -7,16 +7,16 @@ import settings from '$stores/settings'; import InputTemplate from './InputTemplate.svelte'; - let submission = ''; + let submission = $state(''); - $: normalisedSubmission = submission.replace(/.*\/(activity|thread)\/(\d+).*/, '$2'); - $: submissionType = submission.replace(/.*\/(activity|thread)\/(\d+).*/, '$1'); - $: likesPromise = - submissionType === 'activity' + let normalisedSubmission = $derived(submission.replace(/.*\/(activity|thread)\/(\d+).*/, '$2')); + let submissionType = $derived(submission.replace(/.*\/(activity|thread)\/(\d+).*/, '$1')); + let likesPromise = + $derived(submissionType === 'activity' ? activityLikes(Number(normalisedSubmission)) : submissionType === 'thread' ? threadLikes(Number(normalisedSubmission)) - : Promise.resolve(null); + : Promise.resolve(null)); </script> <InputTemplate diff --git a/src/lib/Tools/Picker.svelte b/src/lib/Tools/Picker.svelte index 3f20300f..8628f757 100644 --- a/src/lib/Tools/Picker.svelte +++ b/src/lib/Tools/Picker.svelte @@ -4,13 +4,17 @@ import root from '$lib/Utility/root'; import { tools } from './tools'; - export let tool: string; + interface Props { + tool: string; + } + + let { tool = $bindable() }: Props = $props(); </script> <blockquote> <select bind:value={tool} - on:change={() => { + onchange={() => { if (browser) goto(root(`/tools/${tool}`)); }} > diff --git a/src/lib/Tools/RandomFollower.svelte b/src/lib/Tools/RandomFollower.svelte index acb5a33a..30b58470 100644 --- a/src/lib/Tools/RandomFollower.svelte +++ b/src/lib/Tools/RandomFollower.svelte @@ -5,8 +5,8 @@ import TextSwap from '$lib/Layout/TextTransition.svelte'; import InputTemplate from './InputTemplate.svelte'; - let submission = ''; - let randomSeed = 0; + let submission = $state(''); + let randomSeed = $state(0); </script> <InputTemplate @@ -21,7 +21,7 @@ {:then users} {@const user = users[Math.floor(randomSeed * users.length)]} - <p /> + <p></p> <a href={`https://anilist.co/user/${user.id}`} target="_blank"> <TextSwap text={user.name} /> diff --git a/src/lib/Tools/SequelCatcher/List.svelte b/src/lib/Tools/SequelCatcher/List.svelte index 009df219..788e8e01 100644 --- a/src/lib/Tools/SequelCatcher/List.svelte +++ b/src/lib/Tools/SequelCatcher/List.svelte @@ -4,10 +4,14 @@ import { outboundLink } from '$lib/Media/links'; import settings from '$stores/settings'; - export let mediaListUnchecked: Media[]; + interface Props { + mediaListUnchecked: Media[]; + } + + let { mediaListUnchecked }: Props = $props(); - let includeCurrent = false; - let includeSideStories = false; + let includeCurrent = $state(false); + let includeSideStories = $state(false); const matchCheck = (media: Media | undefined, swap = false) => (media && @@ -30,7 +34,7 @@ paused) <input type="checkbox" bind:checked={includeSideStories} /> Include side stories (e.g., OVAs, specials, etc.) -<p /> +<p></p> <ol class="media-list"> {#each filterRelations( mediaListUnchecked.filter((media) => media.mediaListEntry?.status === 'COMPLETED'), includeSideStories ) as { media, unwatchedRelations }} diff --git a/src/lib/Tools/SequelCatcher/Tool.svelte b/src/lib/Tools/SequelCatcher/Tool.svelte index a954b4d7..547903b7 100644 --- a/src/lib/Tools/SequelCatcher/Tool.svelte +++ b/src/lib/Tools/SequelCatcher/Tool.svelte @@ -12,9 +12,13 @@ import Skeleton from '$lib/Loading/Skeleton.svelte'; import Username from '$lib/Layout/Username.svelte'; - export let user: AniListAuthorisation; + interface Props { + user: AniListAuthorisation; + } - let mediaList: Promise<Media[]>; + let { user }: Props = $props(); + + let mediaList: Promise<Media[]> = $state(); onMount(async () => { if (user === undefined || $identity.id === -2) return; @@ -71,7 +75,7 @@ <Message message="" loader="ripple" slot withReload fullscreen>Error fetching media.</Message> {/await} - <p /> + <p></p> <blockquote style="margin: 0 0 0 1.5rem;"> Thanks to <Username username="sevengirl" /> and <Username username="esthereae" /> for the idea! diff --git a/src/lib/Tools/SequelSpy/Prequels.svelte b/src/lib/Tools/SequelSpy/Prequels.svelte index b22db3af..0b01b646 100644 --- a/src/lib/Tools/SequelSpy/Prequels.svelte +++ b/src/lib/Tools/SequelSpy/Prequels.svelte @@ -6,7 +6,11 @@ import settings from '$stores/settings'; import type { Media } from '$lib/Data/AniList/media'; - export let currentPrequels: MediaPrequel[]; + interface Props { + currentPrequels: MediaPrequel[]; + } + + let { currentPrequels }: Props = $props(); const prequelAiringTime = (prequel: MediaPrequel) => airingTime(prequel as unknown as Media, null, false, true); diff --git a/src/lib/Tools/SequelSpy/Tool.svelte b/src/lib/Tools/SequelSpy/Tool.svelte index caec4a46..b50e2f84 100644 --- a/src/lib/Tools/SequelSpy/Tool.svelte +++ b/src/lib/Tools/SequelSpy/Tool.svelte @@ -1,4 +1,6 @@ <script lang="ts"> + import { run } from 'svelte/legacy'; + import type { AniListAuthorisation } from '$lib/Data/AniList/identity'; import { prequels, type MediaPrequel } from '$lib/Data/AniList/prequels'; import { onMount } from 'svelte'; @@ -11,25 +13,29 @@ import LogInRestricted from '$lib/Error/LogInRestricted.svelte'; import Prequels from './Prequels.svelte'; - export let user: AniListAuthorisation; + interface Props { + user: AniListAuthorisation; + } + + let { user }: Props = $props(); - let currentPrequels: Promise<MediaPrequel[]> = Promise.resolve([]) as Promise<MediaPrequel[]>; + let currentPrequels: Promise<MediaPrequel[]> = $state(Promise.resolve([]) as Promise<MediaPrequel[]>); const urlParameters = browser ? new URLSearchParams(window.location.search) : null; - let year = parseOrDefault(urlParameters, 'year', new Date().getFullYear()); - let season = parseOrDefault(urlParameters, 'season', getSeason()); + let year = $state(parseOrDefault(urlParameters, 'year', new Date().getFullYear())); + let season = $state(parseOrDefault(urlParameters, 'season', getSeason())); - $: { + run(() => { if (year.toString().length === 4 && $identity.id !== -2 && user) currentPrequels = prequels(user, year, season); - } - $: { + }); + run(() => { if (browser) { $page.url.searchParams.set('year', year.toString()); $page.url.searchParams.set('season', season.toString()); clearAllParameters(['year', 'season']); history.replaceState(null, '', `?${$page.url.searchParams.toString()}`); } - } + }); onMount(() => clearAllParameters(['year', 'season'])); </script> @@ -54,7 +60,7 @@ <Prequels {currentPrequels} /> {/await} - <p /> + <p></p> The count ratio is the number of episodes you've seen of any direct prequels, and the total number of episodes of all direct prequels. diff --git a/src/lib/Tools/Tracker/Tool.svelte b/src/lib/Tools/Tracker/Tool.svelte index 8906e72d..2a1b38d7 100644 --- a/src/lib/Tools/Tracker/Tool.svelte +++ b/src/lib/Tools/Tracker/Tool.svelte @@ -4,14 +4,14 @@ import { onMount } from 'svelte'; import Message from '$lib/Loading/Message.svelte'; - let url = ''; - let title = ''; - let progress = 0; - let error = ''; - let masterList: TrackerEntry[] | null = null; + let url = $state(''); + let title = $state(''); + let progress = $state(0); + let error = $state(''); + let masterList: TrackerEntry[] | null = $state(null); let confirmDelete = 0; - $: listAccess = masterList || []; + let listAccess = $derived(masterList || []); onMount(async () => { masterList = await database.entries.toArray(); @@ -74,9 +74,9 @@ <input type="url" placeholder="URL" bind:value={url} /> <input type="text" placeholder="Title" bind:value={title} /> <input type="number" placeholder="Progress (defaults to 0)" bind:value={progress} /> - <button class="button-lined" on:click={() => addEntry(url, title, progress)}> Add </button> + <button class="button-lined" onclick={() => addEntry(url, title, progress)}> Add </button> - <p /> + <p></p> {#if masterList === null} <Message message="Loading entries ..." /> @@ -95,7 +95,7 @@ type="number" value={entry.progress} size={3} - on:change={(e) => + onchange={(e) => adjustEntry(entry.id, e.target ? e.target.value || entry.progress : entry.progress)} /> @@ -103,17 +103,17 @@ <span class="opaque">|</span> <button class="button-square button-action" - on:click={() => adjustEntry(entry.id, entry.progress - 1)} + onclick={() => adjustEntry(entry.id, entry.progress - 1)} >- </button> <button class="button-square button-action" - on:click={() => adjustEntry(entry.id, entry.progress + 1)} + onclick={() => adjustEntry(entry.id, entry.progress + 1)} > + </button> <span class="opaque">|</span> - <button on:click={() => deleteEntry(entry.id)}>Remove</button> + <button onclick={() => deleteEntry(entry.id)}>Remove</button> </span> </div> </li> diff --git a/src/lib/Tools/UmaMusumeBirthdays.svelte b/src/lib/Tools/UmaMusumeBirthdays.svelte index 29b1faa6..c9b03287 100644 --- a/src/lib/Tools/UmaMusumeBirthdays.svelte +++ b/src/lib/Tools/UmaMusumeBirthdays.svelte @@ -1,4 +1,6 @@ <script lang="ts"> + import { run } from 'svelte/legacy'; + import { browser } from '$app/environment'; import { page } from '$app/stores'; import Error from '$lib/Error/RateLimited.svelte'; @@ -23,11 +25,11 @@ const urlParameters = browser ? new URLSearchParams(window.location.search) : null; let date = new Date(); - let month = parseOrDefault(urlParameters, 'month', date.getMonth() + 1); - let day = parseOrDefault(urlParameters, 'day', date.getDate()); - let umapyoi: Promise<Birthday[]>; + let month = $state(parseOrDefault(urlParameters, 'month', date.getMonth() + 1)); + let day = $state(parseOrDefault(urlParameters, 'day', date.getDate())); + let umapyoi: Promise<Birthday[]> = $state(); - $: { + run(() => { month = Math.min(month, 12); month = Math.max(month, 1); day = Math.min(day, new Date(new Date().getFullYear(), month, 0).getDate()); @@ -39,7 +41,7 @@ clearAllParameters(['month', 'day']); history.replaceState(null, '', `?${$page.url.searchParams.toString()}`); } - } + }); onMount(() => { clearAllParameters(['month', 'day']); diff --git a/src/lib/Tools/Wrapped/ActivityHistory.svelte b/src/lib/Tools/Wrapped/ActivityHistory.svelte index 3da401d4..e652a487 100644 --- a/src/lib/Tools/Wrapped/ActivityHistory.svelte +++ b/src/lib/Tools/Wrapped/ActivityHistory.svelte @@ -3,10 +3,19 @@ import type { AniListAuthorisation } from '$lib/Data/AniList/identity'; import ActivityHistoryGrid from '../ActivityHistory/Grid.svelte'; - export let user: AniListAuthorisation; - export let activities: ActivityHistoryEntry[]; - export let year: number; - export let activityHistoryPosition: 'TOP' | 'BELOW_TOP' | 'ORIGINAL'; + interface Props { + user: AniListAuthorisation; + activities: ActivityHistoryEntry[]; + year: number; + activityHistoryPosition: 'TOP' | 'BELOW_TOP' | 'ORIGINAL'; + } + + let { + user, + activities, + year, + activityHistoryPosition + }: Props = $props(); </script> <div diff --git a/src/lib/Tools/Wrapped/Media.svelte b/src/lib/Tools/Wrapped/Media.svelte index ea8a989b..7f8d4f66 100644 --- a/src/lib/Tools/Wrapped/Media.svelte +++ b/src/lib/Tools/Wrapped/Media.svelte @@ -4,14 +4,27 @@ import MediaTitleDisplay from '$lib/List/MediaTitleDisplay.svelte'; import proxy from '$lib/Utility/proxy'; - export let animeList: Media[] | undefined; - export let mangaList: Media[] | undefined; - export let wrapped: Wrapped; - export let updateWidth: () => void; - export let highestRatedMediaPercentage: boolean; - export let highestRatedCount: number; - export let animeMostTitle: string; - export let mangaMostTitle: string; + interface Props { + animeList: Media[] | undefined; + mangaList: Media[] | undefined; + wrapped: Wrapped; + updateWidth: () => void; + highestRatedMediaPercentage: boolean; + highestRatedCount: number; + animeMostTitle: string; + mangaMostTitle: string; + } + + let { + animeList, + mangaList, + wrapped, + updateWidth, + highestRatedMediaPercentage, + highestRatedCount, + animeMostTitle, + mangaMostTitle + }: Props = $props(); </script> {#if animeList !== undefined || mangaList !== undefined} @@ -28,7 +41,7 @@ )} alt="Highest Rated Anime Cover" class="cover-image" - on:load={updateWidth} + onload={updateWidth} /> </a> <div> @@ -67,7 +80,7 @@ )} alt="Highest Rated Manga Cover" class="cover-image" - on:load={updateWidth} + onload={updateWidth} /> </a> <div> diff --git a/src/lib/Tools/Wrapped/MediaExtras.svelte b/src/lib/Tools/Wrapped/MediaExtras.svelte index 9e755ea5..3e083c0b 100644 --- a/src/lib/Tools/Wrapped/MediaExtras.svelte +++ b/src/lib/Tools/Wrapped/MediaExtras.svelte @@ -2,10 +2,19 @@ import type { TopMedia } from '$lib/Data/AniList/wrapped'; import proxy from '$lib/Utility/proxy'; - export let topMedia: TopMedia; - export let updateWidth: () => void; - export let highestRatedGenreTagPercentage: boolean; - export let genreTagTitle: string; + interface Props { + topMedia: TopMedia; + updateWidth: () => void; + highestRatedGenreTagPercentage: boolean; + genreTagTitle: string; + } + + let { + topMedia, + updateWidth, + highestRatedGenreTagPercentage, + genreTagTitle + }: Props = $props(); </script> <div class="categories-grid" style="padding-top: 0;"> @@ -22,7 +31,7 @@ src={proxy(topMedia.topGenreMedia.coverImage.extraLarge)} alt="Highest Rated Genre Cover" class="cover-image" - on:load={updateWidth} + onload={updateWidth} /> </a> <div> @@ -53,7 +62,7 @@ src={proxy(topMedia.topTagMedia.coverImage.extraLarge)} alt="Highest Rated Tag Cover" class="cover-image" - on:load={updateWidth} + onload={updateWidth} /> </a> <div> diff --git a/src/lib/Tools/Wrapped/Tool.svelte b/src/lib/Tools/Wrapped/Tool.svelte index 1484ab5c..af817051 100644 --- a/src/lib/Tools/Wrapped/Tool.svelte +++ b/src/lib/Tools/Wrapped/Tool.svelte @@ -1,4 +1,6 @@ <script lang="ts"> + import { run } from 'svelte/legacy'; + import userIdentity from '$stores/identity'; import type { AniListAuthorisation } from '$lib/Data/AniList/identity'; import { onMount } from 'svelte'; @@ -30,153 +32,50 @@ import tooltip from '$lib/Tooltip/tooltip'; import LogInRestricted from '$lib/Error/LogInRestricted.svelte'; - export let user: AniListAuthorisation; - - const currentYear = new Date(Date.now()).getFullYear(); - let selectedYear = new Date(Date.now()).getFullYear(); - let episodes = 0; - let chapters = 0; - let minutesWatched = 0; - let animeList: Media[] | undefined = undefined; - let mangaList: Media[] | undefined = undefined; - let calculatedAnimeList: Media[] | undefined = undefined; - let calculatedMangaList: Media[] | undefined = undefined; - let originalAnimeList: Media[] | undefined = undefined; - let originalMangaList: Media[] | undefined = undefined; - let transparency = false; - let lightTheme = true; - let watermark = false; - let includeMusic = false; - let includeSpecials = true; - let includeRepeats = false; - let width = 1920; - let lightMode = false; - let highestRatedCount = 5; - let genreTagCount = 5; - let mounted = false; - let generated = false; - let disableActivityHistory = true; - let excludedKeywordsInput = ''; - let excludedKeywords: string[] = []; - let useFullActivityHistory = false; - let topGenresTags = true; - let topMedia: TopMedia; - let highestRatedMediaPercentage = true; - let highestRatedGenreTagPercentage = true; - let genreTagsSort = SortOptions.SCORE; - let mediaSort = SortOptions.SCORE; - let includeMovies = true; - let includeOVAs = true; - let activityHistoryPosition: 'TOP' | 'BELOW_TOP' | 'ORIGINAL' = 'ORIGINAL'; - let includeOngoingMediaFromPreviousYears = false; - - $: { - if (browser && mounted) { - $page.url.searchParams.set('transparency', transparency.toString()); - $page.url.searchParams.set('lightTheme', lightTheme.toString()); - $page.url.searchParams.set('watermark', watermark.toString()); - $page.url.searchParams.set('includeMusic', includeMusic.toString()); - $page.url.searchParams.set('includeSpecials', includeSpecials.toString()); - $page.url.searchParams.set('includeRepeats', includeRepeats.toString()); - $page.url.searchParams.set('lightMode', lightMode.toString()); - $page.url.searchParams.set('highestRatedCount', highestRatedCount.toString()); - $page.url.searchParams.set('genreTagCount', genreTagCount.toString()); - $page.url.searchParams.set('disableActivityHistory', disableActivityHistory.toString()); - $page.url.searchParams.set( - 'highestRatedMediaPercentage', - highestRatedMediaPercentage.toString() - ); - $page.url.searchParams.set( - 'highestRatedGenreTagPercentage', - highestRatedGenreTagPercentage.toString() - ); - $page.url.searchParams.set('genreTagsSort', genreTagsSort.toString()); - $page.url.searchParams.set('mediaSort', mediaSort.toString()); - $page.url.searchParams.set('includeMovies', includeMovies.toString()); - $page.url.searchParams.set('includeOVAs', includeOVAs.toString()); - - history.replaceState(null, '', `?${$page.url.searchParams.toString()}`); - } - } - $: { - includeMusic = includeMusic; - includeSpecials = includeSpecials; - includeRepeats = includeRepeats; - disableActivityHistory = disableActivityHistory; - highestRatedMediaPercentage = highestRatedMediaPercentage; - highestRatedGenreTagPercentage = highestRatedGenreTagPercentage; - topGenresTags = topGenresTags; - genreTagsSort = genreTagsSort; - mediaSort = mediaSort; - includeMovies = includeMovies; - includeOVAs = includeOVAs; - selectedYear = selectedYear; - includeOngoingMediaFromPreviousYears = includeOngoingMediaFromPreviousYears; - - update().then(updateWidth).catch(updateWidth); - } - $: { - animeList = animeList; - mangaList = mangaList; - highestRatedCount = highestRatedCount; - - new Promise((resolve) => setTimeout(resolve, 1)).then(updateWidth); + interface Props { + user: AniListAuthorisation; } - $: { - genreTagCount = genreTagCount; - - if (animeList && mangaList) - topMedia = tops( - [...(animeList || []), ...(mangaList || [])], - genreTagCount, - genreTagsSort, - excludedKeywords - ); - new Promise((resolve) => setTimeout(resolve, 1)).then(updateWidth); - } - $: { - excludedKeywords = excludedKeywords; + let { user }: Props = $props(); - if (excludedKeywords.length > 0 && animeList !== undefined && mangaList !== undefined) { - animeList = originalAnimeList; - mangaList = originalMangaList; - animeList = excludeKeywords(animeList as Media[]); - mangaList = excludeKeywords(mangaList as Media[]); - } + const currentYear = new Date(Date.now()).getFullYear(); + let selectedYear = $state(new Date(Date.now()).getFullYear()); + let episodes = $state(0); + let chapters = $state(0); + let minutesWatched = $state(0); + let animeList: Media[] | undefined = $state(undefined); + let mangaList: Media[] | undefined = $state(undefined); + let calculatedAnimeList: Media[] | undefined = $state(undefined); + let calculatedMangaList: Media[] | undefined = $state(undefined); + let originalAnimeList: Media[] | undefined = $state(undefined); + let originalMangaList: Media[] | undefined = $state(undefined); + let transparency = $state(false); + let lightTheme = $state(true); + let watermark = $state(false); + let includeMusic = $state(false); + let includeSpecials = $state(true); + let includeRepeats = $state(false); + let width = $state(1920); + let lightMode = $state(false); + let highestRatedCount = $state(5); + let genreTagCount = $state(5); + let mounted = $state(false); + let generated = $state(false); + let disableActivityHistory = $state(true); + let excludedKeywordsInput = $state(''); + let excludedKeywords: string[] = $state([]); + let useFullActivityHistory = $state(false); + let topGenresTags = $state(true); + let topMedia: TopMedia = $state(); + let highestRatedMediaPercentage = $state(true); + let highestRatedGenreTagPercentage = $state(true); + let genreTagsSort = $state(SortOptions.SCORE); + let mediaSort = $state(SortOptions.SCORE); + let includeMovies = $state(true); + let includeOVAs = $state(true); + let activityHistoryPosition: 'TOP' | 'BELOW_TOP' | 'ORIGINAL' = $state('ORIGINAL'); + let includeOngoingMediaFromPreviousYears = $state(false); - updateWidth(); - } - $: genreTagTitle = (() => { - switch (genreTagsSort) { - case SortOptions.SCORE: - return 'Highest Rated'; - case SortOptions.MINUTES_WATCHED: - return 'Most Watched'; - case SortOptions.COUNT: - return 'Most Common'; - } - })(); - $: animeMostTitle = (() => { - switch (mediaSort) { - case SortOptions.SCORE: - return 'Highest Rated'; - case SortOptions.MINUTES_WATCHED: - return 'Most Watched'; - case SortOptions.COUNT: - return 'Most Common'; - } - })(); - $: mangaMostTitle = (() => { - switch (mediaSort) { - case SortOptions.SCORE: - return 'Highest Rated'; - case SortOptions.MINUTES_WATCHED: - return 'Most Read'; - case SortOptions.COUNT: - return 'Most Common'; - } - })(); const updateWidth = () => { if (!browser) return; @@ -562,6 +461,113 @@ // return mediaCover(top[Math.floor(Math.random() * top.length)].mediaIds[0]); // }; + run(() => { + includeMusic = includeMusic; + includeSpecials = includeSpecials; + includeRepeats = includeRepeats; + disableActivityHistory = disableActivityHistory; + highestRatedMediaPercentage = highestRatedMediaPercentage; + highestRatedGenreTagPercentage = highestRatedGenreTagPercentage; + topGenresTags = topGenresTags; + genreTagsSort = genreTagsSort; + mediaSort = mediaSort; + includeMovies = includeMovies; + includeOVAs = includeOVAs; + selectedYear = selectedYear; + includeOngoingMediaFromPreviousYears = includeOngoingMediaFromPreviousYears; + + update().then(updateWidth).catch(updateWidth); + }); + run(() => { + animeList = animeList; + mangaList = mangaList; + highestRatedCount = highestRatedCount; + + new Promise((resolve) => setTimeout(resolve, 1)).then(updateWidth); + }); + run(() => { + excludedKeywords = excludedKeywords; + + if (excludedKeywords.length > 0 && animeList !== undefined && mangaList !== undefined) { + animeList = originalAnimeList; + mangaList = originalMangaList; + animeList = excludeKeywords(animeList as Media[]); + mangaList = excludeKeywords(mangaList as Media[]); + } + + updateWidth(); + }); + run(() => { + genreTagCount = genreTagCount; + + if (animeList && mangaList) + topMedia = tops( + [...(animeList || []), ...(mangaList || [])], + genreTagCount, + genreTagsSort, + excludedKeywords + ); + + new Promise((resolve) => setTimeout(resolve, 1)).then(updateWidth); + }); + run(() => { + if (browser && mounted) { + $page.url.searchParams.set('transparency', transparency.toString()); + $page.url.searchParams.set('lightTheme', lightTheme.toString()); + $page.url.searchParams.set('watermark', watermark.toString()); + $page.url.searchParams.set('includeMusic', includeMusic.toString()); + $page.url.searchParams.set('includeSpecials', includeSpecials.toString()); + $page.url.searchParams.set('includeRepeats', includeRepeats.toString()); + $page.url.searchParams.set('lightMode', lightMode.toString()); + $page.url.searchParams.set('highestRatedCount', highestRatedCount.toString()); + $page.url.searchParams.set('genreTagCount', genreTagCount.toString()); + $page.url.searchParams.set('disableActivityHistory', disableActivityHistory.toString()); + $page.url.searchParams.set( + 'highestRatedMediaPercentage', + highestRatedMediaPercentage.toString() + ); + $page.url.searchParams.set( + 'highestRatedGenreTagPercentage', + highestRatedGenreTagPercentage.toString() + ); + $page.url.searchParams.set('genreTagsSort', genreTagsSort.toString()); + $page.url.searchParams.set('mediaSort', mediaSort.toString()); + $page.url.searchParams.set('includeMovies', includeMovies.toString()); + $page.url.searchParams.set('includeOVAs', includeOVAs.toString()); + + history.replaceState(null, '', `?${$page.url.searchParams.toString()}`); + } + }); + let genreTagTitle = $derived((() => { + switch (genreTagsSort) { + case SortOptions.SCORE: + return 'Highest Rated'; + case SortOptions.MINUTES_WATCHED: + return 'Most Watched'; + case SortOptions.COUNT: + return 'Most Common'; + } + })()); + let animeMostTitle = $derived((() => { + switch (mediaSort) { + case SortOptions.SCORE: + return 'Highest Rated'; + case SortOptions.MINUTES_WATCHED: + return 'Most Watched'; + case SortOptions.COUNT: + return 'Most Common'; + } + })()); + let mangaMostTitle = $derived((() => { + switch (mediaSort) { + case SortOptions.SCORE: + return 'Highest Rated'; + case SortOptions.MINUTES_WATCHED: + return 'Most Read'; + case SortOptions.COUNT: + return 'Most Common'; + } + })()); </script> {#if $userIdentity.id === -2 || user === undefined} @@ -630,10 +636,10 @@ </div> <div class="list"> <div class:card={generated}> - <div id="wrapped-final" /> + <div id="wrapped-final"></div> {#if generated} - <p /> + <p></p> <blockquote style="margin: 0 0 0 1.5rem;"> Click on the image to download, or right click and select "Save Image As...". @@ -642,11 +648,11 @@ </div> {#if generated} - <p /> + <p></p> {/if} <div id="options" class="card"> - <button on:click={screenshot} data-umami-event="Generate Wrapped"> + <button onclick={screenshot} data-umami-event="Generate Wrapped"> Generate image </button> @@ -690,9 +696,9 @@ {/each} </select> Highest genre and tag count<br /> - <button on:click={updateWidth}>Find best fit</button> - <button on:click={() => (width -= 25)}>-25px</button> - <button on:click={() => (width += 25)}>+25px</button> + <button onclick={updateWidth}>Find best fit</button> + <button onclick={() => (width -= 25)}>-25px</button> + <button onclick={() => (width += 25)}>+25px</button> Width adjustment<br /> </details> @@ -700,7 +706,7 @@ <summary>Calculation</summary> <input type="checkbox" bind:checked={useFullActivityHistory} /> - Enable full-year activity<button class="smaller-button" on:click={pruneFullYear} + Enable full-year activity<button class="smaller-button" onclick={pruneFullYear} >Refresh data</button > <br /> @@ -732,12 +738,12 @@ <input type="text" bind:value={excludedKeywordsInput} - on:keypress={(e) => { + onkeypress={(e) => { e.key === 'Enter' && submitExcludedKeywords(); }} /> Excluded keywords - <button on:click={submitExcludedKeywords} title="Or click your Enter key" use:tooltip + <button onclick={submitExcludedKeywords} title="Or click your Enter key" use:tooltip >Submit</button > <br /> diff --git a/src/lib/Tools/Wrapped/Top/Activity.svelte b/src/lib/Tools/Wrapped/Top/Activity.svelte index a91bedfb..27dea6a2 100644 --- a/src/lib/Tools/Wrapped/Top/Activity.svelte +++ b/src/lib/Tools/Wrapped/Top/Activity.svelte @@ -4,18 +4,28 @@ import type { Wrapped } from '$lib/Data/AniList/wrapped'; import proxy from '$lib/Utility/proxy'; - export let wrapped: Wrapped; - export let year: number; - export let activities: ActivityHistoryEntry[]; - export let useFullActivityHistory: boolean; - export let updateWidth: () => void; + interface Props { + wrapped: Wrapped; + year: number; + activities: ActivityHistoryEntry[]; + useFullActivityHistory: boolean; + updateWidth: () => void; + } + + let { + wrapped, + year, + activities, + useFullActivityHistory, + updateWidth + }: Props = $props(); const currentYear = new Date(Date.now()).getFullYear(); </script> <div class="grid-item image-grid avatar-grid category top-category"> <a href={`https://anilist.co/user/${$identity.name}`} target="_blank"> - <img src={proxy(wrapped.avatar.large)} alt="User Avatar" on:load={updateWidth} /> + <img src={proxy(wrapped.avatar.large)} alt="User Avatar" onload={updateWidth} /> </a> <div> <div> diff --git a/src/lib/Tools/Wrapped/Top/Anime.svelte b/src/lib/Tools/Wrapped/Top/Anime.svelte index 08df7fd3..275adadf 100644 --- a/src/lib/Tools/Wrapped/Top/Anime.svelte +++ b/src/lib/Tools/Wrapped/Top/Anime.svelte @@ -1,9 +1,13 @@ <script lang="ts"> import type { Media } from '$lib/Data/AniList/media'; - export let minutesWatched: number; - export let animeList: Media[] | undefined; - export let episodes: number; + interface Props { + minutesWatched: number; + animeList: Media[] | undefined; + episodes: number; + } + + let { minutesWatched, animeList, episodes }: Props = $props(); </script> <div class="category-grid pure-category category top-category"> diff --git a/src/lib/Tools/Wrapped/Top/Manga.svelte b/src/lib/Tools/Wrapped/Top/Manga.svelte index a36f7724..a49d1067 100644 --- a/src/lib/Tools/Wrapped/Top/Manga.svelte +++ b/src/lib/Tools/Wrapped/Top/Manga.svelte @@ -2,8 +2,12 @@ import type { Media } from '$lib/Data/AniList/media'; import { estimatedDayReading } from '$lib/Media/Manga/time'; - export let mangaList: Media[] | undefined; - export let chapters: number; + interface Props { + mangaList: Media[] | undefined; + chapters: number; + } + + let { mangaList, chapters }: Props = $props(); </script> <div class="category-grid pure-category category top-category"> diff --git a/src/lib/Tooltip/LinkedTooltip.svelte b/src/lib/Tooltip/LinkedTooltip.svelte index 6e468cd6..f4546d4c 100644 --- a/src/lib/Tooltip/LinkedTooltip.svelte +++ b/src/lib/Tooltip/LinkedTooltip.svelte @@ -2,23 +2,42 @@ import tooltipPosition from '$stores/tooltipPosition'; import { fade } from 'svelte/transition'; - export let id: string | undefined = undefined; - export let pin: string | undefined = undefined; - export let content: string; - export let disable: boolean = false; - export let pinPosition: 'top' | 'bottom' | 'left' | 'right' = 'top'; - export let offset = 10; - export let tooltipTransitionTime = 200; - export let tooltipHideDelay = 10; - export let debounceDelay = 100; - export let tooltipOpacityTransitionTime = 200; - export let relative = false; - export let ignoreAnchorStyling = false; - - let tooltipDiv: HTMLDivElement | null = null; + interface Props { + id?: string | undefined; + pin?: string | undefined; + content: string; + disable?: boolean; + pinPosition?: 'top' | 'bottom' | 'left' | 'right'; + offset?: number; + tooltipTransitionTime?: number; + tooltipHideDelay?: number; + debounceDelay?: number; + tooltipOpacityTransitionTime?: number; + relative?: boolean; + ignoreAnchorStyling?: boolean; + children?: import('svelte').Snippet; + } + + let { + id = undefined, + pin = undefined, + content, + disable = false, + pinPosition = 'top', + offset = 10, + tooltipTransitionTime = 200, + tooltipHideDelay = 10, + debounceDelay = 100, + tooltipOpacityTransitionTime = 200, + relative = false, + ignoreAnchorStyling = false, + children + }: Props = $props(); + + let tooltipDiv: HTMLDivElement | null = $state(null); let hideTimeout: number | null = null; let debounceTimer: number | null = null; - let opacity = 0; + let opacity = $state(0); const createTooltip = () => { if (!tooltipDiv) { @@ -202,12 +221,12 @@ <span {id} - on:mouseenter={handleMouseEnter} - on:mousemove={handleMouseMove} - on:mouseleave={handleMouseLeave} + onmouseenter={handleMouseEnter} + onmousemove={handleMouseMove} + onmouseleave={handleMouseLeave} role="tooltip" > - <slot /> + {@render children?.()} </span> {#if tooltipDiv} diff --git a/src/lib/User/BadgeWall/AWC.svelte b/src/lib/User/BadgeWall/AWC.svelte index 1cf82a1b..2bd21001 100644 --- a/src/lib/User/BadgeWall/AWC.svelte +++ b/src/lib/User/BadgeWall/AWC.svelte @@ -5,10 +5,19 @@ import FallbackBadge from './FallbackBadge.svelte'; import './badges.css'; - export let awcPromise: Promise<Response>; - export let categoryFilter: string | null; - export let isOwner: boolean; - export let preferences: Preferences; + interface Props { + awcPromise: Promise<Response>; + categoryFilter: string | null; + isOwner: boolean; + preferences: Preferences; + } + + let { + awcPromise, + categoryFilter, + isOwner, + preferences + }: Props = $props(); const awcBadgesGrouped = (awcResponse: string): AWCBadgesGroup[] => { return Array.from( @@ -72,7 +81,7 @@ {group.group} </summary> - <p /> + <p></p> <div class="badges"> {#each group.badges as badge, index} @@ -92,7 +101,7 @@ </div> </details> - <p /> + <p></p> {/each} {/if} {/await} diff --git a/src/lib/User/BadgeWall/BadgePreview.svelte b/src/lib/User/BadgeWall/BadgePreview.svelte index 7a54cbc4..5daccdd2 100644 --- a/src/lib/User/BadgeWall/BadgePreview.svelte +++ b/src/lib/User/BadgeWall/BadgePreview.svelte @@ -1,4 +1,6 @@ <script lang="ts"> + import { run } from 'svelte/legacy'; + import { thumbnail } from '$lib/Utility/image'; import type { Badge } from '$lib/Database/SB/User/badges'; import { cdn } from '$lib/Utility/image'; @@ -8,16 +10,26 @@ import root from '$lib/Utility/root'; import ParallaxImage from '$lib/Image/ParallaxImage.svelte'; - export let selectedBadge: Badge | undefined; - export let onNext: () => void = () => {}; - export let onPrevious: () => void = () => {}; - export let hasNext: boolean; - export let hasPrevious: boolean; + interface Props { + selectedBadge: Badge | undefined; + onNext?: () => void; + onPrevious?: () => void; + hasNext: boolean; + hasPrevious: boolean; + } + + let { + selectedBadge = $bindable(), + onNext = () => {}, + onPrevious = () => {}, + hasNext, + hasPrevious + }: Props = $props(); - let source = cdn(thumbnail(selectedBadge?.image || '')) || ''; + let source = $state(cdn(thumbnail(selectedBadge?.image || '')) || ''); let badgeReference: HTMLImageElement; - $: { + run(() => { if (selectedBadge && selectedBadge.image) { const image = new Image(); @@ -26,14 +38,14 @@ source = image.src; }; } - } + }); - $: { + run(() => { if (selectedBadge) fetch(root(`/api/badges?incrementClickCount=${selectedBadge.id}`), { method: 'PUT' }); - } + }); onMount(() => { badgeReference = document.querySelector('.badge-container-image') as HTMLImageElement; @@ -105,7 +117,7 @@ <div class="badge-preview-badge"> {#if selectedBadge.image} <div role="img" class="badge-container"> - <a href={'#'} on:click={onClick} class="badge-container-image"> + <a href={'#'} onclick={onClick} class="badge-container-image"> <ParallaxImage {source} alternativeText="selectedBadge.description" @@ -115,7 +127,7 @@ </a> </div> - <p /> + <p></p> {/if} </div> @@ -124,7 +136,7 @@ {$locale().dateFormatter(databaseTimeToDate(selectedBadge.time))} {#if (selectedBadge.designer || selectedBadge.source || selectedBadge.post) && !selectedBadge.description} - <p /> + <p></p> {:else if selectedBadge.description} <br /> {/if} @@ -133,7 +145,7 @@ {#if selectedBadge.description} {selectedBadge.description} - <p /> + <p></p> {/if} {#if selectedBadge.designer} @@ -185,7 +197,7 @@ <a href={`?category=${selectedBadge.category}`} - on:click={() => (selectedBadge = undefined)} + onclick={() => (selectedBadge = undefined)} > {selectedBadge.category} </a> @@ -200,11 +212,11 @@ <div class="badge-preview-seek"> {#if hasPrevious} - <button on:click={onPrevious}>Previous</button> + <button onclick={onPrevious}>Previous</button> {/if} {#if hasNext} - <button on:click={onNext} style="float: right;">Next</button> + <button onclick={onNext} style="float: right;">Next</button> {/if} </div> </div> diff --git a/src/lib/User/BadgeWall/Badges.svelte b/src/lib/User/BadgeWall/Badges.svelte index b233d0c3..1acd4cce 100644 --- a/src/lib/User/BadgeWall/Badges.svelte +++ b/src/lib/User/BadgeWall/Badges.svelte @@ -9,19 +9,30 @@ import type { Preferences } from '../../../graphql/$types'; import type { IndexedBadge } from './badge'; - export let ungroupedBadges: IndexedBadge[]; - export let groupedBadges: [string, IndexedBadge[]][]; - export let categoryFilter: string | null; - export let editMode: boolean; - export let preferences: Preferences | undefined; - export let selectedBadge: IndexedBadge | undefined = undefined; + interface Props { + ungroupedBadges: IndexedBadge[]; + groupedBadges: [string, IndexedBadge[]][]; + categoryFilter: string | null; + editMode: boolean; + preferences: Preferences | undefined; + selectedBadge?: IndexedBadge | undefined; + } + + let { + ungroupedBadges, + groupedBadges, + categoryFilter, + editMode, + preferences, + selectedBadge = $bindable(undefined) + }: Props = $props(); </script> {#if ungroupedBadges.length === 0} <div class="card"> No due.moe registered badges found for this user. <a href={'#'} - on:click={(e) => e.preventDefault()} + onclick={(e) => e.preventDefault()} title="This alert does not include AWC badges." use:tooltip>?</a > @@ -36,7 +47,7 @@ <details open={categoryFilter ? categoryFilter === category : true}> <summary>{category}</summary> - <p /> + <p></p> <div class="badges"> {#each badges as badge} @@ -54,7 +65,7 @@ > <a href={`#`} - on:click={() => { + onclick={() => { selectedBadge = badge; const hiddenInput = document.querySelector('input[name="hidden"]'); @@ -93,6 +104,6 @@ </details> {#if groupedBadges[groupedBadges.length - 1][0] !== category} - <p /> + <p></p> {/if} {/each} diff --git a/src/lib/User/BadgeWall/FallbackBadge.svelte b/src/lib/User/BadgeWall/FallbackBadge.svelte index 35a50a7d..68667031 100644 --- a/src/lib/User/BadgeWall/FallbackBadge.svelte +++ b/src/lib/User/BadgeWall/FallbackBadge.svelte @@ -9,22 +9,40 @@ import { dev } from '$app/environment'; import type { Preferences } from '../../../graphql/$types'; - export let source: string | null | undefined; - export let alternative: string | null | undefined; - export let fallback: string | null | undefined; - export let maxReplaceCount = 1; - export let replaceDelay = 1000; - export let error = 'https://i2.kym-cdn.com/photos/images/newsfeed/000/290/992/0aa.jpg'; - export let hideOnError = false; - export let badge: Badge; - export let style = ''; - export let selectedBadge: Badge | null = null; - export let awc = false; - export let index: number | null = null; - export let preferences: Preferences | undefined; - - let replaceCount = 0; - let badgeReference: HTMLImageElement; + interface Props { + source: string | null | undefined; + alternative: string | null | undefined; + fallback: string | null | undefined; + maxReplaceCount?: number; + replaceDelay?: number; + error?: string; + hideOnError?: boolean; + badge: Badge; + style?: string; + selectedBadge?: Badge | null; + awc?: boolean; + index?: number | null; + preferences: Preferences | undefined; + } + + let { + source, + alternative, + fallback, + maxReplaceCount = 1, + replaceDelay = 1000, + error = 'https://i2.kym-cdn.com/photos/images/newsfeed/000/290/992/0aa.jpg', + hideOnError = false, + badge, + style = '', + selectedBadge = $bindable(null), + awc = false, + index = null, + preferences + }: Props = $props(); + + let replaceCount = $state(0); + let badgeReference: HTMLImageElement = $state(); const mouse = tweened( { x: 0, y: 0 }, { @@ -82,9 +100,9 @@ href={awc ? badgeToAny(badge).link : '#'} target="_blank" class="badge-container badge" - on:mousemove={handleMouseMove} - on:mouseleave={handleMouseLeave} - on:click={(e) => { + onmousemove={handleMouseMove} + onmouseleave={handleMouseLeave} + onclick={(e) => { if (!awc) { e.preventDefault(); @@ -100,7 +118,7 @@ bind:this={badgeReference} style="transform: perspective(1000px) rotateX({$mouse.y / 10}deg) rotateY({-$mouse.x / 10}deg); ${style}" - on:error={(e) => delayedReplace(e, fallback)} + onerror={(e) => delayedReplace(e, fallback)} /> </a> </Tooltip> diff --git a/src/lib/Utility/Loading.svelte b/src/lib/Utility/Loading.svelte index 92cbc1ac..3d1eeec6 100644 --- a/src/lib/Utility/Loading.svelte +++ b/src/lib/Utility/Loading.svelte @@ -1,13 +1,23 @@ <script lang="ts"> - export let type: string | undefined = undefined; - export let percent: number | undefined = undefined; - export let card = true; + interface Props { + type?: string | undefined; + percent?: number | undefined; + card?: boolean; + children?: import('svelte').Snippet; + } + + let { + type = undefined, + percent = undefined, + card = true, + children + }: Props = $props(); </script> <div class:card> {#if type} Loading {type} ...{percent ? ` ${percent}%` : ''} {:else} - <slot /> + {@render children?.()} {/if} </div> diff --git a/src/lib/Utility/oauth.ts b/src/lib/Utility/oauth.ts index 78f52bfa..bd824b75 100644 --- a/src/lib/Utility/oauth.ts +++ b/src/lib/Utility/oauth.ts @@ -47,5 +47,5 @@ export const callback = async (options: CallbackOptions) => { } ); - throw redirect(303, options.redirect ?? '/'); + redirect(303, options.redirect ?? '/'); }; diff --git a/src/routes/+error.svelte b/src/routes/+error.svelte index f822d521..26a7a69a 100644 --- a/src/routes/+error.svelte +++ b/src/routes/+error.svelte @@ -3,7 +3,7 @@ import { closest } from '$lib/Error/path'; import Popup from '$lib/Layout/Popup.svelte'; - $: suggestion = closest($page.url.pathname.replace('/', ''), [ + let suggestion = $derived(closest($page.url.pathname.replace('/', ''), [ 'birthdays', 'completed', 'schedule', @@ -13,7 +13,7 @@ 'updates', 'user', 'wrapped' - ]); + ])); </script> <Popup> diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 6d7fe757..3431bc86 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,4 +1,6 @@ <script lang="ts"> + import { run } from 'svelte/legacy'; + import type { SubsPleaseEpisode } from '$lib/Media/Anime/Airing/Subtitled/subsPlease'; import { env } from '$env/dynamic/public'; import { userIdentity as getUserIdentity } from '$lib/Data/AniList/identity'; @@ -33,9 +35,9 @@ injectSpeedInsights(); - export let data; + let { data, children } = $props(); - let isHeaderVisible = true; + let isHeaderVisible = $state(true); let previousScrollPosition = 0; let notificationInterval: NodeJS.Timeout | undefined = undefined; @@ -43,7 +45,9 @@ addMessages('ja', japanese as unknown as LocaleDictionary); init({ fallbackLocale: 'en', initialLocale: $settings.displayLanguage }); - $: i18nLocale.set($settings.displayLanguage); + run(() => { + i18nLocale.set($settings.displayLanguage); + }); const navigationOrder = ['/', '/completed', '/schedule', '/updates', '/tools', '/settings']; const previousPage: Readable<string | null> = readable(null, (set) => { @@ -54,13 +58,15 @@ return () => unsubscribe(); }); - $: way = data.url.includes('/user') - ? 200 - : $previousPage && $previousPage.includes('/user') - ? -200 - : navigationOrder.indexOf(data.url) > navigationOrder.indexOf($previousPage ?? '/') - ? 200 - : -200; + let way = $derived( + data.url.includes('/user') + ? 200 + : $previousPage && $previousPage.includes('/user') + ? -200 + : navigationOrder.indexOf(data.url) > navigationOrder.indexOf($previousPage ?? '/') + ? 200 + : -200 + ); const handleScroll = () => { const currentScrollPosition = window.scrollY; @@ -127,7 +133,7 @@ if (notificationInterval) clearInterval(notificationInterval); }); - $: { + run(() => { if ((data.url === '/' || data.url === '/completed' || data.url === '/schedule') && !$subsPlease) fetch(root(`/api/subsplease?tz=${Intl.DateTimeFormat().resolvedOptions().timeZone}`)) .then((r) => r.json()) @@ -147,7 +153,7 @@ subsPlease.set(r); }); - } + }); </script> <HeadTitle /> @@ -225,7 +231,7 @@ <a class="header-item" href={`https://anilist.co/api/v2/oauth/authorize?client_id=${env.PUBLIC_ANILIST_CLIENT_ID}&redirect_uri=${env.PUBLIC_ANILIST_REDIRECT_URI}&response_type=code`} - on:click={() => { + onclick={() => { localStorage.setItem( 'redirect', window.location.origin + window.location.pathname + window.location.search @@ -242,12 +248,12 @@ </div> </div> - <p /> + <p></p> <Notifications item={EventNotification} zIndex={5000}> <Root {data} {way}> {#if $userIdentity.id !== -1} - <slot /> + {@render children?.()} {:else if data.url === '/settings'} <Skeleton grid={true} count={1} height="10vh" /> <Skeleton grid={true} count={1} height="30vh" /> @@ -263,8 +269,19 @@ <style lang="scss"> .header { - font-family: 'DM Sans', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, - Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; + font-family: + 'DM Sans', + system-ui, + -apple-system, + BlinkMacSystemFont, + 'Segoe UI', + Roboto, + Oxygen, + Ubuntu, + Cantarell, + 'Open Sans', + 'Helvetica Neue', + sans-serif; font-size: 1.05em; font-weight: 600; padding: 0.8rem 0.4rem; @@ -320,7 +337,10 @@ display: inline-block; vertical-align: middle; border-radius: 8px; - box-shadow: 0 1.5px 9px var(--base01), 0 0 0 4px var(--base0E), 0 4px 30px var(--base01); + box-shadow: + 0 1.5px 9px var(--base01), + 0 0 0 4px var(--base0E), + 0 4px 30px var(--base01); } .separator { diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 653c3836..5c17dd7d 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -13,7 +13,7 @@ import Landing from '$lib/Landing.svelte'; import IndexColumn from '$lib/List/Anime/DueIndexColumn.svelte'; - export let data; + let { data } = $props(); let heightObserver: NodeJS.Timeout; @@ -29,7 +29,7 @@ {#if data.user === undefined} <div class="card">Please log in to view due media.</div> - <p /> + <p></p> <Landing /> {:else} diff --git a/src/routes/api/authentication/log-out/+server.ts b/src/routes/api/authentication/log-out/+server.ts index 305c846f..26b5dd2c 100644 --- a/src/routes/api/authentication/log-out/+server.ts +++ b/src/routes/api/authentication/log-out/+server.ts @@ -11,5 +11,5 @@ export const GET = ({ cookies }) => { secure: false }); - throw redirect(303, root('/')); + redirect(303, root('/')); }; diff --git a/src/routes/api/oauth/refresh/+server.ts b/src/routes/api/oauth/refresh/+server.ts index 66b4209c..13f4400c 100644 --- a/src/routes/api/oauth/refresh/+server.ts +++ b/src/routes/api/oauth/refresh/+server.ts @@ -25,6 +25,6 @@ export const GET = async ({ url, cookies }) => { secure: false }); - if (url.searchParams.get('redirect')) throw redirect(303, '/'); + if (url.searchParams.get('redirect')) redirect(303, '/'); else return Response.json(newUser); }; diff --git a/src/routes/completed/+page.svelte b/src/routes/completed/+page.svelte index d483d7fe..73968227 100644 --- a/src/routes/completed/+page.svelte +++ b/src/routes/completed/+page.svelte @@ -12,7 +12,7 @@ import locale from '$stores/locale.js'; import Landing from '$lib/Landing.svelte'; - export let data; + let { data } = $props(); let heightObserver: NodeJS.Timeout; @@ -28,7 +28,7 @@ {#if data.user === undefined} <div class="card">Please log in to view completed media.</div> - <p /> + <p></p> <Landing /> {:else} diff --git a/src/routes/events/+page.svelte b/src/routes/events/+page.svelte index d3270e30..1c6e2524 100644 --- a/src/routes/events/+page.svelte +++ b/src/routes/events/+page.svelte @@ -16,7 +16,7 @@ <Event event={rawEvent} avatar /> {#if i < events.length - 1} - <p /> + <p></p> {/if} {/each} {/if} diff --git a/src/routes/events/group/[group]/+page.svelte b/src/routes/events/group/[group]/+page.svelte index 37c23c40..28974fd3 100644 --- a/src/routes/events/group/[group]/+page.svelte +++ b/src/routes/events/group/[group]/+page.svelte @@ -7,9 +7,9 @@ import Group from '$lib/Events/Group.svelte'; import Event from '$lib/Events/Event.svelte'; - export let data; + let { data } = $props(); - let groupsResponse: Promise<Response>; + let groupsResponse: Promise<Response> = $state(); onMount(async () => { groupsResponse = fetch(root(`/api/events/group?slug=${data.group}`)); @@ -30,14 +30,14 @@ {#if json === null} <Message message="" loader="ripple" slot> This group may not exist. Please - <a href={'#'} on:click={() => location.reload()}>try again</a> later. + <a href={'#'} onclick={() => location.reload()}>try again</a> later. </Message> {:else} {@const group = asGroup(json)} <Group {group} /> - <p /> + <p></p> <details open> <summary>Events</summary> @@ -53,7 +53,7 @@ <Event event={asEvent(rawEvent)} /> {#if i < events.length - 1} - <p /> + <p></p> {/if} {/each} {/if} diff --git a/src/routes/events/groups/+page.svelte b/src/routes/events/groups/+page.svelte index d90cce34..930e2d37 100644 --- a/src/routes/events/groups/+page.svelte +++ b/src/routes/events/groups/+page.svelte @@ -5,7 +5,7 @@ import { onMount } from 'svelte'; import Group from '$lib/Events/Group.svelte'; - let groupsResponse: Promise<Response>; + let groupsResponse: Promise<Response> = $state(); onMount(async () => { groupsResponse = fetch(root('/api/events/groups')); @@ -29,7 +29,7 @@ </a> {#if i < json.length - 1} - <p /> + <p></p> {/if} {/each} {:catch} diff --git a/src/routes/girls/+page.svelte b/src/routes/girls/+page.svelte index 71982c32..ecd4acd0 100644 --- a/src/routes/girls/+page.svelte +++ b/src/routes/girls/+page.svelte @@ -27,7 +27,7 @@ <div> The Senpy Club <span class="opaque">|</span> Anime Girls Holding Programming Books - <p /> + <p></p> <ul> <li> @@ -65,7 +65,7 @@ </div> </div> -<p /> +<p></p> <details class="languages" open> <summary>Languages</summary> diff --git a/src/routes/girls/[language]/+page.svelte b/src/routes/girls/[language]/+page.svelte index 91b18628..d61af7db 100644 --- a/src/routes/girls/[language]/+page.svelte +++ b/src/routes/girls/[language]/+page.svelte @@ -4,7 +4,7 @@ import Skeleton from '$lib/Loading/Skeleton.svelte'; import '$styles/girls.scss'; - export let data; + let { data } = $props(); </script> <div class="card"> diff --git a/src/routes/hololive/[[stream]]/+page.svelte b/src/routes/hololive/[[stream]]/+page.svelte index d5419711..33126762 100644 --- a/src/routes/hololive/[[stream]]/+page.svelte +++ b/src/routes/hololive/[[stream]]/+page.svelte @@ -11,10 +11,10 @@ import Lives from '$lib/Hololive/Lives.svelte'; import { typeSchedule } from '$lib/Hololive/hololive'; - export let data; + let { data } = $props(); - let schedulePromise: Promise<Response>; - let pinnedStreams: string[] = []; + let schedulePromise: Promise<Response> = $state(); + let pinnedStreams: string[] = $state([]); onMount(() => getPinnedStreams()); @@ -66,7 +66,7 @@ {:catch} <Message loader="ripple" slot> {$locale().hololive.parseError} - <a href={'#'} on:click={() => location.reload()}>Try again?</a> + <a href={'#'} onclick={() => location.reload()}>Try again?</a> </Message> {/await} {:else} @@ -77,6 +77,6 @@ {:catch} <Message loader="ripple" slot> {$locale().hololive.loadError} Please - <a href={'#'} on:click={() => location.reload()}>try again</a> later. + <a href={'#'} onclick={() => location.reload()}>try again</a> later. </Message> {/await} diff --git a/src/routes/reader/+page.svelte b/src/routes/reader/+page.svelte index 775d3659..cff5ca3e 100644 --- a/src/routes/reader/+page.svelte +++ b/src/routes/reader/+page.svelte @@ -6,9 +6,9 @@ import { decodeResource, fetchResource, identify, Resource } from '$lib/Reader/resource'; import InputTemplate from '$lib/Tools/InputTemplate.svelte'; - let submission = ''; + let submission = $state(''); - $: resourceIdentity = identify(submission); + let resourceIdentity = $derived(identify(submission)); </script> <InputTemplate field="Manga URL" bind:submission submitText="Read" preserveCase> diff --git a/src/routes/schedule/+page.svelte b/src/routes/schedule/+page.svelte index 70b1e1e4..3393cced 100644 --- a/src/routes/schedule/+page.svelte +++ b/src/routes/schedule/+page.svelte @@ -14,9 +14,9 @@ import Message from '$lib/Loading/Message.svelte'; import subsPlease from '$stores/subsPlease'; - export let data; + let { data } = $props(); - let scheduledMediaPromise: Promise<Partial<Media[]>>; + let scheduledMediaPromise: Promise<Partial<Media[]>> = $state(); const urlParameters = browser ? new URLSearchParams(window.location.search) : null; // let crunchyrollExpanded = false; let forceListMode = parseOrDefault(urlParameters, 'list', false); diff --git a/src/routes/settings/+page.svelte b/src/routes/settings/+page.svelte index 42ee4edd..816eaab7 100644 --- a/src/routes/settings/+page.svelte +++ b/src/routes/settings/+page.svelte @@ -1,4 +1,7 @@ <script lang="ts"> + import { createBubbler, preventDefault } from 'svelte/legacy'; + + const bubble = createBubbler(); /* eslint svelte/no-at-html-tags: "off" */ import Attributions from '$lib/Settings/Categories/Attributions.svelte'; @@ -15,7 +18,7 @@ import SettingSync from '$lib/Settings/Categories/SettingSync.svelte'; import RssFeeds from '$lib/Settings/Categories/RSSFeeds.svelte'; - export let data; + let { data } = $props(); // const pruneUnresolved = async () => { // const unresolved = await chapterDatabase.chapters.where('chapters').equals(-1).toArray(); @@ -71,7 +74,7 @@ </Category> </div> - <p /> + <p></p> <Category title={$locale().settings.display.title}><Display /></Category> <Category title={$locale().settings.calculation.title}><Calculation /></Category> @@ -83,7 +86,7 @@ class="smaller-button button-badge badge-info unclickable-button" title={$locale().settings.debug.tooltips.version} use:tooltip - on:click|preventDefault + onclick={preventDefault(bubble('click'))} >{data.commit.slice(0, 7)} </button></summary > diff --git a/src/routes/tools/+page.svelte b/src/routes/tools/+page.svelte index d1650b34..139588a7 100644 --- a/src/routes/tools/+page.svelte +++ b/src/routes/tools/+page.svelte @@ -4,7 +4,7 @@ import { tools } from '$lib/Tools/tools.js'; import root from '$lib/Utility/root'; - let tool = 'default'; + let tool = $state('default'); </script> <Picker {tool} /> @@ -14,13 +14,13 @@ <div class="card"> <div class="tool-grid"> {#each Object.keys(tools).filter((t) => t !== 'default' && !tools[t].hidden) as t} - <a href={root(`/tools/${tools[t].id}`)} on:click={() => (tool = t)}> + <a href={root(`/tools/${tools[t].id}`)} onclick={() => (tool = t)}> <div class="tool-grid-tool card"> <span class="title"> {tools[t].name()} </span> - <p /> + <p></p> {#if tools[t].description} <span class="description"> @@ -32,7 +32,7 @@ {/each} </div> - <p /> + <p></p> <blockquote style="margin: 0 0 0 1.5rem;"> Have any requests for cool tools that you think others might find useful? Send a private message diff --git a/src/routes/tools/[tool]/+page.svelte b/src/routes/tools/[tool]/+page.svelte index c811cf2a..9c7796ba 100644 --- a/src/routes/tools/[tool]/+page.svelte +++ b/src/routes/tools/[tool]/+page.svelte @@ -1,4 +1,6 @@ <script lang="ts"> + import { run } from 'svelte/legacy'; + import Hayai from './../../../lib/Tools/Hayai.svelte'; import UmaMusumeBirthdays from './../../../lib/Tools/UmaMusumeBirthdays.svelte'; import ActivityHistory from '$lib/Tools/ActivityHistory/Tool.svelte'; @@ -21,17 +23,19 @@ import SequelCatcher from '$lib/Tools/SequelCatcher/Tool.svelte'; import Tracker from '$lib/Tools/Tracker/Tool.svelte'; - export let data; + let { data } = $props(); - let tool = data.tool ?? 'default'; + let tool = $state(data.tool ?? 'default'); onMount(() => { if (tool === 'default') goto(root('/tools')); }); - $: suggestion = closest(tool, Object.keys(tools)); + let suggestion = $derived(closest(tool, Object.keys(tools))); - $: if (tool == 'girls') goto(root('/girls')); + run(() => { + if (tool == 'girls') goto(root('/girls')); + }); </script> <Picker bind:tool /> @@ -47,7 +51,7 @@ <blockquote style="margin: 0 0 0 1.5rem;"> Did you mean "<a href={root(`/tools/${tools[suggestion].id}`)} - on:click={() => (tool = suggestion)} + onclick={() => (tool = suggestion)} style={suggestion === '...' ? 'pointer-events: none; color: inherit;' : ''} > {suggestion === '...' ? '...' : tools[suggestion].name()}</a diff --git a/src/routes/updates/+page.svelte b/src/routes/updates/+page.svelte index 9af001b4..f2657ab0 100644 --- a/src/routes/updates/+page.svelte +++ b/src/routes/updates/+page.svelte @@ -9,17 +9,17 @@ import { onDestroy, onMount } from 'svelte'; let feed: { items: { title: string; link: string; content: string }[] } | null | undefined = - undefined; + $state(undefined); let novelFeed: | { data: { items: { srcurl: string; postfix?: string; chapter: number; series: { name: string } }[]; }; } - | undefined = undefined; + | undefined = $state(undefined); let startTime: number; - let mangaEndTime: number; - let novelEndTime: number; + let mangaEndTime: number = $state(); + let novelEndTime: number = $state(); let directLink = browser ? new URLSearchParams(window.location.search).has('d') : false; let heightObserver: NodeJS.Timeout; diff --git a/src/routes/user/[user]/+page.svelte b/src/routes/user/[user]/+page.svelte index d60ea8e5..1a36ccb5 100644 --- a/src/routes/user/[user]/+page.svelte +++ b/src/routes/user/[user]/+page.svelte @@ -22,10 +22,10 @@ import LinkedTooltip from '$lib/Tooltip/LinkedTooltip.svelte'; import { graphql } from '$houdini'; - export let data; + let { data } = $props(); - $: ({ Profile } = data); - $: preferences = $Profile.fetching ? undefined : ($Profile.data?.User.preferences as Preferences); + let { Profile } = $derived(data); + let preferences = $derived($Profile.fetching ? undefined : ($Profile.data?.User.preferences as Preferences)); const setCategoriesQuery = graphql(` mutation SetCategories($categories: [String!]!) { @@ -99,20 +99,20 @@ } `); - $: userData = data.userData; + let userData = $derived(data.userData); let error = false; - let schedule: ParseResult | undefined = undefined; + let schedule: ParseResult | undefined = $state(undefined); let draggedCategory: string | null = null; let draggedOverCategory: string | null = null; - $: displayBadges = (username: string, badges: number | string) => + let displayBadges = $derived((username: string, badges: number | string) => $locale({ values: { badges: badges, username } - }).user.profile.badges; + }).user.profile.badges); const handleDragStart = ( event: DragEvent & { currentTarget: EventTarget & HTMLDivElement }, @@ -302,7 +302,7 @@ {#if schedule && preferences && preferences.biography && preferences.biography.length > 0} <br /> {:else} - <p /> + <p></p> {/if} {#if $Profile.fetching} @@ -325,7 +325,7 @@ {/if} {#if schedule && preferences && preferences.pinned_hololive_streams.length > 0} - <p /> + <p></p> <div class="card"> <div class="hololive-badges"> @@ -352,14 +352,14 @@ {/if} {#if preferences && userData && userData.id === $identity.id} - <p /> + <p></p> <details open> <summary>{$locale().user.preferences.title}</summary> <input type="checkbox" - on:change={() => { + onchange={() => { if (userData) toggleHideMissingBadgesQuery.mutate(null).then(); }} checked={preferences.hide_missing_badges} @@ -367,18 +367,18 @@ {$locale().user.preferences.hideMissingBadges.title} <SettingHint lineBreak>{$locale().user.preferences.hideMissingBadges.hint}</SettingHint> - <p /> + <p></p> <input type="checkbox" - on:change={() => { + onchange={() => { if (userData) toggleHideAWCBadgesQuery.mutate(null).then(); }} checked={preferences.hide_awc_badges} /> {$locale().user.preferences.hideAWCBadges.title} - <p /> + <p></p> Pinned Categories @@ -387,11 +387,11 @@ <div class="card card-small pinned-category" draggable="true" - on:dragstart={(event) => handleDragStart(event, category)} - on:dragover={handleDragOver} - on:dragenter={(event) => handleDragEnter(event, category)} - on:dragleave={(event) => handleDragLeave(event, category)} - on:drop={handleDrop} + ondragstart={(event) => handleDragStart(event, category)} + ondragover={handleDragOver} + ondragenter={(event) => handleDragEnter(event, category)} + ondragleave={(event) => handleDragLeave(event, category)} + ondrop={handleDrop} role="button" tabindex="0" > @@ -400,7 +400,7 @@ </span> <button - on:click={() => { + onclick={() => { if (userData) toggleCategoryQuery.mutate({ category }).then(); }}>Remove</button > @@ -412,16 +412,16 @@ <input type="text" id="category" placeholder="Category" style="width: 10em;" /> </span> - <button class="button-lined" on:click={toggleCategory}>Add</button> + <button class="button-lined" onclick={toggleCategory}>Add</button> </span> </div> - <p /> + <p></p> Biography <button - on:click={() => { + onclick={() => { if (userData) setBiographyQuery .mutate({ @@ -436,14 +436,14 @@ cols="100" id="biography" placeholder="Markdown supported!" - /> +></textarea> - <p /> + <p></p> Badge Wall Custom CSS <button - on:click={() => { + onclick={() => { if (userData) setBadgeWallCSSQuery .mutate({ @@ -458,7 +458,7 @@ cols="100" id="badgeWallCSS" placeholder="/* Use classes and IDs such as .badges, #badges, .badge, or standard elements like body and details, or anything, as long as it's valid CSS! */" - /> +></textarea> </details> {/if} {/if} diff --git a/src/routes/user/[user]/badges/+page.svelte b/src/routes/user/[user]/badges/+page.svelte index 44dd852a..8fe2dbbb 100644 --- a/src/routes/user/[user]/badges/+page.svelte +++ b/src/routes/user/[user]/badges/+page.svelte @@ -1,4 +1,6 @@ <script lang="ts"> + import { run } from 'svelte/legacy'; + import AWC from './../../../../lib/User/BadgeWall/AWC.svelte'; import { user, type User } from '$lib/Data/AniList/user'; import type { Badge } from '../../../../graphql/$types'; @@ -24,32 +26,34 @@ import { graphql } from '$houdini'; import type { Preferences } from '../../../../graphql/user/$types'; - export let data; + let { data } = $props(); - $: ({ BadgeWallUser } = data); - $: preferences = $BadgeWallUser.fetching + let { BadgeWallUser } = $derived(data); + let preferences = $derived($BadgeWallUser.fetching ? undefined - : ($BadgeWallUser.data?.User.preferences as Preferences); - - $: if (browser && preferences && preferences.badge_wall_css) { - const sanitise = (css: string) => - css - .replace(/\/\*[\s\S]*?\*\//g, '') - .replace(/<\/?[^>]+(>|$)/g, '') - .replace( - /(expression|javascript|vbscript|onerror|onload|onclick|onmouseover|onmouseout|onmouseup|onmousedown|onkeydown|onkeyup|onkeypress|onblur|onfocus|onsubmit|onreset|onselect|onchange|ondblclick):/gi, - '' - ) - .replace(/(behaviour|behavior|moz-binding|content):/gi, '') - .replace(/\s+/g, ' ') - .trim(); - const style = document.createElement('style'); - - style.dataset.badgeWall = 'true'; - style.innerHTML = sanitise(preferences.badge_wall_css); - - document.head.appendChild(style); - } + : ($BadgeWallUser.data?.User.preferences as Preferences)); + + run(() => { + if (browser && preferences && preferences.badge_wall_css) { + const sanitise = (css: string) => + css + .replace(/\/\*[\s\S]*?\*\//g, '') + .replace(/<\/?[^>]+(>|$)/g, '') + .replace( + /(expression|javascript|vbscript|onerror|onload|onclick|onmouseover|onmouseout|onmouseup|onmousedown|onkeydown|onkeyup|onkeypress|onblur|onfocus|onsubmit|onreset|onselect|onchange|ondblclick):/gi, + '' + ) + .replace(/(behaviour|behavior|moz-binding|content):/gi, '') + .replace(/\s+/g, ' ') + .trim(); + const style = document.createElement('style'); + + style.dataset.badgeWall = 'true'; + style.innerHTML = sanitise(preferences.badge_wall_css); + + document.head.appendChild(style); + } + }); const updateBadgeQuery = graphql(` mutation UpdateBadge( @@ -176,26 +180,26 @@ image: string; } - let editMode = false; - let importMode = false; - let error: null | string; - let awcPromise: Promise<Response>; + let editMode = $state(false); + let importMode = $state(false); + let error: null | string = $state(); + let awcPromise: Promise<Response> = $state(); let confirmDelete = 0; - let confirmPrune = 0; - let selectedBadge: IndexedBadge | undefined = undefined; - let loadError: string | null = null; + let confirmPrune = $state(0); + let selectedBadge: IndexedBadge | undefined = $state(undefined); + let loadError: string | null = $state(null); const isId = /^\d+$/.test(data.username); - let importImages: ImportImage[] | undefined = undefined; - let importLinks = false; + let importImages: ImportImage[] | undefined = $state(undefined); + let importLinks = $state(false); let importCategory = ''; - let importReplies = false; + let importReplies = $state(false); let badger: Partial<User>; - let migrateMode = false; - let hideMode = false; + let migrateMode = $state(false); + let hideMode = $state(false); const authorised = authorisedJson.includes($identity.id); - let noticeDismissed = false; + let noticeDismissed = $state(false); - $: categoryFilter = new URLSearchParams($page.url.searchParams).get('category'); + let categoryFilter = $derived(new URLSearchParams($page.url.searchParams).get('category')); type GroupedBadges = { [key: string]: IndexedBadge[] }; @@ -557,7 +561,7 @@ <b>Notice:</b> The Badge Wall overseer system has detected badges containing AI-generated material on your wall. {shadowHiddenCount} of your badges have been shadow hidden. - <p /> + <p></p> You may use the "Un-shadow Hide Badges" button to unhide these badges, from where you will be required to use the hide feature to hide these badges from the public, while allowing them to stay visible to you as the account holder. @@ -568,12 +572,12 @@ material, this includes Badge Wall. If you have collected badges with AI-generated elements, kindly use the hide feature to hide these badges from the public, while allowing them to stay visible to you as the account holder. - <p /> + <p></p> Failure to comply with this request at your earliest convenience will result in the hiding of all badges from your Badge Wall. - <p /> + <p></p> <button - on:click={() => { + onclick={() => { noticeDismissed = true; localStorage.setItem('badgeWallNoticeDismissed', 'true'); @@ -584,11 +588,11 @@ </div> {/if} - <p /> + <p></p> <div class="card"> {#if authorised} - <button on:click={setShadowHide}>Shadow Hide Badges</button> + <button onclick={setShadowHide}>Shadow Hide Badges</button> {/if} {#if isOwner && authorised} @@ -597,7 +601,7 @@ {#if isOwner} <button - on:click={() => { + onclick={() => { selectedBadge = undefined; editMode = !editMode; }} @@ -608,7 +612,7 @@ </button> <span style="margin: 0 0.625rem;">•</span> <button - on:click={() => { + onclick={() => { selectedBadge = undefined; importMode = !importMode; }} @@ -619,7 +623,7 @@ </button> <span style="margin: 0 0.625rem;">•</span> <button - on:click={() => { + onclick={() => { selectedBadge = undefined; migrateMode = !migrateMode; }} @@ -628,7 +632,7 @@ </button> <span style="margin: 0 0.625rem;">•</span> <button - on:click={() => { + onclick={() => { selectedBadge = undefined; hideMode = !hideMode; }} @@ -640,7 +644,7 @@ {#if shadowHidden} <span style="margin: 0 0.625rem;">•</span> - <button on:click={setShadowHide}>Un-shadow Hide Badges</button> + <button onclick={setShadowHide}>Un-shadow Hide Badges</button> {/if} {#if editMode && isOwner} @@ -659,7 +663,7 @@ ) ])} - <p /> + <p></p> {#if error} <p style="color: red;">{error}</p> @@ -709,22 +713,24 @@ header={false} center={false} > - <span slot="title"> - <input - type="text" - placeholder={$locale().user.badges.editMode.category} - name="category" - minlength="1" - maxlength="1000" - size="15" - value={selectedBadge - ? selectedBadge.category === 'Uncategorised' - ? '' - : selectedBadge.category - : ''} - list="categories" - /> - </span> + {#snippet title()} + <span > + <input + type="text" + placeholder={$locale().user.badges.editMode.category} + name="category" + minlength="1" + maxlength="1000" + size="15" + value={selectedBadge + ? selectedBadge.category === 'Uncategorised' + ? '' + : selectedBadge.category + : ''} + list="categories" + /> + </span> + {/snippet} </Dropdown> <span style="float: right;"> <input @@ -736,7 +742,7 @@ <small>Must be full date and time, defaults to now if any fields empty</small> </span> - <p /> + <p></p> <div class="edit-row-2"> <input @@ -762,17 +768,19 @@ header={false} center={false} > - <span slot="title"> - <input - type="text" - placeholder={$locale().user.badges.editMode.designer} - name="designer" - minlength="1" - maxlength="1000" - size="17" - value={selectedBadge ? selectedBadge.designer : ''} - /> - </span> + {#snippet title()} + <span > + <input + type="text" + placeholder={$locale().user.badges.editMode.designer} + name="designer" + minlength="1" + maxlength="1000" + size="17" + value={selectedBadge ? selectedBadge.designer : ''} + /> + </span> + {/snippet} </Dropdown> <Dropdown items={[false, true].map((hidden) => ({ @@ -788,23 +796,25 @@ header={false} center={false} > - <span slot="title"> - <input - type="text" - placeholder="Shown" - name="hidden" - minlength="1" - maxlength="1000" - size="15" - value={selectedBadge - ? selectedBadge.hidden - ? 'Hidden' - : 'Shown' - : 'Shown'} - /> - </span> + {#snippet title()} + <span > + <input + type="text" + placeholder="Shown" + name="hidden" + minlength="1" + maxlength="1000" + size="15" + value={selectedBadge + ? selectedBadge.hidden + ? 'Hidden' + : 'Shown' + : 'Shown'} + /> + </span> + {/snippet} </Dropdown> - <button class="button-lined" on:click={submitBadge} + <button class="button-lined" onclick={submitBadge} >{selectedBadge ? $locale().user.badges.editMode.update : $locale().user.badges.editMode.add}</button @@ -813,7 +823,7 @@ {$locale().user.badges.editMode.or} <button class="button-lined" - on:click={() => { + onclick={() => { if (selectedBadge) removeBadge(selectedBadge); }}>{$locale().user.badges.editMode.delete}</button > @@ -824,7 +834,7 @@ </div> {/if} - <p /> + <p></p> <Badges {ungroupedBadges} @@ -858,7 +868,7 @@ /> {#if authorised} - <button on:click={shadowHideBadge}> + <button onclick={shadowHideBadge}> {#if selectedBadge && selectedBadge.shadow_hidden} Un-shadow {:else} @@ -875,7 +885,7 @@ <Popup fullscreen onLeave={() => (importMode = false)} show={importMode}> {$locale().user.badges.importMode.title} - <p /> + <p></p> <input type="text" @@ -894,7 +904,7 @@ size="20" /> - <p /> + <p></p> <input type="checkbox" id="import_links" name="import_links" bind:checked={importLinks} /> {$locale().user.badges.importMode.importLinks.title} @@ -902,15 +912,15 @@ {$locale().user.badges.importMode.importLinks.hint} </SettingHint> - <p /> + <p></p> <input type="checkbox" id="import_links" name="import_links" bind:checked={importReplies} /> {$locale().user.badges.importMode.importReplies} - <p /> + <p></p> <button - on:click={() => { + onclick={() => { importMode = false; importImages = undefined; }} @@ -918,11 +928,11 @@ > {$locale().user.badges.importMode.cancel} </button> - <button on:click={() => parsePost()} class="button-lined" style="float: right;"> + <button onclick={() => parsePost()} class="button-lined" style="float: right;"> {$locale().user.badges.importMode.fetch} </button> - <p /> + <p></p> <details> <summary>{$locale().user.badges.importMode.dangerous}</summary> @@ -930,7 +940,7 @@ <button class="button-lined no-shadow" data-umami-event="Remove All Badges" - on:click={removeAllBadges} + onclick={removeAllBadges} > {$locale({ values: { @@ -944,7 +954,7 @@ </details> {#if importImages && importImages.length > 0} - <p /> + <p></p> {$locale({ values: { @@ -952,7 +962,7 @@ } }).user.badges.importMode.importConfirm} <button - on:click={() => importBadges()} + onclick={() => importBadges()} class="button-lined no-shadow" data-umami-event="Import Badges" > @@ -969,7 +979,7 @@ <Popup fullscreen onLeave={() => (migrateMode = false)} show={migrateMode}> Migrate Category - <p /> + <p></p> <input type="text" @@ -989,10 +999,10 @@ /> <SettingHint lineBreak>Leave category empty to migrate all to or from uncategorised.</SettingHint> - <p /> + <p></p> <button - on:click={() => { + onclick={() => { importMode = false; importImages = undefined; }} @@ -1000,7 +1010,7 @@ > {$locale().user.badges.importMode.cancel} </button> - <button on:click={() => migrateCategory()} class="button-lined" style="float: right;"> + <button onclick={() => migrateCategory()} class="button-lined" style="float: right;"> Migrate </button> </Popup> @@ -1013,7 +1023,7 @@ versa. </SettingHint> - <p /> + <p></p> <input type="text" @@ -1025,10 +1035,10 @@ /> <SettingHint lineBreak>Leave category field empty to hide all.</SettingHint> - <p /> + <p></p> <button - on:click={() => { + onclick={() => { hideMode = false; importImages = undefined; }} @@ -1036,7 +1046,7 @@ > {$locale().user.badges.importMode.cancel} </button> - <button on:click={() => hideCategory()} class="button-lined" style="float: right;" + <button onclick={() => hideCategory()} class="button-lined" style="float: right;" >Toggle Visibility</button > </Popup> diff --git a/svelte.config.js b/svelte.config.js index 540602c1..496b42d8 100644 --- a/svelte.config.js +++ b/svelte.config.js @@ -1,5 +1,5 @@ import adapter from '@sveltejs/adapter-vercel'; -import { vitePreprocess } from '@sveltejs/kit/vite'; +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; import autoprefixer from 'autoprefixer'; import sveltePreprocess from 'svelte-preprocess'; |