beatufitull front end for ozone modration ,, wit catpucoin and ebergarden !
0
fork

Configure Feed

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

update animatons and modals

+91 -61
+39 -40
src/components/Modal.svelte
··· 1 1 <script lang="ts"> 2 2 import { X } from 'lucide-svelte'; 3 - import { cubicOut } from 'svelte/easing'; 4 - import { scale } from 'svelte/transition'; 3 + import type { Snippet } from 'svelte'; 4 + import { cubicOut, cubicIn } from 'svelte/easing'; 5 + import { fade, scale } from 'svelte/transition'; 5 6 6 - export let isOpen = false; 7 - export let title = ''; 8 - export let onClose = () => {}; 7 + let { 8 + isOpen = false, 9 + title = '', 10 + onClose = () => {}, 11 + children 12 + }: { 13 + isOpen?: boolean; 14 + title?: string | Snippet; 15 + onClose?: () => void; 16 + children: Snippet; 17 + } = $props(); 9 18 10 19 function handleBackdropClick() { 11 20 onClose(); 12 21 } 13 - 14 22 function handleKeydown(e: KeyboardEvent) { 15 - if (e.key === 'Escape') { 16 - onClose(); 17 - } 23 + if (e.key === 'Escape') onClose(); 18 24 } 19 25 </script> 20 26 21 - <svelte:window on:keydown={handleKeydown} /> 27 + <svelte:window onkeydown={handleKeydown} /> 22 28 23 29 {#if isOpen} 24 30 <div 25 31 class="fixed inset-0 z-40 bg-black/30" 26 - on:click={handleBackdropClick} 32 + transition:fade={{ duration: 150 }} 33 + onclick={handleBackdropClick} 27 34 role="presentation" 28 35 tabindex="-1" 29 36 ></div> 37 + {/if} 38 + 39 + {#if isOpen} 30 40 <div 31 - class="fixed inset-0 z-50 flex items-center justify-center p-4 md:p-4" 41 + class="fixed inset-0 z-50 flex items-center justify-center" 32 42 style=" 33 - padding-top: max(1rem, env(safe-area-inset-top)); 34 - padding-right: max(1rem, env(safe-area-inset-right)); 35 - padding-bottom: max(1rem, env(safe-area-inset-bottom)); 36 - padding-left: max(1rem, env(safe-area-inset-left)); 37 - " 43 + padding-top: max(1rem, env(safe-area-inset-top)); 44 + padding-right: max(1rem, env(safe-area-inset-right)); 45 + padding-bottom: max(1rem, env(safe-area-inset-bottom)); 46 + padding-left: max(1rem, env(safe-area-inset-left)); 47 + " 38 48 > 39 49 <div 40 - out:scale={{ duration: 100, start: 0.98, easing: cubicOut }} 41 - class="modal-scale-in relative flex h-full max-h-full w-full max-w-3xl flex-col overflow-hidden rounded-lg border border-ctp-surface1 bg-ctp-base shadow-lg transition-transform md:h-auto md:max-h-[calc(100vh-2rem)]" 50 + in:scale={{ duration: 240, start: 0.88, easing: cubicOut }} 51 + out:scale={{ duration: 120, start: 0.95, easing: cubicIn }} 52 + class="relative flex h-full max-h-full w-full max-w-3xl flex-col overflow-hidden rounded-lg border border-ctp-surface1 bg-ctp-base shadow-lg md:h-auto md:max-h-[calc(100vh-2rem)]" 42 53 role="dialog" 43 54 aria-modal="true" 44 55 tabindex="0" 45 56 > 46 57 <div class="flex items-center justify-between border-b border-ctp-surface1 p-4"> 47 - <h2 class="text-lg font-bold text-ctp-text">{title}</h2> 58 + {#if typeof title === 'string'} 59 + <h2 class="text-lg font-bold text-ctp-text"> 60 + {title} 61 + </h2> 62 + {:else} 63 + {@render title()} 64 + {/if} 48 65 <button 49 - on:click={onClose} 66 + onclick={onClose} 50 67 class="cursor-pointer rounded-lg p-1 transition-colors hover:bg-ctp-surface1" 51 68 aria-label="Close modal" 52 69 > ··· 54 71 </button> 55 72 </div> 56 73 <div class="overflow-y-auto p-4"> 57 - <slot /> 74 + {@render children?.()} 58 75 </div> 59 76 </div> 60 77 </div> 61 78 {/if} 62 - 63 - <style> 64 - @media (prefers-reduced-motion: no-preference) { 65 - .modal-scale-in { 66 - animation: modal-scale-in 240ms cubic-bezier(0.22, 1, 0.36, 1); 67 - } 68 - } 69 - 70 - @keyframes modal-scale-in { 71 - from { 72 - transform: scale(0.88); 73 - } 74 - 75 - to { 76 - transform: scale(1); 77 - } 78 - } 79 - </style>
+3 -9
src/components/Search.svelte
··· 6 6 import { createQuery } from '@tanstack/svelte-query'; 7 7 import { CircleQuestionMark, HistoryIcon, SearchIcon, TrashIcon } from 'lucide-svelte'; 8 8 import { cubicOut } from 'svelte/easing'; 9 - import { fade, scale } from 'svelte/transition'; 9 + import { scale } from 'svelte/transition'; 10 10 import Avatar from './ui/Avatar.svelte'; 11 - 12 - type SearchSuggestion = { 13 - handle: string; 14 - displayName?: string; 15 - avatar?: string; 16 - }; 17 11 18 12 type HistoricalSearchSuggestion = { 19 13 handle: string; ··· 33 27 let recentSuggestions = $state<HistoricalSearchSuggestion[]>([]); 34 28 const defaultSuggestions = [ 35 29 { 36 - did: 'did:plc:73gqgbnvpx5syidcponjrics', 37 - handle: 'coil-habdle.ebil.club' 30 + did: 'did:plc:nz5npphohxgjo2blfipdatam', 31 + handle: 'coil-habdle-again.tngl.sh' 38 32 }, 39 33 { 40 34 handle: 'koi.rip',
+12 -5
src/components/view/Post.svelte
··· 1 1 <script lang="ts"> 2 2 import { createQuery } from '@tanstack/svelte-query'; 3 3 import { AppBskyFeedPost, AtUri } from '@atproto/api'; 4 - import { LoaderCircle } from 'lucide-svelte'; 4 + import { LoaderCircle, MessageCircle, MessageCircleMore } from 'lucide-svelte'; 5 5 import Modal from '../Modal.svelte'; 6 6 import Tabs from '../ui/Tabs.svelte'; 7 7 import { session } from '$lib/stores/auth'; 8 - import catsky from '$lib/assets/catsky.png'; 9 - import Bluesky from '$lib/assets/bluesky.svelte'; 10 8 import Events from './Events.svelte'; 11 9 import Actions from './Actions.svelte'; 12 10 import type { RecordViewDetail } from '@atproto/api/dist/client/types/tools/ozone/moderation/defs'; 13 11 import EventsContainer from './EventsContainer.svelte'; 14 - import Witchsky from '$lib/assets/witchsky.svelte'; 15 12 import ClientPicker from '$components/ClientPicker.svelte'; 16 13 17 14 let { ··· 156 153 </div> 157 154 {/snippet} 158 155 159 - <Modal {isOpen} title={`Post by @${profileQuery.data?.handle || uri.host}`} {onClose}> 156 + {#snippet modalTitle()} 157 + <div class="flex items-center gap-2 text-ctp-text"> 158 + <MessageCircleMore size={18} /> 159 + <h1 class="text-[17px] font-medium"> 160 + <span class="font-normal text-ctp-subtext0">Post by </span> 161 + {profileQuery.data?.handle || uri.host} 162 + </h1> 163 + </div> 164 + {/snippet} 165 + 166 + <Modal {isOpen} title={modalTitle} {onClose}> 160 167 <div class="space-y-3"> 161 168 <Tabs 162 169 tabs={[...panelTabs]}
+11 -2
src/components/view/User.svelte
··· 1 1 <script lang="ts"> 2 2 import { createQuery } from '@tanstack/svelte-query'; 3 3 import { AtUri } from '@atproto/api'; 4 - import { LoaderCircle } from 'lucide-svelte'; 4 + import { AtSign, CircleUser, LoaderCircle, UserCircleIcon, UserIcon } from 'lucide-svelte'; 5 5 import Modal from '$components/Modal.svelte'; 6 6 import Tabs from '$ui/Tabs.svelte'; 7 7 import { session } from '$lib/stores/auth'; ··· 149 149 </div> 150 150 {/snippet} 151 151 152 - <Modal {isOpen} title={`@${userQuery.data?.profile?.handle || actor}'s profile`} {onClose}> 152 + {#snippet modalTitle()} 153 + <div class="flex items-center gap-2 text-ctp-text"> 154 + <CircleUser size={18} /> 155 + <h1 class="text-[17px] font-medium"> 156 + {userQuery.data?.profile?.handle || actor} 157 + </h1> 158 + </div> 159 + {/snippet} 160 + 161 + <Modal {isOpen} title={modalTitle} {onClose}> 153 162 <div class="space-y-3"> 154 163 <Tabs 155 164 tabs={[...panelTabs]}
+26 -5
src/routes/+layout.svelte
··· 18 18 import UserModal from '$components/view/User.svelte'; 19 19 import PostModal from '$components/view/Post.svelte'; 20 20 import { appTheme } from '$lib/stores/ui'; 21 + import { fade } from 'svelte/transition'; 22 + import { cubicIn, cubicOut } from 'svelte/easing'; 21 23 22 24 let { children } = $props(); 23 25 let currentPath = $derived(page.url.pathname); 24 - let isInitialized = $state(false); 25 26 let redirecting = $state(false); 26 27 const authMethod = getAuthenticationMethod(); 27 28 const PASSWORD_SESSION_KEY = 'meowzone-password-session'; ··· 122 123 noScroll: true 123 124 }); 124 125 } 126 + 127 + let userModalOpen = $state(false); 128 + let postModalOpen = $state(false); 129 + 130 + // When params appear, open the modal 131 + $effect(() => { 132 + if (modalView === 'user' && modalDid) userModalOpen = true; 133 + if (modalView === 'post' && modalUri) postModalOpen = true; 134 + }); 135 + 136 + function closeUserModal() { 137 + userModalOpen = false; 138 + setTimeout(clearModalFromUrl, 350); // match your longest transition duration 139 + } 140 + 141 + function closePostModal() { 142 + postModalOpen = false; 143 + setTimeout(clearModalFromUrl, 350); 144 + } 125 145 </script> 126 146 127 147 <svelte:head><link rel="icon" href={favicon} /></svelte:head> ··· 137 157 <LabelerSetup /> 138 158 {:else if !redirecting} 139 159 {#if currentPath !== '/login'} 140 - {#if modalView === 'user' && modalDid} 160 + {#if modalDid} 141 161 {#key modalDid} 142 - <UserModal isOpen={true} did={modalDid} onClose={clearModalFromUrl} /> 162 + <UserModal isOpen={userModalOpen} did={modalDid} onClose={closeUserModal} /> 143 163 {/key} 144 - {:else if modalView === 'post' && modalUri} 164 + {/if} 165 + {#if modalUri} 145 166 {#key modalUri.toString()} 146 - <PostModal isOpen={true} uri={modalUri} onClose={clearModalFromUrl} /> 167 + <PostModal isOpen={postModalOpen} uri={modalUri} onClose={closePostModal} /> 147 168 {/key} 148 169 {/if} 149 170 <Search isOpen={searchOpen} onClose={() => (searchOpen = false)} />