From ef4303810fc40e49a3220cb191bfc98e60adfb82 Mon Sep 17 00:00:00 2001 From: ThaUnknown <6506529+ThaUnknown@users.noreply.github.com> Date: Sun, 27 Apr 2025 14:39:12 +0200 Subject: [PATCH] feat: profile view component --- package.json | 2 +- src/lib/components/EpisodesList.svelte | 18 +-- src/lib/components/Shadow.svelte | 6 +- .../components/ui/avatar/avatar-image.svelte | 2 +- src/lib/components/ui/button/favorite.svelte | 3 +- src/lib/components/ui/forums/Comment.svelte | 9 +- src/lib/components/ui/forums/Threads.svelte | 17 +-- src/lib/components/ui/profile/Profile.svelte | 107 ++++++++++++++++++ src/lib/components/ui/profile/index.ts | 1 + src/lib/modules/anilist/client.ts | 3 +- src/lib/modules/anilist/graphql-turbo.d.ts | 30 ++--- src/lib/modules/anilist/queries.ts | 96 +++++++--------- src/lib/modules/navigate.ts | 1 + src/routes/app/anime/[id]/+layout.svelte | 18 +-- .../anime/[id]/thread/[threadId]/+page.svelte | 11 +- 15 files changed, 193 insertions(+), 131 deletions(-) create mode 100644 src/lib/components/ui/profile/Profile.svelte create mode 100644 src/lib/components/ui/profile/index.ts diff --git a/package.json b/package.json index 1ba6778..9ee1355 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ui", - "version": "6.0.14", + "version": "6.0.15", "license": "BUSL-1.1", "private": true, "packageManager": "pnpm@9.14.4", diff --git a/src/lib/components/EpisodesList.svelte b/src/lib/components/EpisodesList.svelte index 1802a93..6ab549a 100644 --- a/src/lib/components/EpisodesList.svelte +++ b/src/lib/components/EpisodesList.svelte @@ -13,11 +13,10 @@ import { searchStore } from './SearchModal.svelte' import { Button } from './ui/button' import { Load } from './ui/img' + import { Profile } from './ui/profile' import type { EpisodesResponse } from '$lib/modules/anizip/types' - import * as Avatar from '$lib/components/ui/avatar' - import * as Tooltip from '$lib/components/ui/tooltip' import { episodes as _episodes, dedupeAiring, episodeByAirDate, notes, type Media } from '$lib/modules/anilist' import { authAggregator, list, progress } from '$lib/modules/auth' import { click, dragScroll } from '$lib/modules/navigate' @@ -116,18 +115,9 @@ {/if}
{#each followerEntries.filter(e => e?.progress === episode) as followerEntry, i (followerEntry?.user?.id ?? i)} - - - - - {followerEntry?.user?.name ?? 'N/A'} - - - -

{followerEntry?.user?.name}

-

{followerEntry?.status?.toLowerCase()}

-
-
+ {#if followerEntry?.user} + + {/if} {/each}
diff --git a/src/lib/components/Shadow.svelte b/src/lib/components/Shadow.svelte index 51b997f..05026d5 100644 --- a/src/lib/components/Shadow.svelte +++ b/src/lib/components/Shadow.svelte @@ -2,6 +2,8 @@ import dompurify from 'dompurify' import { marked } from 'marked' + import { dragScroll } from '$lib/modules/navigate' + marked.setOptions({ gfm: true, breaks: true, @@ -18,7 +20,7 @@ margin-block-start: .5em; margin-block-end: .5em; } - img { + img, video { max-width: 100%; -webkit-user-drag: none; }`) @@ -55,4 +57,4 @@ export { className as class } -
+
diff --git a/src/lib/components/ui/avatar/avatar-image.svelte b/src/lib/components/ui/avatar/avatar-image.svelte index 47dcc8f..3e2e6c4 100644 --- a/src/lib/components/ui/avatar/avatar-image.svelte +++ b/src/lib/components/ui/avatar/avatar-image.svelte @@ -14,6 +14,6 @@ diff --git a/src/lib/components/ui/button/favorite.svelte b/src/lib/components/ui/button/favorite.svelte index 7cb140c..dc69f29 100644 --- a/src/lib/components/ui/button/favorite.svelte +++ b/src/lib/components/ui/button/favorite.svelte @@ -5,6 +5,7 @@ import { Button, iconSizes, type Props } from '$lib/components/ui/button' import { authAggregator, fav } from '$lib/modules/auth' + import { clickwrap, keywrap } from '$lib/modules/navigate' type $$Props = Props & { media: Media } @@ -15,6 +16,6 @@ export let variant: NonNullable<$$Props['variant']> = 'ghost' - diff --git a/src/lib/components/ui/forums/Comment.svelte b/src/lib/components/ui/forums/Comment.svelte index 045ee2c..7decd35 100644 --- a/src/lib/components/ui/forums/Comment.svelte +++ b/src/lib/components/ui/forums/Comment.svelte @@ -3,13 +3,13 @@ import Shadow from '../../Shadow.svelte' import { Button, iconSizes } from '../button' + import { Profile } from '../profile' import { Write } from '.' import type { CommentFrag } from '$lib/modules/anilist/queries' import type { ResultOf } from 'gql.tada' - import * as Avatar from '$lib/components/ui/avatar' import { client } from '$lib/modules/anilist' import { since } from '$lib/utils' @@ -28,10 +28,9 @@
- - - {comment.user?.name ?? 'N/A'} - + {#if comment.user} + + {/if} {comment.user?.name ?? 'N/A'}
diff --git a/src/lib/components/ui/forums/Threads.svelte b/src/lib/components/ui/forums/Threads.svelte index 2682f2b..743e833 100644 --- a/src/lib/components/ui/forums/Threads.svelte +++ b/src/lib/components/ui/forums/Threads.svelte @@ -3,9 +3,8 @@ import Pagination from '../../Pagination.svelte' import { Button } from '../button' + import { Profile } from '../profile' - import * as Avatar from '$lib/components/ui/avatar' - import * as Tooltip from '$lib/components/ui/tooltip' import { client, type Media } from '$lib/modules/anilist' import { isMobile, since } from '$lib/utils' @@ -76,17 +75,9 @@
- - - - - {thread.user?.name ?? 'N/A'} - - - -

{thread.user?.name}

-
-
+ {#if thread.user} + + {/if} {since(new Date(thread.createdAt * 1000))}
diff --git a/src/lib/components/ui/profile/Profile.svelte b/src/lib/components/ui/profile/Profile.svelte new file mode 100644 index 0000000..12cdaa2 --- /dev/null +++ b/src/lib/components/ui/profile/Profile.svelte @@ -0,0 +1,107 @@ + + +{#if user} + {@const name = user.name} + {@const avatar = user.avatar?.medium ?? ''} + {@const banner = user.bannerImage ?? ''} + {@const bubble = user.donatorBadge} +
+ + + + + {name} + + + +
+
+ {#if banner} + banner + {/if} + + + {name} + +
+
+ {name} +
+
+ {#if user.isFollower} + Follows you + {/if} + Joined {since(new Date((user.createdAt ?? 0) * 1000))} +
+
+ {#if bubble && bubble !== 'Donator'} +
+
+ + {bubble} + +
+
+ {/if} +
+ +
+ {user.statistics?.anime?.count ?? 0} anime + {user.statistics?.anime?.episodesWatched ?? 0} episodes + {since(new Date(Date.now() - (user.statistics?.anime?.minutesWatched ?? 0) * 60 * 1000)).replace('ago', 'watched')} +
+
+
+
+
+{/if} + + diff --git a/src/lib/components/ui/profile/index.ts b/src/lib/components/ui/profile/index.ts new file mode 100644 index 0000000..0eec956 --- /dev/null +++ b/src/lib/components/ui/profile/index.ts @@ -0,0 +1 @@ +export { default as Profile } from './Profile.svelte' diff --git a/src/lib/modules/anilist/client.ts b/src/lib/modules/anilist/client.ts index f789ca2..38bfe76 100644 --- a/src/lib/modules/anilist/client.ts +++ b/src/lib/modules/anilist/client.ts @@ -219,7 +219,8 @@ class AnilistClient { AiringSchedule: () => null, MediaListCollection: e => (e.user as {id: string | null}).id, MediaListGroup: () => null, - UserAvatar: () => null + UserAvatar: () => null, + UserOptions: () => null } }), authExchange(async utils => { diff --git a/src/lib/modules/anilist/graphql-turbo.d.ts b/src/lib/modules/anilist/graphql-turbo.d.ts index a2e657c..272c14a 100644 --- a/src/lib/modules/anilist/graphql-turbo.d.ts +++ b/src/lib/modules/anilist/graphql-turbo.d.ts @@ -24,30 +24,30 @@ declare module 'gql.tada' { TadaDocumentNode<{ id: number; title: { userPreferred: string | null; } | null; mediaListEntry: { status: "CURRENT" | "PLANNING" | "COMPLETED" | "DROPPED" | "PAUSED" | "REPEATING" | null; } | null; aired: { n: ({ a: number; e: number; } | null)[] | null; } | null; notaired: { n: ({ a: number; e: number; } | null)[] | null; } | null; }, {}, { fragment: "ScheduleMedia"; on: "Media"; masked: false; }>; "\n query Schedule($seasonCurrent: MediaSeason, $seasonYearCurrent: Int, $seasonLast: MediaSeason, $seasonYearLast: Int, $seasonNext: MediaSeason, $seasonYearNext: Int, $ids: [Int]) {\n curr1: Page(page: 1) {\n media(type: ANIME, season: $seasonCurrent, seasonYear: $seasonYearCurrent, countryOfOrigin: JP, format_not: TV_SHORT, onList: true, id_in: $ids) {\n ...ScheduleMedia\n }\n }\n curr2: Page(page: 2) {\n media(type: ANIME, season: $seasonCurrent, seasonYear: $seasonYearCurrent, countryOfOrigin: JP, format_not: TV_SHORT, onList: true, id_in: $ids) {\n ...ScheduleMedia\n }\n }\n curr3: Page(page: 3) {\n media(type: ANIME, season: $seasonCurrent, seasonYear: $seasonYearCurrent, countryOfOrigin: JP, format_not: TV_SHORT, onList: true, id_in: $ids) {\n ...ScheduleMedia\n }\n }\n residue: Page(page: 1) {\n media(type: ANIME, season: $seasonLast, seasonYear: $seasonYearLast, episodes_greater: 16, countryOfOrigin: JP, format_not: TV_SHORT, onList: true, id_in: $ids) {\n ...ScheduleMedia\n }\n },\n next1: Page(page: 1) {\n media(type: ANIME, season: $seasonNext, seasonYear: $seasonYearNext, sort: [START_DATE], countryOfOrigin: JP, format_not: TV_SHORT, onList: true, id_in: $ids) {\n ...ScheduleMedia\n }\n },\n next2: Page(page: 2) {\n media(type: ANIME, season: $seasonNext, seasonYear: $seasonYearNext, sort: [START_DATE], countryOfOrigin: JP, format_not: TV_SHORT, onList: true, id_in: $ids) {\n ...ScheduleMedia\n }\n }\n }\n": TadaDocumentNode<{ curr1: { media: ({ id: number; title: { userPreferred: string | null; } | null; mediaListEntry: { status: "CURRENT" | "PLANNING" | "COMPLETED" | "DROPPED" | "PAUSED" | "REPEATING" | null; } | null; aired: { n: ({ a: number; e: number; } | null)[] | null; } | null; notaired: { n: ({ a: number; e: number; } | null)[] | null; } | null; } | null)[] | null; } | null; curr2: { media: ({ id: number; title: { userPreferred: string | null; } | null; mediaListEntry: { status: "CURRENT" | "PLANNING" | "COMPLETED" | "DROPPED" | "PAUSED" | "REPEATING" | null; } | null; aired: { n: ({ a: number; e: number; } | null)[] | null; } | null; notaired: { n: ({ a: number; e: number; } | null)[] | null; } | null; } | null)[] | null; } | null; curr3: { media: ({ id: number; title: { userPreferred: string | null; } | null; mediaListEntry: { status: "CURRENT" | "PLANNING" | "COMPLETED" | "DROPPED" | "PAUSED" | "REPEATING" | null; } | null; aired: { n: ({ a: number; e: number; } | null)[] | null; } | null; notaired: { n: ({ a: number; e: number; } | null)[] | null; } | null; } | null)[] | null; } | null; residue: { media: ({ id: number; title: { userPreferred: string | null; } | null; mediaListEntry: { status: "CURRENT" | "PLANNING" | "COMPLETED" | "DROPPED" | "PAUSED" | "REPEATING" | null; } | null; aired: { n: ({ a: number; e: number; } | null)[] | null; } | null; notaired: { n: ({ a: number; e: number; } | null)[] | null; } | null; } | null)[] | null; } | null; next1: { media: ({ id: number; title: { userPreferred: string | null; } | null; mediaListEntry: { status: "CURRENT" | "PLANNING" | "COMPLETED" | "DROPPED" | "PAUSED" | "REPEATING" | null; } | null; aired: { n: ({ a: number; e: number; } | null)[] | null; } | null; notaired: { n: ({ a: number; e: number; } | null)[] | null; } | null; } | null)[] | null; } | null; next2: { media: ({ id: number; title: { userPreferred: string | null; } | null; mediaListEntry: { status: "CURRENT" | "PLANNING" | "COMPLETED" | "DROPPED" | "PAUSED" | "REPEATING" | null; } | null; aired: { n: ({ a: number; e: number; } | null)[] | null; } | null; notaired: { n: ({ a: number; e: number; } | null)[] | null; } | null; } | null)[] | null; } | null; }, { ids?: (number | null)[] | null | undefined; seasonYearNext?: number | null | undefined; seasonNext?: "WINTER" | "SPRING" | "SUMMER" | "FALL" | null | undefined; seasonYearLast?: number | null | undefined; seasonLast?: "WINTER" | "SPRING" | "SUMMER" | "FALL" | null | undefined; seasonYearCurrent?: number | null | undefined; seasonCurrent?: "WINTER" | "SPRING" | "SUMMER" | "FALL" | null | undefined; }, void>; - "\n query Following($id: Int!) {\n Page {\n mediaList(mediaId: $id, isFollowing: true, sort: UPDATED_TIME_DESC) {\n id,\n status,\n score,\n progress,\n user {\n id,\n name,\n avatar {\n medium\n }\n }\n }\n }\n }\n": - TadaDocumentNode<{ Page: { mediaList: ({ id: number; status: "CURRENT" | "PLANNING" | "COMPLETED" | "DROPPED" | "PAUSED" | "REPEATING" | null; score: number | null; progress: number | null; user: { id: number; name: string; avatar: { medium: string | null; } | null; } | null; } | null)[] | null; } | null; }, { id: number; }, void>; - "\n mutation Entry($lists: [String], $id: Int!, $status: MediaListStatus, $progress: Int, $repeat: Int, $score: Int) {\n SaveMediaListEntry(mediaId: $id, status: $status, progress: $progress, repeat: $repeat, scoreRaw: $score, customLists: $lists) {\n id,\n media {\n id,\n status,\n mediaListEntry {\n ...FullMediaList\n },\n nextAiringEpisode {\n episode\n }\n }\n }\n }\n": - TadaDocumentNode<{ SaveMediaListEntry: { id: number; media: { id: number; status: "FINISHED" | "RELEASING" | "NOT_YET_RELEASED" | "CANCELLED" | "HIATUS" | null; mediaListEntry: { id: number; status: "CURRENT" | "PLANNING" | "COMPLETED" | "DROPPED" | "PAUSED" | "REPEATING" | null; progress: number | null; repeat: number | null; score: number | null; customLists: unknown; } | null; nextAiringEpisode: { episode: number; } | null; } | null; } | null; }, { score?: number | null | undefined; repeat?: number | null | undefined; progress?: number | null | undefined; status?: "CURRENT" | "PLANNING" | "COMPLETED" | "DROPPED" | "PAUSED" | "REPEATING" | null | undefined; id: number; lists?: (string | null)[] | null | undefined; }, void>; + "\n fragment UserFrag on User @_unmask {\n id,\n bannerImage,\n about,\n isFollowing,\n isFollower,\n donatorBadge,\n options {\n profileColor\n },\n createdAt,\n name,\n avatar {\n medium\n },\n statistics {\n anime {\n count,\n minutesWatched,\n episodesWatched,\n genres(limit: 3, sort: COUNT_DESC) {\n genre,\n count\n }\n }\n }\n }\n": + TadaDocumentNode<{ id: number; bannerImage: string | null; about: string | null; isFollowing: boolean | null; isFollower: boolean | null; donatorBadge: string | null; options: { profileColor: string | null; } | null; createdAt: number | null; name: string; avatar: { medium: string | null; } | null; statistics: { anime: { count: number; minutesWatched: number; episodesWatched: number; genres: ({ genre: string | null; count: number; } | null)[] | null; } | null; } | null; }, {}, { fragment: "UserFrag"; on: "User"; masked: false; }>; + "\n query Following($id: Int!) {\n Page {\n mediaList(mediaId: $id, isFollowing: true, sort: UPDATED_TIME_DESC) {\n id,\n status,\n score,\n progress,\n user {\n ...UserFrag\n }\n }\n }\n }\n": + TadaDocumentNode<{ Page: { mediaList: ({ id: number; status: "CURRENT" | "PLANNING" | "COMPLETED" | "DROPPED" | "PAUSED" | "REPEATING" | null; score: number | null; progress: number | null; user: { id: number; bannerImage: string | null; about: string | null; isFollowing: boolean | null; isFollower: boolean | null; donatorBadge: string | null; options: { profileColor: string | null; } | null; createdAt: number | null; name: string; avatar: { medium: string | null; } | null; statistics: { anime: { count: number; minutesWatched: number; episodesWatched: number; genres: ({ genre: string | null; count: number; } | null)[] | null; } | null; } | null; } | null; } | null)[] | null; } | null; }, { id: number; }, void>; + "\n mutation Entry($lists: [String], $id: Int!, $status: MediaListStatus, $progress: Int, $repeat: Int, $score: Int) {\n SaveMediaListEntry(mediaId: $id, status: $status, progress: $progress, repeat: $repeat, scoreRaw: $score, customLists: $lists) {\n id,\n ...FullMediaList,\n media {\n id\n }\n }\n }\n": + TadaDocumentNode<{ SaveMediaListEntry: { id: number; status: "CURRENT" | "PLANNING" | "COMPLETED" | "DROPPED" | "PAUSED" | "REPEATING" | null; progress: number | null; repeat: number | null; score: number | null; customLists: unknown; media: { id: number; } | null; } | null; }, { score?: number | null | undefined; repeat?: number | null | undefined; progress?: number | null | undefined; status?: "CURRENT" | "PLANNING" | "COMPLETED" | "DROPPED" | "PAUSED" | "REPEATING" | null | undefined; id: number; lists?: (string | null)[] | null | undefined; }, void>; "\n mutation DeleteEntry($id: Int!) {\n DeleteMediaListEntry(id: $id) {\n deleted\n }\n }\n": TadaDocumentNode<{ DeleteMediaListEntry: { deleted: boolean | null; } | null; }, { id: number; }, void>; "\n mutation ToggleFavourite($id: Int!) {\n ToggleFavourite(animeId: $id) { anime { nodes { id } } } \n }\n": TadaDocumentNode<{ ToggleFavourite: { anime: { nodes: ({ id: number; } | null)[] | null; } | null; } | null; }, { id: number; }, void>; - "\n fragment ThreadFrag on Thread @_unmask {\n id,\n title,\n body(asHtml: true),\n userId,\n replyCount,\n viewCount,\n isLocked,\n isSubscribed,\n isLiked,\n likeCount,\n repliedAt,\n createdAt,\n user {\n id,\n name,\n avatar {\n medium\n }\n },\n categories {\n id,\n name\n }\n }\n": - TadaDocumentNode<{ id: number; title: string | null; body: string | null; userId: number; replyCount: number | null; viewCount: number | null; isLocked: boolean | null; isSubscribed: boolean | null; isLiked: boolean | null; likeCount: number; repliedAt: number | null; createdAt: number; user: { id: number; name: string; avatar: { medium: string | null; } | null; } | null; categories: ({ id: number; name: string; } | null)[] | null; }, {}, { fragment: "ThreadFrag"; on: "Thread"; masked: false; }>; + "\n fragment ThreadFrag on Thread @_unmask {\n id,\n title,\n body,\n userId,\n replyCount,\n viewCount,\n isLocked,\n isSubscribed,\n isLiked,\n likeCount,\n repliedAt,\n createdAt,\n user {\n ...UserFrag\n },\n categories {\n id,\n name\n }\n }\n": + TadaDocumentNode<{ id: number; title: string | null; body: string | null; userId: number; replyCount: number | null; viewCount: number | null; isLocked: boolean | null; isSubscribed: boolean | null; isLiked: boolean | null; likeCount: number; repliedAt: number | null; createdAt: number; user: { id: number; bannerImage: string | null; about: string | null; isFollowing: boolean | null; isFollower: boolean | null; donatorBadge: string | null; options: { profileColor: string | null; } | null; createdAt: number | null; name: string; avatar: { medium: string | null; } | null; statistics: { anime: { count: number; minutesWatched: number; episodesWatched: number; genres: ({ genre: string | null; count: number; } | null)[] | null; } | null; } | null; } | null; categories: ({ id: number; name: string; } | null)[] | null; }, {}, { fragment: "ThreadFrag"; on: "Thread"; masked: false; }>; "\n query Threads($id: Int!, $page: Int, $perPage: Int) {\n Page(page: $page, perPage: $perPage) {\n pageInfo {\n hasNextPage\n },\n threads(mediaCategoryId: $id, sort: ID_DESC) {\n ...ThreadFrag\n }\n }\n }\n": - TadaDocumentNode<{ Page: { pageInfo: { hasNextPage: boolean | null; } | null; threads: ({ id: number; title: string | null; body: string | null; userId: number; replyCount: number | null; viewCount: number | null; isLocked: boolean | null; isSubscribed: boolean | null; isLiked: boolean | null; likeCount: number; repliedAt: number | null; createdAt: number; user: { id: number; name: string; avatar: { medium: string | null; } | null; } | null; categories: ({ id: number; name: string; } | null)[] | null; } | null)[] | null; } | null; }, { perPage?: number | null | undefined; page?: number | null | undefined; id: number; }, void>; + TadaDocumentNode<{ Page: { pageInfo: { hasNextPage: boolean | null; } | null; threads: ({ id: number; title: string | null; body: string | null; userId: number; replyCount: number | null; viewCount: number | null; isLocked: boolean | null; isSubscribed: boolean | null; isLiked: boolean | null; likeCount: number; repliedAt: number | null; createdAt: number; user: { id: number; bannerImage: string | null; about: string | null; isFollowing: boolean | null; isFollower: boolean | null; donatorBadge: string | null; options: { profileColor: string | null; } | null; createdAt: number | null; name: string; avatar: { medium: string | null; } | null; statistics: { anime: { count: number; minutesWatched: number; episodesWatched: number; genres: ({ genre: string | null; count: number; } | null)[] | null; } | null; } | null; } | null; categories: ({ id: number; name: string; } | null)[] | null; } | null)[] | null; } | null; }, { perPage?: number | null | undefined; page?: number | null | undefined; id: number; }, void>; "\n query Thread($threadId: Int!) {\n Thread(id: $threadId) {\n ...ThreadFrag\n }\n }\n": - TadaDocumentNode<{ Thread: { id: number; title: string | null; body: string | null; userId: number; replyCount: number | null; viewCount: number | null; isLocked: boolean | null; isSubscribed: boolean | null; isLiked: boolean | null; likeCount: number; repliedAt: number | null; createdAt: number; user: { id: number; name: string; avatar: { medium: string | null; } | null; } | null; categories: ({ id: number; name: string; } | null)[] | null; } | null; }, { threadId: number; }, void>; - "\n fragment CommentFrag on ThreadComment @_unmask {\n id,\n comment(asHtml: true),\n isLiked,\n likeCount,\n createdAt,\n user {\n id,\n name,\n moderatorRoles,\n avatar {\n medium\n },\n },\n childComments{\n id\n },\n isLocked\n }\n": - TadaDocumentNode<{ id: number; comment: string | null; isLiked: boolean | null; likeCount: number; createdAt: number; user: { id: number; name: string; moderatorRoles: ("ADMIN" | "LEAD_DEVELOPER" | "DEVELOPER" | "LEAD_COMMUNITY" | "COMMUNITY" | "DISCORD_COMMUNITY" | "LEAD_ANIME_DATA" | "ANIME_DATA" | "LEAD_MANGA_DATA" | "MANGA_DATA" | "LEAD_SOCIAL_MEDIA" | "SOCIAL_MEDIA" | "RETIRED" | "CHARACTER_DATA" | "STAFF_DATA" | null)[] | null; avatar: { medium: string | null; } | null; } | null; childComments: unknown; isLocked: boolean | null; }, {}, { fragment: "CommentFrag"; on: "ThreadComment"; masked: false; }>; - "\n query Comments($threadId: Int, $page: Int) {\n Page(page: $page, perPage: 15) {\n pageInfo {\n hasNextPage\n }\n threadComments(threadId: $threadId) {\n id,\n comment(asHtml: true),\n isLiked,\n likeCount,\n createdAt,\n user {\n id,\n name,\n moderatorRoles,\n avatar {\n medium\n },\n },\n childComments{\n id\n },\n isLocked\n }\n }\n }\n": - TadaDocumentNode<{ Page: { pageInfo: { hasNextPage: boolean | null; } | null; threadComments: ({ id: number; comment: string | null; isLiked: boolean | null; likeCount: number; createdAt: number; user: { id: number; name: string; moderatorRoles: ("ADMIN" | "LEAD_DEVELOPER" | "DEVELOPER" | "LEAD_COMMUNITY" | "COMMUNITY" | "DISCORD_COMMUNITY" | "LEAD_ANIME_DATA" | "ANIME_DATA" | "LEAD_MANGA_DATA" | "MANGA_DATA" | "LEAD_SOCIAL_MEDIA" | "SOCIAL_MEDIA" | "RETIRED" | "CHARACTER_DATA" | "STAFF_DATA" | null)[] | null; avatar: { medium: string | null; } | null; } | null; childComments: unknown; isLocked: boolean | null; } | null)[] | null; } | null; }, { page?: number | null | undefined; threadId?: number | null | undefined; }, void>; - "\n query User ($id: Int) {\n User(id: $id) {\n id,\n name,\n avatar {\n large\n },\n bannerImage,\n about(asHtml: true),\n isFollowing,\n isFollower,\n donatorBadge,\n createdAt,\n options {\n profileColor\n },\n statistics {\n anime {\n count,\n minutesWatched,\n episodesWatched,\n genrePreview: genres(limit: 10, sort: COUNT_DESC) {\n genre,\n count\n }\n }\n }\n }\n }\n": - TadaDocumentNode<{ User: { id: number; name: string; avatar: { large: string | null; } | null; bannerImage: string | null; about: string | null; isFollowing: boolean | null; isFollower: boolean | null; donatorBadge: string | null; createdAt: number | null; options: { profileColor: string | null; } | null; statistics: { anime: { count: number; minutesWatched: number; episodesWatched: number; genrePreview: ({ genre: string | null; count: number; } | null)[] | null; } | null; } | null; } | null; }, { id?: number | null | undefined; }, void>; + TadaDocumentNode<{ Thread: { id: number; title: string | null; body: string | null; userId: number; replyCount: number | null; viewCount: number | null; isLocked: boolean | null; isSubscribed: boolean | null; isLiked: boolean | null; likeCount: number; repliedAt: number | null; createdAt: number; user: { id: number; bannerImage: string | null; about: string | null; isFollowing: boolean | null; isFollower: boolean | null; donatorBadge: string | null; options: { profileColor: string | null; } | null; createdAt: number | null; name: string; avatar: { medium: string | null; } | null; statistics: { anime: { count: number; minutesWatched: number; episodesWatched: number; genres: ({ genre: string | null; count: number; } | null)[] | null; } | null; } | null; } | null; categories: ({ id: number; name: string; } | null)[] | null; } | null; }, { threadId: number; }, void>; + "\n fragment CommentFrag on ThreadComment @_unmask {\n id,\n comment,\n isLiked,\n likeCount,\n createdAt,\n user {\n ...UserFrag\n },\n childComments,\n isLocked\n }\n": + TadaDocumentNode<{ id: number; comment: string | null; isLiked: boolean | null; likeCount: number; createdAt: number; user: { id: number; bannerImage: string | null; about: string | null; isFollowing: boolean | null; isFollower: boolean | null; donatorBadge: string | null; options: { profileColor: string | null; } | null; createdAt: number | null; name: string; avatar: { medium: string | null; } | null; statistics: { anime: { count: number; minutesWatched: number; episodesWatched: number; genres: ({ genre: string | null; count: number; } | null)[] | null; } | null; } | null; } | null; childComments: unknown; isLocked: boolean | null; }, {}, { fragment: "CommentFrag"; on: "ThreadComment"; masked: false; }>; + "\n query Comments($threadId: Int, $page: Int) {\n Page(page: $page, perPage: 15) {\n pageInfo {\n hasNextPage\n }\n threadComments(threadId: $threadId) {\n id,\n comment,\n isLiked,\n likeCount,\n createdAt,\n user {\n id,\n name,\n avatar {\n medium\n },\n },\n childComments,\n isLocked\n }\n }\n }\n": + TadaDocumentNode<{ Page: { pageInfo: { hasNextPage: boolean | null; } | null; threadComments: ({ id: number; comment: string | null; isLiked: boolean | null; likeCount: number; createdAt: number; user: { id: number; name: string; avatar: { medium: string | null; } | null; } | null; childComments: unknown; isLocked: boolean | null; } | null)[] | null; } | null; }, { page?: number | null | undefined; threadId?: number | null | undefined; }, void>; "\n mutation ToggleLike ($id: Int!, $type: LikeableType!) {\n ToggleLikeV2(id: $id, type: $type) {\n ... on Thread {\n id\n likeCount\n isLiked\n }\n ... on ThreadComment {\n id\n likeCount\n isLiked\n }\n }\n }\n": TadaDocumentNode<{ ToggleLikeV2: { __typename?: "ActivityReply" | undefined; } | { __typename?: "ListActivity" | undefined; } | { __typename?: "MessageActivity" | undefined; } | { __typename?: "TextActivity" | undefined; } | { __typename?: "Thread" | undefined; id: number; likeCount: number; isLiked: boolean | null; } | { __typename?: "ThreadComment" | undefined; id: number; likeCount: number; isLiked: boolean | null; } | null; }, { type: "THREAD" | "THREAD_COMMENT" | "ACTIVITY" | "ACTIVITY_REPLY"; id: number; }, void>; "\n mutation SaveThreadComment ($id: Int, $threadId: Int, $parentCommentId: Int, $comment: String) {\n SaveThreadComment(id: $id, threadId: $threadId, parentCommentId: $parentCommentId, comment: $comment) {\n ...CommentFrag\n }\n }\n": - TadaDocumentNode<{ SaveThreadComment: { id: number; comment: string | null; isLiked: boolean | null; likeCount: number; createdAt: number; user: { id: number; name: string; moderatorRoles: ("ADMIN" | "LEAD_DEVELOPER" | "DEVELOPER" | "LEAD_COMMUNITY" | "COMMUNITY" | "DISCORD_COMMUNITY" | "LEAD_ANIME_DATA" | "ANIME_DATA" | "LEAD_MANGA_DATA" | "MANGA_DATA" | "LEAD_SOCIAL_MEDIA" | "SOCIAL_MEDIA" | "RETIRED" | "CHARACTER_DATA" | "STAFF_DATA" | null)[] | null; avatar: { medium: string | null; } | null; } | null; childComments: unknown; isLocked: boolean | null; } | null; }, { comment?: string | null | undefined; parentCommentId?: number | null | undefined; threadId?: number | null | undefined; id?: number | null | undefined; }, void>; + TadaDocumentNode<{ SaveThreadComment: { id: number; comment: string | null; isLiked: boolean | null; likeCount: number; createdAt: number; user: { id: number; bannerImage: string | null; about: string | null; isFollowing: boolean | null; isFollower: boolean | null; donatorBadge: string | null; options: { profileColor: string | null; } | null; createdAt: number | null; name: string; avatar: { medium: string | null; } | null; statistics: { anime: { count: number; minutesWatched: number; episodesWatched: number; genres: ({ genre: string | null; count: number; } | null)[] | null; } | null; } | null; } | null; childComments: unknown; isLocked: boolean | null; } | null; }, { comment?: string | null | undefined; parentCommentId?: number | null | undefined; threadId?: number | null | undefined; id?: number | null | undefined; }, void>; "\n mutation DeleteThreadComment ($id: Int) {\n DeleteThreadComment(id: $id) {\n deleted\n }\n }\n": TadaDocumentNode<{ DeleteThreadComment: { deleted: boolean | null; } | null; }, { id?: number | null | undefined; }, void>; "fragment Med on Media {id, isFavourite}": diff --git a/src/lib/modules/anilist/queries.ts b/src/lib/modules/anilist/queries.ts index 060a7e0..c98d765 100644 --- a/src/lib/modules/anilist/queries.ts +++ b/src/lib/modules/anilist/queries.ts @@ -250,6 +250,36 @@ export const Schedule = gql(` } `, [ScheduleMedia]) +export const UserFrag = gql(` + fragment UserFrag on User @_unmask { + id, + bannerImage, + about, + isFollowing, + isFollower, + donatorBadge, + options { + profileColor + }, + createdAt, + name, + avatar { + medium + }, + statistics { + anime { + count, + minutesWatched, + episodesWatched, + genres(limit: 3, sort: COUNT_DESC) { + genre, + count + } + } + } + } +`) + export const Following = gql(` query Following($id: Int!) { Page { @@ -259,16 +289,12 @@ export const Following = gql(` score, progress, user { - id, - name, - avatar { - medium - } + ...UserFrag } } } } -`) +`, [UserFrag]) // AL API is dog, fullmedialist is NULL when queried inside media..., it's possible this can cause cache loops, but there's no other way to do this!!! export const Entry = gql(` @@ -312,18 +338,14 @@ export const ThreadFrag = gql(` repliedAt, createdAt, user { - id, - name, - avatar { - medium - } + ...UserFrag }, categories { id, name } } -`) +`, [UserFrag]) export const Threads = gql(` query Threads($id: Int!, $page: Int, $perPage: Int) { @@ -354,17 +376,12 @@ export const CommentFrag = gql(` likeCount, createdAt, user { - id, - name, - moderatorRoles, - avatar { - medium - }, + ...UserFrag }, childComments, isLocked } -`) +`, [UserFrag]) // AL in their infinite wisdom decided to make childComments infer the schema of the parent comment, so we can't use the CommentFrag here export const Comments = gql(` @@ -380,51 +397,14 @@ export const Comments = gql(` likeCount, createdAt, user { - id, - name, - moderatorRoles, - avatar { - medium - }, + ...UserFrag }, childComments, isLocked } } } -`) - -export const User = gql(` - query User ($id: Int) { - User(id: $id) { - id, - name, - avatar { - large - }, - bannerImage, - about, - isFollowing, - isFollower, - donatorBadge, - createdAt, - options { - profileColor - }, - statistics { - anime { - count, - minutesWatched, - episodesWatched, - genrePreview: genres(limit: 10, sort: COUNT_DESC) { - genre, - count - } - } - } - } - } -`) +`, [UserFrag]) export const ToggleLike = gql(` mutation ToggleLike ($id: Int!, $type: LikeableType!) { diff --git a/src/lib/modules/navigate.ts b/src/lib/modules/navigate.ts index dd5cd89..d90e3f9 100644 --- a/src/lib/modules/navigate.ts +++ b/src/lib/modules/navigate.ts @@ -26,6 +26,7 @@ for (const { pointer, value } of pointerTypes) { const noop: () => void = () => undefined +// this is for nested click elements, its svelte's |preventDefault for other components export function clickwrap (cb: (_: MouseEvent) => unknown = noop) { return (e: MouseEvent) => { e.stopPropagation() diff --git a/src/routes/app/anime/[id]/+layout.svelte b/src/routes/app/anime/[id]/+layout.svelte index 07e3a94..0b76d9b 100644 --- a/src/routes/app/anime/[id]/+layout.svelte +++ b/src/routes/app/anime/[id]/+layout.svelte @@ -8,12 +8,11 @@ import Anilist from '$lib/components/icons/Anilist.svelte' import MyAnimeList from '$lib/components/icons/MyAnimeList.svelte' - import * as Avatar from '$lib/components/ui/avatar' import { bannerSrc, hideBanner } from '$lib/components/ui/banner' import { PlayButton, Button, BookmarkButton, FavoriteButton } from '$lib/components/ui/button' import * as Dialog from '$lib/components/ui/dialog' import { Load } from '$lib/components/ui/img' - import * as Tooltip from '$lib/components/ui/tooltip' + import { Profile } from '$lib/components/ui/profile' import { cover, desc, duration, format, season, status, title } from '$lib/modules/anilist' import { authAggregator, of } from '$lib/modules/auth' import native from '$lib/modules/native' @@ -142,18 +141,9 @@
diff --git a/src/routes/app/anime/[id]/thread/[threadId]/+page.svelte b/src/routes/app/anime/[id]/thread/[threadId]/+page.svelte index 1fccd13..a6da454 100644 --- a/src/routes/app/anime/[id]/thread/[threadId]/+page.svelte +++ b/src/routes/app/anime/[id]/thread/[threadId]/+page.svelte @@ -4,9 +4,9 @@ import type { PageData } from './$types' import Shadow from '$lib/components/Shadow.svelte' - import * as Avatar from '$lib/components/ui/avatar' import { Button, iconSizes } from '$lib/components/ui/button' import { Comments, Write } from '$lib/components/ui/forums' + import { Profile } from '$lib/components/ui/profile' import { client } from '$lib/modules/anilist' import { since } from '$lib/utils' @@ -46,11 +46,10 @@
- - - {thread.user?.name ?? 'N/A'} - - {thread.user?.name ?? 'N/A'} + {#if thread.user} + + {thread.user.name ?? 'N/A'} + {/if}