[READ-ONLY] Mirror of https://github.com/flo-bit/skywatched. review movies and tv shows, based on at proto skywatched.app
0

Configure Feed

Select the types of activity you want to include in your feed.

some fixes, formatting

Florian (Feb 12, 2025, 2:05 AM +0100) dbee4d0a c440536e

+500 -621
+1 -1
package.json
··· 67 67 "svelte-sonner": "^0.3.28", 68 68 "tailwind-merge": "^2.5.4" 69 69 } 70 - } 70 + }
+2 -2
src/lib/Components/Items/BlueskyPost/Avatar.svelte
··· 13 13 {#snippet avatar()} 14 14 <div 15 15 class={[ 16 - 'border-base-400/50 bg-base-100 dark:border-base-700 dark:bg-base-800 overflow-hidden rounded-full border', 16 + 'overflow-hidden rounded-full border border-base-400/50 bg-base-100 dark:border-base-700 dark:bg-base-800', 17 17 size, 18 18 className 19 19 ]} ··· 22 22 <img loading="lazy" class="h-full w-full object-cover" {src} {alt} /> 23 23 {:else} 24 24 <svg 25 - class="text-base-300 dark:text-base-600 size-full" 25 + class="size-full text-base-300 dark:text-base-600" 26 26 fill="currentColor" 27 27 viewBox="0 0 24 24" 28 28 >
+10 -10
src/lib/Components/Items/BlueskyPost/Comment.svelte
··· 13 13 </script> 14 14 15 15 {#snippet top(expand: boolean)} 16 - <div class="text-base-600 dark:text-base-500 -ml-6 flex items-center text-sm"> 16 + <div class="-ml-6 flex items-center text-sm text-base-600 dark:text-base-500"> 17 17 <div class="relative size-6"> 18 18 <Avatar 19 19 src={comment.post.author.avatar} ··· 43 43 <a 44 44 target="_blank" 45 45 rel="noopener noreferrer nofollow" 46 - class="dark:text-base-100 hover:text-base-500 dark:hover:text-base-300 ml-2" 46 + class="ml-2 hover:text-base-500 dark:text-base-100 dark:hover:text-base-300" 47 47 href={`https://bsky.app/profile/${comment.post.author.did}`} 48 48 > 49 49 {comment.post.author.displayName || comment.post.author.handle} 50 50 </a> 51 51 52 - <div class="text-base-400 ml-2 text-xs"> 52 + <div class="ml-2 text-xs text-base-400"> 53 53 <RelativeTime date={new Date(comment.post.record.createdAt)} locale="en" /> 54 54 </div> 55 55 </div> ··· 57 57 58 58 <div class="relative pl-3"> 59 59 <button 60 - class="group absolute top-0 -left-1.5 flex h-full w-3 cursor-pointer items-center" 60 + class="group absolute -left-1.5 top-0 flex h-full w-3 cursor-pointer items-center" 61 61 onclick={() => (expanded = !expanded)} 62 62 > 63 63 <div 64 - class="bg-base-200 dark:bg-base-800 group-hover:bg-base-300 dark:group-hover:bg-base-700 mx-auto h-full w-0.5" 64 + class="mx-auto h-full w-0.5 bg-base-200 group-hover:bg-base-300 dark:bg-base-800 dark:group-hover:bg-base-700" 65 65 ></div> 66 66 <span class="sr-only">collapse comment</span> 67 67 </button> ··· 77 77 <Embed post={comment.post} /> 78 78 {/if} 79 79 80 - <div class="text-base-500 dark:text-base-400 mt-2 flex gap-8"> 80 + <div class="mt-2 flex gap-8 text-base-500 dark:text-base-400"> 81 81 <a 82 82 href={atUriToPostUri(comment.post.uri)} 83 83 target="_blank" ··· 90 90 viewBox="0 0 24 24" 91 91 stroke-width="1.5" 92 92 stroke="currentColor" 93 - class="group-hover:bg-accent-500/10 group-hover:text-accent-700 dark:group-hover:text-accent-400 -m-1.5 size-7 rounded-full p-1.5 transition-all duration-100" 93 + class="-m-1.5 size-7 rounded-full p-1.5 transition-all duration-100 group-hover:bg-accent-500/10 group-hover:text-accent-700 dark:group-hover:text-accent-400" 94 94 > 95 95 <path 96 96 stroke-linecap="round" ··· 114 114 viewBox="0 0 24 24" 115 115 stroke-width="1.5" 116 116 stroke="currentColor" 117 - class="group-hover:bg-accent-500/10 group-hover:text-accent-700 dark:group-hover:text-accent-400 -m-1.5 size-7 rounded-full p-1.5 transition-all duration-100" 117 + class="-m-1.5 size-7 rounded-full p-1.5 transition-all duration-100 group-hover:bg-accent-500/10 group-hover:text-accent-700 dark:group-hover:text-accent-400" 118 118 > 119 119 <path 120 120 stroke-linecap="round" ··· 137 137 viewBox="0 0 24 24" 138 138 stroke-width="1.5" 139 139 stroke="currentColor" 140 - class="group-hover:bg-accent-500/10 group-hover:text-accent-700 dark:group-hover:text-accent-400 -m-1.5 size-7 rounded-full p-1.5 transition-all duration-100" 140 + class="-m-1.5 size-7 rounded-full p-1.5 transition-all duration-100 group-hover:bg-accent-500/10 group-hover:text-accent-700 dark:group-hover:text-accent-400" 141 141 > 142 142 <path 143 143 stroke-linecap="round" ··· 155 155 href={atUriToPostUri(comment.post.uri)} 156 156 target="_blank" 157 157 rel="noopener noreferrer nofollow" 158 - class="text-base-500 dark:text-base-400 dark:hover:text-base-300 hover:text-base-600 text-sm font-medium" 158 + class="text-sm font-medium text-base-500 hover:text-base-600 dark:text-base-400 dark:hover:text-base-300" 159 159 >View more replies on bluesky</a 160 160 > 161 161 {/if}
+30 -30
src/lib/Components/Items/BlueskyPost/Comments.svelte
··· 1 1 <script lang="ts"> 2 - import { onMount } from "svelte"; 3 - import { getUserPosts, getComments, getCommentCount } from "./utils"; 4 - import Comment from "./Comment.svelte"; 2 + import { onMount } from 'svelte'; 3 + import { getUserPosts, getComments, getCommentCount } from './utils'; 4 + import Comment from './Comment.svelte'; 5 5 6 - let { uri, user, comments, url } = $props(); 6 + let { uri, user, comments, url } = $props(); 7 7 8 - let postUri = $state(uri); 8 + let postUri = $state(uri); 9 9 10 - onMount(async () => { 11 - if (!uri && user) { 12 - let posts = await getUserPosts(user); 10 + onMount(async () => { 11 + if (!uri && user) { 12 + let posts = await getUserPosts(user); 13 13 14 - // @ts-expect-error: weird type fuckery 15 - const post = posts.find((post) => post.post.embed?.external?.uri === url); 14 + // @ts-expect-error: weird type fuckery 15 + const post = posts.find((post) => post.post.embed?.external?.uri === url); 16 16 17 - if (post) { 18 - postUri = post.post.uri; 19 - comments = await getComments(post.post.uri); 20 - } 21 - } else if (uri) { 22 - comments = await getComments(uri); 23 - } 24 - }); 17 + if (post) { 18 + postUri = post.post.uri; 19 + comments = await getComments(post.post.uri); 20 + } 21 + } else if (uri) { 22 + comments = await getComments(uri); 23 + } 24 + }); 25 25 </script> 26 26 27 27 <div class="not-prose mt-2"> 28 - {#if comments.length > 0} 29 - <div class="text-sm text-base-950 dark:text-base-100 font-semibold"> 30 - {getCommentCount(comments)} comments, sorted by newest first 31 - </div> 32 - {/if} 28 + {#if comments.length > 0} 29 + <div class="text-sm font-semibold text-base-950 dark:text-base-100"> 30 + {getCommentCount(comments)} comments, sorted by newest first 31 + </div> 32 + {/if} 33 33 34 - {#if comments.length > 0} 35 - <div class="pt-4"> 36 - {#each comments as comment} 37 - <Comment {comment} /> 38 - {/each} 39 - </div> 40 - {/if} 34 + {#if comments.length > 0} 35 + <div class="pt-4"> 36 + {#each comments as comment} 37 + <Comment {comment} /> 38 + {/each} 39 + </div> 40 + {/if} 41 41 </div>
+3 -3
src/lib/Components/Items/BlueskyPost/Likes.svelte
··· 37 37 38 38 {#if postUri} 39 39 <div class="not-prose flex flex-col gap-4"> 40 - <div class="text-base-950 dark:text-base-100 text-sm font-semibold"> 40 + <div class="text-sm font-semibold text-base-950 dark:text-base-100"> 41 41 {postLikesCount} like{postLikesCount === 1 ? '' : 's'} 42 42 </div> 43 43 ··· 46 46 <a 47 47 href={`https://bsky.app/profile/${user.actor.handle}`} 48 48 class={[ 49 - 'ring-base-50 dark:ring-base-900 bg-base-950 relative inline-block size-12 overflow-hidden rounded-full ring-2', 49 + 'relative inline-block size-12 overflow-hidden rounded-full bg-base-950 ring-2 ring-base-50 dark:ring-base-900', 50 50 index === 0 ? '-ml-2' : '' 51 51 ]} 52 52 target="_blank" ··· 62 62 63 63 {#if postLikesData.length < postLikesCount} 64 64 <div 65 - class="text-accent-700 dark:text-accent-300 bg-accent-100 dark:bg-accent-950 ring-base-50 dark:ring-base-900 z-10 mb-4 flex size-12 items-center justify-center rounded-full text-sm font-semibold ring-2" 65 + class="z-10 mb-4 flex size-12 items-center justify-center rounded-full bg-accent-100 text-sm font-semibold text-accent-700 ring-2 ring-base-50 dark:bg-accent-950 dark:text-accent-300 dark:ring-base-900" 66 66 > 67 67 +{postLikesCount - postLikesData.length} 68 68 </div>
+4 -4
src/lib/Components/Items/BlueskyPost/embeds/External.svelte
··· 7 7 </script> 8 8 9 9 <article 10 - class="bg-base-900 ring-base-800 relative isolate flex aspect-16/9 h-64 flex-col justify-end rounded-2xl p-4 ring-1" 10 + class="aspect-16/9 relative isolate flex h-64 flex-col justify-end rounded-2xl bg-base-900 p-4 ring-1 ring-base-800" 11 11 > 12 12 {#if data.thumb} 13 13 <img ··· 17 17 /> 18 18 {/if} 19 19 <div 20 - class="from-base-950/90 via-base-950/40 absolute inset-0 -z-10 rounded-xl bg-linear-to-t" 20 + class="bg-linear-to-t absolute inset-0 -z-10 rounded-xl from-base-950/90 via-base-950/40" 21 21 ></div> 22 22 23 23 <div 24 - class="inset-shadow-md inset-ring-base-950/50 inset-shadow-base-950/40 ring-base-800 absolute inset-0 -z-10 h-full w-full rounded-2xl inset-ring-1 ring-1" 24 + class="inset-shadow-md inset-ring-base-950/50 inset-shadow-base-950/40 inset-ring-1 absolute inset-0 -z-10 h-full w-full rounded-2xl ring-1 ring-base-800" 25 25 ></div> 26 26 27 - <div class="text-base-300 flex flex-wrap items-center gap-y-1 overflow-hidden text-sm/6"> 27 + <div class="flex flex-wrap items-center gap-y-1 overflow-hidden text-sm/6 text-base-300"> 28 28 <div>{domain}</div> 29 29 </div> 30 30 <h3 class="mt-1 text-lg/6 font-semibold text-white">
+2 -2
src/lib/Components/Items/BlueskyPost/embeds/Images.svelte
··· 3 3 </script> 4 4 5 5 {#each data as { fullsize: string; alt: string; thumb: string; aspectRatio: { width: number; height: number } }[] as image} 6 - <div class="ring-base-800 relative h-fit w-fit rounded-2xl ring-1"> 6 + <div class="relative h-fit w-fit rounded-2xl ring-1 ring-base-800"> 7 7 <img 8 8 loading="lazy" 9 9 src={image.thumb} ··· 14 14 /> 15 15 16 16 <div 17 - class="inset-shadow-md inset-shadow-base-950/30 inset-ring-base-950/10 absolute inset-0 h-full w-full rounded-2xl inset-ring-2" 17 + class="inset-shadow-md inset-shadow-base-950/30 inset-ring-base-950/10 inset-ring-2 absolute inset-0 h-full w-full rounded-2xl" 18 18 ></div> 19 19 </div> 20 20 {/each}
+30 -21
src/lib/Components/Items/BlueskyPost/embeds/Video.svelte
··· 1 1 <script lang="ts"> 2 - import { onMount } from "svelte"; 3 - import Hls from "hls.js"; 2 + import { onMount } from 'svelte'; 3 + import Hls from 'hls.js'; 4 4 5 - const { data } = $props(); 5 + const { data } = $props(); 6 6 7 - import Plyr from "plyr"; 7 + import Plyr from 'plyr'; 8 8 9 - onMount(() => { 10 - if (Hls.isSupported()) { 11 - var hls = new Hls(); 12 - hls.loadSource(data.playlist); 13 - hls.attachMedia(element); 14 - } 9 + onMount(() => { 10 + if (Hls.isSupported()) { 11 + var hls = new Hls(); 12 + hls.loadSource(data.playlist); 13 + hls.attachMedia(element); 14 + } 15 15 16 - new Plyr(element, { 17 - controls: ["play-large", "play", "progress", "current-time", "mute", "volume", "fullscreen", "settings"], 18 - hideControls: true 19 - }); 20 - }); 16 + new Plyr(element, { 17 + controls: [ 18 + 'play-large', 19 + 'play', 20 + 'progress', 21 + 'current-time', 22 + 'mute', 23 + 'volume', 24 + 'fullscreen', 25 + 'settings' 26 + ], 27 + hideControls: true 28 + }); 29 + }); 21 30 22 - let element: HTMLMediaElement; 31 + let element: HTMLMediaElement; 23 32 </script> 24 33 25 34 <svelte:head> 26 - <link rel="stylesheet" href="https://cdn.plyr.io/3.7.8/plyr.css" /> 35 + <link rel="stylesheet" href="https://cdn.plyr.io/3.7.8/plyr.css" /> 27 36 </svelte:head> 28 37 29 - <div class="max-w-full w-full aspect-video rounded-2xl overflow-hidden border border-base-700"> 30 - <video bind:this={element}> 31 - <track kind="captions" /> 32 - </video> 38 + <div class="aspect-video w-full max-w-full overflow-hidden rounded-2xl border border-base-700"> 39 + <video bind:this={element}> 40 + <track kind="captions" /> 41 + </video> 33 42 </div> 34 43 35 44 <style>
+9 -9
src/lib/Components/Items/BlueskyPost/relative-time/RelativeTime.svelte
··· 1 1 <script lang="ts"> 2 - import { onDestroy } from 'svelte' 3 - import { register, unregister } from './state' 2 + import { onDestroy } from 'svelte'; 3 + import { register, unregister } from './state'; 4 4 5 - export let date: Date | number 6 - export let locale: string 7 - export let live = true 5 + export let date: Date | number; 6 + export let locale: string; 7 + export let live = true; 8 8 9 - let instance = new Object() 10 - let text = '' 9 + let instance = new Object(); 10 + let text = ''; 11 11 12 - register(instance, date, locale, live, value => ({ text } = value)) 12 + register(instance, date, locale, live, (value) => ({ text } = value)); 13 13 14 - onDestroy(() => unregister(instance)) 14 + onDestroy(() => unregister(instance)); 15 15 </script> 16 16 17 17 <span class={$$props.class}>{text}</span>
+15 -15
src/lib/Components/Items/BlueskyPost/relative-time/action.ts
··· 1 - import type { Callback } from './render' 2 - import { register, unregister } from './state' 1 + import type { Callback } from './render'; 2 + import { register, unregister } from './state'; 3 3 4 4 export interface Options { 5 - date: Date | number 6 - locale?: string 7 - live?: boolean 5 + date: Date | number; 6 + locale?: string; 7 + live?: boolean; 8 8 } 9 9 10 10 export function relativeTime(node: HTMLElement, options: Options) { 11 - const callback: Callback = ({ text }) => (node.textContent = text) 11 + const callback: Callback = ({ text }) => (node.textContent = text); 12 12 13 13 function init(options: Options) { 14 - const date = options.date 15 - const locale = options.locale || navigator.language 16 - const live = (options.live = true) 14 + const date = options.date; 15 + const locale = options.locale || navigator.language; 16 + const live = (options.live = true); 17 17 18 - register(node, date, locale, live, callback) 18 + register(node, date, locale, live, callback); 19 19 } 20 20 21 - init(options) 21 + init(options); 22 22 23 23 return { 24 24 update(options: Options) { 25 - init(options) 25 + init(options); 26 26 }, 27 27 destroy() { 28 - unregister(node) 29 - }, 30 - } 28 + unregister(node); 29 + } 30 + }; 31 31 }
+5 -5
src/lib/Components/Items/BlueskyPost/relative-time/formatter.ts
··· 1 1 // keep a cache of formatter per locale, to avoid re-creating them (GC) 2 - const formatters = new Map<string, Intl.RelativeTimeFormat>() 2 + const formatters = new Map<string, Intl.RelativeTimeFormat>(); 3 3 4 4 // get the Intl.RelativeTimeFormat formatter for the given locale 5 5 export function getFormatter(locale: string) { 6 6 if (formatters.has(locale)) { 7 - return formatters.get(locale)! 7 + return formatters.get(locale)!; 8 8 } 9 - const formatter = new Intl.RelativeTimeFormat(locale, { numeric: 'always', style: 'narrow' }) 10 - formatters.set(locale, formatter) 11 - return formatter 9 + const formatter = new Intl.RelativeTimeFormat(locale, { numeric: 'always', style: 'narrow' }); 10 + formatters.set(locale, formatter); 11 + return formatter; 12 12 }
+4 -4
src/lib/Components/Items/BlueskyPost/relative-time/index.ts
··· 1 - export * from './action' 2 - export type { Callback } from './render' 3 - export { register, unregister } from './state' 4 - export { default as default } from './RelativeTime.svelte' 1 + export * from './action'; 2 + export type { Callback } from './render'; 3 + export { register, unregister } from './state'; 4 + export { default as default } from './RelativeTime.svelte';
+35 -22
src/lib/Components/Items/BlueskyPost/relative-time/render.ts
··· 1 - export type Callback = (result: { seconds: number; count: number; units: Intl.RelativeTimeFormatUnit; text: string }) => void 1 + export type Callback = (result: { 2 + seconds: number; 3 + count: number; 4 + units: Intl.RelativeTimeFormatUnit; 5 + text: string; 6 + }) => void; 2 7 3 8 export interface RenderState { 4 - date: Date | number 5 - callback: Callback 6 - formatter: Intl.RelativeTimeFormat 9 + date: Date | number; 10 + callback: Callback; 11 + formatter: Intl.RelativeTimeFormat; 7 12 } 8 13 9 14 // Array reprsenting one minute, hour, day, week, month, etc in seconds 10 - const cutoffs = [60, 3600, 86400, 86400 * 7, 86400 * 30, 86400 * 365, Infinity] 15 + const cutoffs = [60, 3600, 86400, 86400 * 7, 86400 * 30, 86400 * 365, Infinity]; 11 16 12 17 // Array equivalent to the above but in the string representation of the units 13 - const formatUnits: Intl.RelativeTimeFormatUnit[] = ['seconds', 'minutes', 'hours', 'days', 'weeks', 'months', 'years'] 18 + const formatUnits: Intl.RelativeTimeFormatUnit[] = [ 19 + 'seconds', 20 + 'minutes', 21 + 'hours', 22 + 'days', 23 + 'weeks', 24 + 'months', 25 + 'years' 26 + ]; 14 27 15 28 // function to render relative time into 16 29 export function render(state: RenderState, now: number) { 17 - const { date, callback, formatter } = state 30 + const { date, callback, formatter } = state; 18 31 19 32 // Allow dates or times to be passed 20 - const timeMs = typeof date === 'number' ? date : date.getTime() 33 + const timeMs = typeof date === 'number' ? date : date.getTime(); 21 34 22 35 // Get the amount of seconds between the given date and now 23 - const delta = timeMs - now 24 - const seconds = Math.round(delta / 1000) 36 + const delta = timeMs - now; 37 + const seconds = Math.round(delta / 1000); 25 38 26 39 // Grab the ideal cutoff unit 27 - const unitIndex = cutoffs.findIndex(cutoff => cutoff > Math.abs(seconds)) 40 + const unitIndex = cutoffs.findIndex((cutoff) => cutoff > Math.abs(seconds)); 28 41 29 42 // units 30 - const units = formatUnits[unitIndex] 43 + const units = formatUnits[unitIndex]; 31 44 32 45 // Get the divisor to divide from the seconds. E.g. if our unit is 'day' our divisor 33 46 // is one day in seconds, so we can divide our seconds by this to get the # of days 34 - const divisor = unitIndex ? cutoffs[unitIndex - 1] : 1 47 + const divisor = unitIndex ? cutoffs[unitIndex - 1] : 1; 35 48 36 49 // count of units 37 - const count = Math.round(seconds / divisor) 50 + const count = Math.round(seconds / divisor); 38 51 39 52 // Intl.RelativeTimeFormat do its magic 40 - callback({ seconds: seconds, count, units, text: formatter.format(count, units) }) 53 + callback({ seconds: seconds, count, units, text: formatter.format(count, units) }); 41 54 42 55 // calculate time to next update, taking account rounding (e.g. it goes from 2 minutes to 1 minute at 90 seconds) 43 56 // and also the changeover from units (59 seconds shouldn't show as 1 minute, so at 61 seconds we schedule the next 44 57 // update for 1 second time) 45 58 46 - const divisorMs = divisor * 1000 59 + const divisorMs = divisor * 1000; 47 60 48 - let updateIn 61 + let updateIn; 49 62 50 63 if (unitIndex) { 51 - updateIn = divisorMs / 2 - (Math.abs(delta) % divisorMs) 64 + updateIn = divisorMs / 2 - (Math.abs(delta) % divisorMs); 52 65 if (updateIn < 0) { 53 - updateIn += divisorMs 66 + updateIn += divisorMs; 54 67 } 55 68 } else { 56 - updateIn = divisorMs - (Math.abs(delta) % divisorMs) 69 + updateIn = divisorMs - (Math.abs(delta) % divisorMs); 57 70 } 58 71 59 - const updateAt = now + updateIn 72 + const updateAt = now + updateIn; 60 73 61 - return updateAt 74 + return updateAt; 62 75 }
+24 -18
src/lib/Components/Items/BlueskyPost/relative-time/state.ts
··· 1 - import { getFormatter } from './formatter' 2 - import { render } from './render' 3 - import type { Callback, RenderState } from './render' 1 + import { getFormatter } from './formatter'; 2 + import { render } from './render'; 3 + import type { Callback, RenderState } from './render'; 4 4 5 5 interface UpdateState extends RenderState { 6 - update: number 6 + update: number; 7 7 } 8 8 9 9 // keep track of each instance 10 - const instances = new Map<Object, UpdateState>() 10 + const instances = new Map<Object, UpdateState>(); 11 11 12 12 // we use a single timer for efficiency and to keep updates in sync 13 - let updateInterval: number | NodeJS.Timeout 13 + let updateInterval: number | NodeJS.Timeout; 14 14 15 15 // register or update instance 16 - export function register(instance: Object, date: Date | number, locale: string, live: boolean, callback: Callback) { 16 + export function register( 17 + instance: Object, 18 + date: Date | number, 19 + locale: string, 20 + live: boolean, 21 + callback: Callback 22 + ) { 17 23 // get the formatter for the given locale, we do this here so we don't keep having to look it up on each tick 18 - const formatter = getFormatter(locale) 24 + const formatter = getFormatter(locale); 19 25 20 26 // create state to render 21 - const state = { date, callback, formatter } 27 + const state = { date, callback, formatter }; 22 28 23 29 // initial render is immediate, so works for SSR 24 - const update = render(state, Date.now()) 30 + const update = render(state, Date.now()); 25 31 26 32 // if it's to update live, we keep a track and schedule the next update 27 33 if (live) { 28 - instances.set(instance, { ...state, update }) 34 + instances.set(instance, { ...state, update }); 29 35 } else { 30 - instances.delete(instance) 36 + instances.delete(instance); 31 37 } 32 38 33 39 // start the clock ticking if there are any live instances ··· 35 41 updateInterval = 36 42 updateInterval || 37 43 setInterval(() => { 38 - const now = Date.now() 44 + const now = Date.now(); 39 45 for (const state of instances.values()) { 40 46 if (state.update <= now) { 41 - state.update = render(state, now) 47 + state.update = render(state, now); 42 48 } 43 49 } 44 - }, 1000) 50 + }, 1000); 45 51 } 46 52 } 47 53 48 54 export function unregister(instance: Object) { 49 - instances.delete(instance) 55 + instances.delete(instance); 50 56 if (instances.size === 0) { 51 - clearInterval(updateInterval) 52 - updateInterval = 0 57 + clearInterval(updateInterval); 58 + updateInterval = 0; 53 59 } 54 60 }
+1 -1
src/lib/Components/Items/BlueskyPost/types.ts
··· 5 5 AppBskyEmbedExternal, 6 6 AppBskyEmbedImages, 7 7 AppBskyEmbedRecordWithMedia, 8 - AppBskyEmbedVideo, 8 + AppBskyEmbedVideo 9 9 } from '@atproto/api'; 10 10 11 11 export interface Post extends AppBskyFeedDefs.PostView {
+1 -5
src/lib/Components/Items/ItemCard.svelte
··· 70 70 </div> 71 71 <div class="mt-2 flex justify-between"> 72 72 <h3 class="sm:text-md text-sm font-medium text-base-50"> 73 - <a 74 - href="/{item.media_type}/{item.id}-{nameToId( 75 - item.title ?? '' 76 - )}" 77 - > 73 + <a href="/{item.media_type}/{item.id}-{nameToId(item.title ?? '')}"> 78 74 <span aria-hidden="true" class="absolute inset-0"></span> 79 75 <div class="line-clamp-2 max-w-full"> 80 76 {item.title}
+8 -5
src/lib/Components/Items/ReviewCard.svelte
··· 8 8 9 9 import { cn, nameToId } from '$lib/utils'; 10 10 11 - let { data, showMovieDetails = true, bigText = false }: { data: MainRecord; showMovieDetails?: boolean; bigText?: boolean } = 12 - $props(); 11 + let { 12 + data, 13 + showMovieDetails = true, 14 + bigText = false 15 + }: { data: MainRecord; showMovieDetails?: boolean; bigText?: boolean } = $props(); 13 16 14 17 let isLiked = $state(false); 15 18 </script> ··· 54 57 {data.author.displayName || data.author.handle} 55 58 </div> 56 59 </div> 57 - <div class="shrink-0 pr-2 text-sm text-base-400 font-normal"> 60 + <div class="shrink-0 pr-2 text-sm font-normal text-base-400"> 58 61 <RelativeTime date={new Date(data.updatedAt)} locale="en-US" /> 59 62 </div> 60 63 </a> ··· 95 98 </div> 96 99 97 100 {#if data.record.note?.value} 98 - <div class={cn("mt-4 text-sm text-base-300", bigText ? 'text-xl text-base-100' : '')}> 101 + <div class={cn('mt-4 text-sm text-base-300', bigText ? 'text-xl text-base-100' : '')}> 99 102 {@html data.record.note?.value?.replace('\n', '<br /><br />')} 100 103 </div> 101 104 {/if} ··· 176 179 {(data.record.likes ?? 0) + (isLiked ? 1 : 0)} 177 180 </button> 178 181 179 - <OptionButton data={data} /> 182 + <OptionButton {data} /> 180 183 </div> 181 184 182 185 <!-- <a href={`/review/${encodeURIComponent(data.uri)}`}>
+2 -9
src/lib/Components/Layout/Sidebar.svelte
··· 24 24 }, 25 25 { 26 26 icon: `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6"> 27 - <path stroke-linecap="round" stroke-linejoin="round" d="M12.75 3.03v.568c0 .334.148.65.405.864l1.068.89c.442.369.535 1.01.216 1.49l-.51.766a2.25 2.25 0 0 1-1.161.886l-.143.048a1.107 1.107 0 0 0-.57 1.664c.369.555.169 1.307-.427 1.605L9 13.125l.423 1.059a.956.956 0 0 1-1.652.928l-.679-.906a1.125 1.125 0 0 0-1.906.172L4.5 15.75l-.612.153M12.75 3.031a9 9 0 0 0-8.862 12.872M12.75 3.031a9 9 0 0 1 6.69 14.036m0 0-.177-.529A2.25 2.25 0 0 0 17.128 15H16.5l-.324-.324a1.453 1.453 0 0 0-2.328.377l-.036.073a1.586 1.586 0 0 1-.982.816l-.99.282c-.55.157-.894.702-.8 1.267l.073.438c.08.474.49.821.97.821.846 0 1.598.542 1.865 1.345l.215.643m5.276-3.67a9.012 9.012 0 0 1-5.276 3.67m0 0a9 9 0 0 1-10.275-4.835M15.75 9c0 .896-.393 1.7-1.016 2.25" /> 28 - </svg>`, 29 - label: 'game', 30 - href: '/gamesearch', 31 - }, 32 - { 33 - icon: `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6"> 34 27 <path stroke-linecap="round" stroke-linejoin="round" d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z" /> 35 28 </svg>`, 36 29 label: 'Search', ··· 54 47 icon: `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6"> 55 48 <path stroke-linecap="round" stroke-linejoin="round" d="M12.75 3.03v.568c0 .334.148.65.405.864l1.068.89c.442.369.535 1.01.216 1.49l-.51.766a2.25 2.25 0 0 1-1.161.886l-.143.048a1.107 1.107 0 0 0-.57 1.664c.369.555.169 1.307-.427 1.605L9 13.125l.423 1.059a.956.956 0 0 1-1.652.928l-.679-.906a1.125 1.125 0 0 0-1.906.172L4.5 15.75l-.612.153M12.75 3.031a9 9 0 0 0-8.862 12.872M12.75 3.031a9 9 0 0 1 6.69 14.036m0 0-.177-.529A2.25 2.25 0 0 0 17.128 15H16.5l-.324-.324a1.453 1.453 0 0 0-2.328.377l-.036.073a1.586 1.586 0 0 1-.982.816l-.99.282c-.55.157-.894.702-.8 1.267l.073.438c.08.474.49.821.97.821.846 0 1.598.542 1.865 1.345l.215.643m5.276-3.67a9.012 9.012 0 0 1-5.276 3.67m0 0a9 9 0 0 1-10.275-4.835M15.75 9c0 .896-.393 1.7-1.016 2.25" /> 56 49 </svg>`, 57 - label: 'PersonalFeed', 58 - href: '/personalfeed' 50 + label: 'Following', 51 + href: '/following' 59 52 }, 60 53 { 61 54 icon: `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
+2 -2
src/lib/Components/Modals/OptionMenu.svelte
··· 38 38 39 39 {#if $open} 40 40 <div 41 - class="bg-base-900 border-base-800 shadow-xl shadow-base-950 divide-base-800 z-20 flex flex-col items-start divide-y overflow-hidden rounded-xl border" 41 + class="z-20 flex flex-col items-start divide-y divide-base-800 overflow-hidden rounded-xl border border-base-800 bg-base-900 shadow-xl shadow-base-950" 42 42 use:melt={$menu} 43 43 transition:fly={{ duration: 150, y: -10 }} 44 44 > ··· 192 192 193 193 <style> 194 194 .item { 195 - @apply text-base-300 hover:bg-accent-950/30 hover:text-accent-400 inline-flex w-full items-center gap-2 px-3 py-2 text-sm; 195 + @apply inline-flex w-full items-center gap-2 px-3 py-2 text-sm text-base-300 hover:bg-accent-950/30 hover:text-accent-400; 196 196 } 197 197 </style>
+9 -9
src/lib/Components/Modals/RateMovieModal.svelte
··· 87 87 {#if rateMovieModal.showModal} 88 88 <div class="relative z-50" aria-labelledby="modal-title" role="dialog" aria-modal="true"> 89 89 <div 90 - class="bg-base-950/90 fixed inset-0 backdrop-blur-sm transition-opacity" 90 + class="fixed inset-0 bg-base-950/90 backdrop-blur-sm transition-opacity" 91 91 onclick={() => (rateMovieModal.showModal = false)} 92 92 aria-hidden="true" 93 93 ></div> 94 94 <div class="pointer-events-none fixed inset-0 z-50 h-[100dvh] w-screen overflow-y-auto"> 95 95 <div class="flex h-[100dvh] items-end justify-center p-4 text-center sm:items-center sm:p-0"> 96 96 <div 97 - class="border-base-800 bg-base-900 pointer-events-auto relative w-full transform overflow-hidden rounded-lg border px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 sm:max-w-sm sm:p-6" 97 + class="pointer-events-auto relative w-full transform overflow-hidden rounded-lg border border-base-800 bg-base-900 px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 sm:max-w-sm sm:p-6" 98 98 > 99 99 <button 100 - class="bg-base-800/50 hover:bg-base-800/80 absolute right-2 top-2 rounded-full p-1" 100 + class="absolute right-2 top-2 rounded-full bg-base-800/50 p-1 hover:bg-base-800/80" 101 101 onclick={() => (rateMovieModal.showModal = false)} 102 102 > 103 103 <svg ··· 115 115 </button> 116 116 117 117 <div> 118 - <h3 class="text-md text-base-50 mb-4 font-semibold" id="modal-title"> 118 + <h3 class="text-md mb-4 font-semibold text-base-50" id="modal-title"> 119 119 {#if rateMovieModal.selectedItem?.editUri} 120 120 Edit review 121 121 {:else} ··· 126 126 {#if rateMovieModal.selectedItem?.title} 127 127 <div class="relative flex items-center gap-4"> 128 128 <div 129 - class="border-base-800 bg-base-900/50 relative z-20 aspect-[2/3] h-32 w-auto shrink-0 overflow-hidden rounded-md border" 129 + class="relative z-20 aspect-[2/3] h-32 w-auto shrink-0 overflow-hidden rounded-md border border-base-800 bg-base-900/50" 130 130 > 131 131 {#if rateMovieModal.selectedItem?.poster_path} 132 132 <img ··· 137 137 {/if} 138 138 </div> 139 139 <h3 140 - class="text-base-50 mb-4 flex flex-col gap-2 text-xl font-semibold" 140 + class="mb-4 flex flex-col gap-2 text-xl font-semibold text-base-50" 141 141 id="modal-title" 142 142 > 143 143 {rateMovieModal.selectedItem.title} ··· 158 158 {/if} 159 159 160 160 <div class="mt-4"> 161 - <label for="comment" class="text-base-50 block text-xs font-medium">review</label> 161 + <label for="comment" class="block text-xs font-medium text-base-50">review</label> 162 162 <div class="mt-2"> 163 163 <textarea 164 164 rows="4" ··· 166 166 id="comment" 167 167 bind:value={review} 168 168 placeholder="write a review" 169 - class="outline-nonse border-base-800 bg-base-950 text-base-50 placeholder:text-base-400 focus:outline-accent-400 block w-full rounded-lg border px-3 py-1.5 text-base -outline-offset-1 focus:outline focus:outline-2 focus:-outline-offset-2 sm:text-sm/6" 169 + class="outline-nonse block w-full rounded-lg border border-base-800 bg-base-950 px-3 py-1.5 text-base text-base-50 -outline-offset-1 placeholder:text-base-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-accent-400 sm:text-sm/6" 170 170 ></textarea> 171 171 </div> 172 172 </div> ··· 182 182 }} 183 183 type="button" 184 184 disabled={sending || !rateMovieModal.selectedItem?.title} 185 - class="border-accent-900 bg-accent-950/80 text-accent-300 hover:bg-accent-950 focus-visible:outline-accent-600 inline-flex w-full justify-center rounded-md border px-3 py-2 text-sm font-semibold shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 disabled:cursor-not-allowed disabled:opacity-50" 185 + class="inline-flex w-full justify-center rounded-md border border-accent-900 bg-accent-950/80 px-3 py-2 text-sm font-semibold text-accent-300 shadow-sm hover:bg-accent-950 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent-600 disabled:cursor-not-allowed disabled:opacity-50" 186 186 >{sending 187 187 ? 'Sending...' 188 188 : rateMovieModal.selectedItem?.editUri
+1 -1
src/lib/Components/UI/SearchCombobox.svelte
··· 111 111 rateMovieModal.selectedItem = { 112 112 ...item, 113 113 currentRating: watchedItems.getRating(item)?.rating ?? undefined, 114 - currentReview: watchedItems.getRating(item)?.ratingText ?? undefined, 114 + currentReview: watchedItems.getRating(item)?.ratingText ?? undefined 115 115 }; 116 116 rateMovieModal.showModal = true; 117 117 }}
-8
src/lib/Components/User/Profile.svelte
··· 44 44 @{profile.handle} 45 45 </div> 46 46 </div> 47 - <div class="mt-6 hidden flex-row justify-stretch space-x-4 space-y-0"> 48 - <button 49 - type="button" 50 - class="inline-flex justify-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-base-900 shadow-sm ring-1 ring-inset ring-base-300 hover:bg-base-50" 51 - > 52 - <span>Call</span> 53 - </button> 54 - </div> 55 47 </div> 56 48 </div> 57 49 </div>
+2 -4
src/lib/server/movies.ts
··· 164 164 return data; 165 165 } 166 166 export async function getPopularMovies() { 167 - const i=Math.floor(Math.random() * 6); 168 - const url = `https://api.themoviedb.org/3/movie/top_rated?language=en-US&page=`+i; 167 + const i = Math.floor(Math.random() * 6); 168 + const url = `https://api.themoviedb.org/3/movie/popular?language=en-US&page=` + i; 169 169 const options = { 170 170 method: 'GET', 171 171 headers: { ··· 176 176 177 177 const response = await fetch(url, options); 178 178 const data = await response.json(); 179 - //console.log(data.results[0]); 180 179 181 180 return data; 182 181 } 183 -
+2 -2
src/routes/+page.svelte
··· 36 36 showLoginModal.toggle(); 37 37 }} 38 38 type="button" 39 - class="mt-8 inline-flex w-fit items-center gap-x-1.5 rounded-md border border-accent-500/30 bg-accent-700/20 px-3 py-2 text-sm font-semibold text-accent-400 shadow-sm transition-all duration-100 hover:bg-accent-700/30 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent-600" 39 + class="mt-8 inline-flex w-fit items-center gap-x-1.5 rounded-xl border border-accent-500/30 bg-accent-700/20 px-3 py-2 text-sm font-semibold text-accent-400 shadow-sm transition-all duration-100 hover:bg-accent-700/30 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent-600" 40 40 > 41 41 Sign in with Bluesky 42 42 </button> ··· 46 46 rateMovieModal.showEmpty(); 47 47 }} 48 48 type="button" 49 - class="mt-8 inline-flex w-fit items-center gap-x-1.5 rounded-md border border-accent-500/30 bg-accent-700/20 px-3 py-2 text-sm font-semibold text-accent-400 shadow-sm transition-all duration-100 hover:bg-accent-700/30 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent-600" 49 + class="mt-8 inline-flex w-fit items-center gap-x-1.5 rounded-xl border border-accent-500/30 bg-accent-700/20 px-3 py-2 text-sm font-semibold text-accent-400 shadow-sm transition-all duration-100 hover:bg-accent-700/30 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent-600" 50 50 > 51 51 New review 52 52 </button>
+6 -21
src/routes/[kind=item]/[id]/+page.svelte
··· 17 17 <svelte:head> 18 18 <title>{data.item.title} | skywatched</title> 19 19 20 - <meta 21 - name="description" 22 - content={`Rate and review "${data.item.title}" on skywatched`} 23 - /> 20 + <meta name="description" content={`Rate and review "${data.item.title}" on skywatched`} /> 24 21 25 22 <meta property="og:url" content={$page.url.href} /> 26 23 <meta property="og:type" content="website" /> 27 - <meta 28 - property="og:title" 29 - content="{data.item.title} | skywatched.app" 30 - /> 31 - <meta 32 - property="og:description" 33 - content={`Rate and review "${data.item.title}" on skywatched`} 34 - /> 24 + <meta property="og:title" content="{data.item.title} | skywatched.app" /> 25 + <meta property="og:description" content={`Rate and review "${data.item.title}" on skywatched`} /> 35 26 <meta property="og:image" content="{$page.url.href}/og.png" /> 36 27 37 28 <meta name="twitter:card" content="summary_large_image" /> 38 29 <meta property="twitter:domain" content="skywatched.app" /> 39 30 <meta property="twitter:url" content={$page.url.href} /> 40 - <meta 41 - name="twitter:title" 42 - content="{data.item.title} | skywatched.app" 43 - /> 44 - <meta 45 - name="twitter:description" 46 - content={`Rate and review "${data.item.title}" on skywatched`} 47 - /> 31 + <meta name="twitter:title" content="{data.item.title} | skywatched.app" /> 32 + <meta name="twitter:description" content={`Rate and review "${data.item.title}" on skywatched`} /> 48 33 <meta name="twitter:image" content="{$page.url.href}/og.png" /> 49 34 </svelte:head> 50 35 ··· 59 44 )} 60 45 onclick={() => { 61 46 rateMovieModal.show({ 62 - ...data.item, 47 + ...data.item 63 48 }); 64 49 }} 65 50 >
+2 -2
src/routes/api/follow/+server.ts
··· 21 21 collection: string; 22 22 rkey: string; 23 23 record: { 24 - subject: string; 24 + subject: string; 25 25 createdAt: string; 26 26 }; 27 27 } = { ··· 29 29 collection: 'app.bsky.graph.follow', 30 30 rkey, 31 31 record: { 32 - subject: did , 32 + subject: did, 33 33 createdAt: new Date().toISOString() 34 34 } 35 35 };
+14
src/routes/following/+page.server.ts
··· 1 + import { getFollows } from '$lib/bluesky.js'; 2 + import { getRecentRecords } from '$lib/db'; 3 + 4 + /** @type {import('./$types').PageServerLoad} */ 5 + export async function load(event) { 6 + const feed = await getRecentRecords(); 7 + const did = event.locals.user.did; 8 + const follows = await getFollows({ did }); 9 + const filt = feed.filter((review) => 10 + follows.follows.some((person) => person.did == review.author.did) 11 + ); 12 + 13 + return { truefeed: filt }; 14 + }
+45 -40
src/routes/game/[kind]/[id]/[kind2]/[id2]/[counter]/+page.server.ts
··· 15 15 const id = parseInt(event.params.id.split('-')[0]); 16 16 const id2 = parseInt(event.params.id2.split('-')[0]); 17 17 const kind = event.params.kind; 18 - const kind2= event.params.kind2; 19 - const ids=id; 20 - const ids2=id2; 21 - const couter=event.params.counter; 22 - if(kind==kind2&&id==id2){ 23 - redirect(302,"/win/"+couter); 18 + const kind2 = event.params.kind2; 19 + const ids = id; 20 + const ids2 = id2; 21 + const couter = event.params.counter; 22 + if (kind == kind2 && id == id2) { 23 + redirect(302, '/win/' + couter); 24 24 } 25 - 26 - if (kind !== 'movie' && kind !== 'tv' && kind!=='cast') { 25 + 26 + if (kind !== 'movie' && kind !== 'tv' && kind !== 'cast') { 27 27 return error(404, 'Not found'); 28 28 } 29 - if (kind2 !== 'movie' && kind2 !== 'tv' && kind2!=='cast') { 29 + if (kind2 !== 'movie' && kind2 !== 'tv' && kind2 !== 'cast') { 30 30 return error(404, 'Not found'); 31 31 } 32 32 ··· 44 44 var person2; 45 45 var credits; 46 46 var credits2; 47 - if(kind==="cast"){ 48 - temp = getDetails(68730,"movie"); 49 - casting=getCast(68730,"movie"); 50 - person=getPersonDetails(id); 51 - person2=getPersonDetails(id2); 52 - credits=id; 53 - credits2=id2; 54 - } 55 - else{ 47 + if (kind === 'cast') { 48 + temp = getDetails(68730, 'movie'); 49 + casting = getCast(68730, 'movie'); 50 + person = getPersonDetails(id); 51 + person2 = getPersonDetails(id2); 52 + credits = id; 53 + credits2 = id2; 54 + } else { 56 55 temp = getDetails(id, kind); 57 - casting=getCast(id,kind); 58 - person=getPersonDetails(37625); 59 - person2=getPersonDetails(37625); 60 - credits=37625; 61 - credits2=37625; 62 - 56 + casting = getCast(id, kind); 57 + person = getPersonDetails(37625); 58 + person2 = getPersonDetails(37625); 59 + credits = 37625; 60 + credits2 = 37625; 63 61 } 64 - if(kind2==="cast"){ 65 - temp2 = getDetails(68730,"movie"); 66 - casting2=getCast(68730,"movie"); 67 - person2=getPersonDetails(id2); 68 - credits2=id2; 69 - } 70 - else{ 62 + if (kind2 === 'cast') { 63 + temp2 = getDetails(68730, 'movie'); 64 + casting2 = getCast(68730, 'movie'); 65 + person2 = getPersonDetails(id2); 66 + credits2 = id2; 67 + } else { 71 68 temp2 = getDetails(id2, kind2); 72 - casting2=getCast(id2,kind2); 73 - person2=getPersonDetails(37625); 74 - credits2=37625; 75 - 69 + casting2 = getCast(id2, kind2); 70 + person2 = getPersonDetails(37625); 71 + credits2 = 37625; 76 72 } 77 73 const resultPromise = temp; 78 - const personPromise=person; 79 - const person2Promise=person2; 74 + const personPromise = person; 75 + const person2Promise = person2; 80 76 //if(kind==="cast"){ 81 77 // const resultPromise = getDetails(68730,"movie") 82 78 //} ··· 87 83 88 84 const castPromise = casting; 89 85 const castPromise2 = casting2; 90 - const creditsPromise= credits; 91 - const creditsPromise2= credits2; 86 + const creditsPromise = credits; 87 + const creditsPromise2 = credits2; 92 88 const combinedCredits = await getCombinedCredits(credits); 93 89 const creditsSet = new Set<string>(); 94 90 const combinedCredits2 = await getCombinedCredits(credits2); ··· 126 122 }; 127 123 }); 128 124 129 - const [result,result2, cast,cast2,personDetails,person2Details,creditDetails,creditDetails2] = await Promise.all([ 125 + const [ 126 + result, 127 + result2, 128 + cast, 129 + cast2, 130 + personDetails, 131 + person2Details, 132 + creditDetails, 133 + creditDetails2 134 + ] = await Promise.all([ 130 135 resultPromise, 131 136 resultPromise2, 132 137 castPromise,
+114 -113
src/routes/game/[kind]/[id]/[kind2]/[id2]/[counter]/+page.svelte
··· 1 1 <script lang="ts"> 2 - import { type PageData } from '../../../../../$types'; 2 + import { type PageData } from './$types'; 3 3 import { cn, nameToId } from '$lib/utils'; 4 - import { rateMovieModal, videoPlayer, watchedItems } from '$lib/state.svelte'; 5 4 6 - import Container from '$lib/Components/Container.svelte'; 7 - import Avatar from '$lib/Components/Avatar.svelte'; 5 + import Container from '$lib/Components/Layout/Container.svelte'; 6 + import Avatar from '$lib/Components/User/Avatar.svelte'; 8 7 import { page } from '$app/stores'; 8 + import { watchedItems } from '$lib/state/user.svelte'; 9 + import { rateMovieModal } from '$lib/state/modals.svelte'; 9 10 10 11 let { data }: { data: PageData } = $props(); 11 12 </script> ··· 70 71 {/if} 71 72 {/snippet} 72 73 73 - 74 74 <div class="fixed inset-0 h-full w-full bg-black/50"></div> 75 75 76 76 <Container class="relative z-10 pb-8 pt-4"> 77 77 <div class="flex justify-center gap-4 px-4 pt-8"> 78 78 <div class="flex-column"> 79 - {#if data.kind === "cast"} 80 - <div class="text-center text-sm font-medium">Cast1</div> 81 - <Avatar 82 - src={data.personDetails.profile_path 83 - ? 'https://image.tmdb.org/t/p/w500' + data.personDetails.profile_path 84 - : undefined} 85 - size="size-44" 86 - /> 87 - {:else} 88 - <div class="text-center text-sm font-medium">Movie1</div> 89 - <img 90 - src="https://image.tmdb.org/t/p/w500{data.result.poster_path}" 91 - alt="" 92 - class="poster h-36 w-24 shrink-0 rounded-lg border border-white/10 sm:h-64 sm:w-44" 93 - style:--name={`poster-${data.result.id}`} 94 - /> 95 - {/if} 79 + {#if data.kind === 'cast'} 80 + <div class="text-center text-sm font-medium">Cast1</div> 81 + <Avatar 82 + src={data.personDetails.profile_path 83 + ? 'https://image.tmdb.org/t/p/w500' + data.personDetails.profile_path 84 + : undefined} 85 + size="size-44" 86 + /> 87 + {:else} 88 + <div class="text-center text-sm font-medium">Movie1</div> 89 + <img 90 + src="https://image.tmdb.org/t/p/w500{data.result.poster_path}" 91 + alt="" 92 + class="poster h-36 w-24 shrink-0 rounded-lg border border-white/10 sm:h-64 sm:w-44" 93 + style:--name={`poster-${data.result.id}`} 94 + /> 95 + {/if} 96 96 </div> 97 97 <div class="flex-column"> 98 - {#if data.kind2 === "cast"} 99 - <div class="text-center text-sm font-medium">Cast2</div> 100 - <Avatar 101 - src={data.person2Details.profile_path 102 - ? 'https://image.tmdb.org/t/p/w500' + data.person2Details.profile_path 103 - : undefined} 104 - size="size-44" 105 - /> 106 - {:else} 107 - <div class="text-center text-sm font-medium">Movie2</div> 108 - <img 109 - src="https://image.tmdb.org/t/p/w500{data.result2.poster_path}" 110 - alt="" 111 - class="poster h-36 w-24 shrink-0 rounded-lg border border-white/10 sm:h-64 sm:w-44" 112 - style:--name={`poster-${data.result2.id}`} 113 - /> 114 - {/if} 98 + {#if data.kind2 === 'cast'} 99 + <div class="text-center text-sm font-medium">Cast2</div> 100 + <Avatar 101 + src={data.person2Details.profile_path 102 + ? 'https://image.tmdb.org/t/p/w500' + data.person2Details.profile_path 103 + : undefined} 104 + size="size-44" 105 + /> 106 + {:else} 107 + <div class="text-center text-sm font-medium">Movie2</div> 108 + <img 109 + src="https://image.tmdb.org/t/p/w500{data.result2.poster_path}" 110 + alt="" 111 + class="poster h-36 w-24 shrink-0 rounded-lg border border-white/10 sm:h-64 sm:w-44" 112 + style:--name={`poster-${data.result2.id}`} 113 + /> 114 + {/if} 115 115 </div> 116 - 117 116 </div> 118 117 119 118 <div class="px-4 pt-4 text-sm text-white"> ··· 121 120 {@render buttons()} 122 121 </div> 123 122 </div> 124 - 123 + 125 124 {#if data.cast.length > 0} 126 125 <div class="flex flex-col gap-x-6 px-4 pb-8 pt-4 text-sm text-white"> 127 - {#if data.kind==="cast" } 128 - <div class="text-center text-sm font-medium">Movies of {data.personDetails.name}</div> 129 - <div class={cn('flex gap-x-6 justify-start overflow-x-auto')}> 130 - {#each data.combinedCredits as castMember} 131 - <a 132 - href={`/game/movie/${castMember.id}-${castMember.title ?? castMember.name}/${data.kind2}/${data.ids2}-/${parseInt(data.couter)+1}`} 133 - class="flex flex-col items-center gap-1" 134 - > 135 - <img 136 - src="https://image.tmdb.org/t/p/w342{castMember.poster_path}" 137 - alt="movie poster for {castMember.title ?? castMember.name}" 138 - class="poster size-32 object-center object-cover lg:size-full w-32" 139 - /> 140 - <div class="text-center size-32 text-sm font-medium">{castMember.title ?? castMember.name}</div> 141 - </a> 142 - 143 - {/each} 144 - </div> 126 + {#if data.kind === 'cast'} 127 + <div class="text-center text-sm font-medium">Movies of {data.personDetails.name}</div> 128 + <div class={cn('flex justify-start gap-x-6 overflow-x-auto')}> 129 + {#each data.combinedCredits as castMember} 130 + <a 131 + href={`/game/movie/${castMember.id}-${castMember.title ?? castMember.name}/${data.kind2}/${data.ids2}-/${parseInt(data.couter) + 1}`} 132 + class="flex flex-col items-center gap-1" 133 + > 134 + <img 135 + src="https://image.tmdb.org/t/p/w342{castMember.poster_path}" 136 + alt="movie poster for {castMember.title ?? castMember.name}" 137 + class="poster size-32 w-32 object-cover object-center lg:size-full" 138 + /> 139 + <div class="size-32 text-center text-sm font-medium"> 140 + {castMember.title ?? castMember.name} 141 + </div> 142 + </a> 143 + {/each} 144 + </div> 145 145 {:else} 146 - <div class="text-center text-sm font-medium">cast of {data.result.title}</div> 147 - <div class={'flex gap-x-6 justify-left overflow-x-auto'}> 148 - {#each data.cast as castMember} 149 - <a 150 - href={`/game/cast/${castMember.id}-${nameToId(castMember.name)}/${data.kind2}/${data.ids2}-/${parseInt(data.couter)+1}`} 151 - class="flex flex-col items-center gap-1" 152 - > 153 - <Avatar 154 - src={castMember.profile_path 155 - ? 'https://image.tmdb.org/t/p/w500' + castMember.profile_path 156 - : undefined} 157 - size="size-32" 158 - /> 159 - <div class="text-center text-xs font-medium">{castMember.name}</div> 160 - <div class="text-center text-xs text-base-400">{castMember.character}</div> 161 - </a> 162 - {/each} 163 - </div> 146 + <div class="text-center text-sm font-medium">cast of {data.result.title}</div> 147 + <div class={'justify-left flex gap-x-6 overflow-x-auto'}> 148 + {#each data.cast as castMember} 149 + <a 150 + href={`/game/cast/${castMember.id}-${nameToId(castMember.name)}/${data.kind2}/${data.ids2}-/${parseInt(data.couter) + 1}`} 151 + class="flex flex-col items-center gap-1" 152 + > 153 + <Avatar 154 + src={castMember.profile_path 155 + ? 'https://image.tmdb.org/t/p/w500' + castMember.profile_path 156 + : undefined} 157 + size="size-32" 158 + /> 159 + <div class="text-center text-xs font-medium">{castMember.name}</div> 160 + <div class="text-center text-xs text-base-400">{castMember.character}</div> 161 + </a> 162 + {/each} 163 + </div> 164 164 {/if} 165 - {#if data.kind2==="cast" } 166 - <div class="text-center text-sm font-medium">Movies of {data.person2Details.name}</div> 167 - <div class={cn('flex gap-x-6 overflow-x-auto')}> 168 - {#each data.combinedCredits2 as castMember} 169 - <a 170 - href={`/game/${data.kind}/${data.ids}-/movie/${castMember.id}-${castMember.title ?? castMember.name}/${parseInt(data.couter)+1}`} 171 - class="flex flex-col items-center gap-1" 172 - > 173 - <img 174 - src="https://image.tmdb.org/t/p/w342{castMember.poster_path}" 175 - alt="movie poster for {castMember.title ?? castMember.name}" 176 - class="poster object-center object-cover lg:size-full w-500" 177 - /> 178 - <div class="text-center size-32 text-sm font-medium">{castMember.title ?? castMember.name}</div> 179 - </a> 180 - 181 - {/each} 182 - </div> 165 + {#if data.kind2 === 'cast'} 166 + <div class="text-center text-sm font-medium">Movies of {data.person2Details.name}</div> 167 + <div class={cn('flex gap-x-6 overflow-x-auto')}> 168 + {#each data.combinedCredits2 as castMember} 169 + <a 170 + href={`/game/${data.kind}/${data.ids}-/movie/${castMember.id}-${castMember.title ?? castMember.name}/${parseInt(data.couter) + 1}`} 171 + class="flex flex-col items-center gap-1" 172 + > 173 + <img 174 + src="https://image.tmdb.org/t/p/w342{castMember.poster_path}" 175 + alt="movie poster for {castMember.title ?? castMember.name}" 176 + class="poster w-500 object-cover object-center lg:size-full" 177 + /> 178 + <div class="size-32 text-center text-sm font-medium"> 179 + {castMember.title ?? castMember.name} 180 + </div> 181 + </a> 182 + {/each} 183 + </div> 183 184 {:else} 184 - <div class="text-center text-sm font-medium">Cast of {data.result2.title}</div> 185 - <div class={'flex gap-x-6 justify-right overflow-x-auto'}> 186 - {#each data.cast2 as castMember} 187 - <a 188 - href={`/game/${data.kind}/${data.ids}-/cast/${castMember.id}-${castMember.title ?? castMember.name}/${parseInt(data.couter)+1}`} 189 - class="flex flex-col items-center gap-1" 190 - > 191 - <Avatar 192 - src={castMember.profile_path 193 - ? 'https://image.tmdb.org/t/p/w500' + castMember.profile_path 194 - : undefined} 195 - size="size-32" 196 - /> 197 - <div class="text-center text-xs font-medium">{castMember.name}</div> 198 - <div class="text-center text-xs text-base-400">{castMember.character}</div> 199 - </a> 200 - {/each} 201 - </div> 185 + <div class="text-center text-sm font-medium">Cast of {data.result2.title}</div> 186 + <div class={'justify-right flex gap-x-6 overflow-x-auto'}> 187 + {#each data.cast2 as castMember} 188 + <a 189 + href={`/game/${data.kind}/${data.ids}-/cast/${castMember.id}-${castMember.title ?? castMember.name}/${parseInt(data.couter) + 1}`} 190 + class="flex flex-col items-center gap-1" 191 + > 192 + <Avatar 193 + src={castMember.profile_path 194 + ? 'https://image.tmdb.org/t/p/w500' + castMember.profile_path 195 + : undefined} 196 + size="size-32" 197 + /> 198 + <div class="text-center text-xs font-medium">{castMember.name}</div> 199 + <div class="text-center text-xs text-base-400">{castMember.character}</div> 200 + </a> 201 + {/each} 202 + </div> 202 203 {/if} 203 204 </div> 204 205 {/if}
-72
src/routes/game/[kind]/[id]/[kind2]/[id2]/[counter]/og.png/+server.ts
··· 1 - // src/routes/og/+server.ts 2 - import { getDetails } from '$lib/server/movies'; 3 - import { ImageResponse } from '@ethercorps/sveltekit-og'; 4 - import { error, type RequestHandler } from '@sveltejs/kit'; 5 - 6 - const template = (data: { 7 - backdrop_path: string; 8 - poster_path: string; 9 - title?: string; 10 - name?: string; 11 - }) => { 12 - return ` 13 - <div tw="bg-zinc-900 flex flex-col w-full h-full items-center justify-center"> 14 - 15 - <div tw="flex absolute bottom-0 left-0 right-0 top-0 bg-sky-400"> 16 - <img src="https://image.tmdb.org/t/p/w780${data.backdrop_path}" alt="" class="flex h-full w-full rounded-xl opacity-50" /> 17 - 18 - <div tw="flex absolute h-full w-full bg-black/80"> 19 - </div> 20 - </div> 21 - 22 - <div tw="flex flex-row w-full py-8 px-16 items-center justify-start"> 23 - <div tw="flex h-auto aspect-[3/2] w-72"> 24 - <img src="https://image.tmdb.org/t/p/w500${data.poster_path}" alt="" 25 - class="flex h-full w-full rounded-xl border-2 border-zinc-800" style="border-radius: 16px; border-width: 1px; border-color: #3f3f46;" /> 26 - </div> 27 - 28 - <h2 tw="flex flex-col text-7xl font-bold text-zinc-100 text-left px-12 max-w-3xl"> 29 - <span tw="flex tracking-tight">${data.title ?? data.name}</span> 30 - </h2> 31 - </div> 32 - 33 - <div tw="flex text-4xl text-sky-400 items-end justify-end w-full px-8"> 34 - <div tw="flex"> 35 - rate on skywatched.app 36 - </div> 37 - </div> 38 - </div>`; 39 - }; 40 - 41 - const host = import.meta.env.DEV ? 'http://localhost:5173' : 'https://skywatched.app'; 42 - const fontPath = `fonts/inter-latin-ext-400-normal.woff`; 43 - const fontFile = await fetch(`${host}/${fontPath}`); 44 - const fontData: ArrayBuffer = await fontFile.arrayBuffer(); 45 - 46 - export const GET: RequestHandler = async ({ params }) => { 47 - const id = parseInt(params.id?.split('-')[0] ?? ''); 48 - const kind = params.kind; 49 - 50 - if (kind !== 'movie' && kind !== 'tv') { 51 - return error(404, 'Not found'); 52 - } 53 - 54 - if (!id) { 55 - return error(404, 'Not found'); 56 - } 57 - const result = await getDetails(id, kind); 58 - 59 - return new ImageResponse( 60 - template(result), 61 - { 62 - fonts: [ 63 - { 64 - name: 'Inter Latin', 65 - data: fontData, 66 - weight: 400 67 - } 68 - ] 69 - }, 70 - {} 71 - ); 72 - };
+19 -15
src/routes/gamesearch/+page.server.ts
··· 1 - import { searchMulti,getPopularMovies } from '$lib/server/movies'; 1 + import { searchMulti, getPopularMovies } from '$lib/server/movies'; 2 2 import { redirect } from '@sveltejs/kit'; 3 - import type { Actions, PageServerLoad } from './$types'; 3 + import type { Actions } from './$types'; 4 4 5 5 /** @type {import('./$types').PageServerLoad} */ 6 6 export async function load(event) { 7 7 const query = event.url.searchParams.get('query'); 8 - const query2=event.url.searchParams.get('query2'); 8 + const query2 = event.url.searchParams.get('query2'); 9 9 10 - if (query) { 10 + if (query && query2) { 11 11 const results = (await searchMulti(query)) 12 12 .map((result) => { 13 13 if (result.media_type === 'movie') { ··· 17 17 } 18 18 return null; 19 19 }) 20 - .filter((result) => result !== null && result.poster_path !== null); 20 + .filter((result) => result?.poster_path !== null); 21 21 const results2 = (await searchMulti(query2)) 22 22 .map((result2) => { 23 23 if (result2.media_type === 'movie') { ··· 27 27 } 28 28 return null; 29 29 }) 30 - .filter((result2) => result2 !== null && result2.poster_path !== null); 31 - return { results,results2, query,query2 }; 30 + .filter((result2) => result2?.poster_path !== null); 31 + return { results, results2, query, query2 }; 32 32 } 33 33 34 - return { results: [],results2: [], query,query2 }; 34 + return { results: [], results2: [], query, query2 }; 35 35 } 36 36 export const actions: Actions = { 37 - randomize: async (event) => { 38 - const randmovies=await getPopularMovies(); 39 - const i=Math.floor(Math.random() * randmovies.results.length); 40 - const j=Math.floor(Math.random() * randmovies.results.length); 41 - //console.log(randmovies.results.length); 42 - return redirect(302, '/game/movie/'+randmovies.results[i]?.id+"-/movie/"+randmovies.results[j]?.id+"-/0");} 43 - }; 37 + randomize: async () => { 38 + const randmovies = await getPopularMovies(); 39 + const i = Math.floor(Math.random() * randmovies.results.length); 40 + const j = Math.floor(Math.random() * randmovies.results.length); 41 + 42 + return redirect( 43 + 302, 44 + '/game/movie/' + randmovies.results[i]?.id + '-/movie/' + randmovies.results[j]?.id + '-/0' 45 + ); 46 + } 47 + };
+9 -12
src/routes/gamesearch/+page.svelte
··· 1 1 <script lang="ts"> 2 - import ItemsGrid from '$lib/Components/ItemsGrid.svelte'; 3 - import Container from '$lib/Components/Container.svelte'; 4 - import BaseHeadTags from '$lib/Components/BaseHeadTags.svelte'; 2 + import ItemsGrid from '$lib/Components/Items/ItemsGrid.svelte'; 3 + import Container from '$lib/Components/Layout/Container.svelte'; 4 + import BaseHeadTags from '$lib/Components/Layout/BaseHeadTags.svelte'; 5 5 6 6 import { type PageData } from './$types'; 7 7 let { data }: { data: PageData } = $props(); ··· 18 18 <h1 class="pb-8 text-4xl font-bold tracking-tight text-base-50">search for a movie or show</h1> 19 19 <div class="mt-10 w-full sm:mx-auto sm:max-w-sm"> 20 20 <form class="space-y-6" action="?/randomize" method="POST"> 21 - 22 21 <div> 23 22 <button 24 23 type="submit" ··· 76 75 77 76 {#if data.results.length > 0} 78 77 <div class="flex-row"> 79 - <div class="flex-col"> 80 - <h2 class="mt-8 text-2xl font-bold tracking-tight text-base-50">results</h2> 81 - 78 + <div class="flex-col"> 79 + <h2 class="mt-8 text-2xl font-bold tracking-tight text-base-50">results</h2> 82 80 83 - <ItemsGrid items={data.results} showMark={!!data.user} /> 81 + <ItemsGrid items={data.results} showMark={!!data.user} /> 84 82 </div> 85 - <div class="flex-col"> 86 - <h2 class="mt-8 text-2xl font-bold tracking-tight text-base-50">results</h2> 83 + <div class="flex-col"> 84 + <h2 class="mt-8 text-2xl font-bold tracking-tight text-base-50">results</h2> 87 85 88 - <ItemsGrid items={data.results2} showMark={!!data.user} /> 86 + <ItemsGrid items={data.results2} showMark={!!data.user} /> 89 87 </div> 90 88 </div> 91 89 {:else if data.query} 92 90 <p class="text-md mt-8 text-base-50">no results found</p> 93 91 {/if} 94 - 95 92 </div> 96 93 </Container>
-23
src/routes/logout/+page.server.ts
··· 1 - import { redirect } from '@sveltejs/kit'; 2 - 3 - import type { Actions, PageServerLoad } from './$types'; 4 - import { register } from '$lib/Components/relative-time'; 5 - 6 - export const load: PageServerLoad = async (event) => { 7 - if (event.cookies.get('sid')) { 8 - const sid = event.cookies.get('sid'); 9 - 10 - 11 - } 12 - return {}; 13 - }; 14 - 15 - export const actions: Actions = { 16 - login: async (event) => 17 - {event.cookies.delete('sid', { path: '/' }); 18 - return redirect(302, '/');}, 19 - register: async (event) => 20 - {event.cookies.delete('sid', { path: '/' }); 21 - return redirect(302, '/');} 22 - 23 - };
-26
src/routes/logout/+page.svelte
··· 1 - <script lang="ts"> 2 - import type { ActionData } from './$types'; 3 - import Logo from '$lib/Components/Logo.svelte'; 4 - 5 - let { form }: { form: ActionData } = $props(); 6 - </script> 7 - 8 - <div class="flex min-h-screen flex-col items-center justify-center px-6 py-12 lg:px-8"> 9 - <div class="sm:mx-auto sm:w-full sm:max-w-sm"> 10 - <Logo class="mx-auto size-10" /> 11 - <h2 class="mt-10 text-center text-2xl/9 font-bold tracking-tight text-white">Are you sure you want to logout</h2> 12 - </div> 13 - 14 - <div class="mt-10 w-full sm:mx-auto sm:max-w-sm"> 15 - <form class="space-y-6" action="?/login" method="POST"> 16 - 17 - <div> 18 - <button 19 - type="submit" 20 - class="flex w-full justify-center rounded-xl bg-accent-500 px-3 py-1.5 text-sm/6 font-semibold text-white shadow-sm hover:bg-accent-400 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent-500" 21 - >logout</button 22 - > 23 - </div> 24 - </form> 25 - </div> 26 - </div>
-18
src/routes/personalfeed/+page.server.ts
··· 1 - import { getProfile, resolveHandle,getFollows } from '$lib/bluesky.js'; 2 - import {getRecentRecords,getRecentRecordOfUser,getAuthorDids } from '$lib/db'; 3 - import { settings, user } from '$lib/state.svelte'; 4 - 5 - /** @type {import('./$types').PageServerLoad} */ 6 - export async function load(event) { 7 - const feed = await getRecentRecords(); 8 - const did = event.locals.user.did; 9 - var filt=feed; 10 - const followsPromise = getFollows({ did }); 11 - const authors=await getAuthorDids(); 12 - const [follows] = await Promise.all([followsPromise]); 13 - filt=feed.filter((review)=>(follows.follows.some((person)=>(person.did==review.author.did)))); 14 - console.log(filt); 15 - 16 - const truefeed=filt; 17 - return { truefeed }; 18 - }
+3 -4
src/routes/personalfeed/+page.svelte src/routes/following/+page.svelte
··· 1 1 <script lang="ts"> 2 - import Container from '$lib/Components/Container.svelte'; 3 - import { onMount } from 'svelte'; 2 + import Container from '$lib/Components/Layout/Container.svelte'; 4 3 import { type PageData } from './$types'; 5 - import ReviewList from '$lib/Components/ReviewList.svelte'; 6 - import BaseHeadTags from '$lib/Components/BaseHeadTags.svelte'; 4 + import ReviewList from '$lib/Components/Items/ReviewList.svelte'; 5 + import BaseHeadTags from '$lib/Components/Layout/BaseHeadTags.svelte'; 7 6 8 7 let { data }: { data: PageData } = $props(); 9 8 </script>
+3 -3
src/routes/review/[uri]/+page.svelte
··· 58 58 <ReviewCard data={data.record} bigText={true} /> 59 59 60 60 {#if data.record.record.crosspost?.uri} 61 - <div class="relative w-full max-w-2xl backdrop-blur-sm mb-8"> 61 + <div class="relative mb-8 w-full max-w-2xl backdrop-blur-sm"> 62 62 <div class="flex max-w-full items-center gap-4 overflow-hidden px-4"> 63 63 <Comments uri={data.record.record.crosspost.uri} user={''} comments={[]} url={''} /> 64 64 </div> ··· 66 66 {:else} 67 67 <div class="flex flex-wrap items-center justify-center gap-4"> 68 68 <div 69 - class="border-base-700 bg-base-50/5 text-base-200 hover:bg-base-50/10 z-10 rounded-lg border px-3 py-2 text-xs font-medium transition-all duration-100" 69 + class="z-10 rounded-lg border border-base-700 bg-base-50/5 px-3 py-2 text-xs font-medium text-base-200 transition-all duration-100 hover:bg-base-50/10" 70 70 > 71 71 <a 72 72 href={`/${data.record.record.item.ref === 'tmdb:m' ? 'movie' : 'tv'}/${data.record.record.item.value}`} ··· 75 75 </a> 76 76 </div> 77 77 <div 78 - class="border-base-700 bg-base-50/5 text-base-200 hover:bg-base-50/10 z-10 rounded-lg border px-3 py-2 text-xs font-medium transition-all duration-100" 78 + class="z-10 rounded-lg border border-base-700 bg-base-50/5 px-3 py-2 text-xs font-medium text-base-200 transition-all duration-100 hover:bg-base-50/10" 79 79 > 80 80 <a href={`/user/${data.record.author.handle}`}> 81 81 all reviews by {data.record.author.displayName || data.record.author.handle}
+31 -20
src/routes/user/[handle]/+page.server.ts
··· 1 - import { getProfile, resolveHandle,getFollows } from '$lib/bluesky.js'; 2 - import { getRecentRecordOfUser,getAuthorDids } from '$lib/db'; 3 - import type { Actions, PageServerLoad } from './$types'; 1 + import { getProfile, resolveHandle, getFollows } from '$lib/bluesky.js'; 2 + import { getRecentRecordOfUser, getAuthorDids } from '$lib/db'; 3 + 4 4 /** @type {import('./$types').PageServerLoad} */ 5 5 export async function load(event) { 6 6 const handle = event.params.handle; 7 7 8 8 const did = await resolveHandle({ handle: handle }); 9 - const userdid=event.locals.user?.did; 10 9 11 10 const profilePromise = getProfile({ did }); 12 - const itemsPromise = getRecentRecordOfUser({ did }); 13 - const followsPromise = getFollows({ did }); 14 - const userfollowsPromise=getFollows({ did }); 15 - const authors=await getAuthorDids(); 16 - const [profile, items,follows,userfollows] = await Promise.all([profilePromise, itemsPromise,followsPromise,userfollowsPromise]); 17 - var i=follows.follows.length-1; 18 - const isfollowed=userfollows.follows.some((word)=>(word.did==did)); 19 - while(i>=0){ 20 - if(authors.includes(follows.follows[i].did)){ 21 - } 22 - else{ 23 - follows.follows.splice(i,1); 24 - } 25 - i--; 26 - 11 + const reviewsPromise = getRecentRecordOfUser({ did }); 12 + 13 + const authorsPromise = getAuthorDids(); 14 + 15 + const [profile, reviews, authors] = await Promise.all([ 16 + profilePromise, 17 + reviewsPromise, 18 + authorsPromise 19 + ]); 20 + 21 + if (event.locals.user) { 22 + const follows = await getFollows({ did: event.locals.user.did }); 23 + 24 + return { 25 + isUser: event.locals.user?.did === did, 26 + username: event.params.handle, 27 + profile, 28 + items: reviews, 29 + follows: follows.follows.filter((profile) => authors.includes(profile.did)), 30 + following: follows.follows.some((profile) => profile.did == did), 31 + did 32 + }; 27 33 } 28 34 29 - return { isUser: event.locals.user?.did === did, username: event.params.handle, profile, items,follows,isfollowed,did }; 35 + return { 36 + isUser: false, 37 + profile, 38 + items: reviews, 39 + did 40 + }; 30 41 }
+45 -38
src/routes/user/[handle]/+page.svelte
··· 1 1 <script lang="ts"> 2 - import { getFollows } from '$lib/bluesky.js'; 3 2 import Avatar from '$lib/Components/User/Avatar.svelte'; 4 3 import BaseHeadTags from '$lib/Components/Layout/BaseHeadTags.svelte'; 5 4 import Container from '$lib/Components/Layout/Container.svelte'; 6 5 import Profile from '$lib/Components/User/Profile.svelte'; 7 6 import ReviewList from '$lib/Components/Items/ReviewList.svelte'; 8 7 import { toast } from 'svelte-sonner'; 8 + import { cn } from '$lib/utils.js'; 9 9 10 10 let { data } = $props(); 11 11 </script> ··· 21 21 <Profile profile={data.profile} /> 22 22 </div> 23 23 {#if data.isUser} 24 - <div class="text-center"> 25 - <a 26 - href={'/logout/'} 27 - class="button position-relative align-items-center w-full flex-row-reverse justify-center rounded-xl bg-accent-500 px-3 py-1.5 text-center text-sm/6 font-semibold text-white shadow-sm hover:bg-accent-400 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent-500" 28 - >logout</a 29 - > 30 - </div> 31 - {:else if data.isfollowed} 32 - <div class="text-center"> 33 - <a 34 - href={'/logout/'} 35 - class="button w-full flex-row-reverse justify-end rounded-xl bg-accent-500 px-3 py-1.5 text-center text-sm/6 font-semibold text-white shadow-sm hover:bg-accent-400 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent-500" 36 - >logout</a 37 - > 24 + <div class="mx-auto mt-2 flex max-w-2xl px-4 sm:-mt-24 sm:justify-end sm:px-0"> 25 + <form class="space-y-6" action="/?/logout" method="POST"> 26 + <button 27 + type="submit" 28 + class={cn( 29 + 'inline-flex justify-center rounded-xl bg-white/10 px-3 py-2 text-sm font-semibold text-base-50 shadow-sm ring-1 ring-inset ring-base-300/20 hover:bg-base-50/20', 30 + data.profile.banner ? 'mt-6' : 'mt-3' 31 + )} 32 + > 33 + <span>Logout</span> 34 + </button> 35 + </form> 38 36 </div> 39 - {:else} 40 - <div class="text-center"> 37 + {:else if data.user} 38 + <div class="mx-auto mt-2 flex max-w-2xl px-4 sm:-mt-24 sm:justify-end sm:px-0"> 41 39 <button 42 - class="danger lg justify-center rounded-xl bg-accent-500 px-3 py-1.5 text-sm/6 font-semibold text-white shadow-sm hover:bg-accent-400 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent-500" 40 + type="button" 43 41 onclick={async () => { 44 - if (data.isfollowed) return; 42 + if (data.following) return; 45 43 46 - data.isfollowed = true; 44 + data.following = true; 47 45 48 46 try { 49 47 const response = await fetch('/api/follow', { ··· 51 49 body: JSON.stringify({ did: data.did }) 52 50 }); 53 51 if (response.ok) { 54 - toast.success('Liked'); 52 + toast.success('Followed'); 55 53 } else { 56 - toast.error('must be logged in to like'); 57 - data.isfollowed = false; 54 + toast.error('You must be logged in to follow'); 55 + data.following = false; 58 56 } 59 57 } catch (e) { 60 58 console.error(e); 61 - toast.error('must be logged in to like'); 62 - data.isfollowed = false; 59 + toast.error('Must be logged in to follow'); 60 + 61 + data.following = false; 63 62 } 64 63 }} 64 + class={cn( 65 + 'inline-flex justify-center rounded-xl bg-accent-500/10 px-3 py-2 text-sm font-semibold text-accent-400 shadow-sm ring-1 ring-inset ring-accent-300/20 hover:bg-accent-400/20', 66 + data.profile.banner ? 'mt-6' : 'mt-3', 67 + data.following ? 'bg-accent-500/10' : '' 68 + )} 65 69 > 66 - Follow 70 + <span>{data.following ? 'Following' : 'Follow'}</span> 67 71 </button> 68 72 </div> 69 73 {/if} 70 - {#if data.items.length > 0} 74 + 75 + {#if data.isUser && data.follows} 71 76 <p 72 - class="max-w-full truncate text-center text-lg font-bold text-base-900 dark:text-base-100 sm:text-xl" 77 + class="mx-auto mt-16 max-w-2xl truncate px-4 text-lg font-bold text-base-900 dark:text-base-100 sm:text-xl" 73 78 > 74 - Follows 79 + People you follow 75 80 </p> 76 - <div class={'mx-auto mt-8 flex max-w-2xl gap-x-6 overflow-x-auto'}> 77 - {#each data.follows.follows as castMember} 78 - {#if castMember.did} 79 - <a href={`/user/` + castMember.handle} class="flex flex-col items-center gap-1"> 80 - <Avatar src={castMember.avatar ? castMember.avatar : undefined} size="size-16" /> 81 - <div class="text-center text-xs font-medium">{castMember.displayName}</div> 82 - <div class="text-center text-xs text-base-400">{castMember.handle}</div> 81 + <div class="mx-auto mt-4 flex max-w-2xl gap-x-6 overflow-x-auto px-4"> 82 + {#each data.follows as profile} 83 + {#if profile.did} 84 + <a href={`/user/` + profile.handle} class="flex flex-col items-center gap-1"> 85 + <Avatar src={profile.avatar ? profile.avatar : undefined} size="size-16" /> 86 + <div class="text-center text-xs font-medium">{profile.displayName}</div> 87 + <div class="text-center text-xs text-base-400">{profile.handle}</div> 83 88 </a> 84 89 {/if} 85 90 {/each} 86 91 </div> 92 + {/if} 93 + {#if data.items.length > 0} 87 94 <p 88 - class="max-w-full truncate text-center text-lg font-bold text-base-900 dark:text-base-100 sm:text-xl" 95 + class="mx-auto mt-16 max-w-2xl truncate px-4 text-lg font-bold text-base-900 dark:text-base-100 sm:text-xl" 89 96 > 90 97 Reviews 91 98 </p> 92 - <div><ReviewList reviews={data.items} class="mx-auto mt-8 max-w-2xl" /></div> 99 + <div><ReviewList reviews={data.items} class="mx-auto mt-2 max-w-2xl" /></div> 93 100 {:else} 94 101 <p class="py-8 text-center text-base-500">No movies or tv shows rated yet.</p> 95 102 {/if}
-14
src/routes/win/[counter]/+page.server.ts
··· 1 - import { redirect } from '@sveltejs/kit'; 2 - 3 - import type { Actions, PageServerLoad } from './$types'; 4 - import { register } from '$lib/Components/relative-time'; 5 - 6 - export const load: PageServerLoad = async (event) => { 7 - if (event.cookies.get('sid')) { 8 - const sid = event.cookies.get('sid'); 9 - 10 - 11 - } 12 - return {}; 13 - }; 14 -
+4 -5
src/routes/win/[counter]/+page.svelte
··· 1 1 <script lang="ts"> 2 - import type { ActionData } from './$types'; 3 - import Logo from '$lib/Components/Logo.svelte'; 4 - 5 - let { form }: { form: ActionData } = $props(); 2 + import Logo from '$lib/Components/UI/Logo.svelte'; 6 3 </script> 7 4 8 5 <div class="flex min-h-screen flex-col items-center justify-center px-6 py-12 lg:px-8"> 9 6 <div class="sm:mx-auto sm:w-full sm:max-w-sm"> 10 7 <Logo class="mx-auto size-10" /> 11 - <h2 class="mt-10 text-center text-2xl/9 font-bold tracking-tight text-white">You have Won. Congratulations</h2> 8 + <h2 class="mt-10 text-center text-2xl/9 font-bold tracking-tight text-white"> 9 + You have Won. Congratulations 10 + </h2> 12 11 </div> 13 12 </div>
+3 -3
static/plyr.css
··· 306 306 font-size: 13px; 307 307 font-size: var(--plyr-font-size-menu, var(--plyr-font-size-small, 13px)); 308 308 padding: 4.66667px 10.5px; 309 - padding: calc(var(--plyr-control-spacing, 10px) * 0.7/1.5) 309 + padding: calc(var(--plyr-control-spacing, 10px) * 0.7 / 1.5) 310 310 calc(var(--plyr-control-spacing, 10px) * 0.7 * 1.5); 311 311 -webkit-user-select: none; 312 312 user-select: none; ··· 347 347 margin: 7px; 348 348 margin: calc(var(--plyr-control-spacing, 10px) * 0.7); 349 349 margin-bottom: 3.5px; 350 - margin-bottom: calc(var(--plyr-control-spacing, 10px) * 0.7/2); 350 + margin-bottom: calc(var(--plyr-control-spacing, 10px) * 0.7 / 2); 351 351 padding-left: 28px; 352 352 padding-left: calc(var(--plyr-control-spacing, 10px) * 0.7 * 4); 353 353 position: relative; ··· 369 369 height: 1px; 370 370 left: 0; 371 371 margin-top: 3.5px; 372 - margin-top: calc(var(--plyr-control-spacing, 10px) * 0.7/2); 372 + margin-top: calc(var(--plyr-control-spacing, 10px) * 0.7 / 2); 373 373 overflow: hidden; 374 374 position: absolute; 375 375 right: 0;