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.

filters yasyyy

+84 -31
+13 -9
src/components/Search.svelte
··· 4 4 import { page } from '$app/state'; 5 5 import { AtpAgent, type AppBskyActorDefs } from '@atproto/api'; 6 6 import { createQuery } from '@tanstack/svelte-query'; 7 - import { SearchIcon, TrashIcon } from 'lucide-svelte'; 7 + import { CircleQuestionMark, HistoryIcon, SearchIcon, TrashIcon } from 'lucide-svelte'; 8 + import Avatar from './ui/Avatar.svelte'; 8 9 9 10 type SearchSuggestion = { 10 11 handle: string; ··· 162 163 tabindex="0" 163 164 > 164 165 <div class="overflow-y-auto"> 165 - <div class="flex items-center gap-2 border-b border-ctp-surface1 px-4 py-2"> 166 - <SearchIcon class="text-ctp-text" size={15} /> 166 + <div class="flex items-center gap-2 border-b border-ctp-surface1 px-4 py-3"> 167 + <SearchIcon class="text-ctp-text" size={16} /> 167 168 <input 168 169 type="text" 169 170 placeholder="Search..." ··· 172 173 /> 173 174 </div> 174 175 {#if debouncedQuery.length < 2} 175 - <div class="mt-2 space-y-1"> 176 + <div class="mt-3 space-y-1"> 176 177 <div class="mb-2 flex items-center justify-between px-3"> 177 - <p class="text-xs text-ctp-subtext1"> 178 + <p class="flex items-center gap-2 text-xs text-ctp-subtext1"> 179 + {#if recentSuggestions.length > 0} 180 + <HistoryIcon size={14} /> 181 + {:else} 182 + <CircleQuestionMark size={14} /> 183 + {/if} 178 184 {recentSuggestions.length > 0 ? 'Recent searches' : 'Examples'} 179 185 </p> 180 186 {#if recentSuggestions.length > 0} ··· 183 189 onclick={() => persistRecentSuggestions([])} 184 190 class="flex cursor-pointer items-center gap-1 rounded text-xs text-ctp-subtext0 hover:text-ctp-red" 185 191 > 186 - <TrashIcon size={16} /> clear 192 + <TrashIcon size={12} /> clear 187 193 </button> 188 194 {/if} 189 195 </div> ··· 216 222 class={buttonClass} 217 223 > 218 224 <div class="flex items-center gap-2 px-3 py-0.5"> 219 - {#if actor.avatar} 220 - <img src={actor.avatar} alt={actor.handle} class="size-6 rounded-full" /> 221 - {/if} 225 + <Avatar src={actor.avatar} alt={actor.handle} size={28} /> 222 226 <div> 223 227 {#if actor.displayName} 224 228 <h3 class="font-medium text-ctp-text">{actor.displayName}</h3>
+14 -14
src/components/ui/Tabs.svelte
··· 1 1 <script lang="ts"> 2 2 import Button from './Button.svelte'; 3 3 4 - type TabItem = { 5 - id: string; 4 + type TabItem<T = unknown> = { 5 + id: T; 6 6 label: string; 7 7 }; 8 8 ··· 10 10 tabs, 11 11 activeTab, 12 12 onTabChange, 13 - className = '' 13 + className = '', 14 + size = 'md' 14 15 }: { 15 16 tabs: TabItem[]; 16 - activeTab: string; 17 - onTabChange: (tabId: string) => void; 17 + activeTab: unknown; 18 + onTabChange: (tabId: unknown) => void; 18 19 className?: string; 20 + size?: 'sm' | 'md'; 19 21 } = $props(); 22 + 23 + const isSmall = $derived(size === 'sm'); 20 24 </script> 21 25 22 - <div class={`flex gap-2 rounded-lg border border-ctp-surface1 bg-ctp-surface0 p-1 ${className}`}> 26 + <div class={`flex rounded-lg ${isSmall ? 'gap-1 p-0.5' : 'gap-2 p-1'} ${className}`}> 23 27 {#each tabs as tab} 24 - <Button 25 - type="button" 26 - variant={activeTab === tab.id ? 'surface' : 'ghost'} 27 - size="sm" 28 - fullWidth={true} 29 - className={`flex-1 rounded-md ${activeTab === tab.id ? 'bg-ctp-surface1 text-ctp-text' : 'text-ctp-subtext0'}`} 30 - on:click={() => onTabChange(tab.id)} 28 + <button 29 + class={`flex-1 cursor-pointer rounded-md transition-colors duration-100 ${isSmall ? 'px-2 py-1 text-xs' : 'px-2 py-1'} ${activeTab === tab.id ? 'border-ctp-surface1 bg-ctp-surface0 text-ctp-text hover:border-ctp-surface2 hover:bg-ctp-surface1' : 'border-ctp-surface0 text-ctp-subtext0 hover:border-ctp-surface1 hover:bg-ctp-surface0 hover:text-ctp-text'} border`} 30 + onclick={() => onTabChange(tab.id)} 31 31 > 32 32 {tab.label} 33 - </Button> 33 + </button> 34 34 {/each} 35 35 </div>
+16 -4
src/components/view/Events.svelte
··· 20 20 import { 21 21 CheckIcon, 22 22 CircleCheck, 23 + CircleQuestionMark, 23 24 CircleUserRound, 24 25 ClipboardIcon, 25 26 ExternalLink, ··· 58 59 const iconSize = 15; 59 60 </script> 60 61 62 + {#snippet reason(value: string)} 63 + <div class="mt-2 flex items-center gap-1 text-sm"> 64 + <CircleQuestionMark size={16} class="text-ctp-text" /> 65 + <p class="text-ctp-text">Reason:</p> 66 + <p class="text-ctp-text">{value}</p> 67 + </div> 68 + {/snippet} 61 69 <div> 62 70 {#if eventsQuery.isLoading} 63 71 <div class="flex items-center justify-center p-10"> ··· 135 143 - {event.event.negateLabelVals.join(', ')} 136 144 </span> 137 145 {/if} 138 - <p class="text-sm text-ctp-text">Reason: {event.event.comment}</p> 146 + {#if event.event.comment} 147 + {@render reason(event.event.comment)} 148 + {/if} 139 149 </div> 140 150 {:else if isModEventTag(event.event)} 141 151 <div class="space-y-1 py-1"> ··· 151 161 {/if} 152 162 </div> 153 163 {:else if isModEventReport(event.event)} 154 - <div class="space-y-1 py-1"> 164 + <div class="space-y-1"> 155 165 <p 156 - class={`w-fit rounded-md border ${elevation == 'lower' ? 'border-ctp-surface1' : 'border-ctp-surface2'} px-2 py-1 text-xs text-ctp-text`} 166 + class={`w-fit rounded-md border ${elevation == 'lower' ? 'border-ctp-surface1' : 'border-ctp-surface2'} my-2 px-2 py-1 text-xs text-ctp-text`} 157 167 > 158 168 {event.event.reportType 159 169 .replace('com.atproto.moderation.defs#reason', '') 160 170 .replace('tools.ozone.report.defs#reason', '')} 161 171 </p> 162 - <p class="text-sm text-ctp-text">Reason: {event.event.comment}</p> 172 + {#if event.event.comment} 173 + {@render reason(event.event.comment)} 174 + {/if} 163 175 </div> 164 176 {/if} 165 177
+41 -4
src/routes/+page.svelte
··· 17 17 import { page } from '$app/state'; 18 18 import { goto } from '$app/navigation'; 19 19 import AdvertisementOverlay from '$components/AdvertisementOverlay.svelte'; 20 + import Tabs from '$components/ui/Tabs.svelte'; 20 21 21 22 function buildUrlForView(view: 'user' | 'post' | null, value?: string) { 22 23 const url = new URL(page.url); ··· 60 61 return 'unknown'; 61 62 } 62 63 } 63 - // switch to infinite query so we can page with the cursor returned by the API 64 + 65 + type State = 66 + | 'tools.ozone.moderation.defs#reviewOpen' 67 + | 'tools.ozone.moderation.defs#reviewClosed' 68 + | 'tools.ozone.moderation.defs#reviewEscalated' 69 + | 'tools.ozone.moderation.defs#reviewNone'; 70 + let filter = $state<State | undefined>(undefined); 64 71 const reportsQuery = createInfiniteQuery(() => ({ 65 72 queryKey: ['reports'], 66 73 queryFn: async ({ pageParam }) => { ··· 71 78 limit: 10, 72 79 sortField: 'lastReportedAt', 73 80 sortDirection: 'desc', 81 + reviewState: filter, 74 82 // pageParam will be the cursor string if present 75 83 cursor: pageParam == '' ? undefined : pageParam 76 84 }); ··· 127 135 <p>{reportsQuery.error?.message}</p> 128 136 </div> 129 137 {:else if reportsQuery.data} 130 - <div class="flex items-center gap-2 pb-5 text-ctp-subtext0"> 131 - <ShieldAlertIcon size={16} /> 132 - <h2 class="text-md font-medium">Recent Reports</h2> 138 + <div class="mb-4 flex items-center gap-2"> 139 + <div class="flex items-center gap-2 text-ctp-subtext0"> 140 + <ShieldAlertIcon size={16} /> 141 + <h2 class="text-base font-medium">Recent Reports</h2> 142 + </div> 143 + <Tabs 144 + tabs={[ 145 + { 146 + id: undefined, 147 + label: 'All' 148 + }, 149 + { 150 + id: 'tools.ozone.moderation.defs#reviewOpen', 151 + label: 'Open' 152 + }, 153 + { 154 + id: 'tools.ozone.moderation.defs#reviewClosed', 155 + label: 'Closed' 156 + }, 157 + { 158 + id: 'tools.ozone.moderation.defs#reviewEscalated', 159 + label: 'Escalated' 160 + } 161 + ]} 162 + activeTab={filter} 163 + onTabChange={(tabId) => { 164 + filter = tabId as State | undefined; 165 + reportsQuery.refetch(); 166 + }} 167 + size="sm" 168 + className="ml-auto" 169 + /> 133 170 </div> 134 171 <ul class="space-y-4 text-ctp-text"> 135 172 {#each reportsQuery.data.pages as page}