aboutsummaryrefslogtreecommitdiff
path: root/src/routes
diff options
context:
space:
mode:
authorFuwn <[email protected]>2024-10-09 00:41:20 -0700
committerFuwn <[email protected]>2024-10-09 00:41:43 -0700
commit998b63a35256ac985a5a2714dd1ca451af4dfd8a (patch)
tree50796121a9d5ab0330fdc5d7e098bda2860d9726 /src/routes
parentfeat(graphql): add badgeCount field (diff)
downloaddue.moe-998b63a35256ac985a5a2714dd1ca451af4dfd8a.tar.xz
due.moe-998b63a35256ac985a5a2714dd1ca451af4dfd8a.zip
chore(prettier): use spaces instead of tabs
Diffstat (limited to 'src/routes')
-rw-r--r--src/routes/+error.svelte48
-rw-r--r--src/routes/+layout.server.ts20
-rw-r--r--src/routes/+layout.svelte636
-rw-r--r--src/routes/+page.svelte212
-rw-r--r--src/routes/api/animeschedule/oauth/callback/+server.ts24
-rw-r--r--src/routes/api/authentication/log-out/+server.ts18
-rw-r--r--src/routes/api/badges/+server.ts6
-rw-r--r--src/routes/api/birthdays/primary/+server.ts60
-rw-r--r--src/routes/api/birthdays/secondary/+server.ts46
-rw-r--r--src/routes/api/configuration/+server.ts102
-rw-r--r--src/routes/api/events/+server.ts10
-rw-r--r--src/routes/api/events/group/+server.ts2
-rw-r--r--src/routes/api/myanimelist/oauth/callback/+server.ts26
-rw-r--r--src/routes/api/notifications/subscribe/+server.ts30
-rw-r--r--src/routes/api/notifications/unsubscribe/+server.ts30
-rw-r--r--src/routes/api/oauth/callback/+server.ts22
-rw-r--r--src/routes/api/oauth/refresh/+server.ts40
-rw-r--r--src/routes/api/preferences/+server.ts136
-rw-r--r--src/routes/api/preferences/pin/+server.ts44
-rw-r--r--src/routes/api/subsplease/+server.ts30
-rw-r--r--src/routes/api/updates/all-novels/+server.ts34
-rw-r--r--src/routes/api/updates/manga/+server.ts26
-rw-r--r--src/routes/api/updates/novels/+server.ts26
-rw-r--r--src/routes/completed/+page.svelte112
-rw-r--r--src/routes/events/+page.svelte36
-rw-r--r--src/routes/events/group/[group]/+page.server.ts6
-rw-r--r--src/routes/events/group/[group]/+page.svelte114
-rw-r--r--src/routes/events/groups/+page.svelte62
-rw-r--r--src/routes/feeds/activity-notifications/+server.ts86
-rw-r--r--src/routes/girls/+page.svelte234
-rw-r--r--src/routes/girls/[language]/+page.server.ts6
-rw-r--r--src/routes/girls/[language]/+page.svelte56
-rw-r--r--src/routes/hololive/[[stream]]/+page.server.ts6
-rw-r--r--src/routes/hololive/[[stream]]/+page.svelte120
-rw-r--r--src/routes/reader/+page.svelte62
-rw-r--r--src/routes/schedule/+page.svelte96
-rw-r--r--src/routes/settings/+page.svelte160
-rw-r--r--src/routes/tools/+page.svelte106
-rw-r--r--src/routes/tools/[tool]/+page.server.ts6
-rw-r--r--src/routes/tools/[tool]/+page.svelte136
-rw-r--r--src/routes/updates/+page.svelte268
-rw-r--r--src/routes/user/+page.svelte40
-rw-r--r--src/routes/user/[user]/+page.gql30
-rw-r--r--src/routes/user/[user]/+page.svelte1076
-rw-r--r--src/routes/user/[user]/+page.ts24
-rw-r--r--src/routes/user/[user]/badges/+page.gql54
-rw-r--r--src/routes/user/[user]/badges/+page.svelte2040
-rw-r--r--src/routes/welcome/+page.svelte2
48 files changed, 3283 insertions, 3283 deletions
diff --git a/src/routes/+error.svelte b/src/routes/+error.svelte
index 9e1bca13..f822d521 100644
--- a/src/routes/+error.svelte
+++ b/src/routes/+error.svelte
@@ -1,31 +1,31 @@
<script lang="ts">
- import { page } from '$app/stores';
- import { closest } from '$lib/Error/path';
- import Popup from '$lib/Layout/Popup.svelte';
+ import { page } from '$app/stores';
+ import { closest } from '$lib/Error/path';
+ import Popup from '$lib/Layout/Popup.svelte';
- $: suggestion = closest($page.url.pathname.replace('/', ''), [
- 'birthdays',
- 'completed',
- 'schedule',
- 'hololive',
- 'settings',
- 'tools',
- 'updates',
- 'user',
- 'wrapped'
- ]);
+ $: suggestion = closest($page.url.pathname.replace('/', ''), [
+ 'birthdays',
+ 'completed',
+ 'schedule',
+ 'hololive',
+ 'settings',
+ 'tools',
+ 'updates',
+ 'user',
+ 'wrapped'
+ ]);
</script>
<Popup>
- <p style="text-align: center;">
- <a href={$page.url.pathname}>{$page.url.pathname}</a> not found
- </p>
+ <p style="text-align: center;">
+ <a href={$page.url.pathname}>{$page.url.pathname}</a> not found
+ </p>
- <blockquote style="margin: 0 0 0 1.5rem;">
- Did you mean "<a
- href={suggestion}
- style={suggestion === '...' ? 'pointer-events: none; color: inherit;' : ''}
- >{suggestion.charAt(0).toUpperCase() + suggestion.slice(1)}</a
- >"?
- </blockquote>
+ <blockquote style="margin: 0 0 0 1.5rem;">
+ Did you mean "<a
+ href={suggestion}
+ style={suggestion === '...' ? 'pointer-events: none; color: inherit;' : ''}
+ >{suggestion.charAt(0).toUpperCase() + suggestion.slice(1)}</a
+ >"?
+ </blockquote>
</Popup>
diff --git a/src/routes/+layout.server.ts b/src/routes/+layout.server.ts
index d2fdce1b..1b7bf69a 100644
--- a/src/routes/+layout.server.ts
+++ b/src/routes/+layout.server.ts
@@ -1,14 +1,14 @@
export const load = ({ locals, url, cookies }) => {
- const { user } = locals;
+ const { user } = locals;
- if (cookies.get('logout') === '1') {
- cookies.delete('user', { path: '/' });
- cookies.delete('logout', { path: '/' });
- }
+ if (cookies.get('logout') === '1') {
+ cookies.delete('user', { path: '/' });
+ cookies.delete('logout', { path: '/' });
+ }
- return {
- user,
- url: url.pathname,
- commit: process.env.VERCEL_GIT_COMMIT_SHA ?? 'ffffffffffffffffffffffffffffffffffffffff'
- };
+ return {
+ user,
+ url: url.pathname,
+ commit: process.env.VERCEL_GIT_COMMIT_SHA ?? 'ffffffffffffffffffffffffffffffffffffffff'
+ };
};
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte
index d9b257e9..6d7fe757 100644
--- a/src/routes/+layout.svelte
+++ b/src/routes/+layout.svelte
@@ -1,329 +1,329 @@
<script lang="ts">
- 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';
- import { onDestroy, onMount } from 'svelte';
- import userIdentity from '$stores/identity';
- import settings from '$stores/settings';
- import { browser } from '$app/environment';
- import HeadTitle from '$lib/Home/HeadTitle.svelte';
- import '../app.css';
- import { readable, type Readable } from 'svelte/store';
- import { navigating } from '$app/stores';
- import Notifications from 'svelte-notifications';
- import * as EventNotification from '$lib/Notification/Notification.svelte';
- import Root from '$lib/Home/Root.svelte';
- import root from '$lib/Utility/root';
- import { addMessages, init, locale as i18nLocale, locales } from 'svelte-i18n';
- import english from '$lib/Locale/english';
- import japanese from '$lib/Locale/japanese';
- import type { LocaleDictionary } from '$lib/Locale/layout';
- import locale from '$stores/locale';
- import Skeleton from '$lib/Loading/Skeleton.svelte';
- import subsPlease from '$stores/subsPlease';
- import Dropdown from '$lib/Layout/Dropdown.svelte';
- import { injectSpeedInsights } from '@vercel/speed-insights/sveltekit';
- import subtitles from '$lib/Data/Static/subtitles.json';
- import settingsSyncPulled from '$stores/settingsSyncPulled';
- import settingsSyncTimes from '$stores/settingsSyncTimes';
- import Announcement from '$lib/Announcement.svelte';
- import Message from '$lib/Loading/Message.svelte';
- import { requestNotifications } from '$lib/Utility/notifications';
- import { database as userDatabase } from '$lib/Database/IDB/user';
-
- injectSpeedInsights();
-
- export let data;
-
- let isHeaderVisible = true;
- let previousScrollPosition = 0;
- let notificationInterval: NodeJS.Timeout | undefined = undefined;
-
- addMessages('en', english as unknown as LocaleDictionary);
- addMessages('ja', japanese as unknown as LocaleDictionary);
- init({ fallbackLocale: 'en', initialLocale: $settings.displayLanguage });
-
- $: i18nLocale.set($settings.displayLanguage);
-
- const navigationOrder = ['/', '/completed', '/schedule', '/updates', '/tools', '/settings'];
- const previousPage: Readable<string | null> = readable(null, (set) => {
- const unsubscribe = navigating.subscribe(($navigating) => {
- if ($navigating && $navigating.from) set($navigating.from.url.pathname as unknown as null);
- });
-
- return () => unsubscribe();
- });
-
- $: way = 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;
-
- isHeaderVisible =
- currentScrollPosition <= 100 || currentScrollPosition < previousScrollPosition;
- previousScrollPosition = currentScrollPosition;
- };
-
- onMount(async () => {
- if (browser) {
- if (localStorage.getItem('redirect')) {
- window.location.href = localStorage.getItem('redirect') ?? '/';
-
- localStorage.removeItem('redirect');
- }
-
- window.addEventListener('scroll', handleScroll);
-
- if (localStorage.getItem('commit') !== data.commit) {
- localStorage.removeItem('identity');
- localStorage.removeItem('anime');
- localStorage.removeItem('manga');
- localStorage.removeItem('lastPruneTimes');
- localStorage.setItem('commit', data.commit);
- }
- }
-
- settings.get();
-
- if (data.user !== undefined && $userIdentity.id === -2)
- getUserIdentity(data.user).then((h) => userIdentity.set(h));
-
- if ($settings.settingsSync && $userIdentity.id !== -2)
- fetch(root(`/api/configuration?id=${$userIdentity.id}`)).then((response) => {
- if (response.ok)
- response.json().then((data) => {
- if (data && data.configuration) {
- console.log('Pulled remote configuration');
- settings.set(data.configuration);
- settingsSyncPulled.set(true);
- settingsSyncTimes.set({
- lastPull: new Date(),
- lastPush: new Date(data.updated_at + 'Z')
- });
- }
- });
- });
-
- if (!(await userDatabase.users.get($userIdentity.id)))
- userDatabase.users.put({
- id: $userIdentity.id,
- user: data.user,
- lastNotificationID: null
- });
-
- if ($settings.displayAniListNotifications && data.user !== undefined)
- if ('Notification' in window && navigator.serviceWorker) requestNotifications();
- });
-
- onDestroy(() => {
- if (browser) window.removeEventListener('scroll', handleScroll);
-
- if (notificationInterval) clearInterval(notificationInterval);
- });
-
- $: {
- if ((data.url === '/' || data.url === '/completed' || data.url === '/schedule') && !$subsPlease)
- fetch(root(`/api/subsplease?tz=${Intl.DateTimeFormat().resolvedOptions().timeZone}`))
- .then((r) => r.json())
- .then((r) => {
- for (const day in subtitles) {
- if (!r.schedule[day]) r.schedule[day] = [];
-
- (subtitles[day as keyof typeof subtitles] as SubsPleaseEpisode[]).forEach((episode) => {
- r.schedule[day].push({
- title: episode.title,
- page: episode.page || '',
- image_url: episode.image_url || '',
- time: episode.time
- });
- });
- }
-
- subsPlease.set(r);
- });
- }
+ 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';
+ import { onDestroy, onMount } from 'svelte';
+ import userIdentity from '$stores/identity';
+ import settings from '$stores/settings';
+ import { browser } from '$app/environment';
+ import HeadTitle from '$lib/Home/HeadTitle.svelte';
+ import '../app.css';
+ import { readable, type Readable } from 'svelte/store';
+ import { navigating } from '$app/stores';
+ import Notifications from 'svelte-notifications';
+ import * as EventNotification from '$lib/Notification/Notification.svelte';
+ import Root from '$lib/Home/Root.svelte';
+ import root from '$lib/Utility/root';
+ import { addMessages, init, locale as i18nLocale, locales } from 'svelte-i18n';
+ import english from '$lib/Locale/english';
+ import japanese from '$lib/Locale/japanese';
+ import type { LocaleDictionary } from '$lib/Locale/layout';
+ import locale from '$stores/locale';
+ import Skeleton from '$lib/Loading/Skeleton.svelte';
+ import subsPlease from '$stores/subsPlease';
+ import Dropdown from '$lib/Layout/Dropdown.svelte';
+ import { injectSpeedInsights } from '@vercel/speed-insights/sveltekit';
+ import subtitles from '$lib/Data/Static/subtitles.json';
+ import settingsSyncPulled from '$stores/settingsSyncPulled';
+ import settingsSyncTimes from '$stores/settingsSyncTimes';
+ import Announcement from '$lib/Announcement.svelte';
+ import Message from '$lib/Loading/Message.svelte';
+ import { requestNotifications } from '$lib/Utility/notifications';
+ import { database as userDatabase } from '$lib/Database/IDB/user';
+
+ injectSpeedInsights();
+
+ export let data;
+
+ let isHeaderVisible = true;
+ let previousScrollPosition = 0;
+ let notificationInterval: NodeJS.Timeout | undefined = undefined;
+
+ addMessages('en', english as unknown as LocaleDictionary);
+ addMessages('ja', japanese as unknown as LocaleDictionary);
+ init({ fallbackLocale: 'en', initialLocale: $settings.displayLanguage });
+
+ $: i18nLocale.set($settings.displayLanguage);
+
+ const navigationOrder = ['/', '/completed', '/schedule', '/updates', '/tools', '/settings'];
+ const previousPage: Readable<string | null> = readable(null, (set) => {
+ const unsubscribe = navigating.subscribe(($navigating) => {
+ if ($navigating && $navigating.from) set($navigating.from.url.pathname as unknown as null);
+ });
+
+ return () => unsubscribe();
+ });
+
+ $: way = 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;
+
+ isHeaderVisible =
+ currentScrollPosition <= 100 || currentScrollPosition < previousScrollPosition;
+ previousScrollPosition = currentScrollPosition;
+ };
+
+ onMount(async () => {
+ if (browser) {
+ if (localStorage.getItem('redirect')) {
+ window.location.href = localStorage.getItem('redirect') ?? '/';
+
+ localStorage.removeItem('redirect');
+ }
+
+ window.addEventListener('scroll', handleScroll);
+
+ if (localStorage.getItem('commit') !== data.commit) {
+ localStorage.removeItem('identity');
+ localStorage.removeItem('anime');
+ localStorage.removeItem('manga');
+ localStorage.removeItem('lastPruneTimes');
+ localStorage.setItem('commit', data.commit);
+ }
+ }
+
+ settings.get();
+
+ if (data.user !== undefined && $userIdentity.id === -2)
+ getUserIdentity(data.user).then((h) => userIdentity.set(h));
+
+ if ($settings.settingsSync && $userIdentity.id !== -2)
+ fetch(root(`/api/configuration?id=${$userIdentity.id}`)).then((response) => {
+ if (response.ok)
+ response.json().then((data) => {
+ if (data && data.configuration) {
+ console.log('Pulled remote configuration');
+ settings.set(data.configuration);
+ settingsSyncPulled.set(true);
+ settingsSyncTimes.set({
+ lastPull: new Date(),
+ lastPush: new Date(data.updated_at + 'Z')
+ });
+ }
+ });
+ });
+
+ if (!(await userDatabase.users.get($userIdentity.id)))
+ userDatabase.users.put({
+ id: $userIdentity.id,
+ user: data.user,
+ lastNotificationID: null
+ });
+
+ if ($settings.displayAniListNotifications && data.user !== undefined)
+ if ('Notification' in window && navigator.serviceWorker) requestNotifications();
+ });
+
+ onDestroy(() => {
+ if (browser) window.removeEventListener('scroll', handleScroll);
+
+ if (notificationInterval) clearInterval(notificationInterval);
+ });
+
+ $: {
+ if ((data.url === '/' || data.url === '/completed' || data.url === '/schedule') && !$subsPlease)
+ fetch(root(`/api/subsplease?tz=${Intl.DateTimeFormat().resolvedOptions().timeZone}`))
+ .then((r) => r.json())
+ .then((r) => {
+ for (const day in subtitles) {
+ if (!r.schedule[day]) r.schedule[day] = [];
+
+ (subtitles[day as keyof typeof subtitles] as SubsPleaseEpisode[]).forEach((episode) => {
+ r.schedule[day].push({
+ title: episode.title,
+ page: episode.page || '',
+ image_url: episode.image_url || '',
+ time: episode.time
+ });
+ });
+ }
+
+ subsPlease.set(r);
+ });
+ }
</script>
<HeadTitle />
{#if !$locales.includes('en') || !$locale().navigation}
- <Message />
-
- {#if data.url === '/settings'}
- <Skeleton grid={true} count={1} height="10vh" />
- <Skeleton grid={true} count={1} height="30vh" />
- <Skeleton grid={true} count={1} height="30vh" />
- {:else}
- <Skeleton grid={true} count={1} height="10vh" />
- <Skeleton grid={true} count={1} height="80vh" />
- {/if}
+ <Message />
+
+ {#if data.url === '/settings'}
+ <Skeleton grid={true} count={1} height="10vh" />
+ <Skeleton grid={true} count={1} height="30vh" />
+ <Skeleton grid={true} count={1} height="30vh" />
+ {:else}
+ <Skeleton grid={true} count={1} height="10vh" />
+ <Skeleton grid={true} count={1} height="80vh" />
+ {/if}
{:else}
- <Announcement />
-
- <div class="container">
- <div class="card card-centered header" class:header-hidden={!isHeaderVisible}>
- <div>
- <a href={root('/')} class="header-item">{$locale().navigation.home}</a><a
- href={root('/completed')}
- class="header-item"
- >
- {$locale().navigation.completed}
- </a>
- <Dropdown
- items={[
- { name: $locale().navigation.subtitleSchedule, url: root('/schedule') },
- { name: $locale().navigation.hololive, url: root('/hololive') },
- { name: $locale().tools.tool.characterBirthdays.short, url: root('/birthdays') },
- { name: $locale().navigation.newReleases, url: root('/updates') }
- ]}
- header={false}
- >
- <span slot="title" class="header-item">{$locale().navigation.schedule}</span>
- </Dropdown>
- <a href={root('/tools')} class="header-item">{$locale().navigation.tools}</a>
- <a href={root('/settings')} class="header-item">{$locale().navigation.settings}</a>
-
- <span class="header-item opaque separator">•</span>
-
- {#if data.user}
- <Dropdown
- items={[
- { name: $locale().navigation.myProfile, url: root(`/user/${$userIdentity.name}`) },
- {
- name: $locale().navigation.myBadgeWall,
- url: root(`/user/${$userIdentity.name}/badges`)
- },
- {
- name: $locale().navigation.logOut,
- url: '#',
- preventDefault: true,
- onClick: () => {
- localStorage.removeItem('identity');
- localStorage.removeItem('commit');
-
- document.cookie = 'user=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
-
- window.location.href = root('/api/authentication/log-out');
- }
- }
- ]}
- header={false}
- >
- <span slot="title" class="header-item">
- {$locale().navigation.profile}
- </span>
- </Dropdown>
- {/if}
-
- {#if data.user === undefined}
- <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={() => {
- localStorage.setItem(
- 'redirect',
- window.location.origin + window.location.pathname + window.location.search
- );
- }}
- >
- {$locale().navigation.logIn}
- </a>
- {:else if data.user}
- <a href={root(`/user/${$userIdentity.name}`)} class="header-item">
- <img class="avatar" src={$userIdentity.avatar} alt="Avatar" />
- </a>
- {/if}
- </div>
- </div>
-
- <p />
-
- <Notifications item={EventNotification} zIndex={5000}>
- <Root {data} {way}>
- {#if $userIdentity.id !== -1}
- <slot />
- {:else if data.url === '/settings'}
- <Skeleton grid={true} count={1} height="10vh" />
- <Skeleton grid={true} count={1} height="30vh" />
- <Skeleton grid={true} count={1} height="30vh" />
- {:else}
- <Skeleton grid={true} count={1} height="10vh" />
- <Skeleton grid={true} count={1} height="80vh" />
- {/if}
- </Root>
- </Notifications>
- </div>
+ <Announcement />
+
+ <div class="container">
+ <div class="card card-centered header" class:header-hidden={!isHeaderVisible}>
+ <div>
+ <a href={root('/')} class="header-item">{$locale().navigation.home}</a><a
+ href={root('/completed')}
+ class="header-item"
+ >
+ {$locale().navigation.completed}
+ </a>
+ <Dropdown
+ items={[
+ { name: $locale().navigation.subtitleSchedule, url: root('/schedule') },
+ { name: $locale().navigation.hololive, url: root('/hololive') },
+ { name: $locale().tools.tool.characterBirthdays.short, url: root('/birthdays') },
+ { name: $locale().navigation.newReleases, url: root('/updates') }
+ ]}
+ header={false}
+ >
+ <span slot="title" class="header-item">{$locale().navigation.schedule}</span>
+ </Dropdown>
+ <a href={root('/tools')} class="header-item">{$locale().navigation.tools}</a>
+ <a href={root('/settings')} class="header-item">{$locale().navigation.settings}</a>
+
+ <span class="header-item opaque separator">•</span>
+
+ {#if data.user}
+ <Dropdown
+ items={[
+ { name: $locale().navigation.myProfile, url: root(`/user/${$userIdentity.name}`) },
+ {
+ name: $locale().navigation.myBadgeWall,
+ url: root(`/user/${$userIdentity.name}/badges`)
+ },
+ {
+ name: $locale().navigation.logOut,
+ url: '#',
+ preventDefault: true,
+ onClick: () => {
+ localStorage.removeItem('identity');
+ localStorage.removeItem('commit');
+
+ document.cookie = 'user=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
+
+ window.location.href = root('/api/authentication/log-out');
+ }
+ }
+ ]}
+ header={false}
+ >
+ <span slot="title" class="header-item">
+ {$locale().navigation.profile}
+ </span>
+ </Dropdown>
+ {/if}
+
+ {#if data.user === undefined}
+ <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={() => {
+ localStorage.setItem(
+ 'redirect',
+ window.location.origin + window.location.pathname + window.location.search
+ );
+ }}
+ >
+ {$locale().navigation.logIn}
+ </a>
+ {:else if data.user}
+ <a href={root(`/user/${$userIdentity.name}`)} class="header-item">
+ <img class="avatar" src={$userIdentity.avatar} alt="Avatar" />
+ </a>
+ {/if}
+ </div>
+ </div>
+
+ <p />
+
+ <Notifications item={EventNotification} zIndex={5000}>
+ <Root {data} {way}>
+ {#if $userIdentity.id !== -1}
+ <slot />
+ {:else if data.url === '/settings'}
+ <Skeleton grid={true} count={1} height="10vh" />
+ <Skeleton grid={true} count={1} height="30vh" />
+ <Skeleton grid={true} count={1} height="30vh" />
+ {:else}
+ <Skeleton grid={true} count={1} height="10vh" />
+ <Skeleton grid={true} count={1} height="80vh" />
+ {/if}
+ </Root>
+ </Notifications>
+ </div>
{/if}
<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-size: 1.05em;
- font-weight: 600;
- padding: 0.8rem 0.4rem;
- z-index: 4;
- position: sticky;
- top: 1.25rem;
- transition: transform 0.3s ease;
- }
-
- .header-hidden {
- transform: translateY(-150%);
- }
-
- :global(html.light-theme) {
- filter: invert(0);
- }
-
- :global(html.light-theme img) {
- filter: invert(0);
- }
-
- .container {
- height: 100%;
- display: flex;
- flex-direction: column;
- }
-
- :global(a) {
- text-decoration: none;
- transition: all 0.15s ease-in-out;
- }
-
- :global(a:hover) {
- text-decoration: underline;
- }
-
- .header-item {
- margin: 0 0.5rem;
- color: var(--base06);
- white-space: nowrap;
- }
-
- .header-item:hover {
- text-decoration: none;
- }
-
- .header-item:active {
- outline: none;
- }
-
- .avatar {
- width: 2em;
- 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);
- }
-
- .separator {
- color: var(--base04);
- }
+ .header {
+ 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;
+ z-index: 4;
+ position: sticky;
+ top: 1.25rem;
+ transition: transform 0.3s ease;
+ }
+
+ .header-hidden {
+ transform: translateY(-150%);
+ }
+
+ :global(html.light-theme) {
+ filter: invert(0);
+ }
+
+ :global(html.light-theme img) {
+ filter: invert(0);
+ }
+
+ .container {
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ }
+
+ :global(a) {
+ text-decoration: none;
+ transition: all 0.15s ease-in-out;
+ }
+
+ :global(a:hover) {
+ text-decoration: underline;
+ }
+
+ .header-item {
+ margin: 0 0.5rem;
+ color: var(--base06);
+ white-space: nowrap;
+ }
+
+ .header-item:hover {
+ text-decoration: none;
+ }
+
+ .header-item:active {
+ outline: none;
+ }
+
+ .avatar {
+ width: 2em;
+ 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);
+ }
+
+ .separator {
+ color: var(--base04);
+ }
</style>
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte
index 3f130f96..653c3836 100644
--- a/src/routes/+page.svelte
+++ b/src/routes/+page.svelte
@@ -1,25 +1,25 @@
<script lang="ts">
- import { onDestroy, onMount } from 'svelte';
- import MangaListTemplate from '$lib/List/Manga/MangaListTemplate.svelte';
- import UpcomingAnimeList from '$lib/List/Anime/UpcomingAnimeList.svelte';
- import userIdentity from '$stores/identity.js';
- import settings from '$stores/settings';
- import ListTitle from '$lib/List/ListTitle.svelte';
- import HeadTitle from '$lib/Home/HeadTitle.svelte';
- import LastActivity from '$lib/Home/LastActivity.svelte';
- import { createHeightObserver } from '$lib/Utility/html.js';
- import Skeleton from '$lib/Loading/Skeleton.svelte';
- import locale from '$stores/locale.js';
- import Landing from '$lib/Landing.svelte';
- import IndexColumn from '$lib/List/Anime/DueIndexColumn.svelte';
-
- export let data;
-
- let heightObserver: NodeJS.Timeout;
-
- onMount(() => (heightObserver = setInterval(() => createHeightObserver(), 0)));
-
- onDestroy(() => clearInterval(heightObserver));
+ import { onDestroy, onMount } from 'svelte';
+ import MangaListTemplate from '$lib/List/Manga/MangaListTemplate.svelte';
+ import UpcomingAnimeList from '$lib/List/Anime/UpcomingAnimeList.svelte';
+ import userIdentity from '$stores/identity.js';
+ import settings from '$stores/settings';
+ import ListTitle from '$lib/List/ListTitle.svelte';
+ import HeadTitle from '$lib/Home/HeadTitle.svelte';
+ import LastActivity from '$lib/Home/LastActivity.svelte';
+ import { createHeightObserver } from '$lib/Utility/html.js';
+ import Skeleton from '$lib/Loading/Skeleton.svelte';
+ import locale from '$stores/locale.js';
+ import Landing from '$lib/Landing.svelte';
+ import IndexColumn from '$lib/List/Anime/DueIndexColumn.svelte';
+
+ export let data;
+
+ let heightObserver: NodeJS.Timeout;
+
+ onMount(() => (heightObserver = setInterval(() => createHeightObserver(), 0)));
+
+ onDestroy(() => clearInterval(heightObserver));
</script>
<HeadTitle />
@@ -27,97 +27,97 @@
<LastActivity user={data.user} />
{#if data.user === undefined}
- <div class="card">Please log in to view due media.</div>
+ <div class="card">Please log in to view due media.</div>
- <p />
+ <p />
- <Landing />
+ <Landing />
{:else}
- <div
- class="grid-container"
- style={`
+ <div
+ class="grid-container"
+ style={`
grid-template-columns: ${
- [!$settings.disableUpcomingAnime, !$settings.disableAnime, !$settings.disableManga]
- .map(Number)
- .reduce((a, b) => a + b) > 1
- ? '1fr 1fr'
- : '1fr'
- }
+ [!$settings.disableUpcomingAnime, !$settings.disableAnime, !$settings.disableManga]
+ .map(Number)
+ .reduce((a, b) => a + b) > 1
+ ? '1fr 1fr'
+ : '1fr'
+ }
`}
- >
- <div class="left-column">
- {#if !$settings.disableUpcomingAnime}
- <details open={!$settings.displayUpcomingAnimeCollapsed} class="list list-upcoming">
- {#if $userIdentity.id !== -2}
- <UpcomingAnimeList user={data.user} />
- {:else}
- <ListTitle title={$locale().lists.upcoming.episodes} />
-
- <Skeleton card={false} count={5} height="0.9rem" list />
- {/if}
- </details>
- {/if}
-
- {#if !$settings.disableAnime && !$settings.disableManga}
- <IndexColumn user={data.user} userIdentity={$userIdentity} />
- {/if}
- </div>
-
- <div class="right-column">
- {#if !$settings.disableAnime && $settings.disableManga}
- <IndexColumn user={data.user} userIdentity={$userIdentity} />
- {/if}
-
- {#if !$settings.disableManga}
- <details open={!$settings.displayMangaCollapsed} class="list list-manga">
- {#if $userIdentity.id !== -2}
- <MangaListTemplate
- user={data.user}
- displayUnresolved={$settings.displayUnresolved}
- due={true}
- />
- {:else}
- <ListTitle title={$locale().lists.due.mangaAndLightNovels} />
-
- <Skeleton card={false} count={5} height="0.9rem" list />
- {/if}
- </details>
- {/if}
- </div>
-
- {#if $settings.disableUpcomingAnime && $settings.disableAnime && $settings.disableManga}
- <video src="https://video.twimg.com/tweet_video/Do_eDPnX0AAKV9f.mp4" autoplay loop>
- <track kind="captions" />
- </video>
- {/if}
- </div>
+ >
+ <div class="left-column">
+ {#if !$settings.disableUpcomingAnime}
+ <details open={!$settings.displayUpcomingAnimeCollapsed} class="list list-upcoming">
+ {#if $userIdentity.id !== -2}
+ <UpcomingAnimeList user={data.user} />
+ {:else}
+ <ListTitle title={$locale().lists.upcoming.episodes} />
+
+ <Skeleton card={false} count={5} height="0.9rem" list />
+ {/if}
+ </details>
+ {/if}
+
+ {#if !$settings.disableAnime && !$settings.disableManga}
+ <IndexColumn user={data.user} userIdentity={$userIdentity} />
+ {/if}
+ </div>
+
+ <div class="right-column">
+ {#if !$settings.disableAnime && $settings.disableManga}
+ <IndexColumn user={data.user} userIdentity={$userIdentity} />
+ {/if}
+
+ {#if !$settings.disableManga}
+ <details open={!$settings.displayMangaCollapsed} class="list list-manga">
+ {#if $userIdentity.id !== -2}
+ <MangaListTemplate
+ user={data.user}
+ displayUnresolved={$settings.displayUnresolved}
+ due={true}
+ />
+ {:else}
+ <ListTitle title={$locale().lists.due.mangaAndLightNovels} />
+
+ <Skeleton card={false} count={5} height="0.9rem" list />
+ {/if}
+ </details>
+ {/if}
+ </div>
+
+ {#if $settings.disableUpcomingAnime && $settings.disableAnime && $settings.disableManga}
+ <video src="https://video.twimg.com/tweet_video/Do_eDPnX0AAKV9f.mp4" autoplay loop>
+ <track kind="captions" />
+ </video>
+ {/if}
+ </div>
{/if}
<style>
- .grid-container {
- display: grid;
- gap: 1rem;
- }
-
- .left-column {
- display: grid;
- gap: 1rem;
- align-content: start;
- }
-
- .right-column {
- align-self: start;
- }
-
- .list {
- overflow-y: auto;
- break-inside: avoid;
- page-break-inside: avoid;
- }
-
- @media (max-width: 800px) {
- .grid-container {
- grid-template-columns: 1fr !important;
- }
- }
+ .grid-container {
+ display: grid;
+ gap: 1rem;
+ }
+
+ .left-column {
+ display: grid;
+ gap: 1rem;
+ align-content: start;
+ }
+
+ .right-column {
+ align-self: start;
+ }
+
+ .list {
+ overflow-y: auto;
+ break-inside: avoid;
+ page-break-inside: avoid;
+ }
+
+ @media (max-width: 800px) {
+ .grid-container {
+ grid-template-columns: 1fr !important;
+ }
+ }
</style>
diff --git a/src/routes/api/animeschedule/oauth/callback/+server.ts b/src/routes/api/animeschedule/oauth/callback/+server.ts
index 69a48600..2b96ab81 100644
--- a/src/routes/api/animeschedule/oauth/callback/+server.ts
+++ b/src/routes/api/animeschedule/oauth/callback/+server.ts
@@ -3,15 +3,15 @@ import { env } from '$env/dynamic/private';
import { env as env2 } from '$env/dynamic/public';
export const GET = async ({ url, cookies }) =>
- callback({
- url,
- cookies,
- cookie: 'animeschedule',
- authorise: 'https://animeschedule.net/api/v3/oauth2/token',
- redirect: '/settings',
- client: {
- id: env2.PUBLIC_ANIMESCHEDULE_CLIENT_ID,
- secret: env.ANIMESCHEDULE_CLIENT_SECRET,
- redirectURI: env2.PUBLIC_ANIMESCHEDULE_REDIRECT_URI
- }
- });
+ callback({
+ url,
+ cookies,
+ cookie: 'animeschedule',
+ authorise: 'https://animeschedule.net/api/v3/oauth2/token',
+ redirect: '/settings',
+ client: {
+ id: env2.PUBLIC_ANIMESCHEDULE_CLIENT_ID,
+ secret: env.ANIMESCHEDULE_CLIENT_SECRET,
+ redirectURI: env2.PUBLIC_ANIMESCHEDULE_REDIRECT_URI
+ }
+ });
diff --git a/src/routes/api/authentication/log-out/+server.ts b/src/routes/api/authentication/log-out/+server.ts
index e3ce347a..305c846f 100644
--- a/src/routes/api/authentication/log-out/+server.ts
+++ b/src/routes/api/authentication/log-out/+server.ts
@@ -2,14 +2,14 @@ import root from '$lib/Utility/root.js';
import { redirect } from '@sveltejs/kit';
export const GET = ({ cookies }) => {
- cookies.delete('user', { path: '/' });
- cookies.set('logout', '1', {
- path: '/',
- maxAge: 60 * 60 * 24 * 7,
- httpOnly: false,
- sameSite: 'lax',
- secure: false
- });
+ cookies.delete('user', { path: '/' });
+ cookies.set('logout', '1', {
+ path: '/',
+ maxAge: 60 * 60 * 24 * 7,
+ httpOnly: false,
+ sameSite: 'lax',
+ secure: false
+ });
- throw redirect(303, root('/'));
+ throw redirect(303, root('/'));
};
diff --git a/src/routes/api/badges/+server.ts b/src/routes/api/badges/+server.ts
index e7a26320..7c62285d 100644
--- a/src/routes/api/badges/+server.ts
+++ b/src/routes/api/badges/+server.ts
@@ -100,9 +100,9 @@ export const PUT = async ({ cookies, url, request }) => {
allBadges
.filter((badge) => badge.category === (url.searchParams.get('category') || ''))
.filter((badge) => badge.hidden).length >
- allBadges.filter(
- (badge) => badge.category === (url.searchParams.get('category') || '')
- ).length /
+ allBadges.filter(
+ (badge) => badge.category === (url.searchParams.get('category') || '')
+ ).length /
2
? false
: true
diff --git a/src/routes/api/birthdays/primary/+server.ts b/src/routes/api/birthdays/primary/+server.ts
index 0b92a11e..1e480283 100644
--- a/src/routes/api/birthdays/primary/+server.ts
+++ b/src/routes/api/birthdays/primary/+server.ts
@@ -1,40 +1,40 @@
import { JSDOM } from 'jsdom';
export const GET = async ({ url }: { url: URL }) => {
- const document = new JSDOM(
- await (
- await fetch(
- `https://www.anisearch.com/character/birthdays?month=${url.searchParams.get('month')}`
- )
- ).text()
- ).window.document;
- const section = document.querySelector(`#day-${url.searchParams.get('day')}`);
+ const document = new JSDOM(
+ await (
+ await fetch(
+ `https://www.anisearch.com/character/birthdays?month=${url.searchParams.get('month')}`
+ )
+ ).text()
+ ).window.document;
+ const section = document.querySelector(`#day-${url.searchParams.get('day')}`);
- if (!section) return Response.json([]);
+ if (!section) return Response.json([]);
- const ul = section.querySelector('ul.covers.simple');
+ const ul = section.querySelector('ul.covers.simple');
- if (!ul) return Response.json([]);
+ if (!ul) return Response.json([]);
- return Response.json(
- Array.from(ul.querySelectorAll('li')).map((li) => {
- const anchor = li.querySelector('a');
- const title = li.querySelector('.title');
+ return Response.json(
+ Array.from(ul.querySelectorAll('li')).map((li) => {
+ const anchor = li.querySelector('a');
+ const title = li.querySelector('.title');
- if (!anchor || !title) return { image: '', title: '' };
+ if (!anchor || !title) return { image: '', title: '' };
- return {
- image: anchor.getAttribute('data-bg')
- ? `https://cdn.anisearch.com/images/${anchor.getAttribute('data-bg')}`
- : null,
- name: title.textContent?.trim()
- };
- }),
- {
- headers: {
- 'Cache-Control': 'max-age=10800, s-maxage=10800',
- 'Access-Control-Allow-Origin': 'https://due.moe'
- }
- }
- );
+ return {
+ image: anchor.getAttribute('data-bg')
+ ? `https://cdn.anisearch.com/images/${anchor.getAttribute('data-bg')}`
+ : null,
+ name: title.textContent?.trim()
+ };
+ }),
+ {
+ headers: {
+ 'Cache-Control': 'max-age=10800, s-maxage=10800',
+ 'Access-Control-Allow-Origin': 'https://due.moe'
+ }
+ }
+ );
};
diff --git a/src/routes/api/birthdays/secondary/+server.ts b/src/routes/api/birthdays/secondary/+server.ts
index ae9178a0..f28e2e71 100644
--- a/src/routes/api/birthdays/secondary/+server.ts
+++ b/src/routes/api/birthdays/secondary/+server.ts
@@ -1,25 +1,25 @@
export const GET = async ({ url }: { url: URL }) => {
- return Response.json(
- await (
- await fetch(
- `https://www.animecharactersdatabase.com/api_series_characters.php?month=${url.searchParams.get(
- 'month'
- )}&day=${url.searchParams.get('day')}`,
- {
- headers: {
- Accept: 'application/json',
- 'Content-Type': 'application/json',
- 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:120.0) Gecko/20100101 Firefox/120.0',
- 'Cache-Control': 'max-age=10, s-maxage=10'
- }
- }
- )
- ).json(),
- {
- headers: {
- 'Cache-Control': 'max-age=10800, s-maxage=10800',
- 'Access-Control-Allow-Origin': 'https://due.moe'
- }
- }
- );
+ return Response.json(
+ await (
+ await fetch(
+ `https://www.animecharactersdatabase.com/api_series_characters.php?month=${url.searchParams.get(
+ 'month'
+ )}&day=${url.searchParams.get('day')}`,
+ {
+ headers: {
+ Accept: 'application/json',
+ 'Content-Type': 'application/json',
+ 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:120.0) Gecko/20100101 Firefox/120.0',
+ 'Cache-Control': 'max-age=10, s-maxage=10'
+ }
+ }
+ )
+ ).json(),
+ {
+ headers: {
+ 'Cache-Control': 'max-age=10800, s-maxage=10800',
+ 'Access-Control-Allow-Origin': 'https://due.moe'
+ }
+ }
+ );
};
diff --git a/src/routes/api/configuration/+server.ts b/src/routes/api/configuration/+server.ts
index 65fd8d8b..41a70f7b 100644
--- a/src/routes/api/configuration/+server.ts
+++ b/src/routes/api/configuration/+server.ts
@@ -1,70 +1,70 @@
import { userIdentity } from '$lib/Data/AniList/identity';
import {
- deleteUserConfiguration,
- getUserConfiguration,
- setUserConfiguration
+ deleteUserConfiguration,
+ getUserConfiguration,
+ setUserConfiguration
} from '$lib/Database/SB/User/configuration';
const unauthorised = new Response('Unauthorised', { status: 401 });
export const GET = async ({ url }) =>
- Response.json(await getUserConfiguration(Number(url.searchParams.get('id') || 0)), {
- headers: {
- 'Access-Control-Allow-Origin': 'https://due.moe'
- }
- });
+ Response.json(await getUserConfiguration(Number(url.searchParams.get('id') || 0)), {
+ headers: {
+ 'Access-Control-Allow-Origin': 'https://due.moe'
+ }
+ });
export const PUT = async ({ cookies, request }) => {
- const userCookie = cookies.get('user');
+ const userCookie = cookies.get('user');
- if (!userCookie) return unauthorised;
+ if (!userCookie) return unauthorised;
- const user = JSON.parse(userCookie);
+ const user = JSON.parse(userCookie);
- return Response.json(
- await setUserConfiguration(
- (
- await userIdentity({
- tokenType: user['token_type'],
- expiresIn: user['expires_in'],
- accessToken: user['access_token'],
- refreshToken: user['refresh_token']
- })
- ).id,
- {
- configuration: await request.json()
- }
- ),
- {
- headers: {
- 'Access-Control-Allow-Origin': 'https://due.moe'
- }
- }
- );
+ return Response.json(
+ await setUserConfiguration(
+ (
+ await userIdentity({
+ tokenType: user['token_type'],
+ expiresIn: user['expires_in'],
+ accessToken: user['access_token'],
+ refreshToken: user['refresh_token']
+ })
+ ).id,
+ {
+ configuration: await request.json()
+ }
+ ),
+ {
+ headers: {
+ 'Access-Control-Allow-Origin': 'https://due.moe'
+ }
+ }
+ );
};
export const DELETE = async ({ cookies }) => {
- const userCookie = cookies.get('user');
+ const userCookie = cookies.get('user');
- if (!userCookie) return unauthorised;
+ if (!userCookie) return unauthorised;
- const user = JSON.parse(userCookie);
+ const user = JSON.parse(userCookie);
- return Response.json(
- await deleteUserConfiguration(
- (
- await userIdentity({
- tokenType: user['token_type'],
- expiresIn: user['expires_in'],
- accessToken: user['access_token'],
- refreshToken: user['refresh_token']
- })
- ).id
- ),
- {
- headers: {
- 'Access-Control-Allow-Origin': 'https://due.moe'
- }
- }
- );
+ return Response.json(
+ await deleteUserConfiguration(
+ (
+ await userIdentity({
+ tokenType: user['token_type'],
+ expiresIn: user['expires_in'],
+ accessToken: user['access_token'],
+ refreshToken: user['refresh_token']
+ })
+ ).id
+ ),
+ {
+ headers: {
+ 'Access-Control-Allow-Origin': 'https://due.moe'
+ }
+ }
+ );
};
diff --git a/src/routes/api/events/+server.ts b/src/routes/api/events/+server.ts
index 2f7d72d4..22280394 100644
--- a/src/routes/api/events/+server.ts
+++ b/src/routes/api/events/+server.ts
@@ -1,8 +1,8 @@
import { getEvents, getGroupEvents } from '$lib/Database/SB/events';
export const GET = async ({ url }) =>
- Response.json(
- url.searchParams.get('group')
- ? await getGroupEvents(url.searchParams.get('group') || '')
- : await getEvents()
- );
+ Response.json(
+ url.searchParams.get('group')
+ ? await getGroupEvents(url.searchParams.get('group') || '')
+ : await getEvents()
+ );
diff --git a/src/routes/api/events/group/+server.ts b/src/routes/api/events/group/+server.ts
index 430578a3..9bd4c33a 100644
--- a/src/routes/api/events/group/+server.ts
+++ b/src/routes/api/events/group/+server.ts
@@ -1,4 +1,4 @@
import { getGroup } from '$lib/Database/SB/groups';
export const GET = async ({ url }) =>
- Response.json(await getGroup(url.searchParams.get('slug') || ''));
+ Response.json(await getGroup(url.searchParams.get('slug') || ''));
diff --git a/src/routes/api/myanimelist/oauth/callback/+server.ts b/src/routes/api/myanimelist/oauth/callback/+server.ts
index d5dce3c1..0bb78a64 100644
--- a/src/routes/api/myanimelist/oauth/callback/+server.ts
+++ b/src/routes/api/myanimelist/oauth/callback/+server.ts
@@ -3,16 +3,16 @@ import { env } from '$env/dynamic/private';
import { env as env2 } from '$env/dynamic/public';
export const GET = async ({ url, cookies }) =>
- callback({
- url,
- cookies,
- cookie: 'myanimelist',
- authorise: 'https://myanimelist.net/v1/oauth2/token',
- redirect: '/settings',
- verifier: env.CODE_VERIFIER,
- client: {
- id: env2.PUBLIC_MYANIMELIST_CLIENT_ID,
- secret: env.MYANIMELIST_CLIENT_SECRET,
- redirectURI: env2.PUBLIC_MYANIMLIST_REDIRECT_URI
- }
- });
+ callback({
+ url,
+ cookies,
+ cookie: 'myanimelist',
+ authorise: 'https://myanimelist.net/v1/oauth2/token',
+ redirect: '/settings',
+ verifier: env.CODE_VERIFIER,
+ client: {
+ id: env2.PUBLIC_MYANIMELIST_CLIENT_ID,
+ secret: env.MYANIMELIST_CLIENT_SECRET,
+ redirectURI: env2.PUBLIC_MYANIMLIST_REDIRECT_URI
+ }
+ });
diff --git a/src/routes/api/notifications/subscribe/+server.ts b/src/routes/api/notifications/subscribe/+server.ts
index 3ffa0331..d410dc9d 100644
--- a/src/routes/api/notifications/subscribe/+server.ts
+++ b/src/routes/api/notifications/subscribe/+server.ts
@@ -4,24 +4,24 @@ import { setUserSubscription } from '$lib/Database/SB/User/notifications';
const unauthorised = new Response('Unauthorised', { status: 401 });
export const POST = async ({ cookies, request, url }) => {
- const userCookie = cookies.get('user');
- const fingerprint = url.searchParams.get('p');
+ const userCookie = cookies.get('user');
+ const fingerprint = url.searchParams.get('p');
- if (!userCookie || !fingerprint) return unauthorised;
+ if (!userCookie || !fingerprint) return unauthorised;
- const user = JSON.parse(userCookie);
- const userId = (
- await userIdentity({
- tokenType: user['token_type'],
- expiresIn: user['expires_in'],
- accessToken: user['access_token'],
- refreshToken: user['refresh_token']
- })
- ).id;
+ const user = JSON.parse(userCookie);
+ const userId = (
+ await userIdentity({
+ tokenType: user['token_type'],
+ expiresIn: user['expires_in'],
+ accessToken: user['access_token'],
+ refreshToken: user['refresh_token']
+ })
+ ).id;
- if (!userId) return unauthorised;
+ if (!userId) return unauthorised;
- await setUserSubscription(userId, await request.json(), fingerprint);
+ await setUserSubscription(userId, await request.json(), fingerprint);
- return new Response(null, { status: 200 });
+ return new Response(null, { status: 200 });
};
diff --git a/src/routes/api/notifications/unsubscribe/+server.ts b/src/routes/api/notifications/unsubscribe/+server.ts
index 92494ce7..ded228f3 100644
--- a/src/routes/api/notifications/unsubscribe/+server.ts
+++ b/src/routes/api/notifications/unsubscribe/+server.ts
@@ -4,24 +4,24 @@ import { deleteUserSubscription } from '$lib/Database/SB/User/notifications';
const unauthorised = new Response('Unauthorised', { status: 401 });
export const POST = async ({ cookies, url }) => {
- const userCookie = cookies.get('user');
- const fingerprint = url.searchParams.get('p');
+ const userCookie = cookies.get('user');
+ const fingerprint = url.searchParams.get('p');
- if (!userCookie || !fingerprint) return unauthorised;
+ if (!userCookie || !fingerprint) return unauthorised;
- const user = JSON.parse(userCookie);
- const userId = (
- await userIdentity({
- tokenType: user['token_type'],
- expiresIn: user['expires_in'],
- accessToken: user['access_token'],
- refreshToken: user['refresh_token']
- })
- ).id;
+ const user = JSON.parse(userCookie);
+ const userId = (
+ await userIdentity({
+ tokenType: user['token_type'],
+ expiresIn: user['expires_in'],
+ accessToken: user['access_token'],
+ refreshToken: user['refresh_token']
+ })
+ ).id;
- if (!userId) return unauthorised;
+ if (!userId) return unauthorised;
- await deleteUserSubscription(userId, fingerprint);
+ await deleteUserSubscription(userId, fingerprint);
- return new Response(null, { status: 200 });
+ return new Response(null, { status: 200 });
};
diff --git a/src/routes/api/oauth/callback/+server.ts b/src/routes/api/oauth/callback/+server.ts
index 4510c4a2..986520f9 100644
--- a/src/routes/api/oauth/callback/+server.ts
+++ b/src/routes/api/oauth/callback/+server.ts
@@ -3,14 +3,14 @@ import { env } from '$env/dynamic/private';
import { env as env2 } from '$env/dynamic/public';
export const GET = async ({ url, cookies }) =>
- callback({
- url,
- cookies,
- cookie: 'user',
- authorise: 'https://anilist.co/api/v2/oauth/token',
- client: {
- id: env2.PUBLIC_ANILIST_CLIENT_ID,
- secret: env.ANILIST_CLIENT_SECRET,
- redirectURI: env2.PUBLIC_ANILIST_REDIRECT_URI
- }
- });
+ callback({
+ url,
+ cookies,
+ cookie: 'user',
+ authorise: 'https://anilist.co/api/v2/oauth/token',
+ client: {
+ id: env2.PUBLIC_ANILIST_CLIENT_ID,
+ secret: env.ANILIST_CLIENT_SECRET,
+ redirectURI: env2.PUBLIC_ANILIST_REDIRECT_URI
+ }
+ });
diff --git a/src/routes/api/oauth/refresh/+server.ts b/src/routes/api/oauth/refresh/+server.ts
index b2b36db2..66b4209c 100644
--- a/src/routes/api/oauth/refresh/+server.ts
+++ b/src/routes/api/oauth/refresh/+server.ts
@@ -3,28 +3,28 @@ import { env as env2 } from '$env/dynamic/public';
import { redirect } from '@sveltejs/kit';
export const GET = async ({ url, cookies }) => {
- const formData = new FormData();
+ const formData = new FormData();
- formData.append('grant_type', 'refresh_token');
- formData.append('client_id', env2.PUBLIC_ANILIST_CLIENT_ID as string);
- formData.append('client_secret', env.ANILIST_CLIENT_SECRET as string);
- formData.append('refresh_token', url.searchParams.get('token') || '');
+ formData.append('grant_type', 'refresh_token');
+ formData.append('client_id', env2.PUBLIC_ANILIST_CLIENT_ID as string);
+ formData.append('client_secret', env.ANILIST_CLIENT_SECRET as string);
+ formData.append('refresh_token', url.searchParams.get('token') || '');
- const newUser = await (
- await fetch('https://anilist.co/api/v2/oauth/token', {
- method: 'POST',
- body: formData
- })
- ).json();
+ const newUser = await (
+ await fetch('https://anilist.co/api/v2/oauth/token', {
+ method: 'POST',
+ body: formData
+ })
+ ).json();
- cookies.set('user', JSON.stringify(newUser), {
- path: '/',
- maxAge: 60 * 60 * 24 * 7,
- httpOnly: false,
- sameSite: 'lax',
- secure: false
- });
+ cookies.set('user', JSON.stringify(newUser), {
+ path: '/',
+ maxAge: 60 * 60 * 24 * 7,
+ httpOnly: false,
+ sameSite: 'lax',
+ secure: false
+ });
- if (url.searchParams.get('redirect')) throw redirect(303, '/');
- else return Response.json(newUser);
+ if (url.searchParams.get('redirect')) throw redirect(303, '/');
+ else return Response.json(newUser);
};
diff --git a/src/routes/api/preferences/+server.ts b/src/routes/api/preferences/+server.ts
index 0b3be7e5..0fb91f45 100644
--- a/src/routes/api/preferences/+server.ts
+++ b/src/routes/api/preferences/+server.ts
@@ -1,89 +1,89 @@
import { userIdentity } from '$lib/Data/AniList/identity';
import {
- getUserPreferences,
- toggleHideMissingBadges,
- setCSS,
- setBiography,
- toggleHideAWCBadges,
- togglePinnedBadgeWallCategory,
- setPinnedBadgeWallCategories
+ getUserPreferences,
+ toggleHideMissingBadges,
+ setCSS,
+ setBiography,
+ toggleHideAWCBadges,
+ togglePinnedBadgeWallCategory,
+ setPinnedBadgeWallCategories
} from '$lib/Database/SB/User/preferences';
const unauthorised = new Response('Unauthorised', { status: 401 });
export const GET = async ({ url }) => {
- const preferences = await getUserPreferences(Number(url.searchParams.get('id') || 0));
+ const preferences = await getUserPreferences(Number(url.searchParams.get('id') || 0));
- return Response.json(preferences ? preferences : {}, {
- headers: {
- 'Access-Control-Allow-Origin': 'https://due.moe'
- }
- });
+ return Response.json(preferences ? preferences : {}, {
+ headers: {
+ 'Access-Control-Allow-Origin': 'https://due.moe'
+ }
+ });
};
export const PUT = async ({ url, cookies, request }) => {
- const userCookie = cookies.get('user');
+ const userCookie = cookies.get('user');
- if (!userCookie) return unauthorised;
+ if (!userCookie) return unauthorised;
- const user = JSON.parse(userCookie);
- const userId = (
- await userIdentity({
- tokenType: user['token_type'],
- expiresIn: user['expires_in'],
- accessToken: user['access_token'],
- refreshToken: user['refresh_token']
- })
- ).id;
+ const user = JSON.parse(userCookie);
+ const userId = (
+ await userIdentity({
+ tokenType: user['token_type'],
+ expiresIn: user['expires_in'],
+ accessToken: user['access_token'],
+ refreshToken: user['refresh_token']
+ })
+ ).id;
- if (url.searchParams.get('toggleHideMissingBadges') !== null)
- return Response.json(await toggleHideMissingBadges(userId), {
- headers: {
- 'Access-Control-Allow-Origin': 'https://due.moe'
- }
- });
+ if (url.searchParams.get('toggleHideMissingBadges') !== null)
+ return Response.json(await toggleHideMissingBadges(userId), {
+ headers: {
+ 'Access-Control-Allow-Origin': 'https://due.moe'
+ }
+ });
- if (url.searchParams.get('toggleHideAWCBadges') !== null)
- return Response.json(await toggleHideAWCBadges(userId), {
- headers: {
- 'Access-Control-Allow-Origin': 'https://due.moe'
- }
- });
+ if (url.searchParams.get('toggleHideAWCBadges') !== null)
+ return Response.json(await toggleHideAWCBadges(userId), {
+ headers: {
+ 'Access-Control-Allow-Origin': 'https://due.moe'
+ }
+ });
- if (url.searchParams.get('badgeWallCSS') !== null)
- return Response.json(await setCSS(userId, await request.text()), {
- headers: {
- 'Access-Control-Allow-Origin': 'https://due.moe'
- }
- });
+ if (url.searchParams.get('badgeWallCSS') !== null)
+ return Response.json(await setCSS(userId, await request.text()), {
+ headers: {
+ 'Access-Control-Allow-Origin': 'https://due.moe'
+ }
+ });
- if (url.searchParams.get('toggleCategory') !== null)
- return Response.json(
- await togglePinnedBadgeWallCategory(userId, url.searchParams.get('toggleCategory') || ''),
- {
- headers: {
- 'Access-Control-Allow-Origin': 'https://due.moe'
- }
- }
- );
+ if (url.searchParams.get('toggleCategory') !== null)
+ return Response.json(
+ await togglePinnedBadgeWallCategory(userId, url.searchParams.get('toggleCategory') || ''),
+ {
+ headers: {
+ 'Access-Control-Allow-Origin': 'https://due.moe'
+ }
+ }
+ );
- if (url.searchParams.get('setCategories') !== null)
- return Response.json(await setPinnedBadgeWallCategories(userId, await request.json()), {
- headers: {
- 'Access-Control-Allow-Origin': 'https://due.moe'
- }
- });
+ if (url.searchParams.get('setCategories') !== null)
+ return Response.json(await setPinnedBadgeWallCategories(userId, await request.json()), {
+ headers: {
+ 'Access-Control-Allow-Origin': 'https://due.moe'
+ }
+ });
- if (url.searchParams.get('biography') !== null)
- return Response.json(await setBiography(userId, (await request.text()).slice(0, 3000)), {
- headers: {
- 'Access-Control-Allow-Origin': 'https://due.moe'
- }
- });
+ if (url.searchParams.get('biography') !== null)
+ return Response.json(await setBiography(userId, (await request.text()).slice(0, 3000)), {
+ headers: {
+ 'Access-Control-Allow-Origin': 'https://due.moe'
+ }
+ });
- return Response.json(await getUserPreferences(Number(url.searchParams.get('id') || 0)), {
- headers: {
- 'Access-Control-Allow-Origin': 'https://due.moe'
- }
- });
+ return Response.json(await getUserPreferences(Number(url.searchParams.get('id') || 0)), {
+ headers: {
+ 'Access-Control-Allow-Origin': 'https://due.moe'
+ }
+ });
};
diff --git a/src/routes/api/preferences/pin/+server.ts b/src/routes/api/preferences/pin/+server.ts
index 73c4d5de..28398cf0 100644
--- a/src/routes/api/preferences/pin/+server.ts
+++ b/src/routes/api/preferences/pin/+server.ts
@@ -4,29 +4,29 @@ import { toggleHololiveStreamPinning } from '$lib/Database/SB/User/preferences';
const unauthorised = new Response('Unauthorised', { status: 401 });
export const PUT = async ({ cookies, url }) => {
- const userCookie = cookies.get('user');
+ const userCookie = cookies.get('user');
- if (!userCookie) return unauthorised;
+ if (!userCookie) return unauthorised;
- const user = JSON.parse(userCookie);
+ const user = JSON.parse(userCookie);
- return Response.json(
- await toggleHololiveStreamPinning(
- (
- await userIdentity({
- tokenType: user['token_type'],
- expiresIn: user['expires_in'],
- accessToken: user['access_token'],
- refreshToken: user['refresh_token']
- })
- ).id,
- url.searchParams.get('stream') || ''
- ),
- {
- headers: {
- method: 'PUT',
- 'Access-Control-Allow-Origin': 'https://due.moe'
- }
- }
- );
+ return Response.json(
+ await toggleHololiveStreamPinning(
+ (
+ await userIdentity({
+ tokenType: user['token_type'],
+ expiresIn: user['expires_in'],
+ accessToken: user['access_token'],
+ refreshToken: user['refresh_token']
+ })
+ ).id,
+ url.searchParams.get('stream') || ''
+ ),
+ {
+ headers: {
+ method: 'PUT',
+ 'Access-Control-Allow-Origin': 'https://due.moe'
+ }
+ }
+ );
};
diff --git a/src/routes/api/subsplease/+server.ts b/src/routes/api/subsplease/+server.ts
index 82631047..93e734c2 100644
--- a/src/routes/api/subsplease/+server.ts
+++ b/src/routes/api/subsplease/+server.ts
@@ -1,16 +1,16 @@
export const GET = async ({ url }) =>
- Response.json(
- await (
- await fetch(
- `https://subsplease.org/api/?f=schedule&tz=${
- url.searchParams.get('tz') || 'America/Los_Angeles'
- }`
- )
- ).json(),
- {
- headers: {
- 'Cache-Control': 'max-age=86400, s-maxage=86400',
- 'Access-Control-Allow-Origin': 'https://due.moe'
- }
- }
- );
+ Response.json(
+ await (
+ await fetch(
+ `https://subsplease.org/api/?f=schedule&tz=${
+ url.searchParams.get('tz') || 'America/Los_Angeles'
+ }`
+ )
+ ).json(),
+ {
+ headers: {
+ 'Cache-Control': 'max-age=86400, s-maxage=86400',
+ 'Access-Control-Allow-Origin': 'https://due.moe'
+ }
+ }
+ );
diff --git a/src/routes/api/updates/all-novels/+server.ts b/src/routes/api/updates/all-novels/+server.ts
index b5dea142..5dd2aaad 100644
--- a/src/routes/api/updates/all-novels/+server.ts
+++ b/src/routes/api/updates/all-novels/+server.ts
@@ -1,20 +1,20 @@
export const GET = async ({ setHeaders }) => {
- setHeaders({
- 'Cache-Control': 'public, max-age=600, s-maxage=600'
- });
+ setHeaders({
+ 'Cache-Control': 'public, max-age=600, s-maxage=600'
+ });
- return Response.json(
- await (
- await fetch('https://www.wlnupdates.com/api', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'Access-Control-Allow-Origin': 'https://due.moe'
- },
- body: JSON.stringify({
- mode: 'get-releases'
- })
- })
- ).json()
- );
+ return Response.json(
+ await (
+ await fetch('https://www.wlnupdates.com/api', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Access-Control-Allow-Origin': 'https://due.moe'
+ },
+ body: JSON.stringify({
+ mode: 'get-releases'
+ })
+ })
+ ).json()
+ );
};
diff --git a/src/routes/api/updates/manga/+server.ts b/src/routes/api/updates/manga/+server.ts
index dcc0df5c..af078c05 100644
--- a/src/routes/api/updates/manga/+server.ts
+++ b/src/routes/api/updates/manga/+server.ts
@@ -1,18 +1,18 @@
import Parser from 'rss-parser';
export const GET = async ({ setHeaders }) => {
- setHeaders({
- 'Cache-Control': 'public, max-age=600, s-maxage=600'
- });
+ setHeaders({
+ 'Cache-Control': 'public, max-age=600, s-maxage=600'
+ });
- return Response.json(
- await new Parser().parseString(
- await (await fetch('https://www.mangaupdates.com/rss.php')).text()
- ),
- {
- headers: {
- 'Access-Control-Allow-Origin': 'https://due.moe'
- }
- }
- );
+ return Response.json(
+ await new Parser().parseString(
+ await (await fetch('https://www.mangaupdates.com/rss.php')).text()
+ ),
+ {
+ headers: {
+ 'Access-Control-Allow-Origin': 'https://due.moe'
+ }
+ }
+ );
};
diff --git a/src/routes/api/updates/novels/+server.ts b/src/routes/api/updates/novels/+server.ts
index 676d1bd2..7ca6ca43 100644
--- a/src/routes/api/updates/novels/+server.ts
+++ b/src/routes/api/updates/novels/+server.ts
@@ -1,18 +1,18 @@
import Parser from 'rss-parser';
export const GET = async ({ setHeaders }) => {
- setHeaders({
- 'Cache-Control': 'public, max-age=600, s-maxage=600'
- });
+ setHeaders({
+ 'Cache-Control': 'public, max-age=600, s-maxage=600'
+ });
- return Response.json(
- await new Parser().parseString(
- await (await fetch('https://api.syosetu.com/allnovel.Atom')).text()
- ),
- {
- headers: {
- 'Access-Control-Allow-Origin': 'https://due.moe'
- }
- }
- );
+ return Response.json(
+ await new Parser().parseString(
+ await (await fetch('https://api.syosetu.com/allnovel.Atom')).text()
+ ),
+ {
+ headers: {
+ 'Access-Control-Allow-Origin': 'https://due.moe'
+ }
+ }
+ );
};
diff --git a/src/routes/completed/+page.svelte b/src/routes/completed/+page.svelte
index 8a5724fd..d483d7fe 100644
--- a/src/routes/completed/+page.svelte
+++ b/src/routes/completed/+page.svelte
@@ -1,24 +1,24 @@
<script lang="ts">
- import { onDestroy, onMount } from 'svelte';
- import userIdentity from '$stores/identity.js';
- import settings from '$stores/settings';
- import WatchingAnimeList from '$lib/List/Anime/CompletedAnimeList.svelte';
- import ListTitle from '$lib/List/ListTitle.svelte';
- import MangaListTemplate from '$lib/List/Manga/MangaListTemplate.svelte';
- import HeadTitle from '$lib/Home/HeadTitle.svelte';
- import LastActivity from '$lib/Home/LastActivity.svelte';
- import { createHeightObserver } from '$lib/Utility/html.js';
- import Skeleton from '$lib/Loading/Skeleton.svelte';
- import locale from '$stores/locale.js';
- import Landing from '$lib/Landing.svelte';
+ import { onDestroy, onMount } from 'svelte';
+ import userIdentity from '$stores/identity.js';
+ import settings from '$stores/settings';
+ import WatchingAnimeList from '$lib/List/Anime/CompletedAnimeList.svelte';
+ import ListTitle from '$lib/List/ListTitle.svelte';
+ import MangaListTemplate from '$lib/List/Manga/MangaListTemplate.svelte';
+ import HeadTitle from '$lib/Home/HeadTitle.svelte';
+ import LastActivity from '$lib/Home/LastActivity.svelte';
+ import { createHeightObserver } from '$lib/Utility/html.js';
+ import Skeleton from '$lib/Loading/Skeleton.svelte';
+ import locale from '$stores/locale.js';
+ import Landing from '$lib/Landing.svelte';
- export let data;
+ export let data;
- let heightObserver: NodeJS.Timeout;
+ let heightObserver: NodeJS.Timeout;
- onMount(() => (heightObserver = setInterval(() => createHeightObserver(), 0)));
+ onMount(() => (heightObserver = setInterval(() => createHeightObserver(), 0)));
- onDestroy(() => clearInterval(heightObserver));
+ onDestroy(() => clearInterval(heightObserver));
</script>
<HeadTitle route="Completed" path="/completed" />
@@ -26,54 +26,54 @@
<LastActivity user={data.user} />
{#if data.user === undefined}
- <div class="card">Please log in to view completed media.</div>
+ <div class="card">Please log in to view completed media.</div>
- <p />
+ <p />
- <Landing />
+ <Landing />
{:else}
- <div class="list-container">
- {#if !$settings.displayFiltersIncludeCompleted || !$settings.disableAnime}
- <details open={!$settings.displayAnimeCollapsed} class="list">
- {#if $userIdentity.id !== -2}
- <WatchingAnimeList user={data.user} />
- {:else}
- <ListTitle title={$locale().lists.completed.anime} />
+ <div class="list-container">
+ {#if !$settings.displayFiltersIncludeCompleted || !$settings.disableAnime}
+ <details open={!$settings.displayAnimeCollapsed} class="list">
+ {#if $userIdentity.id !== -2}
+ <WatchingAnimeList user={data.user} />
+ {:else}
+ <ListTitle title={$locale().lists.completed.anime} />
- <Skeleton card={false} count={5} height="0.9rem" list />
- {/if}
- </details>
- {/if}
+ <Skeleton card={false} count={5} height="0.9rem" list />
+ {/if}
+ </details>
+ {/if}
- {#if !$settings.displayFiltersIncludeCompleted || !$settings.disableManga}
- <details open={!$settings.displayMangaCollapsed} class="list">
- {#if $userIdentity.id !== -2}
- <MangaListTemplate
- user={data.user}
- displayUnresolved={$settings.displayUnresolved}
- due={false}
- />
- {:else}
- <ListTitle title={$locale().lists.completed.mangaAndLightNovels} />
+ {#if !$settings.displayFiltersIncludeCompleted || !$settings.disableManga}
+ <details open={!$settings.displayMangaCollapsed} class="list">
+ {#if $userIdentity.id !== -2}
+ <MangaListTemplate
+ user={data.user}
+ displayUnresolved={$settings.displayUnresolved}
+ due={false}
+ />
+ {:else}
+ <ListTitle title={$locale().lists.completed.mangaAndLightNovels} />
- <Skeleton card={false} count={5} height="0.9rem" list />
- {/if}
- </details>
- {/if}
- </div>
+ <Skeleton card={false} count={5} height="0.9rem" list />
+ {/if}
+ </details>
+ {/if}
+ </div>
{/if}
<style>
- .list-container {
- display: flex;
- flex-wrap: wrap;
- align-items: start;
- gap: 1em;
- }
+ .list-container {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: start;
+ gap: 1em;
+ }
- .list {
- overflow-y: auto;
- min-width: 300px;
- flex: 1 1 300px;
- }
+ .list {
+ overflow-y: auto;
+ min-width: 300px;
+ flex: 1 1 300px;
+ }
</style>
diff --git a/src/routes/events/+page.svelte b/src/routes/events/+page.svelte
index f7018ec9..d3270e30 100644
--- a/src/routes/events/+page.svelte
+++ b/src/routes/events/+page.svelte
@@ -1,26 +1,26 @@
<script>
- import Event from '$lib/Events/Event.svelte';
- import Message from '$lib/Loading/Message.svelte';
+ import Event from '$lib/Events/Event.svelte';
+ import Message from '$lib/Loading/Message.svelte';
- import root from '$lib/Utility/root';
+ import root from '$lib/Utility/root';
</script>
{#await fetch(root(`/api/events`))}
- <Message message="Loading events ..." />
+ <Message message="Loading events ..." />
{:then eventsResponse}
- {#await eventsResponse.json()}
- <Message message="Parsing events ..." />
- {:then events}
- {#if events}
- {#each events as rawEvent, i}
- <Event event={rawEvent} avatar />
+ {#await eventsResponse.json()}
+ <Message message="Parsing events ..." />
+ {:then events}
+ {#if events}
+ {#each events as rawEvent, i}
+ <Event event={rawEvent} avatar />
- {#if i < events.length - 1}
- <p />
- {/if}
- {/each}
- {/if}
- {:catch}
- <Message message="" loader="ripple" slot withReload>Error parsing events.</Message>
- {/await}
+ {#if i < events.length - 1}
+ <p />
+ {/if}
+ {/each}
+ {/if}
+ {:catch}
+ <Message message="" loader="ripple" slot withReload>Error parsing events.</Message>
+ {/await}
{/await}
diff --git a/src/routes/events/group/[group]/+page.server.ts b/src/routes/events/group/[group]/+page.server.ts
index b4094b4f..3df284fb 100644
--- a/src/routes/events/group/[group]/+page.server.ts
+++ b/src/routes/events/group/[group]/+page.server.ts
@@ -1,5 +1,5 @@
export const load = ({ params }) => {
- return {
- group: params.group
- };
+ return {
+ group: params.group
+ };
};
diff --git a/src/routes/events/group/[group]/+page.svelte b/src/routes/events/group/[group]/+page.svelte
index 01203563..37c23c40 100644
--- a/src/routes/events/group/[group]/+page.svelte
+++ b/src/routes/events/group/[group]/+page.svelte
@@ -1,74 +1,74 @@
<script lang="ts">
- import type { Group as GroupType } from '$lib/Database/SB/groups.js';
- import type { Event as EventType } from '$lib/Database/SB/events.js';
- import Message from '$lib/Loading/Message.svelte';
- import root from '$lib/Utility/root';
- import { onMount } from 'svelte';
- import Group from '$lib/Events/Group.svelte';
- import Event from '$lib/Events/Event.svelte';
+ import type { Group as GroupType } from '$lib/Database/SB/groups.js';
+ import type { Event as EventType } from '$lib/Database/SB/events.js';
+ import Message from '$lib/Loading/Message.svelte';
+ import root from '$lib/Utility/root';
+ import { onMount } from 'svelte';
+ import Group from '$lib/Events/Group.svelte';
+ import Event from '$lib/Events/Event.svelte';
- export let data;
+ export let data;
- let groupsResponse: Promise<Response>;
+ let groupsResponse: Promise<Response>;
- onMount(async () => {
- groupsResponse = fetch(root(`/api/events/group?slug=${data.group}`));
- });
+ onMount(async () => {
+ groupsResponse = fetch(root(`/api/events/group?slug=${data.group}`));
+ });
- const asGroup = (group: any) => group as GroupType;
+ const asGroup = (group: any) => group as GroupType;
- const asEvent = (event: any) => event as EventType;
+ const asEvent = (event: any) => event as EventType;
</script>
{#await groupsResponse}
- <Message message="Loading group ..." />
+ <Message message="Loading group ..." />
{:then group}
- {#if group}
- {#await group.json()}
- <Message message="Parsing group ..." />
- {:then json}
- {#if json === null}
- <Message message="" loader="ripple" slot>
- This group may not exist. Please
- <a href={'#'} on:click={() => location.reload()}>try again</a> later.
- </Message>
- {:else}
- {@const group = asGroup(json)}
+ {#if group}
+ {#await group.json()}
+ <Message message="Parsing group ..." />
+ {:then json}
+ {#if json === null}
+ <Message message="" loader="ripple" slot>
+ This group may not exist. Please
+ <a href={'#'} on:click={() => location.reload()}>try again</a> later.
+ </Message>
+ {:else}
+ {@const group = asGroup(json)}
- <Group {group} />
+ <Group {group} />
- <p />
+ <p />
- <details open>
- <summary>Events</summary>
+ <details open>
+ <summary>Events</summary>
- {#await fetch(root(`/api/events?group=${data.group}`))}
- <Message message="Loading events ..." />
- {:then eventsResponse}
- {#await eventsResponse.json()}
- <Message message="Parsing events ..." />
- {:then events}
- {#if events}
- {#each events as rawEvent, i}
- <Event event={asEvent(rawEvent)} />
+ {#await fetch(root(`/api/events?group=${data.group}`))}
+ <Message message="Loading events ..." />
+ {:then eventsResponse}
+ {#await eventsResponse.json()}
+ <Message message="Parsing events ..." />
+ {:then events}
+ {#if events}
+ {#each events as rawEvent, i}
+ <Event event={asEvent(rawEvent)} />
- {#if i < events.length - 1}
- <p />
- {/if}
- {/each}
- {/if}
- {:catch}
- <Message message="" loader="ripple" slot withReload>Error parsing events.</Message>
- {/await}
- {/await}
- </details>
- {/if}
- {:catch}
- <Message message="" loader="ripple" slot withReload>Error parsing group.</Message>
- {/await}
- {:else}
- <Message message="Parsing groups ..." />
- {/if}
+ {#if i < events.length - 1}
+ <p />
+ {/if}
+ {/each}
+ {/if}
+ {:catch}
+ <Message message="" loader="ripple" slot withReload>Error parsing events.</Message>
+ {/await}
+ {/await}
+ </details>
+ {/if}
+ {:catch}
+ <Message message="" loader="ripple" slot withReload>Error parsing group.</Message>
+ {/await}
+ {:else}
+ <Message message="Parsing groups ..." />
+ {/if}
{:catch}
- <Message message="" loader="ripple" slot withReload>Error loading group.</Message>
+ <Message message="" loader="ripple" slot withReload>Error loading group.</Message>
{/await}
diff --git a/src/routes/events/groups/+page.svelte b/src/routes/events/groups/+page.svelte
index a28e11a6..d90cce34 100644
--- a/src/routes/events/groups/+page.svelte
+++ b/src/routes/events/groups/+page.svelte
@@ -1,43 +1,43 @@
<script lang="ts">
- import type { Group as GroupType } from '$lib/Database/SB/groups';
- import Message from '$lib/Loading/Message.svelte';
- import root from '$lib/Utility/root';
- import { onMount } from 'svelte';
- import Group from '$lib/Events/Group.svelte';
+ import type { Group as GroupType } from '$lib/Database/SB/groups';
+ import Message from '$lib/Loading/Message.svelte';
+ import root from '$lib/Utility/root';
+ import { onMount } from 'svelte';
+ import Group from '$lib/Events/Group.svelte';
- let groupsResponse: Promise<Response>;
+ let groupsResponse: Promise<Response>;
- onMount(async () => {
- groupsResponse = fetch(root('/api/events/groups'));
- });
+ onMount(async () => {
+ groupsResponse = fetch(root('/api/events/groups'));
+ });
- const asGroup = (group: any) => group as GroupType;
+ const asGroup = (group: any) => group as GroupType;
</script>
{#await groupsResponse}
- <Message message="Loading groups ..." />
+ <Message message="Loading groups ..." />
{:then groups}
- {#if groups}
- {#await groups.json()}
- <Message message="Parsing groups ..." />
- {:then json}
- {#each json as rawGroup, i}
- {@const group = asGroup(rawGroup)}
+ {#if groups}
+ {#await groups.json()}
+ <Message message="Parsing groups ..." />
+ {:then json}
+ {#each json as rawGroup, i}
+ {@const group = asGroup(rawGroup)}
- <a href={root(`/events/group/${group.anilist_username}`)}>
- <Group {group} />
- </a>
+ <a href={root(`/events/group/${group.anilist_username}`)}>
+ <Group {group} />
+ </a>
- {#if i < json.length - 1}
- <p />
- {/if}
- {/each}
- {:catch}
- <Message message="" loader="ripple" slot withReload>Error parsing groups.</Message>
- {/await}
- {:else}
- <Message message="Parsing groups ..." />
- {/if}
+ {#if i < json.length - 1}
+ <p />
+ {/if}
+ {/each}
+ {:catch}
+ <Message message="" loader="ripple" slot withReload>Error parsing groups.</Message>
+ {/await}
+ {:else}
+ <Message message="Parsing groups ..." />
+ {/if}
{:catch}
- <Message message="" loader="ripple" slot withReload>Error loading groups.</Message>
+ <Message message="" loader="ripple" slot withReload>Error loading groups.</Message>
{/await}
diff --git a/src/routes/feeds/activity-notifications/+server.ts b/src/routes/feeds/activity-notifications/+server.ts
index 354f80a0..b8675de9 100644
--- a/src/routes/feeds/activity-notifications/+server.ts
+++ b/src/routes/feeds/activity-notifications/+server.ts
@@ -2,7 +2,7 @@ import { notifications, type Notification } from '$lib/Data/AniList/notification
import root from '$lib/Utility/root';
const htmlEncode = (input: string) => {
- return input.replace(/[\u00A0-\u9999<>&]/g, (i) => '&#' + i.charCodeAt(0) + ';');
+ return input.replace(/[\u00A0-\u9999<>&]/g, (i) => '&#' + i.charCodeAt(0) + ';');
};
const render = (posts: Notification[] = []) => `<?xml version="1.0" encoding="UTF-8" ?>
@@ -22,33 +22,33 @@ const render = (posts: Notification[] = []) => `<?xml version="1.0" encoding="UT
<language>en-US</language>
<snf:logo><url>https://due.moe/favicon-196x196.png</url></snf:logo>
${posts
- .filter((notification: Notification) => notification.type !== undefined)
- .map((notification: Notification) => {
- let title = `${notification.user.name} ${notification.context}`;
- let link = `https://anilist.co/user/${notification.user.name}`;
- const prettyType = notification.type
- .toString()
- .replace(/_/g, ' ')
- .toLowerCase()
- .replace(/\w\S*/g, (text) => {
- return text.charAt(0).toUpperCase() + text.substr(1).toLowerCase();
- });
+ .filter((notification: Notification) => notification.type !== undefined)
+ .map((notification: Notification) => {
+ let title = `${notification.user.name} ${notification.context}`;
+ let link = `https://anilist.co/user/${notification.user.name}`;
+ const prettyType = notification.type
+ .toString()
+ .replace(/_/g, ' ')
+ .toLowerCase()
+ .replace(/\w\S*/g, (text) => {
+ return text.charAt(0).toUpperCase() + text.substr(1).toLowerCase();
+ });
- try {
- if (
- !['FOLLOWING', 'ACTIVITY_MESSAGE'].includes(notification.type.toString()) &&
- !notification.type.toString().includes('THREAD')
- ) {
- link = `https://anilist.co/activity/${notification.activity.id}`;
- } else if (notification.type.toString().includes('THREAD')) {
- title += `${notification.thread.title}`;
- link = `https://anilist.co/forum/thread/${notification.thread.id}`;
- }
- } catch {
- return '';
- }
+ try {
+ if (
+ !['FOLLOWING', 'ACTIVITY_MESSAGE'].includes(notification.type.toString()) &&
+ !notification.type.toString().includes('THREAD')
+ ) {
+ link = `https://anilist.co/activity/${notification.activity.id}`;
+ } else if (notification.type.toString().includes('THREAD')) {
+ title += `${notification.thread.title}`;
+ link = `https://anilist.co/forum/thread/${notification.thread.id}`;
+ }
+ } catch {
+ return '';
+ }
- return `<item>
+ return `<item>
<guid isPermaLink="false">${notification.id}</guid>
<title>${htmlEncode(title)}</title>
<link>${link}</link>
@@ -57,29 +57,29 @@ const render = (posts: Notification[] = []) => `<?xml version="1.0" encoding="UT
<category>${prettyType}</category>
<pubDate>${new Date(notification.createdAt * 1000).toUTCString()}</pubDate>
</item>`;
- })
- .join('')}
+ })
+ .join('')}
</channel>
</rss>
`;
export const GET = async ({ url }) => {
- let token = url.searchParams.get('token');
- const refresh = url.searchParams.get('refresh');
- let notification = await notifications(token || '');
+ let token = url.searchParams.get('token');
+ const refresh = url.searchParams.get('refresh');
+ let notification = await notifications(token || '');
- if (notification === null) {
- token = (await (await fetch(root(`/api/oauth/refresh?token=${refresh}`))).json())[
- 'access_token'
- ];
+ if (notification === null) {
+ token = (await (await fetch(root(`/api/oauth/refresh?token=${refresh}`))).json())[
+ 'access_token'
+ ];
- notification = await notifications(token as string);
- }
+ notification = await notifications(token as string);
+ }
- return new Response(token ? render(notification || []) : render(), {
- headers: {
- 'Cache-Control': `max-age=0`,
- 'Content-Type': 'application/xml'
- }
- });
+ return new Response(token ? render(notification || []) : render(), {
+ headers: {
+ 'Cache-Control': `max-age=0`,
+ 'Content-Type': 'application/xml'
+ }
+ });
};
diff --git a/src/routes/girls/+page.svelte b/src/routes/girls/+page.svelte
index 6d3cbc62..71982c32 100644
--- a/src/routes/girls/+page.svelte
+++ b/src/routes/girls/+page.svelte
@@ -1,132 +1,132 @@
<script lang="ts">
- import Senpy from '$lib/Data/senpy';
- import HeadTitle from '$lib/Home/HeadTitle.svelte';
- import Message from '$lib/Loading/Message.svelte';
- import Skeleton from '$lib/Loading/Skeleton.svelte';
- import root from '$lib/Utility/root';
- import '$styles/girls.scss';
+ import Senpy from '$lib/Data/senpy';
+ import HeadTitle from '$lib/Home/HeadTitle.svelte';
+ import Message from '$lib/Loading/Message.svelte';
+ import Skeleton from '$lib/Loading/Skeleton.svelte';
+ import root from '$lib/Utility/root';
+ import '$styles/girls.scss';
</script>
<HeadTitle route="Anime Girls Holding Programming Books" path="/girls" />
<div class="card">
- <div class="split">
- <div>
- {#await Senpy.getRandomImage()}
- <Message message="Loading image ..." />
-
- <Skeleton grid={true} count={1} width="49%" height="16.25em" />
- {:then randomImage}
- <div class="preview">
- <a href={randomImage.image} target="_blank">
- <img src={randomImage.image} alt="A random anime girl holding a programming book" />
- </a>
- </div>
- {/await}
- </div>
- <div>
- The Senpy Club <span class="opaque">|</span> Anime Girls Holding Programming Books
-
- <p />
-
- <ul>
- <li>
- <a href="https://github.com/senpy-club/frontend-next" target="_blank"
- >Front-end Version 1.</a
- > — Similar functionality to this page, but with a different design
- </li>
- <li>
- <a href="https://github.com/senpy-club/api-worker" target="_blank">REST API</a> — The Official
- Senpy Club REST API
- </li>
- <li>
- <a href="https://github.com/senpy-club/cli" target="_blank">Command-line Interface</a> — Access
- The Senpy Club API from your terminal (and export to files too)
- </li>
- <li>
- <a href="https://docs.rs/senpy" target="_blank"><code>senpy-rs</code></a> — Rust bindings to
- The Senpy Club API
- </li>
- <li>
- <a href="https://docs.rs/senpy-ffi" target="_blank"><code>senpy-ffi</code></a> — Access The
- Senpy Club API from any programming language (with FFI support)
- </li>
- <li>
- <a href="https://github.com/senpy-club/graphql-api" target="_blank">GraphQL API</a> —
- Official GraphQL access to The Senpy Club API (<a
- href="https://graphql.senpy.club/playground"
- target="_blank"
- >
- Playground</a
- >)
- </li>
- </ul>
- </div>
- </div>
+ <div class="split">
+ <div>
+ {#await Senpy.getRandomImage()}
+ <Message message="Loading image ..." />
+
+ <Skeleton grid={true} count={1} width="49%" height="16.25em" />
+ {:then randomImage}
+ <div class="preview">
+ <a href={randomImage.image} target="_blank">
+ <img src={randomImage.image} alt="A random anime girl holding a programming book" />
+ </a>
+ </div>
+ {/await}
+ </div>
+ <div>
+ The Senpy Club <span class="opaque">|</span> Anime Girls Holding Programming Books
+
+ <p />
+
+ <ul>
+ <li>
+ <a href="https://github.com/senpy-club/frontend-next" target="_blank"
+ >Front-end Version 1.</a
+ > — Similar functionality to this page, but with a different design
+ </li>
+ <li>
+ <a href="https://github.com/senpy-club/api-worker" target="_blank">REST API</a> — The Official
+ Senpy Club REST API
+ </li>
+ <li>
+ <a href="https://github.com/senpy-club/cli" target="_blank">Command-line Interface</a> — Access
+ The Senpy Club API from your terminal (and export to files too)
+ </li>
+ <li>
+ <a href="https://docs.rs/senpy" target="_blank"><code>senpy-rs</code></a> — Rust bindings to
+ The Senpy Club API
+ </li>
+ <li>
+ <a href="https://docs.rs/senpy-ffi" target="_blank"><code>senpy-ffi</code></a> — Access The
+ Senpy Club API from any programming language (with FFI support)
+ </li>
+ <li>
+ <a href="https://github.com/senpy-club/graphql-api" target="_blank">GraphQL API</a> —
+ Official GraphQL access to The Senpy Club API (<a
+ href="https://graphql.senpy.club/playground"
+ target="_blank"
+ >
+ Playground</a
+ >)
+ </li>
+ </ul>
+ </div>
+ </div>
</div>
<p />
<details class="languages" open>
- <summary>Languages</summary>
-
- {#await Senpy.getLanguages()}
- <Message message="Loading languages ..." />
-
- <Skeleton
- card={false}
- count={10}
- pad={false}
- height={'0.9rem'}
- width={'100%'}
- list={true}
- grid={false}
- />
- {:then languages}
- <ul>
- {#each languages as language}
- <li><a href={root(`/girls/${encodeURIComponent(language)}`)}>{language}</a></li>
- {/each}
- </ul>
- {/await}
+ <summary>Languages</summary>
+
+ {#await Senpy.getLanguages()}
+ <Message message="Loading languages ..." />
+
+ <Skeleton
+ card={false}
+ count={10}
+ pad={false}
+ height={'0.9rem'}
+ width={'100%'}
+ list={true}
+ grid={false}
+ />
+ {:then languages}
+ <ul>
+ {#each languages as language}
+ <li><a href={root(`/girls/${encodeURIComponent(language)}`)}>{language}</a></li>
+ {/each}
+ </ul>
+ {/await}
</details>
<style>
- img {
- border-radius: 8px;
- width: 25vh;
- }
-
- .languages ul {
- columns: 6;
- }
-
- @media (max-width: 1280px) {
- .languages ul {
- columns: 5;
- }
- }
-
- @media (max-width: 1024px) {
- .languages ul {
- columns: 4;
- }
- }
-
- @media (max-width: 768px) {
- .languages ul {
- columns: 3;
- }
- }
-
- @media (max-width: 512px) {
- .languages ul {
- columns: 2;
- }
- }
-
- .split {
- display: flex;
- gap: 1em;
- }
+ img {
+ border-radius: 8px;
+ width: 25vh;
+ }
+
+ .languages ul {
+ columns: 6;
+ }
+
+ @media (max-width: 1280px) {
+ .languages ul {
+ columns: 5;
+ }
+ }
+
+ @media (max-width: 1024px) {
+ .languages ul {
+ columns: 4;
+ }
+ }
+
+ @media (max-width: 768px) {
+ .languages ul {
+ columns: 3;
+ }
+ }
+
+ @media (max-width: 512px) {
+ .languages ul {
+ columns: 2;
+ }
+ }
+
+ .split {
+ display: flex;
+ gap: 1em;
+ }
</style>
diff --git a/src/routes/girls/[language]/+page.server.ts b/src/routes/girls/[language]/+page.server.ts
index 7084986f..62fbb311 100644
--- a/src/routes/girls/[language]/+page.server.ts
+++ b/src/routes/girls/[language]/+page.server.ts
@@ -1,5 +1,5 @@
export const load = ({ params }) => {
- return {
- language: params.language
- };
+ return {
+ language: params.language
+ };
};
diff --git a/src/routes/girls/[language]/+page.svelte b/src/routes/girls/[language]/+page.svelte
index 8b8764d0..91b18628 100644
--- a/src/routes/girls/[language]/+page.svelte
+++ b/src/routes/girls/[language]/+page.svelte
@@ -1,39 +1,39 @@
<script lang="ts">
- import Senpy from '$lib/Data/senpy';
- import Message from '$lib/Loading/Message.svelte';
- import Skeleton from '$lib/Loading/Skeleton.svelte';
- import '$styles/girls.scss';
+ import Senpy from '$lib/Data/senpy';
+ import Message from '$lib/Loading/Message.svelte';
+ import Skeleton from '$lib/Loading/Skeleton.svelte';
+ import '$styles/girls.scss';
- export let data;
+ export let data;
</script>
<div class="card">
- {#await Senpy.getImages(data.language)}
- <Message message="Loading images ..." />
+ {#await Senpy.getImages(data.language)}
+ <Message message="Loading images ..." />
- <Skeleton grid={true} count={1} width="49%" height="16.25em" />
- {:then images}
- <div class="images">
- {#each images as image}
- <a href={image} target="_blank">
- <div class="preview">
- <img src={image} alt="An anime girl holding a programming book" />
- </div>
- </a>
- {/each}
- </div>
- {/await}
+ <Skeleton grid={true} count={1} width="49%" height="16.25em" />
+ {:then images}
+ <div class="images">
+ {#each images as image}
+ <a href={image} target="_blank">
+ <div class="preview">
+ <img src={image} alt="An anime girl holding a programming book" />
+ </div>
+ </a>
+ {/each}
+ </div>
+ {/await}
</div>
<style lang="scss">
- .images {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(16.25em, 1fr));
- gap: 1em;
+ .images {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(16.25em, 1fr));
+ gap: 1em;
- img {
- border-radius: 8px;
- width: 100%;
- }
- }
+ img {
+ border-radius: 8px;
+ width: 100%;
+ }
+ }
</style>
diff --git a/src/routes/hololive/[[stream]]/+page.server.ts b/src/routes/hololive/[[stream]]/+page.server.ts
index 87839c14..6eb5dad1 100644
--- a/src/routes/hololive/[[stream]]/+page.server.ts
+++ b/src/routes/hololive/[[stream]]/+page.server.ts
@@ -1,5 +1,5 @@
export const load = ({ params }) => {
- return {
- stream: params.stream
- };
+ return {
+ stream: params.stream
+ };
};
diff --git a/src/routes/hololive/[[stream]]/+page.svelte b/src/routes/hololive/[[stream]]/+page.svelte
index 777f1e70..d5419711 100644
--- a/src/routes/hololive/[[stream]]/+page.svelte
+++ b/src/routes/hololive/[[stream]]/+page.svelte
@@ -1,82 +1,82 @@
<script lang="ts">
- import { onMount } from 'svelte';
- import Message from '$lib/Loading/Message.svelte';
- import Skeleton from '$lib/Loading/Skeleton.svelte';
- import HeadTitle from '$lib/Home/HeadTitle.svelte';
- import { parseScheduleHtml } from '$lib/Data/hololive';
- import proxy from '$lib/Utility/proxy';
- import locale from '$stores/locale';
- import root from '$lib/Utility/root';
- import identity from '$stores/identity';
- import Lives from '$lib/Hololive/Lives.svelte';
- import { typeSchedule } from '$lib/Hololive/hololive';
+ import { onMount } from 'svelte';
+ import Message from '$lib/Loading/Message.svelte';
+ import Skeleton from '$lib/Loading/Skeleton.svelte';
+ import HeadTitle from '$lib/Home/HeadTitle.svelte';
+ import { parseScheduleHtml } from '$lib/Data/hololive';
+ import proxy from '$lib/Utility/proxy';
+ import locale from '$stores/locale';
+ import root from '$lib/Utility/root';
+ import identity from '$stores/identity';
+ import Lives from '$lib/Hololive/Lives.svelte';
+ import { typeSchedule } from '$lib/Hololive/hololive';
- export let data;
+ export let data;
- let schedulePromise: Promise<Response>;
- let pinnedStreams: string[] = [];
+ let schedulePromise: Promise<Response>;
+ let pinnedStreams: string[] = [];
- onMount(() => getPinnedStreams());
+ onMount(() => getPinnedStreams());
- const getPinnedStreams = () => {
- let streams: string[] = [];
+ const getPinnedStreams = () => {
+ let streams: string[] = [];
- const setSchedule = () => {
- pinnedStreams = streams;
- schedulePromise = fetch(proxy('https://schedule.hololive.tv'), {
- headers: {
- Cookie: 'timezone=Asia/Tokyo'
- }
- });
- };
+ const setSchedule = () => {
+ pinnedStreams = streams;
+ schedulePromise = fetch(proxy('https://schedule.hololive.tv'), {
+ headers: {
+ Cookie: 'timezone=Asia/Tokyo'
+ }
+ });
+ };
- if ($identity.id !== -2) {
- fetch(root(`/api/preferences?id=${$identity.id}`)).then((response) => {
- if (response.ok)
- response.json().then((data) => {
- if (data && data.pinned_hololive_streams) streams = data.pinned_hololive_streams;
+ if ($identity.id !== -2) {
+ fetch(root(`/api/preferences?id=${$identity.id}`)).then((response) => {
+ if (response.ok)
+ response.json().then((data) => {
+ if (data && data.pinned_hololive_streams) streams = data.pinned_hololive_streams;
- setSchedule();
- });
- });
+ setSchedule();
+ });
+ });
- return;
- }
+ return;
+ }
- setSchedule();
- };
+ setSchedule();
+ };
</script>
<HeadTitle route="hololive Schedule" path="/hololive" />
{#await schedulePromise}
- <Message message="Loading schedule ..." />
+ <Message message="Loading schedule ..." />
- <Skeleton grid={true} count={100} width="49%" height="16.25em" />
+ <Skeleton grid={true} count={100} width="49%" height="16.25em" />
{:then scheduleResponse}
- {#if scheduleResponse}
- {#await scheduleResponse.text()}
- <Message message="Parsing schedule ..." />
+ {#if scheduleResponse}
+ {#await scheduleResponse.text()}
+ <Message message="Parsing schedule ..." />
- <Skeleton grid={true} count={100} width="49%" height="16.25em" />
- {:then untypedSchedule}
- {@const schedule = typeSchedule(parseScheduleHtml(untypedSchedule))}
+ <Skeleton grid={true} count={100} width="49%" height="16.25em" />
+ {:then untypedSchedule}
+ {@const schedule = typeSchedule(parseScheduleHtml(untypedSchedule))}
- <Lives {schedule} {pinnedStreams} {getPinnedStreams} filter={data.stream} />
- {:catch}
- <Message loader="ripple" slot>
- {$locale().hololive.parseError}
- <a href={'#'} on:click={() => location.reload()}>Try again?</a>
- </Message>
- {/await}
- {:else}
- <Message message="Loading schedule ..." />
+ <Lives {schedule} {pinnedStreams} {getPinnedStreams} filter={data.stream} />
+ {:catch}
+ <Message loader="ripple" slot>
+ {$locale().hololive.parseError}
+ <a href={'#'} on:click={() => location.reload()}>Try again?</a>
+ </Message>
+ {/await}
+ {:else}
+ <Message message="Loading schedule ..." />
- <Skeleton grid={true} count={100} width="49%" height="16.25em" />
- {/if}
+ <Skeleton grid={true} count={100} width="49%" height="16.25em" />
+ {/if}
{:catch}
- <Message loader="ripple" slot>
- {$locale().hololive.loadError} Please
- <a href={'#'} on:click={() => location.reload()}>try again</a> later.
- </Message>
+ <Message loader="ripple" slot>
+ {$locale().hololive.loadError} Please
+ <a href={'#'} on:click={() => location.reload()}>try again</a> later.
+ </Message>
{/await}
diff --git a/src/routes/reader/+page.svelte b/src/routes/reader/+page.svelte
index 98c3ae1c..775d3659 100644
--- a/src/routes/reader/+page.svelte
+++ b/src/routes/reader/+page.svelte
@@ -1,38 +1,38 @@
<script>
- import Notice from '$lib/Error/Notice.svelte';
- import Message from '$lib/Loading/Message.svelte';
- import MangaDexChapters from '$lib/Reader/Chapters/MangaDex.svelte';
- import RawkumaChapters from '$lib/Reader/Chapters/Rawkuma.svelte';
- import { decodeResource, fetchResource, identify, Resource } from '$lib/Reader/resource';
- import InputTemplate from '$lib/Tools/InputTemplate.svelte';
+ import Notice from '$lib/Error/Notice.svelte';
+ import Message from '$lib/Loading/Message.svelte';
+ import MangaDexChapters from '$lib/Reader/Chapters/MangaDex.svelte';
+ import RawkumaChapters from '$lib/Reader/Chapters/Rawkuma.svelte';
+ import { decodeResource, fetchResource, identify, Resource } from '$lib/Reader/resource';
+ import InputTemplate from '$lib/Tools/InputTemplate.svelte';
- let submission = '';
+ let submission = '';
- $: resourceIdentity = identify(submission);
+ $: resourceIdentity = identify(submission);
</script>
<InputTemplate field="Manga URL" bind:submission submitText="Read" preserveCase>
- {#if resourceIdentity}
- {#await fetchResource(submission)}
- <Message message="Loading chapters ..." />
- {:then response}
- {#if response.ok}
- {#await decodeResource(response, submission) then data}
- {#if resourceIdentity === Resource.MangaDex}
- <MangaDexChapters {data} />
- {:else if resourceIdentity === Resource.Rawkuma}
- <RawkumaChapters {data} />
- {/if}
- {:catch error}
- <Notice>{error}</Notice>
- {/await}
- {:else}
- <Notice>Failed to fetch data</Notice>
- {/if}
- {:catch}
- <Notice>An unknown error has occurred.</Notice>
- {/await}
- {:else}
- <Notice>Invalid URL</Notice>
- {/if}
+ {#if resourceIdentity}
+ {#await fetchResource(submission)}
+ <Message message="Loading chapters ..." />
+ {:then response}
+ {#if response.ok}
+ {#await decodeResource(response, submission) then data}
+ {#if resourceIdentity === Resource.MangaDex}
+ <MangaDexChapters {data} />
+ {:else if resourceIdentity === Resource.Rawkuma}
+ <RawkumaChapters {data} />
+ {/if}
+ {:catch error}
+ <Notice>{error}</Notice>
+ {/await}
+ {:else}
+ <Notice>Failed to fetch data</Notice>
+ {/if}
+ {:catch}
+ <Notice>An unknown error has occurred.</Notice>
+ {/await}
+ {:else}
+ <Notice>Invalid URL</Notice>
+ {/if}
</InputTemplate>
diff --git a/src/routes/schedule/+page.svelte b/src/routes/schedule/+page.svelte
index c1447e5f..78744347 100644
--- a/src/routes/schedule/+page.svelte
+++ b/src/routes/schedule/+page.svelte
@@ -1,27 +1,27 @@
<script lang="ts">
- import Error from '$lib/Error/RateLimited.svelte';
- import { onMount } from 'svelte';
- import { parseOrDefault } from '$lib/Utility/parameters';
- import { browser } from '$app/environment';
- import type { Media } from '$lib/Data/AniList/media';
- import { scheduleMediaListCollection } from '$lib/Data/AniList/schedule';
- import { season } from '$lib/Media/Anime/season';
- import HeadTitle from '$lib/Home/HeadTitle.svelte';
- // import Crunchyroll from '$lib/Schedule/Crunchyroll.svelte';
- import '$lib/Schedule/container.css';
- import Days from '$lib/Schedule/Days.svelte';
- import Skeleton from '$lib/Loading/Skeleton.svelte';
- import Message from '$lib/Loading/Message.svelte';
- import subsPlease from '$stores/subsPlease';
+ import Error from '$lib/Error/RateLimited.svelte';
+ import { onMount } from 'svelte';
+ import { parseOrDefault } from '$lib/Utility/parameters';
+ import { browser } from '$app/environment';
+ import type { Media } from '$lib/Data/AniList/media';
+ import { scheduleMediaListCollection } from '$lib/Data/AniList/schedule';
+ import { season } from '$lib/Media/Anime/season';
+ import HeadTitle from '$lib/Home/HeadTitle.svelte';
+ // import Crunchyroll from '$lib/Schedule/Crunchyroll.svelte';
+ import '$lib/Schedule/container.css';
+ import Days from '$lib/Schedule/Days.svelte';
+ import Skeleton from '$lib/Loading/Skeleton.svelte';
+ import Message from '$lib/Loading/Message.svelte';
+ import subsPlease from '$stores/subsPlease';
- let scheduledMediaPromise: Promise<Partial<Media[]>>;
- const urlParameters = browser ? new URLSearchParams(window.location.search) : null;
- // let crunchyrollExpanded = false;
- let forceListMode = parseOrDefault(urlParameters, 'list', false);
+ let scheduledMediaPromise: Promise<Partial<Media[]>>;
+ const urlParameters = browser ? new URLSearchParams(window.location.search) : null;
+ // let crunchyrollExpanded = false;
+ let forceListMode = parseOrDefault(urlParameters, 'list', false);
- onMount(async () => {
- scheduledMediaPromise = scheduleMediaListCollection(new Date().getFullYear(), season(), true);
- });
+ onMount(async () => {
+ scheduledMediaPromise = scheduleMediaListCollection(new Date().getFullYear(), season(), true);
+ });
</script>
<HeadTitle route="Schedule" path="/schedule" />
@@ -53,39 +53,39 @@
<p /> -->
{#if !$subsPlease}
- <Message message="Loading subtitle schedule ..." />
+ <Message message="Loading subtitle schedule ..." />
- <Skeleton grid={true} count={7} height="15em" width="49.5%" />
+ <Skeleton grid={true} count={7} height="15em" width="49.5%" />
{:else}
- {#await scheduledMediaPromise}
- <Message message="Loading schedule ..." />
+ {#await scheduledMediaPromise}
+ <Message message="Loading schedule ..." />
- <Skeleton grid={true} count={7} height="15em" width="49.5%" />
- {:then scheduledMedia}
- {#if scheduledMedia}
- <div class="schedule-container" id="schedule">
- <Days subsPlease={$subsPlease} {scheduledMedia} {forceListMode} />
- </div>
- {:else}
- <Message message="Loading schedule ..." />
+ <Skeleton grid={true} count={7} height="15em" width="49.5%" />
+ {:then scheduledMedia}
+ {#if scheduledMedia}
+ <div class="schedule-container" id="schedule">
+ <Days subsPlease={$subsPlease} {scheduledMedia} {forceListMode} />
+ </div>
+ {:else}
+ <Message message="Loading schedule ..." />
- <Skeleton grid={true} count={7} height="15em" width="49.5%" />
- {/if}
- {:catch}
- <Error type="Media" loginSessionError={false} card list={false} />
- {/await}
+ <Skeleton grid={true} count={7} height="15em" width="49.5%" />
+ {/if}
+ {:catch}
+ <Error type="Media" loginSessionError={false} card list={false} />
+ {/await}
{/if}
<style>
- .schedule-container {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(35%, 1fr));
- gap: 0.5em;
- }
+ .schedule-container {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(35%, 1fr));
+ gap: 0.5em;
+ }
- @media (max-width: 800px) {
- .schedule-container {
- grid-template-columns: 1fr !important;
- }
- }
+ @media (max-width: 800px) {
+ .schedule-container {
+ grid-template-columns: 1fr !important;
+ }
+ }
</style>
diff --git a/src/routes/settings/+page.svelte b/src/routes/settings/+page.svelte
index b507eeca..42ee4edd 100644
--- a/src/routes/settings/+page.svelte
+++ b/src/routes/settings/+page.svelte
@@ -1,56 +1,56 @@
<script lang="ts">
- /* eslint svelte/no-at-html-tags: "off" */
+ /* eslint svelte/no-at-html-tags: "off" */
- import Attributions from '$lib/Settings/Categories/Attributions.svelte';
- import HeadTitle from '$lib/Home/HeadTitle.svelte';
- import Display from '$lib/Settings/Categories/Display.svelte';
- import Calculation from '$lib/Settings/Categories/Calculation.svelte';
- import Debug from '$lib/Settings/Categories/Debug.svelte';
- import Cache from '$lib/Settings/Categories/Cache.svelte';
- import Category from '$lib/Settings/Category.svelte';
- import tooltip from '$lib/Tooltip/tooltip';
- import locale from '$stores/locale.js';
- import settings from '$stores/settings';
- import LogInRestricted from '$lib/Error/LogInRestricted.svelte';
- import SettingSync from '$lib/Settings/Categories/SettingSync.svelte';
- import RssFeeds from '$lib/Settings/Categories/RSSFeeds.svelte';
+ import Attributions from '$lib/Settings/Categories/Attributions.svelte';
+ import HeadTitle from '$lib/Home/HeadTitle.svelte';
+ import Display from '$lib/Settings/Categories/Display.svelte';
+ import Calculation from '$lib/Settings/Categories/Calculation.svelte';
+ import Debug from '$lib/Settings/Categories/Debug.svelte';
+ import Cache from '$lib/Settings/Categories/Cache.svelte';
+ import Category from '$lib/Settings/Category.svelte';
+ import tooltip from '$lib/Tooltip/tooltip';
+ import locale from '$stores/locale.js';
+ import settings from '$stores/settings';
+ import LogInRestricted from '$lib/Error/LogInRestricted.svelte';
+ import SettingSync from '$lib/Settings/Categories/SettingSync.svelte';
+ import RssFeeds from '$lib/Settings/Categories/RSSFeeds.svelte';
- export let data;
+ export let data;
- // const pruneUnresolved = async () => {
- // const unresolved = await chapterDatabase.chapters.where('chapters').equals(-1).toArray();
- // const ids = unresolved.map((m) => m.id);
+ // const pruneUnresolved = async () => {
+ // const unresolved = await chapterDatabase.chapters.where('chapters').equals(-1).toArray();
+ // const ids = unresolved.map((m) => m.id);
- // manga.set('');
- // anime.set('');
- // await chapterDatabase.chapters.bulkDelete(ids);
- // };
+ // manga.set('');
+ // anime.set('');
+ // await chapterDatabase.chapters.bulkDelete(ids);
+ // };
</script>
<HeadTitle route="Settings" path="/settings" />
<blockquote>
- {#if $settings.displayLanguage == 'en'}
- Have feedback or suggestions? Send a private message to
- <a
- href="https://anilist.co/user/fuwn"
- target="_blank"
- title={$locale().settings.tooltips.author}
- use:tooltip>@fuwn</a
- >
- on AniList!
- {:else if $settings.displayLanguage == 'ja'}
- フィードバックや提案はありますか?AniListで
- <a
- href="https://anilist.co/user/fuwn"
- target="_blank"
- title={$locale().settings.tooltips.author}
- use:tooltip>@fuwn</a
- >
- にDMを送ってください!
- {/if}
+ {#if $settings.displayLanguage == 'en'}
+ Have feedback or suggestions? Send a private message to
+ <a
+ href="https://anilist.co/user/fuwn"
+ target="_blank"
+ title={$locale().settings.tooltips.author}
+ use:tooltip>@fuwn</a
+ >
+ on AniList!
+ {:else if $settings.displayLanguage == 'ja'}
+ フィードバックや提案はありますか?AniListで
+ <a
+ href="https://anilist.co/user/fuwn"
+ target="_blank"
+ title={$locale().settings.tooltips.author}
+ use:tooltip>@fuwn</a
+ >
+ にDMを送ってください!
+ {/if}
- <!-- <p />
+ <!-- <p />
<p>
<b>{$locale().settings.fields.notice}</b>
@@ -60,50 +60,50 @@
</blockquote>
{#if data.user === undefined}
- <LogInRestricted />
+ <LogInRestricted />
{:else}
- <div class="small-categories">
- <Category title={$locale().settings.settingsSync.title} id="sync" newLine={false}>
- <SettingSync />
- </Category>
- <Category title={$locale().settings.rssFeeds.title} id="feeds" newLine={false}>
- <RssFeeds user={data.user} />
- </Category>
- </div>
+ <div class="small-categories">
+ <Category title={$locale().settings.settingsSync.title} id="sync" newLine={false}>
+ <SettingSync />
+ </Category>
+ <Category title={$locale().settings.rssFeeds.title} id="feeds" newLine={false}>
+ <RssFeeds user={data.user} />
+ </Category>
+ </div>
- <p />
+ <p />
- <Category title={$locale().settings.display.title}><Display /></Category>
- <Category title={$locale().settings.calculation.title}><Calculation /></Category>
- <Category title={$locale().settings.cache.title}><Cache /></Category>
- <Category id="debug">
- <summary>
- {$locale().settings.debug.title}
- <button
- class="smaller-button button-badge badge-info unclickable-button"
- title={$locale().settings.debug.tooltips.version}
- use:tooltip
- on:click|preventDefault
- >{data.commit.slice(0, 7)}
- </button></summary
- >
+ <Category title={$locale().settings.display.title}><Display /></Category>
+ <Category title={$locale().settings.calculation.title}><Calculation /></Category>
+ <Category title={$locale().settings.cache.title}><Cache /></Category>
+ <Category id="debug">
+ <summary>
+ {$locale().settings.debug.title}
+ <button
+ class="smaller-button button-badge badge-info unclickable-button"
+ title={$locale().settings.debug.tooltips.version}
+ use:tooltip
+ on:click|preventDefault
+ >{data.commit.slice(0, 7)}
+ </button></summary
+ >
- <Debug />
- </Category>
- <Category title={$locale().settings.attributions.title} open={false}><Attributions /></Category>
+ <Debug />
+ </Category>
+ <Category title={$locale().settings.attributions.title} open={false}><Attributions /></Category>
{/if}
<style>
- .small-categories {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 1rem;
- align-items: start;
- }
+ .small-categories {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 1rem;
+ align-items: start;
+ }
- @media (max-width: 600px) {
- .small-categories {
- grid-template-columns: 1fr;
- }
- }
+ @media (max-width: 600px) {
+ .small-categories {
+ grid-template-columns: 1fr;
+ }
+ }
</style>
diff --git a/src/routes/tools/+page.svelte b/src/routes/tools/+page.svelte
index 01a73877..97e27e0e 100644
--- a/src/routes/tools/+page.svelte
+++ b/src/routes/tools/+page.svelte
@@ -1,10 +1,10 @@
<script lang="ts">
- import HeadTitle from '$lib/Home/HeadTitle.svelte';
- import Picker from '$lib/Tools/Picker.svelte';
- import { tools } from '$lib/Tools/tools.js';
- import root from '$lib/Utility/root';
+ import HeadTitle from '$lib/Home/HeadTitle.svelte';
+ import Picker from '$lib/Tools/Picker.svelte';
+ import { tools } from '$lib/Tools/tools.js';
+ import root from '$lib/Utility/root';
- let tool = 'default';
+ let tool = 'default';
</script>
<Picker {tool} />
@@ -12,63 +12,63 @@
<HeadTitle route={tools[tool].name()} path={`/tools?tool=${tool}`} />
<div class="card">
- <div class="tool-grid">
- {#each Object.keys(tools).filter((t) => t !== 'default') as t}
- <a href={root(`/tools/${tools[t].id}`)} on:click={() => (tool = t)}>
- <div class="tool-grid-tool card">
- <span class="title">
- {tools[t].name()}
- </span>
+ <div class="tool-grid">
+ {#each Object.keys(tools).filter((t) => t !== 'default') as t}
+ <a href={root(`/tools/${tools[t].id}`)} on:click={() => (tool = t)}>
+ <div class="tool-grid-tool card">
+ <span class="title">
+ {tools[t].name()}
+ </span>
- <p />
+ <p />
- {#if tools[t].description}
- <span class="description">
- {tools[t].description?.()}
- </span>
- {/if}
- </div>
- </a>
- {/each}
- </div>
+ {#if tools[t].description}
+ <span class="description">
+ {tools[t].description?.()}
+ </span>
+ {/if}
+ </div>
+ </a>
+ {/each}
+ </div>
- <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
- to
- <a href="https://anilist.co/user/fuwn" target="_blank" rel="noopener">@fuwn</a> on AniList!
- </blockquote>
+ <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
+ to
+ <a href="https://anilist.co/user/fuwn" target="_blank" rel="noopener">@fuwn</a> on AniList!
+ </blockquote>
</div>
<style lang="scss">
- .tool-grid {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
- grid-gap: 1rem;
- }
+ .tool-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
+ grid-gap: 1rem;
+ }
- .tool-grid-tool {
- display: flex;
- flex-direction: column;
- height: 100%;
- $delay: 0.25s;
- transition: transform $delay ease;
+ .tool-grid-tool {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ $delay: 0.25s;
+ transition: transform $delay ease;
- .description {
- // font-size: 0.9rem;
- color: var(--fg);
- }
+ .description {
+ // font-size: 0.9rem;
+ color: var(--fg);
+ }
- &:hover {
- transform: scale(1.05);
- position: relative;
- z-index: 2;
- transition: transform $delay ease;
- }
- }
+ &:hover {
+ transform: scale(1.05);
+ position: relative;
+ z-index: 2;
+ transition: transform $delay ease;
+ }
+ }
- .title {
- font-weight: 500;
- }
+ .title {
+ font-weight: 500;
+ }
</style>
diff --git a/src/routes/tools/[tool]/+page.server.ts b/src/routes/tools/[tool]/+page.server.ts
index c7e72d18..11ba6d15 100644
--- a/src/routes/tools/[tool]/+page.server.ts
+++ b/src/routes/tools/[tool]/+page.server.ts
@@ -1,5 +1,5 @@
export const load = ({ params }) => {
- return {
- tool: params.tool
- };
+ return {
+ tool: params.tool
+ };
};
diff --git a/src/routes/tools/[tool]/+page.svelte b/src/routes/tools/[tool]/+page.svelte
index 3749c096..9140ae1c 100644
--- a/src/routes/tools/[tool]/+page.svelte
+++ b/src/routes/tools/[tool]/+page.svelte
@@ -1,84 +1,84 @@
<script lang="ts">
- import Hayai from './../../../lib/Tools/Hayai.svelte';
- import UmaMusumeBirthdays from './../../../lib/Tools/UmaMusumeBirthdays.svelte';
- import ActivityHistory from '$lib/Tools/ActivityHistory/Tool.svelte';
- import Wrapped from '$lib/Tools/Wrapped/Tool.svelte';
- import EpisodeDiscussionCollector from '$lib/Tools/EpisodeDiscussionCollector.svelte';
- import CharacterBirthdays from '$lib/Tools/Birthdays.svelte';
- import SequelSpy from '$lib/Tools/SequelSpy/Tool.svelte';
- import { closest } from '$lib/Error/path';
- import HeadTitle from '$lib/Home/HeadTitle.svelte';
- import RandomFollower from '$lib/Tools/RandomFollower.svelte';
- import DumpProfile from '$lib/Tools/DumpProfile.svelte';
- import { tools } from '$lib/Tools/tools.js';
- import { onMount } from 'svelte';
- import { goto } from '$app/navigation';
- import Picker from '$lib/Tools/Picker.svelte';
- import Likes from '$lib/Tools/Likes.svelte';
- import root from '$lib/Utility/root.js';
- import Popup from '$lib/Layout/Popup.svelte';
- import HololiveBirthdays from '$lib/Tools/HololiveBirthdays.svelte';
- import SequelCatcher from '$lib/Tools/SequelCatcher/Tool.svelte';
+ import Hayai from './../../../lib/Tools/Hayai.svelte';
+ import UmaMusumeBirthdays from './../../../lib/Tools/UmaMusumeBirthdays.svelte';
+ import ActivityHistory from '$lib/Tools/ActivityHistory/Tool.svelte';
+ import Wrapped from '$lib/Tools/Wrapped/Tool.svelte';
+ import EpisodeDiscussionCollector from '$lib/Tools/EpisodeDiscussionCollector.svelte';
+ import CharacterBirthdays from '$lib/Tools/Birthdays.svelte';
+ import SequelSpy from '$lib/Tools/SequelSpy/Tool.svelte';
+ import { closest } from '$lib/Error/path';
+ import HeadTitle from '$lib/Home/HeadTitle.svelte';
+ import RandomFollower from '$lib/Tools/RandomFollower.svelte';
+ import DumpProfile from '$lib/Tools/DumpProfile.svelte';
+ import { tools } from '$lib/Tools/tools.js';
+ import { onMount } from 'svelte';
+ import { goto } from '$app/navigation';
+ import Picker from '$lib/Tools/Picker.svelte';
+ import Likes from '$lib/Tools/Likes.svelte';
+ import root from '$lib/Utility/root.js';
+ import Popup from '$lib/Layout/Popup.svelte';
+ import HololiveBirthdays from '$lib/Tools/HololiveBirthdays.svelte';
+ import SequelCatcher from '$lib/Tools/SequelCatcher/Tool.svelte';
- export let data;
+ export let data;
- let tool = data.tool ?? 'default';
+ let tool = data.tool ?? 'default';
- onMount(() => {
- if (tool === 'default') goto(root('/tools'));
- });
+ onMount(() => {
+ if (tool === 'default') goto(root('/tools'));
+ });
- $: suggestion = closest(tool, Object.keys(tools));
+ $: suggestion = closest(tool, Object.keys(tools));
- $: if (tool == 'girls') goto(root('/girls'));
+ $: if (tool == 'girls') goto(root('/girls'));
</script>
<Picker bind:tool />
{#if !Object.keys(tools).includes(tool)}
- <HeadTitle route="Tools" path="/tools" />
+ <HeadTitle route="Tools" path="/tools" />
- <Popup>
- <p style="text-align: center;">
- Tool "<a href={root(`/tools/${tool}`)}>{tool}</a>" not found
- </p>
+ <Popup>
+ <p style="text-align: center;">
+ Tool "<a href={root(`/tools/${tool}`)}>{tool}</a>" not found
+ </p>
- <blockquote style="margin: 0 0 0 1.5rem;">
- Did you mean "<a
- href={root(`/tools/${tools[suggestion].id}`)}
- on:click={() => (tool = suggestion)}
- style={suggestion === '...' ? 'pointer-events: none; color: inherit;' : ''}
- >
- {suggestion === '...' ? '...' : tools[suggestion].name()}</a
- >"?
- </blockquote>
- </Popup>
+ <blockquote style="margin: 0 0 0 1.5rem;">
+ Did you mean "<a
+ href={root(`/tools/${tools[suggestion].id}`)}
+ on:click={() => (tool = suggestion)}
+ style={suggestion === '...' ? 'pointer-events: none; color: inherit;' : ''}
+ >
+ {suggestion === '...' ? '...' : tools[suggestion].name()}</a
+ >"?
+ </blockquote>
+ </Popup>
{:else}
- <HeadTitle route={tools[tool].name()} path={`/tools?tool=${tool}`} />
+ <HeadTitle route={tools[tool].name()} path={`/tools?tool=${tool}`} />
- {#if tool === 'activity_history'}
- <ActivityHistory user={data.user} />
- {:else if tool === 'wrapped'}
- <Wrapped user={data.user} />
- {:else if tool === 'discussions'}
- <EpisodeDiscussionCollector />
- {:else if tool === 'birthdays'}
- <CharacterBirthdays />
- {:else if tool === 'sequel_spy'}
- <SequelSpy user={data.user} />
- {:else if tool === 'random_follower'}
- <RandomFollower />
- {:else if tool === 'dump_profile'}
- <DumpProfile />
- {:else if tool === 'likes'}
- <Likes />
- {:else if tool === 'uma_musume_birthdays'}
- <UmaMusumeBirthdays />
- {:else if tool === 'hayai'}
- <Hayai />
- {:else if tool === 'hololive_birthdays'}
- <HololiveBirthdays />
- {:else if tool === 'sequel_catcher'}
- <SequelCatcher user={data.user} />
- {/if}
+ {#if tool === 'activity_history'}
+ <ActivityHistory user={data.user} />
+ {:else if tool === 'wrapped'}
+ <Wrapped user={data.user} />
+ {:else if tool === 'discussions'}
+ <EpisodeDiscussionCollector />
+ {:else if tool === 'birthdays'}
+ <CharacterBirthdays />
+ {:else if tool === 'sequel_spy'}
+ <SequelSpy user={data.user} />
+ {:else if tool === 'random_follower'}
+ <RandomFollower />
+ {:else if tool === 'dump_profile'}
+ <DumpProfile />
+ {:else if tool === 'likes'}
+ <Likes />
+ {:else if tool === 'uma_musume_birthdays'}
+ <UmaMusumeBirthdays />
+ {:else if tool === 'hayai'}
+ <Hayai />
+ {:else if tool === 'hololive_birthdays'}
+ <HololiveBirthdays />
+ {:else if tool === 'sequel_catcher'}
+ <SequelCatcher user={data.user} />
+ {/if}
{/if}
diff --git a/src/routes/updates/+page.svelte b/src/routes/updates/+page.svelte
index 61ed9678..9af001b4 100644
--- a/src/routes/updates/+page.svelte
+++ b/src/routes/updates/+page.svelte
@@ -1,144 +1,144 @@
<script lang="ts">
- /* eslint svelte/no-at-html-tags: "off" */
-
- import { browser } from '$app/environment';
- import HeadTitle from '$lib/Home/HeadTitle.svelte';
- import Skeleton from '$lib/Loading/Skeleton.svelte';
- import { createHeightObserver } from '$lib/Utility/html';
- import root from '$lib/Utility/root';
- import { onDestroy, onMount } from 'svelte';
-
- let feed: { items: { title: string; link: string; content: string }[] } | null | undefined =
- undefined;
- let novelFeed:
- | {
- data: {
- items: { srcurl: string; postfix?: string; chapter: number; series: { name: string } }[];
- };
- }
- | undefined = undefined;
- let startTime: number;
- let mangaEndTime: number;
- let novelEndTime: number;
- let directLink = browser ? new URLSearchParams(window.location.search).has('d') : false;
- let heightObserver: NodeJS.Timeout;
-
- onMount(async () => {
- heightObserver = setInterval(() => createHeightObserver(false), 0);
-
- startTime = performance.now();
- novelFeed = await (await fetch(root('/api/updates/all-novels'))).json();
- novelEndTime = performance.now() - startTime;
- startTime = performance.now();
- feed = await (await fetch(root('/api/updates/manga'))).json();
- mangaEndTime = performance.now() - startTime;
- });
-
- onDestroy(() => clearInterval(heightObserver));
-
- const reformatChapter = (title: string) =>
- title
- .replace(/\[.*?\]\s/, '')
- .replace(/c\.Oneshot/, 'Oneshot')
- .replace(/c\.(\d+-\d+)/, 'Ch. $1')
- .replace(/v\.(\d+)\s/, 'Vol. $1 ')
- .replace(/c\.(\d+)/, 'Ch. $1');
-
- const clipTitle = (title: string) =>
- title
- .replace(/(Vol\. \d+ )?Ch\. \d+(-\d+(\.\d+)?)?$/, '')
- .replace(/\? ~.*$/, '')
- .trim();
-
- // const italicTitle = (title: string) =>
- // title.replace(/^(.*?) (Vol\.|Ch\.|\bOneshot\b)/, '<i>$1</i> $2');
-
- const chapterTitle = (title: string) => title.replace(/^(.*?) (Vol\.|Ch\.|\bOneshot\b)/, '$2');
+ /* eslint svelte/no-at-html-tags: "off" */
+
+ import { browser } from '$app/environment';
+ import HeadTitle from '$lib/Home/HeadTitle.svelte';
+ import Skeleton from '$lib/Loading/Skeleton.svelte';
+ import { createHeightObserver } from '$lib/Utility/html';
+ import root from '$lib/Utility/root';
+ import { onDestroy, onMount } from 'svelte';
+
+ let feed: { items: { title: string; link: string; content: string }[] } | null | undefined =
+ undefined;
+ let novelFeed:
+ | {
+ data: {
+ items: { srcurl: string; postfix?: string; chapter: number; series: { name: string } }[];
+ };
+ }
+ | undefined = undefined;
+ let startTime: number;
+ let mangaEndTime: number;
+ let novelEndTime: number;
+ let directLink = browser ? new URLSearchParams(window.location.search).has('d') : false;
+ let heightObserver: NodeJS.Timeout;
+
+ onMount(async () => {
+ heightObserver = setInterval(() => createHeightObserver(false), 0);
+
+ startTime = performance.now();
+ novelFeed = await (await fetch(root('/api/updates/all-novels'))).json();
+ novelEndTime = performance.now() - startTime;
+ startTime = performance.now();
+ feed = await (await fetch(root('/api/updates/manga'))).json();
+ mangaEndTime = performance.now() - startTime;
+ });
+
+ onDestroy(() => clearInterval(heightObserver));
+
+ const reformatChapter = (title: string) =>
+ title
+ .replace(/\[.*?\]\s/, '')
+ .replace(/c\.Oneshot/, 'Oneshot')
+ .replace(/c\.(\d+-\d+)/, 'Ch. $1')
+ .replace(/v\.(\d+)\s/, 'Vol. $1 ')
+ .replace(/c\.(\d+)/, 'Ch. $1');
+
+ const clipTitle = (title: string) =>
+ title
+ .replace(/(Vol\. \d+ )?Ch\. \d+(-\d+(\.\d+)?)?$/, '')
+ .replace(/\? ~.*$/, '')
+ .trim();
+
+ // const italicTitle = (title: string) =>
+ // title.replace(/^(.*?) (Vol\.|Ch\.|\bOneshot\b)/, '<i>$1</i> $2');
+
+ const chapterTitle = (title: string) => title.replace(/^(.*?) (Vol\.|Ch\.|\bOneshot\b)/, '$2');
</script>
<HeadTitle route="Updates" path="/updates" />
<div class="list-container">
- <details open class="list">
- <summary>
- Manga
- <small class="opaque">{mangaEndTime ? mangaEndTime / 1000 : '...'}s</small>
- </summary>
-
- {#if feed === null}
- Failed to load feed
- {:else if feed !== undefined}
- <ul>
- {#each feed.items as item}
- <li>
- {#if directLink}
- <i>{reformatChapter(item.title)}</i>
-
- {@html item.content}
- {:else}
- <a
- href={`https://anilist.co/search/manga?search=${clipTitle(
- reformatChapter(item.title)
- )}&sort=SEARCH_MATCH`}
- >
- <i>{@html clipTitle(reformatChapter(item.title))}</i>
- </a>
- {@html chapterTitle(reformatChapter(item.title))}
- {/if}
- </li>
- {/each}
- </ul>
- {:else}
- <Skeleton card={false} count={5} height="0.9rem" list />
- {/if}
- </details>
-
- <details open class="list">
- <summary>
- Novels
- <small class="opaque">{novelEndTime ? novelEndTime / 1000 : '...'}s</small>
- </summary>
-
- {#if novelFeed === null}
- Failed to load feed
- {:else if novelFeed !== undefined}
- <ul>
- {#each novelFeed.data.items as item}
- <li>
- {#if directLink}
- <a href={item.srcurl}>
- <i>{@html item.series.name}</i>
- {@html item.postfix || `Ch. ${item.chapter}`}
- </a>
- {:else}
- <a
- href={`https://anilist.co/search/manga?search=${item.series.name}&sort=SEARCH_MATCH`}
- >
- <i>{@html item.series.name}</i>
- </a>
- {@html item.postfix || `Ch. ${item.chapter}`}
- {/if}
- </li>
- {/each}
- </ul>
- {:else}
- <Skeleton card={false} count={5} height="0.9rem" list />
- {/if}
- </details>
+ <details open class="list">
+ <summary>
+ Manga
+ <small class="opaque">{mangaEndTime ? mangaEndTime / 1000 : '...'}s</small>
+ </summary>
+
+ {#if feed === null}
+ Failed to load feed
+ {:else if feed !== undefined}
+ <ul>
+ {#each feed.items as item}
+ <li>
+ {#if directLink}
+ <i>{reformatChapter(item.title)}</i>
+
+ {@html item.content}
+ {:else}
+ <a
+ href={`https://anilist.co/search/manga?search=${clipTitle(
+ reformatChapter(item.title)
+ )}&sort=SEARCH_MATCH`}
+ >
+ <i>{@html clipTitle(reformatChapter(item.title))}</i>
+ </a>
+ {@html chapterTitle(reformatChapter(item.title))}
+ {/if}
+ </li>
+ {/each}
+ </ul>
+ {:else}
+ <Skeleton card={false} count={5} height="0.9rem" list />
+ {/if}
+ </details>
+
+ <details open class="list">
+ <summary>
+ Novels
+ <small class="opaque">{novelEndTime ? novelEndTime / 1000 : '...'}s</small>
+ </summary>
+
+ {#if novelFeed === null}
+ Failed to load feed
+ {:else if novelFeed !== undefined}
+ <ul>
+ {#each novelFeed.data.items as item}
+ <li>
+ {#if directLink}
+ <a href={item.srcurl}>
+ <i>{@html item.series.name}</i>
+ {@html item.postfix || `Ch. ${item.chapter}`}
+ </a>
+ {:else}
+ <a
+ href={`https://anilist.co/search/manga?search=${item.series.name}&sort=SEARCH_MATCH`}
+ >
+ <i>{@html item.series.name}</i>
+ </a>
+ {@html item.postfix || `Ch. ${item.chapter}`}
+ {/if}
+ </li>
+ {/each}
+ </ul>
+ {:else}
+ <Skeleton card={false} count={5} height="0.9rem" list />
+ {/if}
+ </details>
</div>
<style>
- .list-container {
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
- align-items: start;
- gap: 1em;
- flex-wrap: wrap;
- }
-
- .list {
- overflow-y: auto;
- min-width: 300px;
- flex: 1 1 300px;
- }
+ .list-container {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
+ align-items: start;
+ gap: 1em;
+ flex-wrap: wrap;
+ }
+
+ .list {
+ overflow-y: auto;
+ min-width: 300px;
+ flex: 1 1 300px;
+ }
</style>
diff --git a/src/routes/user/+page.svelte b/src/routes/user/+page.svelte
index e66c8503..8db13d7d 100644
--- a/src/routes/user/+page.svelte
+++ b/src/routes/user/+page.svelte
@@ -1,26 +1,26 @@
<script lang="ts">
- import { browser } from '$app/environment';
- import { goto } from '$app/navigation';
- import type { UserIdentity } from '$lib/Data/AniList/identity';
- import { onMount } from 'svelte';
- import { env } from '$env/dynamic/public';
- import HeadTitle from '$lib/Home/HeadTitle.svelte';
- import root from '$lib/Utility/root';
+ import { browser } from '$app/environment';
+ import { goto } from '$app/navigation';
+ import type { UserIdentity } from '$lib/Data/AniList/identity';
+ import { onMount } from 'svelte';
+ import { env } from '$env/dynamic/public';
+ import HeadTitle from '$lib/Home/HeadTitle.svelte';
+ import root from '$lib/Utility/root';
- const user =
- browser && localStorage.getItem('identity')
- ? (JSON.parse(localStorage.getItem('identity') || '') as UserIdentity).name
- : null;
+ const user =
+ browser && localStorage.getItem('identity')
+ ? (JSON.parse(localStorage.getItem('identity') || '') as UserIdentity).name
+ : null;
- onMount(() => {
- if (user) {
- goto(root(`/user/${user}`));
- } else {
- goto(
- `https://anilist.co/api/v2/oauth/authorize?client_id=${env.PUBLIC_ANILIST_CLIENT_ID}&redirect_uri=${env.PUBLIC_ANILIST_REDIRECT_URI}&response_type=code`
- );
- }
- });
+ onMount(() => {
+ if (user) {
+ goto(root(`/user/${user}`));
+ } else {
+ goto(
+ `https://anilist.co/api/v2/oauth/authorize?client_id=${env.PUBLIC_ANILIST_CLIENT_ID}&redirect_uri=${env.PUBLIC_ANILIST_REDIRECT_URI}&response_type=code`
+ );
+ }
+ });
</script>
<HeadTitle route="Profile" path="/user" />
diff --git a/src/routes/user/[user]/+page.gql b/src/routes/user/[user]/+page.gql
index fd31248b..491290aa 100644
--- a/src/routes/user/[user]/+page.gql
+++ b/src/routes/user/[user]/+page.gql
@@ -1,18 +1,18 @@
query Profile($id: Int!) {
- User(id: $id) {
- id
- badgesCount
+ User(id: $id) {
+ id
+ badgesCount
- preferences {
- created_at
- updated_at
- user_id
- pinned_hololive_streams
- hide_missing_badges
- biography
- badge_wall_css
- hide_awc_badges
- pinned_badge_wall_categories
- }
- }
+ preferences {
+ created_at
+ updated_at
+ user_id
+ pinned_hololive_streams
+ hide_missing_badges
+ biography
+ badge_wall_css
+ hide_awc_badges
+ pinned_badge_wall_categories
+ }
+ }
}
diff --git a/src/routes/user/[user]/+page.svelte b/src/routes/user/[user]/+page.svelte
index 84f121ea..d60ea8e5 100644
--- a/src/routes/user/[user]/+page.svelte
+++ b/src/routes/user/[user]/+page.svelte
@@ -1,550 +1,550 @@
<script lang="ts">
- import settings from '$stores/settings';
- import ParallaxImage from '../../../lib/Image/ParallaxImage.svelte';
- import { typeSchedule, type ParseResult } from '$lib/Hololive/hololive';
- import HeadTitle from '$lib/Home/HeadTitle.svelte';
- import Message from '$lib/Loading/Message.svelte';
- import { estimatedDayReading } from '$lib/Media/Manga/time';
- import Skeleton from '$lib/Loading/Skeleton.svelte';
- import root from '$lib/Utility/root';
- import locale from '$stores/locale';
- import { onMount } from 'svelte';
- import authorisedUsers from '$lib/Data/Static/authorised.json';
- import tooltip from '$lib/Tooltip/tooltip';
- import AnimeRateLimited from '$lib/Error/AnimeRateLimited.svelte';
- import identity from '$stores/identity';
- import SettingHint from '$lib/Settings/SettingHint.svelte';
- import proxy from '$lib/Utility/proxy';
- import { parseScheduleHtml } from '$lib/Data/hololive';
- import type { Preferences } from '../../../graphql/$types';
- import SvelteMarkdown from 'svelte-markdown';
- import MarkdownLink from '$lib/MarkdownLink.svelte';
- import LinkedTooltip from '$lib/Tooltip/LinkedTooltip.svelte';
- import { graphql } from '$houdini';
-
- export let data;
-
- $: ({ Profile } = data);
- $: preferences = $Profile.fetching ? undefined : ($Profile.data?.User.preferences as Preferences);
-
- const setCategoriesQuery = graphql(`
- mutation SetCategories($categories: [String!]!) {
- setPinnedBadgeWallCategories(categories: $categories) {
- id
-
- preferences {
- pinned_badge_wall_categories
- }
- }
- }
- `);
-
- const toggleCategoryQuery = graphql(`
- mutation ToggleCategory($category: String!) {
- togglePinnedBadgeWallCategory(category: $category) {
- id
-
- preferences {
- pinned_badge_wall_categories
- }
- }
- }
- `);
-
- const toggleHideMissingBadgesQuery = graphql(`
- mutation ToggleHideMissingBadges {
- toggleHideMissingBadges {
- id
-
- preferences {
- hide_missing_badges
- }
- }
- }
- `);
-
- const toggleHideAWCBadgesQuery = graphql(`
- mutation ToggleHideAWCBadges {
- toggleHideAWCBadges {
- id
-
- preferences {
- hide_awc_badges
- }
- }
- }
- `);
-
- const setBiographyQuery = graphql(`
- mutation SetBiography($biography: String!) {
- setBiography(biography: $biography) {
- id
-
- preferences {
- biography
- }
- }
- }
- `);
-
- const setBadgeWallCSSQuery = graphql(`
- mutation SetBadgeWallCSS($css: String!) {
- setBadgeWallCSS(css: $css) {
- id
-
- preferences {
- badge_wall_css
- }
- }
- }
- `);
-
- $: userData = data.userData;
-
- let error = false;
- let schedule: ParseResult | undefined = undefined;
- let draggedCategory: string | null = null;
- let draggedOverCategory: string | null = null;
-
- $: displayBadges = (username: string, badges: number | string) =>
- $locale({
- values: {
- badges: badges,
- username
- }
- }).user.profile.badges;
-
- const handleDragStart = (
- event: DragEvent & { currentTarget: EventTarget & HTMLDivElement },
- category: string | null
- ) => {
- draggedCategory = category;
-
- if (event.dataTransfer) event.dataTransfer.effectAllowed = 'move';
- };
-
- const handleDragOver = (event: any) => {
- event.preventDefault();
-
- event.dataTransfer.dropEffect = 'move';
- };
-
- const handleDragEnter = (
- event: DragEvent & { currentTarget: EventTarget & HTMLDivElement },
- category: string | null
- ) => {
- event.preventDefault();
-
- if (draggedCategory !== category && preferences && draggedCategory) {
- draggedOverCategory = category;
-
- const categories = preferences.pinned_badge_wall_categories;
- const draggedIndex = categories.indexOf(draggedCategory);
- const targetIndex = categories.indexOf(category || '');
-
- categories.splice(draggedIndex, 1);
- categories.splice(targetIndex, 0, draggedCategory);
-
- preferences.pinned_badge_wall_categories = categories;
- }
- };
-
- const handleDragLeave = (
- event: DragEvent & { currentTarget: EventTarget & HTMLDivElement },
- category: string
- ) => {
- event.preventDefault();
-
- if (draggedOverCategory === category && preferences && draggedCategory) {
- draggedOverCategory = null;
-
- const categories = preferences.pinned_badge_wall_categories;
- const draggedIndex = categories.indexOf(draggedCategory);
-
- categories.splice(draggedIndex, 1);
- categories.splice(categories.indexOf(category) + 1, 0, draggedCategory);
-
- preferences.pinned_badge_wall_categories = categories;
- }
- };
-
- const handleDrop = (event: { preventDefault: () => void }) => {
- event.preventDefault();
-
- if (userData && preferences)
- setCategoriesQuery
- .mutate({
- categories: preferences.pinned_badge_wall_categories
- })
- .then();
-
- draggedCategory = null;
- draggedOverCategory = null;
- };
-
- onMount(async () => {
- schedule = typeSchedule(
- parseScheduleHtml(
- await (
- await fetch(proxy('https://schedule.hololive.tv'), {
- headers: {
- Cookie: 'timezone=Asia/Tokyo'
- }
- })
- ).text()
- )
- );
- });
-
- const getBadgeWallCSS = () =>
- (document.getElementById('badgeWallCSS') as HTMLTextAreaElement).value;
-
- const getBiography = () =>
- (document.getElementById('biography') as HTMLTextAreaElement).value.slice(0, 3000);
-
- const toggleCategory = () => {
- if (!userData) return;
-
- const categoryElement = document.getElementById('category') as HTMLInputElement;
- const category = categoryElement.value;
-
- toggleCategoryQuery.mutate({ category }).then();
-
- categoryElement.value = '';
- };
+ import settings from '$stores/settings';
+ import ParallaxImage from '../../../lib/Image/ParallaxImage.svelte';
+ import { typeSchedule, type ParseResult } from '$lib/Hololive/hololive';
+ import HeadTitle from '$lib/Home/HeadTitle.svelte';
+ import Message from '$lib/Loading/Message.svelte';
+ import { estimatedDayReading } from '$lib/Media/Manga/time';
+ import Skeleton from '$lib/Loading/Skeleton.svelte';
+ import root from '$lib/Utility/root';
+ import locale from '$stores/locale';
+ import { onMount } from 'svelte';
+ import authorisedUsers from '$lib/Data/Static/authorised.json';
+ import tooltip from '$lib/Tooltip/tooltip';
+ import AnimeRateLimited from '$lib/Error/AnimeRateLimited.svelte';
+ import identity from '$stores/identity';
+ import SettingHint from '$lib/Settings/SettingHint.svelte';
+ import proxy from '$lib/Utility/proxy';
+ import { parseScheduleHtml } from '$lib/Data/hololive';
+ import type { Preferences } from '../../../graphql/$types';
+ import SvelteMarkdown from 'svelte-markdown';
+ import MarkdownLink from '$lib/MarkdownLink.svelte';
+ import LinkedTooltip from '$lib/Tooltip/LinkedTooltip.svelte';
+ import { graphql } from '$houdini';
+
+ export let data;
+
+ $: ({ Profile } = data);
+ $: preferences = $Profile.fetching ? undefined : ($Profile.data?.User.preferences as Preferences);
+
+ const setCategoriesQuery = graphql(`
+ mutation SetCategories($categories: [String!]!) {
+ setPinnedBadgeWallCategories(categories: $categories) {
+ id
+
+ preferences {
+ pinned_badge_wall_categories
+ }
+ }
+ }
+ `);
+
+ const toggleCategoryQuery = graphql(`
+ mutation ToggleCategory($category: String!) {
+ togglePinnedBadgeWallCategory(category: $category) {
+ id
+
+ preferences {
+ pinned_badge_wall_categories
+ }
+ }
+ }
+ `);
+
+ const toggleHideMissingBadgesQuery = graphql(`
+ mutation ToggleHideMissingBadges {
+ toggleHideMissingBadges {
+ id
+
+ preferences {
+ hide_missing_badges
+ }
+ }
+ }
+ `);
+
+ const toggleHideAWCBadgesQuery = graphql(`
+ mutation ToggleHideAWCBadges {
+ toggleHideAWCBadges {
+ id
+
+ preferences {
+ hide_awc_badges
+ }
+ }
+ }
+ `);
+
+ const setBiographyQuery = graphql(`
+ mutation SetBiography($biography: String!) {
+ setBiography(biography: $biography) {
+ id
+
+ preferences {
+ biography
+ }
+ }
+ }
+ `);
+
+ const setBadgeWallCSSQuery = graphql(`
+ mutation SetBadgeWallCSS($css: String!) {
+ setBadgeWallCSS(css: $css) {
+ id
+
+ preferences {
+ badge_wall_css
+ }
+ }
+ }
+ `);
+
+ $: userData = data.userData;
+
+ let error = false;
+ let schedule: ParseResult | undefined = undefined;
+ let draggedCategory: string | null = null;
+ let draggedOverCategory: string | null = null;
+
+ $: displayBadges = (username: string, badges: number | string) =>
+ $locale({
+ values: {
+ badges: badges,
+ username
+ }
+ }).user.profile.badges;
+
+ const handleDragStart = (
+ event: DragEvent & { currentTarget: EventTarget & HTMLDivElement },
+ category: string | null
+ ) => {
+ draggedCategory = category;
+
+ if (event.dataTransfer) event.dataTransfer.effectAllowed = 'move';
+ };
+
+ const handleDragOver = (event: any) => {
+ event.preventDefault();
+
+ event.dataTransfer.dropEffect = 'move';
+ };
+
+ const handleDragEnter = (
+ event: DragEvent & { currentTarget: EventTarget & HTMLDivElement },
+ category: string | null
+ ) => {
+ event.preventDefault();
+
+ if (draggedCategory !== category && preferences && draggedCategory) {
+ draggedOverCategory = category;
+
+ const categories = preferences.pinned_badge_wall_categories;
+ const draggedIndex = categories.indexOf(draggedCategory);
+ const targetIndex = categories.indexOf(category || '');
+
+ categories.splice(draggedIndex, 1);
+ categories.splice(targetIndex, 0, draggedCategory);
+
+ preferences.pinned_badge_wall_categories = categories;
+ }
+ };
+
+ const handleDragLeave = (
+ event: DragEvent & { currentTarget: EventTarget & HTMLDivElement },
+ category: string
+ ) => {
+ event.preventDefault();
+
+ if (draggedOverCategory === category && preferences && draggedCategory) {
+ draggedOverCategory = null;
+
+ const categories = preferences.pinned_badge_wall_categories;
+ const draggedIndex = categories.indexOf(draggedCategory);
+
+ categories.splice(draggedIndex, 1);
+ categories.splice(categories.indexOf(category) + 1, 0, draggedCategory);
+
+ preferences.pinned_badge_wall_categories = categories;
+ }
+ };
+
+ const handleDrop = (event: { preventDefault: () => void }) => {
+ event.preventDefault();
+
+ if (userData && preferences)
+ setCategoriesQuery
+ .mutate({
+ categories: preferences.pinned_badge_wall_categories
+ })
+ .then();
+
+ draggedCategory = null;
+ draggedOverCategory = null;
+ };
+
+ onMount(async () => {
+ schedule = typeSchedule(
+ parseScheduleHtml(
+ await (
+ await fetch(proxy('https://schedule.hololive.tv'), {
+ headers: {
+ Cookie: 'timezone=Asia/Tokyo'
+ }
+ })
+ ).text()
+ )
+ );
+ });
+
+ const getBadgeWallCSS = () =>
+ (document.getElementById('badgeWallCSS') as HTMLTextAreaElement).value;
+
+ const getBiography = () =>
+ (document.getElementById('biography') as HTMLTextAreaElement).value.slice(0, 3000);
+
+ const toggleCategory = () => {
+ if (!userData) return;
+
+ const categoryElement = document.getElementById('category') as HTMLInputElement;
+ const category = categoryElement.value;
+
+ toggleCategoryQuery.mutate({ category }).then();
+
+ categoryElement.value = '';
+ };
- // 8.5827814569536423841e0
+ // 8.5827814569536423841e0
</script>
<HeadTitle route={`${data.username}'s Profile`} path={`/user/${data.username}`} />
{#if error}
- <AnimeRateLimited>
- <a href={`https://anilist.co/user/${data.username}`} target="_blank">@{data.username}</a>'s
- profile could not be loaded.
- </AnimeRateLimited>
+ <AnimeRateLimited>
+ <a href={`https://anilist.co/user/${data.username}`} target="_blank">@{data.username}</a>'s
+ profile could not be loaded.
+ </AnimeRateLimited>
{:else}
- {#if userData === null}
- <Message slot withReload>
- <p>
- Could not load user profile for <a
- href={`https://anilist.co/user/${data.username}`}
- target="_blank">@{data.username}</a
- >.
- </p>
- </Message>
- {:else if userData === undefined}
- <Skeleton card={false} bigCard count={1} height="224px" />
-
- <Message message="Loading user profile ..." />
- {:else}
- <div class="card card-small">
- <div
- class="card user-grid"
- style={`background-image: ${
- userData ? `url(${userData.bannerImage})` : 'none'
- }; padding: 0;`}
- >
- {#if userData}
- <img src={userData.bannerImage} alt="" class="cover-image" />
- {/if}
-
- <div class="card user-grid-content">
- <div class="user-grid-avatar">
- <LinkedTooltip content={`${userData.name}`} id="avatar" pin="avatar" relative>
- <a href={`https://anilist.co/user/${userData.name}`} target="_blank">
- <ParallaxImage
- source={$settings.displayDataSaver
- ? userData.avatar.medium
- : userData.avatar.large}
- alternativeText=""
- style="border-radius: 8px; width: 6.5em;"
- />
- </a>
- </LinkedTooltip>
- </div>
-
- <div class="user-grid-rest">
- <p>
- <a
- href={`https://anilist.co/user/${userData.name}`}
- target="_blank"
- title={String(userData.id)}
- use:tooltip
- >
- @{userData.name}
- </a>
- {#if userData && authorisedUsers.includes(userData.id)}
- &#8204;
- <button class="unclickable-button button-badge badge-rainbow">Owner</button>
- {/if}
- <span class="click-item separator opaque">•</span>
- <a href={root(`/user/${userData.name}/badges`)}>Badge Wall</a>
- </p>
-
- {#if preferences && preferences.biography && preferences.biography.length > 0}
- <SvelteMarkdown
- source={preferences.biography}
- renderers={{
- link: MarkdownLink
- }}
- />
- {/if}
-
- {$locale({
- values: {
- username: userData.name,
- anime: (userData.statistics.anime.minutesWatched / 60 / 24).toFixed(1),
- manga: estimatedDayReading(userData.statistics.manga.chaptersRead).toFixed(1)
- }
- }).user.profile.statistics}
-
- {#if schedule && preferences && preferences.biography && preferences.biography.length > 0}
- <br />
- {:else}
- <p />
- {/if}
-
- {#if $Profile.fetching}
- {displayBadges(userData.name, '...')}
- {:else if $Profile.data && $Profile.data.User}
- {@const badges = $Profile.data.User.badgesCount}
-
- {#if badges}
- {displayBadges(userData.name, badges)}
- {:else}
- {displayBadges(userData.name, '?')}
- {/if}
- {:else}
- {displayBadges(userData.name, '?')}
- {/if}
- </div>
- </div>
- </div>
- </div>
- {/if}
-
- {#if schedule && preferences && preferences.pinned_hololive_streams.length > 0}
- <p />
-
- <div class="card">
- <div class="hololive-badges">
- {#each preferences.pinned_hololive_streams as stream, index}
- {@const avatar = schedule.dict[stream]}
-
- {#if avatar}
- <LinkedTooltip
- content={stream}
- id={`hololive-badge-${index}`}
- pin={`hololive-badge-${index}`}
- relative
- >
- <a href={root(`/hololive/${encodeURIComponent(stream)}`)}>
- <div class="user-grid-hololive-badges">
- <ParallaxImage source={avatar} alternativeText="Avatar" />
- </div>
- </a>
- </LinkedTooltip>
- {/if}
- {/each}
- </div>
- </div>
- {/if}
-
- {#if preferences && userData && userData.id === $identity.id}
- <p />
-
- <details open>
- <summary>{$locale().user.preferences.title}</summary>
-
- <input
- type="checkbox"
- on:change={() => {
- if (userData) toggleHideMissingBadgesQuery.mutate(null).then();
- }}
- checked={preferences.hide_missing_badges}
- />
- {$locale().user.preferences.hideMissingBadges.title}
- <SettingHint lineBreak>{$locale().user.preferences.hideMissingBadges.hint}</SettingHint>
-
- <p />
-
- <input
- type="checkbox"
- on:change={() => {
- if (userData) toggleHideAWCBadgesQuery.mutate(null).then();
- }}
- checked={preferences.hide_awc_badges}
- />
- {$locale().user.preferences.hideAWCBadges.title}
-
- <p />
-
- Pinned Categories
-
- <div class="pinned-categories">
- {#each preferences.pinned_badge_wall_categories as category}
- <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}
- role="button"
- tabindex="0"
- >
- <span class="pinned-category-name">
- {category}
- </span>
-
- <button
- on:click={() => {
- if (userData) toggleCategoryQuery.mutate({ category }).then();
- }}>Remove</button
- >
- </div>
- {/each}
-
- <span class="card card-small pinned-category">
- <span class="pinned-category-name">
- <input type="text" id="category" placeholder="Category" style="width: 10em;" />
- </span>
-
- <button class="button-lined" on:click={toggleCategory}>Add</button>
- </span>
- </div>
-
- <p />
-
- Biography
-
- <button
- on:click={() => {
- if (userData)
- setBiographyQuery
- .mutate({
- biography: getBiography()
- })
- .then();
- }}>Save</button
- >
- <textarea
- value={preferences.biography}
- rows="5"
- cols="100"
- id="biography"
- placeholder="Markdown supported!"
- />
-
- <p />
-
- Badge Wall Custom CSS
-
- <button
- on:click={() => {
- if (userData)
- setBadgeWallCSSQuery
- .mutate({
- css: getBadgeWallCSS()
- })
- .then();
- }}>Save</button
- >
- <textarea
- value={preferences.badge_wall_css}
- rows="10"
- 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! */"
- />
- </details>
- {/if}
+ {#if userData === null}
+ <Message slot withReload>
+ <p>
+ Could not load user profile for <a
+ href={`https://anilist.co/user/${data.username}`}
+ target="_blank">@{data.username}</a
+ >.
+ </p>
+ </Message>
+ {:else if userData === undefined}
+ <Skeleton card={false} bigCard count={1} height="224px" />
+
+ <Message message="Loading user profile ..." />
+ {:else}
+ <div class="card card-small">
+ <div
+ class="card user-grid"
+ style={`background-image: ${
+ userData ? `url(${userData.bannerImage})` : 'none'
+ }; padding: 0;`}
+ >
+ {#if userData}
+ <img src={userData.bannerImage} alt="" class="cover-image" />
+ {/if}
+
+ <div class="card user-grid-content">
+ <div class="user-grid-avatar">
+ <LinkedTooltip content={`${userData.name}`} id="avatar" pin="avatar" relative>
+ <a href={`https://anilist.co/user/${userData.name}`} target="_blank">
+ <ParallaxImage
+ source={$settings.displayDataSaver
+ ? userData.avatar.medium
+ : userData.avatar.large}
+ alternativeText=""
+ style="border-radius: 8px; width: 6.5em;"
+ />
+ </a>
+ </LinkedTooltip>
+ </div>
+
+ <div class="user-grid-rest">
+ <p>
+ <a
+ href={`https://anilist.co/user/${userData.name}`}
+ target="_blank"
+ title={String(userData.id)}
+ use:tooltip
+ >
+ @{userData.name}
+ </a>
+ {#if userData && authorisedUsers.includes(userData.id)}
+ &#8204;
+ <button class="unclickable-button button-badge badge-rainbow">Owner</button>
+ {/if}
+ <span class="click-item separator opaque">•</span>
+ <a href={root(`/user/${userData.name}/badges`)}>Badge Wall</a>
+ </p>
+
+ {#if preferences && preferences.biography && preferences.biography.length > 0}
+ <SvelteMarkdown
+ source={preferences.biography}
+ renderers={{
+ link: MarkdownLink
+ }}
+ />
+ {/if}
+
+ {$locale({
+ values: {
+ username: userData.name,
+ anime: (userData.statistics.anime.minutesWatched / 60 / 24).toFixed(1),
+ manga: estimatedDayReading(userData.statistics.manga.chaptersRead).toFixed(1)
+ }
+ }).user.profile.statistics}
+
+ {#if schedule && preferences && preferences.biography && preferences.biography.length > 0}
+ <br />
+ {:else}
+ <p />
+ {/if}
+
+ {#if $Profile.fetching}
+ {displayBadges(userData.name, '...')}
+ {:else if $Profile.data && $Profile.data.User}
+ {@const badges = $Profile.data.User.badgesCount}
+
+ {#if badges}
+ {displayBadges(userData.name, badges)}
+ {:else}
+ {displayBadges(userData.name, '?')}
+ {/if}
+ {:else}
+ {displayBadges(userData.name, '?')}
+ {/if}
+ </div>
+ </div>
+ </div>
+ </div>
+ {/if}
+
+ {#if schedule && preferences && preferences.pinned_hololive_streams.length > 0}
+ <p />
+
+ <div class="card">
+ <div class="hololive-badges">
+ {#each preferences.pinned_hololive_streams as stream, index}
+ {@const avatar = schedule.dict[stream]}
+
+ {#if avatar}
+ <LinkedTooltip
+ content={stream}
+ id={`hololive-badge-${index}`}
+ pin={`hololive-badge-${index}`}
+ relative
+ >
+ <a href={root(`/hololive/${encodeURIComponent(stream)}`)}>
+ <div class="user-grid-hololive-badges">
+ <ParallaxImage source={avatar} alternativeText="Avatar" />
+ </div>
+ </a>
+ </LinkedTooltip>
+ {/if}
+ {/each}
+ </div>
+ </div>
+ {/if}
+
+ {#if preferences && userData && userData.id === $identity.id}
+ <p />
+
+ <details open>
+ <summary>{$locale().user.preferences.title}</summary>
+
+ <input
+ type="checkbox"
+ on:change={() => {
+ if (userData) toggleHideMissingBadgesQuery.mutate(null).then();
+ }}
+ checked={preferences.hide_missing_badges}
+ />
+ {$locale().user.preferences.hideMissingBadges.title}
+ <SettingHint lineBreak>{$locale().user.preferences.hideMissingBadges.hint}</SettingHint>
+
+ <p />
+
+ <input
+ type="checkbox"
+ on:change={() => {
+ if (userData) toggleHideAWCBadgesQuery.mutate(null).then();
+ }}
+ checked={preferences.hide_awc_badges}
+ />
+ {$locale().user.preferences.hideAWCBadges.title}
+
+ <p />
+
+ Pinned Categories
+
+ <div class="pinned-categories">
+ {#each preferences.pinned_badge_wall_categories as category}
+ <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}
+ role="button"
+ tabindex="0"
+ >
+ <span class="pinned-category-name">
+ {category}
+ </span>
+
+ <button
+ on:click={() => {
+ if (userData) toggleCategoryQuery.mutate({ category }).then();
+ }}>Remove</button
+ >
+ </div>
+ {/each}
+
+ <span class="card card-small pinned-category">
+ <span class="pinned-category-name">
+ <input type="text" id="category" placeholder="Category" style="width: 10em;" />
+ </span>
+
+ <button class="button-lined" on:click={toggleCategory}>Add</button>
+ </span>
+ </div>
+
+ <p />
+
+ Biography
+
+ <button
+ on:click={() => {
+ if (userData)
+ setBiographyQuery
+ .mutate({
+ biography: getBiography()
+ })
+ .then();
+ }}>Save</button
+ >
+ <textarea
+ value={preferences.biography}
+ rows="5"
+ cols="100"
+ id="biography"
+ placeholder="Markdown supported!"
+ />
+
+ <p />
+
+ Badge Wall Custom CSS
+
+ <button
+ on:click={() => {
+ if (userData)
+ setBadgeWallCSSQuery
+ .mutate({
+ css: getBadgeWallCSS()
+ })
+ .then();
+ }}>Save</button
+ >
+ <textarea
+ value={preferences.badge_wall_css}
+ rows="10"
+ 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! */"
+ />
+ </details>
+ {/if}
{/if}
<style lang="scss">
- .user-grid-content {
- display: flex;
- flex-wrap: wrap;
- column-gap: 1.5em;
- background-color: rgba(0, 0, 0, 0.468);
- color: #d8d8d8;
- border-top-left-radius: 0;
- border-top-right-radius: 0;
- }
-
- .user-grid-avatar {
- display: flex;
- flex-direction: column;
- justify-content: center;
- align-items: center;
- }
-
- .click-item {
- margin: 0 0.625rem;
- }
-
- .user-grid {
- background-size: cover;
- background-position: center;
- background-repeat: no-repeat;
- }
-
- .cover-image {
- visibility: hidden;
- height: 4.5em;
- }
-
- .user-grid-hololive-badges {
- $transitionDuration: 0.45s;
- $transition: transform $transitionDuration ease, box-shadow $transitionDuration ease;
- $size: 5.25em;
-
- border-radius: 8px;
- width: $size;
- height: $size;
- object-fit: cover;
- transition: $transition;
- box-shadow: rgba(0, 0, 11, 0.1) 0px 7px 29px 0px;
- overflow: hidden;
-
- &:hover {
- z-index: 2;
- transition: $transition;
- transform: scale(1.15);
- box-shadow: 0 1.5px 9px var(--base01), 0 0 0 4px var(--base02), 0 4px 30px var(--base01);
- }
- }
-
- .hololive-badges {
- display: flex;
- gap: 1rem;
- border-radius: 8px;
- justify-content: space-around;
- flex-wrap: wrap;
- }
-
- .separator {
- color: var(--base04);
- }
-
- .user-grid-rest {
- flex: 1;
- }
-
- .pinned-categories {
- display: flex;
- flex-wrap: wrap;
- gap: 1rem;
- }
-
- .pinned-category {
- display: flex;
- align-items: center;
- }
-
- .pinned-category-name {
- margin-right: 0.5em;
- }
+ .user-grid-content {
+ display: flex;
+ flex-wrap: wrap;
+ column-gap: 1.5em;
+ background-color: rgba(0, 0, 0, 0.468);
+ color: #d8d8d8;
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+ }
+
+ .user-grid-avatar {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ }
+
+ .click-item {
+ margin: 0 0.625rem;
+ }
+
+ .user-grid {
+ background-size: cover;
+ background-position: center;
+ background-repeat: no-repeat;
+ }
+
+ .cover-image {
+ visibility: hidden;
+ height: 4.5em;
+ }
+
+ .user-grid-hololive-badges {
+ $transitionDuration: 0.45s;
+ $transition: transform $transitionDuration ease, box-shadow $transitionDuration ease;
+ $size: 5.25em;
+
+ border-radius: 8px;
+ width: $size;
+ height: $size;
+ object-fit: cover;
+ transition: $transition;
+ box-shadow: rgba(0, 0, 11, 0.1) 0px 7px 29px 0px;
+ overflow: hidden;
+
+ &:hover {
+ z-index: 2;
+ transition: $transition;
+ transform: scale(1.15);
+ box-shadow: 0 1.5px 9px var(--base01), 0 0 0 4px var(--base02), 0 4px 30px var(--base01);
+ }
+ }
+
+ .hololive-badges {
+ display: flex;
+ gap: 1rem;
+ border-radius: 8px;
+ justify-content: space-around;
+ flex-wrap: wrap;
+ }
+
+ .separator {
+ color: var(--base04);
+ }
+
+ .user-grid-rest {
+ flex: 1;
+ }
+
+ .pinned-categories {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 1rem;
+ }
+
+ .pinned-category {
+ display: flex;
+ align-items: center;
+ }
+
+ .pinned-category-name {
+ margin-right: 0.5em;
+ }
</style>
diff --git a/src/routes/user/[user]/+page.ts b/src/routes/user/[user]/+page.ts
index c84caafb..e44f06aa 100644
--- a/src/routes/user/[user]/+page.ts
+++ b/src/routes/user/[user]/+page.ts
@@ -3,17 +3,17 @@ import { user } from '$lib/Data/AniList/user';
import type { LoadEvent } from '@sveltejs/kit';
export const load = async (event: LoadEvent) => {
- const username = event.params.user as string;
- const userData = await user(username, /^\d+$/.test(username));
+ const username = event.params.user as string;
+ const userData = await user(username, /^\d+$/.test(username));
- return {
- ...(await load_Profile({
- event,
- variables: {
- id: userData.id
- }
- })),
- username,
- userData
- };
+ return {
+ ...(await load_Profile({
+ event,
+ variables: {
+ id: userData.id
+ }
+ })),
+ username,
+ userData
+ };
};
diff --git a/src/routes/user/[user]/badges/+page.gql b/src/routes/user/[user]/badges/+page.gql
index afdd797d..060b38e7 100644
--- a/src/routes/user/[user]/badges/+page.gql
+++ b/src/routes/user/[user]/badges/+page.gql
@@ -1,31 +1,31 @@
query BadgeWallUser($id: Int!) {
- User(id: $id) {
- id
+ User(id: $id) {
+ id
- badges {
- post
- image
- description
- id
- time
- category
- hidden
- source
- designer
- shadow_hidden
- click_count
- }
+ badges {
+ post
+ image
+ description
+ id
+ time
+ category
+ hidden
+ source
+ designer
+ shadow_hidden
+ click_count
+ }
- preferences {
- created_at
- updated_at
- user_id
- pinned_hololive_streams
- hide_missing_badges
- biography
- badge_wall_css
- hide_awc_badges
- pinned_badge_wall_categories
- }
- }
+ preferences {
+ created_at
+ updated_at
+ user_id
+ pinned_hololive_streams
+ hide_missing_badges
+ biography
+ badge_wall_css
+ hide_awc_badges
+ pinned_badge_wall_categories
+ }
+ }
}
diff --git a/src/routes/user/[user]/badges/+page.svelte b/src/routes/user/[user]/badges/+page.svelte
index 3857d18b..44dd852a 100644
--- a/src/routes/user/[user]/badges/+page.svelte
+++ b/src/routes/user/[user]/badges/+page.svelte
@@ -1,1042 +1,1042 @@
<script lang="ts">
- import AWC from './../../../../lib/User/BadgeWall/AWC.svelte';
- import { user, type User } from '$lib/Data/AniList/user';
- import type { Badge } from '../../../../graphql/$types';
- import { onDestroy, onMount } from 'svelte';
- import HeadTitle from '$lib/Home/HeadTitle.svelte';
- import { databaseTimeToDate, dateToInputTime, inputTimeToDatabaseTime } from '$lib/Utility/time';
- import proxy from '$lib/Utility/proxy';
- import locale from '$stores/locale';
- import Skeleton from '$lib/Loading/Skeleton.svelte';
- import Message from '$lib/Loading/Message.svelte';
- import Dropdown from '$lib/Layout/Dropdown.svelte';
- import { activityText } from '$lib/Data/AniList/activity';
- import SettingHint from '$lib/Settings/SettingHint.svelte';
- import Popup from '$lib/Layout/Popup.svelte';
- import { page } from '$app/stores';
- import { browser } from '$app/environment';
- import BadgePreview from '$lib/User/BadgeWall/BadgePreview.svelte';
- import authorisedJson from '$lib/Data/Static/authorised.json';
- import identity from '$stores/identity';
- import '$lib/User/BadgeWall/badges.css';
- import Badges from '$lib/User/BadgeWall/Badges.svelte';
- import type { IndexedBadge } from '$lib/User/BadgeWall/badge';
- import { graphql } from '$houdini';
- import type { Preferences } from '../../../../graphql/user/$types';
-
- export let data;
-
- $: ({ BadgeWallUser } = data);
- $: preferences = $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);
- }
-
- const updateBadgeQuery = graphql(`
- mutation UpdateBadge(
- $id: Int
- $post: String
- $image: String
- $description: String
- $time: String
- $category: String
- $hidden: Boolean
- $source: String
- $designer: String
- ) {
- updateBadge(
- id: $id
- post: $post
- image: $image
- description: $description
- time: $time
- category: $category
- hidden: $hidden
- source: $source
- designer: $designer
- ) {
- id
-
- badges {
- post
- image
- description
- id
- time
- category
- hidden
- source
- designer
- shadow_hidden
- click_count
- }
- }
- }
- `);
-
- const pruneBadgesQuery = graphql(`
- mutation PruneUserBadges {
- pruneUserBadges {
- id
-
- badges {
- post
- image
- description
- id
- time
- category
- hidden
- source
- designer
- shadow_hidden
- click_count
- }
- }
- }
- `);
-
- const hideCategoryQuery = graphql(`
- mutation HideCategory($category: String) {
- hideBadge(category: $category) {
- id
-
- badges {
- post
- image
- description
- id
- time
- category
- hidden
- source
- designer
- shadow_hidden
- click_count
- }
- }
- }
- `);
-
- const deleteBadgeQuery = graphql(`
- mutation DeleteBadge($id: Int!) {
- deleteBadge(id: $id) {
- id
-
- badges {
- post
- image
- description
- id
- time
- category
- hidden
- source
- designer
- shadow_hidden
- click_count
- }
- }
- }
- `);
-
- const shadowHideBadgeQuery = graphql(`
- mutation ShadowHideBadge($id: Int!, $state: Boolean) {
- shadowHideBadge(id: $id, state: $state) {
- id
-
- badges {
- id
- }
- }
- }
- `);
-
- interface ImportImage {
- link?: string;
- image: string;
- }
-
- let editMode = false;
- let importMode = false;
- let error: null | string;
- let awcPromise: Promise<Response>;
- let confirmDelete = 0;
- let confirmPrune = 0;
- let selectedBadge: IndexedBadge | undefined = undefined;
- let loadError: string | null = null;
- const isId = /^\d+$/.test(data.username);
- let importImages: ImportImage[] | undefined = undefined;
- let importLinks = false;
- let importCategory = '';
- let importReplies = false;
- let badger: Partial<User>;
- let migrateMode = false;
- let hideMode = false;
- const authorised = authorisedJson.includes($identity.id);
- let noticeDismissed = false;
-
- $: categoryFilter = new URLSearchParams($page.url.searchParams).get('category');
-
- type GroupedBadges = { [key: string]: IndexedBadge[] };
-
- const setShadowHide = () =>
- shadowHideBadgeQuery.mutate({
- id: badger.id as number
- });
-
- onMount(async () => {
- if (browser && localStorage.getItem('badgeWallNoticeDismissed')) noticeDismissed = true;
-
- badger = isId
- ? {
- id: parseInt(data.username),
- name: 'User'
- }
- : await user(data.username);
-
- if (!isId && !badger) {
- loadError = 'User not found.';
-
- return;
- }
-
- awcPromise = fetch(proxy(`https://awc.moe/challenger/${badger.name}`));
- });
-
- onDestroy(() => {
- if (browser)
- Array.from(document.head.querySelectorAll('style')).forEach((style) => {
- if (style.dataset.badgeWall) style.remove();
- });
- });
-
- const submitBadge = () => {
- const imageURL = document.querySelector('input[name="image_url"]') as HTMLInputElement;
- const activityURL = document.querySelector('input[name="activity_url"]') as HTMLInputElement;
- const description = document.querySelector('input[name="description"]') as HTMLInputElement;
- const time = document.querySelector('input[type="datetime-local"]') as HTMLInputElement;
- const category = document.querySelector('input[name="category"]') as HTMLInputElement;
- const hidden = document.querySelector('input[name="hidden"]') as HTMLInputElement;
- const source = document.querySelector('input[name="source"]') as HTMLInputElement;
- const designer = document.querySelector('input[name="designer"]') as HTMLInputElement;
-
- if (!imageURL.value) {
- error = 'Image URL cannot be empty.';
-
- return;
- }
-
- if (
- !imageURL.value.startsWith('http') ||
- (activityURL.value.length > 0 && !activityURL.value.startsWith('http'))
- ) {
- error = 'URLs must start with http or https.';
-
- return;
- }
-
- updateBadgeQuery
- .mutate({
- id: selectedBadge?.id,
- image: imageURL.value,
- post: activityURL.value || '#',
- description: description.value,
- category: category.value,
- time: time.valueAsDate ? inputTimeToDatabaseTime(time.valueAsDate) : undefined,
- hidden: hidden.value === 'Hidden',
- source: source.value,
- designer: designer.value
- })
- .then(() => {
- error = null;
- imageURL.value = '';
- activityURL.value = '';
- description.value = '';
- category.value = '';
- hidden.value = 'Shown';
- selectedBadge = undefined;
- source.value = '';
- designer.value = '';
- });
- };
-
- const removeAllBadges = () => {
- if (confirmPrune === 2) {
- confirmPrune = 0;
- } else if (confirmPrune === 0) {
- confirmPrune = 1;
-
- return;
- } else {
- confirmPrune = 2;
-
- return;
- }
-
- selectedBadge = undefined;
-
- pruneBadgesQuery.mutate(null).then();
- };
-
- const removeBadge = (badge: Badge) => {
- if (!badge.id) return;
-
- if (confirmDelete === badge.id * 2) {
- confirmDelete = 0;
- } else if (confirmDelete / 4 === badge.id) {
- confirmDelete = badge.id * 2;
-
- return;
- } else {
- confirmDelete = badge.id * 2;
-
- return;
- }
-
- selectedBadge = undefined;
-
- deleteBadgeQuery
- .mutate({
- id: badge.id
- })
- .then();
- };
-
- const groupBadges = (badges: IndexedBadge[]) => {
- const groupedBadges: GroupedBadges = {};
-
- badges.forEach((badge) => {
- if (!badge.category) badge.category = 'Uncategorised';
-
- if (!groupedBadges[badge.category]) groupedBadges[badge.category] = [];
-
- groupedBadges[badge.category].push(badge);
- });
-
- Object.entries(groupedBadges).forEach(([_category, badges]) => {
- badges.forEach((badge, index) => {
- badge.index = index;
- });
- });
-
- return Object.entries(groupedBadges)
- .sort((a, b) => a[1].length - b[1].length)
- .sort((a, b) => {
- const pinnedCategories =
- preferences && preferences.pinned_badge_wall_categories
- ? preferences.pinned_badge_wall_categories
- : ([] as string[]);
- const aIndex = pinnedCategories.indexOf(a[0]);
- const bIndex = pinnedCategories.indexOf(b[0]);
-
- if (aIndex === -1 && bIndex === -1) return 0;
- if (aIndex === -1) return 1;
- if (bIndex === -1) return -1;
-
- return aIndex - bIndex;
- })
- .reduce((set: GroupedBadges, [key, value]) => {
- set[key] = value;
-
- return set;
- }, {});
- };
-
- const parsePost = async () => {
- if (importImages && importImages.length > 0) importImages = undefined;
-
- const link = (document.querySelector('#import_activity_url') as HTMLInputElement).value;
- const type = link.replace(/.*\/(activity|thread)\/(\d+).*/, '$1');
- const id = link.replace(/.*\/(activity|thread)\/(\d+).*/, '$2');
-
- if (type !== 'activity') return null;
-
- let text = await activityText(parseInt(id), importReplies);
-
- const images: ImportImage[] = [];
-
- if (importLinks) {
- Array.from(new DOMParser().parseFromString(text, 'text/html').querySelectorAll('a')).forEach(
- (a) => {
- const anchor = a as HTMLAnchorElement;
-
- if (anchor.querySelector('img')) {
- images.push({
- link: anchor.href,
- image: (anchor.querySelector('img') as HTMLImageElement).src
- });
- }
- }
- );
-
- text = text.replace(/<a.*?>.*?<img.*?>.*?<\/a>/g, '');
-
- Array.from(
- new DOMParser().parseFromString(text, 'text/html').querySelectorAll('img')
- ).forEach((img) => {
- const image = img as HTMLImageElement;
-
- images.push({
- image: image.src
- });
- });
- } else {
- Array.from(
- new DOMParser().parseFromString(text, 'text/html').querySelectorAll('img')
- ).forEach((img) => {
- const image = img as HTMLImageElement;
-
- images.push({
- image: image.src
- });
- });
- }
-
- importImages = images;
- };
-
- const importBadges = () =>
- fetch(
- `/api/badges?import=true
+ import AWC from './../../../../lib/User/BadgeWall/AWC.svelte';
+ import { user, type User } from '$lib/Data/AniList/user';
+ import type { Badge } from '../../../../graphql/$types';
+ import { onDestroy, onMount } from 'svelte';
+ import HeadTitle from '$lib/Home/HeadTitle.svelte';
+ import { databaseTimeToDate, dateToInputTime, inputTimeToDatabaseTime } from '$lib/Utility/time';
+ import proxy from '$lib/Utility/proxy';
+ import locale from '$stores/locale';
+ import Skeleton from '$lib/Loading/Skeleton.svelte';
+ import Message from '$lib/Loading/Message.svelte';
+ import Dropdown from '$lib/Layout/Dropdown.svelte';
+ import { activityText } from '$lib/Data/AniList/activity';
+ import SettingHint from '$lib/Settings/SettingHint.svelte';
+ import Popup from '$lib/Layout/Popup.svelte';
+ import { page } from '$app/stores';
+ import { browser } from '$app/environment';
+ import BadgePreview from '$lib/User/BadgeWall/BadgePreview.svelte';
+ import authorisedJson from '$lib/Data/Static/authorised.json';
+ import identity from '$stores/identity';
+ import '$lib/User/BadgeWall/badges.css';
+ import Badges from '$lib/User/BadgeWall/Badges.svelte';
+ import type { IndexedBadge } from '$lib/User/BadgeWall/badge';
+ import { graphql } from '$houdini';
+ import type { Preferences } from '../../../../graphql/user/$types';
+
+ export let data;
+
+ $: ({ BadgeWallUser } = data);
+ $: preferences = $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);
+ }
+
+ const updateBadgeQuery = graphql(`
+ mutation UpdateBadge(
+ $id: Int
+ $post: String
+ $image: String
+ $description: String
+ $time: String
+ $category: String
+ $hidden: Boolean
+ $source: String
+ $designer: String
+ ) {
+ updateBadge(
+ id: $id
+ post: $post
+ image: $image
+ description: $description
+ time: $time
+ category: $category
+ hidden: $hidden
+ source: $source
+ designer: $designer
+ ) {
+ id
+
+ badges {
+ post
+ image
+ description
+ id
+ time
+ category
+ hidden
+ source
+ designer
+ shadow_hidden
+ click_count
+ }
+ }
+ }
+ `);
+
+ const pruneBadgesQuery = graphql(`
+ mutation PruneUserBadges {
+ pruneUserBadges {
+ id
+
+ badges {
+ post
+ image
+ description
+ id
+ time
+ category
+ hidden
+ source
+ designer
+ shadow_hidden
+ click_count
+ }
+ }
+ }
+ `);
+
+ const hideCategoryQuery = graphql(`
+ mutation HideCategory($category: String) {
+ hideBadge(category: $category) {
+ id
+
+ badges {
+ post
+ image
+ description
+ id
+ time
+ category
+ hidden
+ source
+ designer
+ shadow_hidden
+ click_count
+ }
+ }
+ }
+ `);
+
+ const deleteBadgeQuery = graphql(`
+ mutation DeleteBadge($id: Int!) {
+ deleteBadge(id: $id) {
+ id
+
+ badges {
+ post
+ image
+ description
+ id
+ time
+ category
+ hidden
+ source
+ designer
+ shadow_hidden
+ click_count
+ }
+ }
+ }
+ `);
+
+ const shadowHideBadgeQuery = graphql(`
+ mutation ShadowHideBadge($id: Int!, $state: Boolean) {
+ shadowHideBadge(id: $id, state: $state) {
+ id
+
+ badges {
+ id
+ }
+ }
+ }
+ `);
+
+ interface ImportImage {
+ link?: string;
+ image: string;
+ }
+
+ let editMode = false;
+ let importMode = false;
+ let error: null | string;
+ let awcPromise: Promise<Response>;
+ let confirmDelete = 0;
+ let confirmPrune = 0;
+ let selectedBadge: IndexedBadge | undefined = undefined;
+ let loadError: string | null = null;
+ const isId = /^\d+$/.test(data.username);
+ let importImages: ImportImage[] | undefined = undefined;
+ let importLinks = false;
+ let importCategory = '';
+ let importReplies = false;
+ let badger: Partial<User>;
+ let migrateMode = false;
+ let hideMode = false;
+ const authorised = authorisedJson.includes($identity.id);
+ let noticeDismissed = false;
+
+ $: categoryFilter = new URLSearchParams($page.url.searchParams).get('category');
+
+ type GroupedBadges = { [key: string]: IndexedBadge[] };
+
+ const setShadowHide = () =>
+ shadowHideBadgeQuery.mutate({
+ id: badger.id as number
+ });
+
+ onMount(async () => {
+ if (browser && localStorage.getItem('badgeWallNoticeDismissed')) noticeDismissed = true;
+
+ badger = isId
+ ? {
+ id: parseInt(data.username),
+ name: 'User'
+ }
+ : await user(data.username);
+
+ if (!isId && !badger) {
+ loadError = 'User not found.';
+
+ return;
+ }
+
+ awcPromise = fetch(proxy(`https://awc.moe/challenger/${badger.name}`));
+ });
+
+ onDestroy(() => {
+ if (browser)
+ Array.from(document.head.querySelectorAll('style')).forEach((style) => {
+ if (style.dataset.badgeWall) style.remove();
+ });
+ });
+
+ const submitBadge = () => {
+ const imageURL = document.querySelector('input[name="image_url"]') as HTMLInputElement;
+ const activityURL = document.querySelector('input[name="activity_url"]') as HTMLInputElement;
+ const description = document.querySelector('input[name="description"]') as HTMLInputElement;
+ const time = document.querySelector('input[type="datetime-local"]') as HTMLInputElement;
+ const category = document.querySelector('input[name="category"]') as HTMLInputElement;
+ const hidden = document.querySelector('input[name="hidden"]') as HTMLInputElement;
+ const source = document.querySelector('input[name="source"]') as HTMLInputElement;
+ const designer = document.querySelector('input[name="designer"]') as HTMLInputElement;
+
+ if (!imageURL.value) {
+ error = 'Image URL cannot be empty.';
+
+ return;
+ }
+
+ if (
+ !imageURL.value.startsWith('http') ||
+ (activityURL.value.length > 0 && !activityURL.value.startsWith('http'))
+ ) {
+ error = 'URLs must start with http or https.';
+
+ return;
+ }
+
+ updateBadgeQuery
+ .mutate({
+ id: selectedBadge?.id,
+ image: imageURL.value,
+ post: activityURL.value || '#',
+ description: description.value,
+ category: category.value,
+ time: time.valueAsDate ? inputTimeToDatabaseTime(time.valueAsDate) : undefined,
+ hidden: hidden.value === 'Hidden',
+ source: source.value,
+ designer: designer.value
+ })
+ .then(() => {
+ error = null;
+ imageURL.value = '';
+ activityURL.value = '';
+ description.value = '';
+ category.value = '';
+ hidden.value = 'Shown';
+ selectedBadge = undefined;
+ source.value = '';
+ designer.value = '';
+ });
+ };
+
+ const removeAllBadges = () => {
+ if (confirmPrune === 2) {
+ confirmPrune = 0;
+ } else if (confirmPrune === 0) {
+ confirmPrune = 1;
+
+ return;
+ } else {
+ confirmPrune = 2;
+
+ return;
+ }
+
+ selectedBadge = undefined;
+
+ pruneBadgesQuery.mutate(null).then();
+ };
+
+ const removeBadge = (badge: Badge) => {
+ if (!badge.id) return;
+
+ if (confirmDelete === badge.id * 2) {
+ confirmDelete = 0;
+ } else if (confirmDelete / 4 === badge.id) {
+ confirmDelete = badge.id * 2;
+
+ return;
+ } else {
+ confirmDelete = badge.id * 2;
+
+ return;
+ }
+
+ selectedBadge = undefined;
+
+ deleteBadgeQuery
+ .mutate({
+ id: badge.id
+ })
+ .then();
+ };
+
+ const groupBadges = (badges: IndexedBadge[]) => {
+ const groupedBadges: GroupedBadges = {};
+
+ badges.forEach((badge) => {
+ if (!badge.category) badge.category = 'Uncategorised';
+
+ if (!groupedBadges[badge.category]) groupedBadges[badge.category] = [];
+
+ groupedBadges[badge.category].push(badge);
+ });
+
+ Object.entries(groupedBadges).forEach(([_category, badges]) => {
+ badges.forEach((badge, index) => {
+ badge.index = index;
+ });
+ });
+
+ return Object.entries(groupedBadges)
+ .sort((a, b) => a[1].length - b[1].length)
+ .sort((a, b) => {
+ const pinnedCategories =
+ preferences && preferences.pinned_badge_wall_categories
+ ? preferences.pinned_badge_wall_categories
+ : ([] as string[]);
+ const aIndex = pinnedCategories.indexOf(a[0]);
+ const bIndex = pinnedCategories.indexOf(b[0]);
+
+ if (aIndex === -1 && bIndex === -1) return 0;
+ if (aIndex === -1) return 1;
+ if (bIndex === -1) return -1;
+
+ return aIndex - bIndex;
+ })
+ .reduce((set: GroupedBadges, [key, value]) => {
+ set[key] = value;
+
+ return set;
+ }, {});
+ };
+
+ const parsePost = async () => {
+ if (importImages && importImages.length > 0) importImages = undefined;
+
+ const link = (document.querySelector('#import_activity_url') as HTMLInputElement).value;
+ const type = link.replace(/.*\/(activity|thread)\/(\d+).*/, '$1');
+ const id = link.replace(/.*\/(activity|thread)\/(\d+).*/, '$2');
+
+ if (type !== 'activity') return null;
+
+ let text = await activityText(parseInt(id), importReplies);
+
+ const images: ImportImage[] = [];
+
+ if (importLinks) {
+ Array.from(new DOMParser().parseFromString(text, 'text/html').querySelectorAll('a')).forEach(
+ (a) => {
+ const anchor = a as HTMLAnchorElement;
+
+ if (anchor.querySelector('img')) {
+ images.push({
+ link: anchor.href,
+ image: (anchor.querySelector('img') as HTMLImageElement).src
+ });
+ }
+ }
+ );
+
+ text = text.replace(/<a.*?>.*?<img.*?>.*?<\/a>/g, '');
+
+ Array.from(
+ new DOMParser().parseFromString(text, 'text/html').querySelectorAll('img')
+ ).forEach((img) => {
+ const image = img as HTMLImageElement;
+
+ images.push({
+ image: image.src
+ });
+ });
+ } else {
+ Array.from(
+ new DOMParser().parseFromString(text, 'text/html').querySelectorAll('img')
+ ).forEach((img) => {
+ const image = img as HTMLImageElement;
+
+ images.push({
+ image: image.src
+ });
+ });
+ }
+
+ importImages = images;
+ };
+
+ const importBadges = () =>
+ fetch(
+ `/api/badges?import=true
${importCategory.length > 0 ? `&category=${encodeURIComponent(importCategory)}` : ''}
`,
- {
- method: 'PUT',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify(
- importImages?.map((image) => ({
- image: image.image,
- post: image.link || '#',
- category: importCategory
- }))
- )
- }
- ).then(() => {
- importMode = false;
- importImages = undefined;
- });
-
- const migrateCategory = () => {
- fetch(
- `/api/badges?migrate=true&original=${encodeURIComponent(
- (document.querySelector('#migrate_original') as HTMLInputElement).value
- )}&new=${encodeURIComponent(
- (document.querySelector('#migrate_new') as HTMLInputElement).value
- )}`,
- {
- method: 'PUT'
- }
- ).then(() => (migrateMode = false));
- };
-
- const hideCategory = () => {
- hideCategoryQuery
- .mutate({
- category: (document.querySelector('#category_hide') as HTMLInputElement).value
- })
- .then(() => (hideMode = false));
- };
-
- const removeHiddenBadges = (isOwner: boolean, badges: IndexedBadge[]) =>
- isOwner || authorised ? badges : badges.filter((b) => !b.hidden && !b.shadow_hidden);
-
- const setAdjacentCursor = (badges: IndexedBadge[], direction: number) => {
- const currentCategory = selectedBadge?.category || 'Uncategorised';
- const currentBadge = selectedBadge?.index;
- const categoryBadges = groupBadges(badges)[currentCategory];
-
- if (!currentCategory || currentBadge === undefined) return;
-
- let previousBadge = categoryBadges[currentBadge + direction];
-
- while (previousBadge && (previousBadge.hidden || previousBadge.shadow_hidden))
- previousBadge = categoryBadges[previousBadge.index + direction];
-
- if (previousBadge) selectedBadge = previousBadge;
- };
-
- const adjacentBadgeExists = (
- selectedBadge: IndexedBadge | undefined,
- badges: IndexedBadge[],
- direction: number
- ) => {
- const currentCategory = selectedBadge?.category || 'Uncategorised';
- const currentBadge = selectedBadge?.index;
- const categoryBadges = groupBadges(badges)[currentCategory];
-
- if (!currentCategory || currentBadge === undefined || !categoryBadges) return;
-
- let previousBadge = categoryBadges[currentBadge + direction];
-
- while (previousBadge && (previousBadge.hidden || previousBadge.shadow_hidden))
- previousBadge = categoryBadges[previousBadge.index + direction];
-
- return previousBadge;
- };
-
- const castAsStringArray = (array: any[]) => array as string[];
-
- const castBadgesToIndexedBadges = (array: any[]) => array as IndexedBadge[];
-
- const shadowHideBadge = () => {
- if (!selectedBadge && !authorised) return;
-
- shadowHideBadgeQuery
- .mutate({
- id: badger.id as number,
- state: selectedBadge?.shadow_hidden as boolean
- })
- .then();
- };
+ {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify(
+ importImages?.map((image) => ({
+ image: image.image,
+ post: image.link || '#',
+ category: importCategory
+ }))
+ )
+ }
+ ).then(() => {
+ importMode = false;
+ importImages = undefined;
+ });
+
+ const migrateCategory = () => {
+ fetch(
+ `/api/badges?migrate=true&original=${encodeURIComponent(
+ (document.querySelector('#migrate_original') as HTMLInputElement).value
+ )}&new=${encodeURIComponent(
+ (document.querySelector('#migrate_new') as HTMLInputElement).value
+ )}`,
+ {
+ method: 'PUT'
+ }
+ ).then(() => (migrateMode = false));
+ };
+
+ const hideCategory = () => {
+ hideCategoryQuery
+ .mutate({
+ category: (document.querySelector('#category_hide') as HTMLInputElement).value
+ })
+ .then(() => (hideMode = false));
+ };
+
+ const removeHiddenBadges = (isOwner: boolean, badges: IndexedBadge[]) =>
+ isOwner || authorised ? badges : badges.filter((b) => !b.hidden && !b.shadow_hidden);
+
+ const setAdjacentCursor = (badges: IndexedBadge[], direction: number) => {
+ const currentCategory = selectedBadge?.category || 'Uncategorised';
+ const currentBadge = selectedBadge?.index;
+ const categoryBadges = groupBadges(badges)[currentCategory];
+
+ if (!currentCategory || currentBadge === undefined) return;
+
+ let previousBadge = categoryBadges[currentBadge + direction];
+
+ while (previousBadge && (previousBadge.hidden || previousBadge.shadow_hidden))
+ previousBadge = categoryBadges[previousBadge.index + direction];
+
+ if (previousBadge) selectedBadge = previousBadge;
+ };
+
+ const adjacentBadgeExists = (
+ selectedBadge: IndexedBadge | undefined,
+ badges: IndexedBadge[],
+ direction: number
+ ) => {
+ const currentCategory = selectedBadge?.category || 'Uncategorised';
+ const currentBadge = selectedBadge?.index;
+ const categoryBadges = groupBadges(badges)[currentCategory];
+
+ if (!currentCategory || currentBadge === undefined || !categoryBadges) return;
+
+ let previousBadge = categoryBadges[currentBadge + direction];
+
+ while (previousBadge && (previousBadge.hidden || previousBadge.shadow_hidden))
+ previousBadge = categoryBadges[previousBadge.index + direction];
+
+ return previousBadge;
+ };
+
+ const castAsStringArray = (array: any[]) => array as string[];
+
+ const castBadgesToIndexedBadges = (array: any[]) => array as IndexedBadge[];
+
+ const shadowHideBadge = () => {
+ if (!selectedBadge && !authorised) return;
+
+ shadowHideBadgeQuery
+ .mutate({
+ id: badger.id as number,
+ state: selectedBadge?.shadow_hidden as boolean
+ })
+ .then();
+ };
</script>
<HeadTitle route={`${data.username}'s Badge Wall`} path={`/user/${data.username}`} />
{#if loadError}
- <Popup fullscreen locked>
- {loadError}
- </Popup>
+ <Popup fullscreen locked>
+ {loadError}
+ </Popup>
{:else}
- {@const isOwner = $identity && (isId ? $identity.id : $identity.name) === data.username}
-
- {#if $BadgeWallUser.fetching || !$BadgeWallUser.data}
- <Message message="Loading badges ..." />
-
- <Skeleton grid={true} count={100} width="150px" height="170px" />
- {:else}
- {@const ungroupedBadges = castBadgesToIndexedBadges($BadgeWallUser.data.User.badges)}
- {@const isBadgeSelected =
- selectedBadge &&
- selectedBadge !== undefined &&
- selectedBadge.image &&
- selectedBadge.image !== undefined &&
- !editMode}
-
- <div id="badges">
- {#if preferences && !preferences.hide_awc_badges}
- <AWC {awcPromise} {categoryFilter} {isOwner} {preferences} />
- {/if}
-
- {#if ungroupedBadges === null}
- <Message message="Loading badges ..." />
-
- <Skeleton grid={true} count={10} width="150px" height="170px" />
- {:else}
- {@const groupedBadges = Object.entries(
- groupBadges(removeHiddenBadges(isOwner, ungroupedBadges))
- )}
-
- {#if isOwner || authorised}
- {@const shadowHiddenCount = ungroupedBadges.filter((badge) => badge.shadow_hidden).length}
- {@const shadowHidden = shadowHiddenCount > 0}
-
- {#if shadowHidden}
- <div class="card">
- <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 />
- 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.
- </div>
- {:else if !noticeDismissed}
- <div class="card">
- <b>Notice:</b> AniList has begun purging outbound links which contain AI-generated
- 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 />
- Failure to comply with this request at your earliest convenience will result in the hiding
- of all badges from your Badge Wall.
- <p />
- <button
- on:click={() => {
- noticeDismissed = true;
-
- localStorage.setItem('badgeWallNoticeDismissed', 'true');
- }}
- >
- Dismiss
- </button>
- </div>
- {/if}
-
- <p />
-
- <div class="card">
- {#if authorised}
- <button on:click={setShadowHide}>Shadow Hide Badges</button>
- {/if}
-
- {#if isOwner && authorised}
- <span style="margin: 0 0.625rem;">•</span>
- {/if}
-
- {#if isOwner}
- <button
- on:click={() => {
- selectedBadge = undefined;
- editMode = !editMode;
- }}
- >
- {editMode
- ? $locale().user.badges.editMode.disable
- : $locale().user.badges.editMode.enable}
- </button>
- <span style="margin: 0 0.625rem;">•</span>
- <button
- on:click={() => {
- selectedBadge = undefined;
- importMode = !importMode;
- }}
- >
- {importMode
- ? $locale().user.badges.importMode.disable
- : $locale().user.badges.importMode.enable}
- </button>
- <span style="margin: 0 0.625rem;">•</span>
- <button
- on:click={() => {
- selectedBadge = undefined;
- migrateMode = !migrateMode;
- }}
- >
- Migrate Category
- </button>
- <span style="margin: 0 0.625rem;">•</span>
- <button
- on:click={() => {
- selectedBadge = undefined;
- hideMode = !hideMode;
- }}
- >
- Hide Category
- </button>
- <!-- <!-- <span style="margin: 0 0.625rem;">•</span> -->
- <!-- <button on:click={() => exportBadges(groupedBadges)}>Export Badges</button> -->
-
- {#if shadowHidden}
- <span style="margin: 0 0.625rem;">•</span>
- <button on:click={setShadowHide}>Un-shadow Hide Badges</button>
- {/if}
-
- {#if editMode && isOwner}
- {@const groups = groupedBadges
- .map((group) => group[0])
- .filter((group) => group !== 'Uncategorised')}
- {@const designers = castAsStringArray([
- ...new Set(
- ungroupedBadges
- .map((badge) => badge.designer)
- .filter((designer) => designer !== undefined && designer !== null)
- .filter(
- (designer, index, array) =>
- array.indexOf(designer) === index && !array.includes(`@${designer}`)
- )
- )
- ])}
-
- <p />
-
- {#if error}
- <p style="color: red;">{error}</p>
- {/if}
-
- <input
- type="text"
- placeholder={$locale().user.badges.editMode.imageURL}
- name="image_url"
- minlength="1"
- maxlength="1000"
- size="15"
- value={selectedBadge ? selectedBadge.image : ''}
- />
- <input
- type="text"
- placeholder={$locale().user.badges.editMode.activityURL}
- name="activity_url"
- minlength="1"
- maxlength="1000"
- size="15"
- value={selectedBadge
- ? selectedBadge.post === '#'
- ? ''
- : selectedBadge.post
- : ''}
- />
- <input
- type="text"
- placeholder={$locale().user.badges.editMode.description}
- name="description"
- minlength="1"
- maxlength="1000"
- size="15"
- value={selectedBadge ? selectedBadge.description : ''}
- />
- <Dropdown
- items={groups.map((group) => ({
- name: group,
- url: '#',
- onClick: () => {
- const category = document.querySelector('input[name="category"]');
-
- if (category instanceof HTMLInputElement) category.value = group;
- }
- }))}
- 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>
- </Dropdown>
- <span style="float: right;">
- <input
- type="datetime-local"
- value={selectedBadge && selectedBadge.time
- ? dateToInputTime(databaseTimeToDate(selectedBadge.time))
- : ''}
- />
- <small>Must be full date and time, defaults to now if any fields empty</small>
- </span>
-
- <p />
-
- <div class="edit-row-2">
- <input
- type="text"
- placeholder={$locale().user.badges.editMode.source}
- name="source"
- minlength="1"
- maxlength="1000"
- size="16"
- value={selectedBadge ? selectedBadge.source : ''}
- />
- <Dropdown
- items={designers.map((designer) => ({
- name: designer,
- url: '#',
- onClick: () => {
- const designerField = document.querySelector('input[name="designer"]');
-
- if (designerField instanceof HTMLInputElement)
- designerField.value = designer;
- }
- }))}
- 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>
- </Dropdown>
- <Dropdown
- items={[false, true].map((hidden) => ({
- name: hidden ? 'Hidden' : 'Shown',
- url: '#',
- onClick: () => {
- const hiddenInput = document.querySelector('input[name="hidden"]');
-
- if (hiddenInput instanceof HTMLInputElement)
- hiddenInput.value = hidden ? 'Hidden' : 'Shown';
- }
- }))}
- 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>
- </Dropdown>
- <button class="button-lined" on:click={submitBadge}
- >{selectedBadge
- ? $locale().user.badges.editMode.update
- : $locale().user.badges.editMode.add}</button
- >
- {#if selectedBadge}
- {$locale().user.badges.editMode.or}
- <button
- class="button-lined"
- on:click={() => {
- if (selectedBadge) removeBadge(selectedBadge);
- }}>{$locale().user.badges.editMode.delete}</button
- >
- {/if}
- </div>
- {/if}
- {/if}
- </div>
- {/if}
-
- <p />
-
- <Badges
- {ungroupedBadges}
- {groupedBadges}
- {categoryFilter}
- {editMode}
- {preferences}
- bind:selectedBadge
- />
- {/if}
- </div>
-
- {#if isBadgeSelected}
- <!-- {@const anyAdjacentBadgeExists =
+ {@const isOwner = $identity && (isId ? $identity.id : $identity.name) === data.username}
+
+ {#if $BadgeWallUser.fetching || !$BadgeWallUser.data}
+ <Message message="Loading badges ..." />
+
+ <Skeleton grid={true} count={100} width="150px" height="170px" />
+ {:else}
+ {@const ungroupedBadges = castBadgesToIndexedBadges($BadgeWallUser.data.User.badges)}
+ {@const isBadgeSelected =
+ selectedBadge &&
+ selectedBadge !== undefined &&
+ selectedBadge.image &&
+ selectedBadge.image !== undefined &&
+ !editMode}
+
+ <div id="badges">
+ {#if preferences && !preferences.hide_awc_badges}
+ <AWC {awcPromise} {categoryFilter} {isOwner} {preferences} />
+ {/if}
+
+ {#if ungroupedBadges === null}
+ <Message message="Loading badges ..." />
+
+ <Skeleton grid={true} count={10} width="150px" height="170px" />
+ {:else}
+ {@const groupedBadges = Object.entries(
+ groupBadges(removeHiddenBadges(isOwner, ungroupedBadges))
+ )}
+
+ {#if isOwner || authorised}
+ {@const shadowHiddenCount = ungroupedBadges.filter((badge) => badge.shadow_hidden).length}
+ {@const shadowHidden = shadowHiddenCount > 0}
+
+ {#if shadowHidden}
+ <div class="card">
+ <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 />
+ 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.
+ </div>
+ {:else if !noticeDismissed}
+ <div class="card">
+ <b>Notice:</b> AniList has begun purging outbound links which contain AI-generated
+ 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 />
+ Failure to comply with this request at your earliest convenience will result in the hiding
+ of all badges from your Badge Wall.
+ <p />
+ <button
+ on:click={() => {
+ noticeDismissed = true;
+
+ localStorage.setItem('badgeWallNoticeDismissed', 'true');
+ }}
+ >
+ Dismiss
+ </button>
+ </div>
+ {/if}
+
+ <p />
+
+ <div class="card">
+ {#if authorised}
+ <button on:click={setShadowHide}>Shadow Hide Badges</button>
+ {/if}
+
+ {#if isOwner && authorised}
+ <span style="margin: 0 0.625rem;">•</span>
+ {/if}
+
+ {#if isOwner}
+ <button
+ on:click={() => {
+ selectedBadge = undefined;
+ editMode = !editMode;
+ }}
+ >
+ {editMode
+ ? $locale().user.badges.editMode.disable
+ : $locale().user.badges.editMode.enable}
+ </button>
+ <span style="margin: 0 0.625rem;">•</span>
+ <button
+ on:click={() => {
+ selectedBadge = undefined;
+ importMode = !importMode;
+ }}
+ >
+ {importMode
+ ? $locale().user.badges.importMode.disable
+ : $locale().user.badges.importMode.enable}
+ </button>
+ <span style="margin: 0 0.625rem;">•</span>
+ <button
+ on:click={() => {
+ selectedBadge = undefined;
+ migrateMode = !migrateMode;
+ }}
+ >
+ Migrate Category
+ </button>
+ <span style="margin: 0 0.625rem;">•</span>
+ <button
+ on:click={() => {
+ selectedBadge = undefined;
+ hideMode = !hideMode;
+ }}
+ >
+ Hide Category
+ </button>
+ <!-- <!-- <span style="margin: 0 0.625rem;">•</span> -->
+ <!-- <button on:click={() => exportBadges(groupedBadges)}>Export Badges</button> -->
+
+ {#if shadowHidden}
+ <span style="margin: 0 0.625rem;">•</span>
+ <button on:click={setShadowHide}>Un-shadow Hide Badges</button>
+ {/if}
+
+ {#if editMode && isOwner}
+ {@const groups = groupedBadges
+ .map((group) => group[0])
+ .filter((group) => group !== 'Uncategorised')}
+ {@const designers = castAsStringArray([
+ ...new Set(
+ ungroupedBadges
+ .map((badge) => badge.designer)
+ .filter((designer) => designer !== undefined && designer !== null)
+ .filter(
+ (designer, index, array) =>
+ array.indexOf(designer) === index && !array.includes(`@${designer}`)
+ )
+ )
+ ])}
+
+ <p />
+
+ {#if error}
+ <p style="color: red;">{error}</p>
+ {/if}
+
+ <input
+ type="text"
+ placeholder={$locale().user.badges.editMode.imageURL}
+ name="image_url"
+ minlength="1"
+ maxlength="1000"
+ size="15"
+ value={selectedBadge ? selectedBadge.image : ''}
+ />
+ <input
+ type="text"
+ placeholder={$locale().user.badges.editMode.activityURL}
+ name="activity_url"
+ minlength="1"
+ maxlength="1000"
+ size="15"
+ value={selectedBadge
+ ? selectedBadge.post === '#'
+ ? ''
+ : selectedBadge.post
+ : ''}
+ />
+ <input
+ type="text"
+ placeholder={$locale().user.badges.editMode.description}
+ name="description"
+ minlength="1"
+ maxlength="1000"
+ size="15"
+ value={selectedBadge ? selectedBadge.description : ''}
+ />
+ <Dropdown
+ items={groups.map((group) => ({
+ name: group,
+ url: '#',
+ onClick: () => {
+ const category = document.querySelector('input[name="category"]');
+
+ if (category instanceof HTMLInputElement) category.value = group;
+ }
+ }))}
+ 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>
+ </Dropdown>
+ <span style="float: right;">
+ <input
+ type="datetime-local"
+ value={selectedBadge && selectedBadge.time
+ ? dateToInputTime(databaseTimeToDate(selectedBadge.time))
+ : ''}
+ />
+ <small>Must be full date and time, defaults to now if any fields empty</small>
+ </span>
+
+ <p />
+
+ <div class="edit-row-2">
+ <input
+ type="text"
+ placeholder={$locale().user.badges.editMode.source}
+ name="source"
+ minlength="1"
+ maxlength="1000"
+ size="16"
+ value={selectedBadge ? selectedBadge.source : ''}
+ />
+ <Dropdown
+ items={designers.map((designer) => ({
+ name: designer,
+ url: '#',
+ onClick: () => {
+ const designerField = document.querySelector('input[name="designer"]');
+
+ if (designerField instanceof HTMLInputElement)
+ designerField.value = designer;
+ }
+ }))}
+ 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>
+ </Dropdown>
+ <Dropdown
+ items={[false, true].map((hidden) => ({
+ name: hidden ? 'Hidden' : 'Shown',
+ url: '#',
+ onClick: () => {
+ const hiddenInput = document.querySelector('input[name="hidden"]');
+
+ if (hiddenInput instanceof HTMLInputElement)
+ hiddenInput.value = hidden ? 'Hidden' : 'Shown';
+ }
+ }))}
+ 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>
+ </Dropdown>
+ <button class="button-lined" on:click={submitBadge}
+ >{selectedBadge
+ ? $locale().user.badges.editMode.update
+ : $locale().user.badges.editMode.add}</button
+ >
+ {#if selectedBadge}
+ {$locale().user.badges.editMode.or}
+ <button
+ class="button-lined"
+ on:click={() => {
+ if (selectedBadge) removeBadge(selectedBadge);
+ }}>{$locale().user.badges.editMode.delete}</button
+ >
+ {/if}
+ </div>
+ {/if}
+ {/if}
+ </div>
+ {/if}
+
+ <p />
+
+ <Badges
+ {ungroupedBadges}
+ {groupedBadges}
+ {categoryFilter}
+ {editMode}
+ {preferences}
+ bind:selectedBadge
+ />
+ {/if}
+ </div>
+
+ {#if isBadgeSelected}
+ <!-- {@const anyAdjacentBadgeExists =
adjacentBadgeExists(selectedBadge, ungroupedBadges, -1) ||
adjacentBadgeExists(selectedBadge, ungroupedBadges, 1)} -->
- <Popup
- fullscreen
- show={isBadgeSelected}
- onLeave={() => {
- selectedBadge = undefined;
- }}
- >
- <BadgePreview
- bind:selectedBadge
- onNext={() => setAdjacentCursor(ungroupedBadges, 1)}
- onPrevious={() => setAdjacentCursor(ungroupedBadges, -1)}
- hasNext={adjacentBadgeExists(selectedBadge, ungroupedBadges, 1) !== undefined}
- hasPrevious={adjacentBadgeExists(selectedBadge, ungroupedBadges, -1) !== undefined}
- />
-
- {#if authorised}
- <button on:click={shadowHideBadge}>
- {#if selectedBadge && selectedBadge.shadow_hidden}
- Un-shadow
- {:else}
- Shadow
- {/if} Hide Badge ({selectedBadge ? selectedBadge.id : 0})
- </button>
- {/if}
- </Popup>
- {/if}
- {/if}
+ <Popup
+ fullscreen
+ show={isBadgeSelected}
+ onLeave={() => {
+ selectedBadge = undefined;
+ }}
+ >
+ <BadgePreview
+ bind:selectedBadge
+ onNext={() => setAdjacentCursor(ungroupedBadges, 1)}
+ onPrevious={() => setAdjacentCursor(ungroupedBadges, -1)}
+ hasNext={adjacentBadgeExists(selectedBadge, ungroupedBadges, 1) !== undefined}
+ hasPrevious={adjacentBadgeExists(selectedBadge, ungroupedBadges, -1) !== undefined}
+ />
+
+ {#if authorised}
+ <button on:click={shadowHideBadge}>
+ {#if selectedBadge && selectedBadge.shadow_hidden}
+ Un-shadow
+ {:else}
+ Shadow
+ {/if} Hide Badge ({selectedBadge ? selectedBadge.id : 0})
+ </button>
+ {/if}
+ </Popup>
+ {/if}
+ {/if}
{/if}
{#if true}
- <Popup fullscreen onLeave={() => (importMode = false)} show={importMode}>
- {$locale().user.badges.importMode.title}
-
- <p />
-
- <input
- type="text"
- placeholder={$locale().user.badges.editMode.activityURL}
- id="import_activity_url"
- minlength="1"
- maxlength="1000"
- size="20"
- />
- <input
- type="text"
- placeholder={$locale().user.badges.editMode.category}
- id="import_category"
- minlength="1"
- maxlength="1000"
- size="20"
- />
-
- <p />
-
- <input type="checkbox" id="import_links" name="import_links" bind:checked={importLinks} />
- {$locale().user.badges.importMode.importLinks.title}
- <SettingHint lineBreak>
- {$locale().user.badges.importMode.importLinks.hint}
- </SettingHint>
-
- <p />
-
- <input type="checkbox" id="import_links" name="import_links" bind:checked={importReplies} />
- {$locale().user.badges.importMode.importReplies}
-
- <p />
-
- <button
- on:click={() => {
- importMode = false;
- importImages = undefined;
- }}
- class="button-lined"
- >
- {$locale().user.badges.importMode.cancel}
- </button>
- <button on:click={() => parsePost()} class="button-lined" style="float: right;">
- {$locale().user.badges.importMode.fetch}
- </button>
-
- <p />
-
- <details>
- <summary>{$locale().user.badges.importMode.dangerous}</summary>
-
- <button
- class="button-lined no-shadow"
- data-umami-event="Remove All Badges"
- on:click={removeAllBadges}
- >
- {$locale({
- values: {
- times: 3 - confirmPrune
- }
- }).user.badges.importMode.deleteAll.title}
- </button>
- <SettingHint lineBreak>
- {$locale().user.badges.importMode.deleteAll.hint}
- </SettingHint>
- </details>
-
- {#if importImages && importImages.length > 0}
- <p />
-
- {$locale({
- values: {
- count: importImages.length
- }
- }).user.badges.importMode.importConfirm}&nbsp;
- <button
- on:click={() => importBadges()}
- class="button-lined no-shadow"
- data-umami-event="Import Badges"
- >
- {$locale().user.badges.importMode.import}
- </button>
-
- <SettingHint lineBreak>
- {$locale().user.badges.importMode.importWait}
- </SettingHint>
- {/if}
- </Popup>
+ <Popup fullscreen onLeave={() => (importMode = false)} show={importMode}>
+ {$locale().user.badges.importMode.title}
+
+ <p />
+
+ <input
+ type="text"
+ placeholder={$locale().user.badges.editMode.activityURL}
+ id="import_activity_url"
+ minlength="1"
+ maxlength="1000"
+ size="20"
+ />
+ <input
+ type="text"
+ placeholder={$locale().user.badges.editMode.category}
+ id="import_category"
+ minlength="1"
+ maxlength="1000"
+ size="20"
+ />
+
+ <p />
+
+ <input type="checkbox" id="import_links" name="import_links" bind:checked={importLinks} />
+ {$locale().user.badges.importMode.importLinks.title}
+ <SettingHint lineBreak>
+ {$locale().user.badges.importMode.importLinks.hint}
+ </SettingHint>
+
+ <p />
+
+ <input type="checkbox" id="import_links" name="import_links" bind:checked={importReplies} />
+ {$locale().user.badges.importMode.importReplies}
+
+ <p />
+
+ <button
+ on:click={() => {
+ importMode = false;
+ importImages = undefined;
+ }}
+ class="button-lined"
+ >
+ {$locale().user.badges.importMode.cancel}
+ </button>
+ <button on:click={() => parsePost()} class="button-lined" style="float: right;">
+ {$locale().user.badges.importMode.fetch}
+ </button>
+
+ <p />
+
+ <details>
+ <summary>{$locale().user.badges.importMode.dangerous}</summary>
+
+ <button
+ class="button-lined no-shadow"
+ data-umami-event="Remove All Badges"
+ on:click={removeAllBadges}
+ >
+ {$locale({
+ values: {
+ times: 3 - confirmPrune
+ }
+ }).user.badges.importMode.deleteAll.title}
+ </button>
+ <SettingHint lineBreak>
+ {$locale().user.badges.importMode.deleteAll.hint}
+ </SettingHint>
+ </details>
+
+ {#if importImages && importImages.length > 0}
+ <p />
+
+ {$locale({
+ values: {
+ count: importImages.length
+ }
+ }).user.badges.importMode.importConfirm}&nbsp;
+ <button
+ on:click={() => importBadges()}
+ class="button-lined no-shadow"
+ data-umami-event="Import Badges"
+ >
+ {$locale().user.badges.importMode.import}
+ </button>
+
+ <SettingHint lineBreak>
+ {$locale().user.badges.importMode.importWait}
+ </SettingHint>
+ {/if}
+ </Popup>
{/if}
<Popup fullscreen onLeave={() => (migrateMode = false)} show={migrateMode}>
- Migrate Category
-
- <p />
-
- <input
- type="text"
- placeholder="Original Category"
- id="migrate_original"
- minlength="1"
- maxlength="1000"
- size="20"
- />
- <input
- type="text"
- placeholder="New Category"
- id="migrate_new"
- minlength="1"
- maxlength="1000"
- size="20"
- />
- <SettingHint lineBreak>Leave category empty to migrate all to or from uncategorised.</SettingHint>
-
- <p />
-
- <button
- on:click={() => {
- importMode = false;
- importImages = undefined;
- }}
- class="button-lined"
- >
- {$locale().user.badges.importMode.cancel}
- </button>
- <button on:click={() => migrateCategory()} class="button-lined" style="float: right;">
- Migrate
- </button>
+ Migrate Category
+
+ <p />
+
+ <input
+ type="text"
+ placeholder="Original Category"
+ id="migrate_original"
+ minlength="1"
+ maxlength="1000"
+ size="20"
+ />
+ <input
+ type="text"
+ placeholder="New Category"
+ id="migrate_new"
+ minlength="1"
+ maxlength="1000"
+ size="20"
+ />
+ <SettingHint lineBreak>Leave category empty to migrate all to or from uncategorised.</SettingHint>
+
+ <p />
+
+ <button
+ on:click={() => {
+ importMode = false;
+ importImages = undefined;
+ }}
+ class="button-lined"
+ >
+ {$locale().user.badges.importMode.cancel}
+ </button>
+ <button on:click={() => migrateCategory()} class="button-lined" style="float: right;">
+ Migrate
+ </button>
</Popup>
<Popup fullscreen onLeave={() => (hideMode = false)} show={hideMode}>
- Hide Category
-
- <SettingHint lineBreak>
- If the majority of the badges in a category are shown, the category will be hidden, and vice
- versa.
- </SettingHint>
-
- <p />
-
- <input
- type="text"
- placeholder="Category"
- id="category_hide"
- minlength="1"
- maxlength="1000"
- size="20"
- />
- <SettingHint lineBreak>Leave category field empty to hide all.</SettingHint>
-
- <p />
-
- <button
- on:click={() => {
- hideMode = false;
- importImages = undefined;
- }}
- class="button-lined"
- >
- {$locale().user.badges.importMode.cancel}
- </button>
- <button on:click={() => hideCategory()} class="button-lined" style="float: right;"
- >Toggle Visibility</button
- >
+ Hide Category
+
+ <SettingHint lineBreak>
+ If the majority of the badges in a category are shown, the category will be hidden, and vice
+ versa.
+ </SettingHint>
+
+ <p />
+
+ <input
+ type="text"
+ placeholder="Category"
+ id="category_hide"
+ minlength="1"
+ maxlength="1000"
+ size="20"
+ />
+ <SettingHint lineBreak>Leave category field empty to hide all.</SettingHint>
+
+ <p />
+
+ <button
+ on:click={() => {
+ hideMode = false;
+ importImages = undefined;
+ }}
+ class="button-lined"
+ >
+ {$locale().user.badges.importMode.cancel}
+ </button>
+ <button on:click={() => hideCategory()} class="button-lined" style="float: right;"
+ >Toggle Visibility</button
+ >
</Popup>
diff --git a/src/routes/welcome/+page.svelte b/src/routes/welcome/+page.svelte
index 296ce30e..603e1305 100644
--- a/src/routes/welcome/+page.svelte
+++ b/src/routes/welcome/+page.svelte
@@ -1,5 +1,5 @@
<script>
- import Landing from '$lib/Landing.svelte';
+ import Landing from '$lib/Landing.svelte';
</script>
<Landing />