Coves frontend - a photon fork
at main 225 lines 6.4 kB view raw
1<script lang="ts"> 2 import { goto } from '$app/navigation' 3 import { client, site } from '$lib/api/client.svelte' 4 import { profile } from '$lib/app/auth.svelte' 5 import { errorMessage } from '$lib/app/error' 6 import { t } from '$lib/app/i18n' 7 import MarkdownEditor from '$lib/app/markdown/MarkdownEditor.svelte' 8 import ImageInputUpload from '$lib/ui/form/ImageInputUpload.svelte' 9 import { Header } from '$lib/ui/layout' 10 import { 11 Badge, 12 Button, 13 Label, 14 Material, 15 Menu, 16 MenuButton, 17 Option, 18 Select, 19 Switch, 20 TextInput, 21 toast, 22 } from 'mono-svelte' 23 import { GlobeAlt, Icon, MapPin, Plus } from 'svelte-hero-icons/dist' 24 import { addSubscription } from '../user' 25 26 interface Props { 27 /** 28 * The community ID to edit. 29 */ 30 edit?: number 31 formData?: { 32 name: string 33 displayName: string 34 icon?: string 35 banner?: string 36 sidebar?: string 37 nsfw: boolean 38 postsLockedToModerators: boolean 39 submitting: boolean 40 visibility: 'Public' | 'LocalOnly' 41 languages?: number[] 42 } 43 formtitle?: import('svelte').Snippet 44 } 45 46 let { 47 edit = undefined, 48 formData: passedFormData = $bindable({ 49 name: '', 50 displayName: '', 51 sidebar: '', 52 nsfw: false, 53 postsLockedToModerators: false, 54 submitting: false, 55 visibility: 'Public', 56 languages: undefined, 57 }), 58 formtitle, 59 }: Props = $props() 60 61 let formData = $state(passedFormData) 62 63 async function submit() { 64 if (!profile.current?.jwt) return 65 if ((!edit && formData.name == '') || formData.displayName == '') return 66 67 formData.submitting = true 68 69 try { 70 const res = edit 71 ? await client().editCommunity({ 72 title: formData.displayName, 73 description: formData.sidebar, 74 nsfw: formData.nsfw, 75 posting_restricted_to_mods: formData.postsLockedToModerators, 76 icon: formData.icon, 77 banner: formData.banner, 78 community_id: edit, 79 visibility: formData.visibility, 80 discussion_languages: formData.languages, 81 }) 82 : await client().createCommunity({ 83 name: formData.name, 84 title: formData.displayName, 85 description: formData.sidebar, 86 nsfw: formData.nsfw, 87 posting_restricted_to_mods: formData.postsLockedToModerators, 88 icon: formData.icon, 89 banner: formData.banner, 90 visibility: formData.visibility, 91 discussion_languages: formData.languages, 92 }) 93 94 toast({ 95 content: $t('toast.updatedCommunity'), 96 type: 'success', 97 }) 98 99 // TODO: Update local state when Coves API provides user moderates data 100 addSubscription(res.community_view.community, true) 101 102 if (!edit) 103 goto( 104 `/c/${res.community_view.community.name}@${ 105 new URL(res.community_view.community.actor_id).hostname 106 }`, 107 ) 108 } catch (err) { 109 toast({ 110 content: errorMessage(err as string), 111 type: 'error', 112 }) 113 } 114 115 formData.submitting = false 116 } 117</script> 118 119<form 120 onsubmit={(e) => { 121 e.preventDefault() 122 submit() 123 }} 124 class="flex flex-col gap-4 h-full w-full" 125> 126 {#if formtitle}{@render formtitle()}{:else} 127 <Header>{$t('form.post.community')}</Header> 128 {/if} 129 <TextInput 130 required 131 label={$t('form.name')} 132 bind:value={formData.name} 133 oninput={() => { 134 formData.name = formData.name.toLowerCase().replaceAll(' ', '_') 135 }} 136 disabled={edit != undefined} 137 /> 138 <TextInput 139 required 140 label={$t('form.profile.displayName')} 141 bind:value={formData.displayName} 142 /> 143 <div class="flex flex-row gap-4 flex-wrap *:flex-1"> 144 <ImageInputUpload 145 bind:imageUrl={formData.icon} 146 label={$t('routes.admin.config.icon')} 147 /> 148 <ImageInputUpload 149 bind:imageUrl={formData.banner} 150 label={$t('routes.admin.config.banner')} 151 /> 152 </div> 153 <MarkdownEditor 154 previewButton 155 label={$t('routes.admin.config.sidebar')} 156 bind:value={formData.sidebar} 157 /> 158 159 <Switch bind:checked={formData.nsfw}>{$t('post.badges.nsfw')}</Switch> 160 <Switch bind:checked={formData.postsLockedToModerators}> 161 Only moderators can post 162 </Switch> 163 <Select label="Visibility" class="w-max" bind:value={formData.visibility}> 164 <Option icon={GlobeAlt} value="Public">Public</Option> 165 <Option icon={MapPin} value="LocalOnly">Local Only</Option> 166 </Select> 167 168 <div class="space-y-1"> 169 <Label>{$t('form.profile.languages.title')}</Label> 170 <Material rounding="xl" color="uniform" class="dark:bg-zinc-950"> 171 {#if site.data} 172 <div class="flex gap-2 flex-wrap flex-row"> 173 <Menu class="gap-px"> 174 {#snippet target(attachment)} 175 <button {@attach attachment} type="button"> 176 <Badge color="blue-subtle"> 177 <Icon src={Plus} micro size="14" /> 178 {$t('common.add')} 179 </Badge> 180 </button> 181 {/snippet} 182 {#each site.data.all_languages.filter((l) => !formData.languages?.includes(l.id)) as language (language.id)} 183 <MenuButton 184 class="min-h-[16px] py-0" 185 onclick={() => { 186 formData.languages = [ 187 ...(formData.languages ?? []), 188 language.id, 189 ] 190 }} 191 > 192 {language.name} 193 </MenuButton> 194 {/each} 195 </Menu> 196 {#each formData.languages ?? [] as languageId, index (languageId)} 197 {@const language = site.data.all_languages.find( 198 (l) => l.id == languageId, 199 )} 200 <button 201 type="button" 202 class="hover:brightness-150 transition-all" 203 onclick={() => { 204 formData.languages?.splice(index, 1) 205 }} 206 > 207 <Badge class="cursor-pointer">{language?.name}</Badge> 208 </button> 209 {/each} 210 </div> 211 {/if} 212 </Material> 213 </div> 214 215 <Button 216 submit 217 color="primary" 218 size="lg" 219 class="mt-auto" 220 loading={formData.submitting} 221 disabled={formData.submitting} 222 > 223 {edit ? $t('common.save') : $t('form.submit')} 224 </Button> 225</form>