chore(front): correct types
This commit is contained in:
		
							parent
							
								
									e1bc6f524e
								
							
						
					
					
						commit
						8ec00c26f8
					
				|  | @ -9,7 +9,6 @@ import { color } from '~/composables/color' | ||||||
| 
 | 
 | ||||||
| import { generateTrackCreditStringFromQueue } from '~/utils/utils' | import { generateTrackCreditStringFromQueue } from '~/utils/utils' | ||||||
| 
 | 
 | ||||||
| // import ChannelUploadModal from '~/components/channels/UploadModal.vue' |  | ||||||
| import PlaylistModal from '~/components/playlists/PlaylistModal.vue' | import PlaylistModal from '~/components/playlists/PlaylistModal.vue' | ||||||
| import FilterModal from '~/components/moderation/FilterModal.vue' | import FilterModal from '~/components/moderation/FilterModal.vue' | ||||||
| import ReportModal from '~/components/moderation/ReportModal.vue' | import ReportModal from '~/components/moderation/ReportModal.vue' | ||||||
|  |  | ||||||
|  | @ -1,592 +0,0 @@ | ||||||
| <script setup lang="ts"> |  | ||||||
| import type { RouteRecordName } from 'vue-router' |  | ||||||
| 
 |  | ||||||
| import { computed, ref, watch, watchEffect, onMounted } from 'vue' |  | ||||||
| import { setI18nLanguage, SUPPORTED_LOCALES } from '~/init/locale' |  | ||||||
| // import { useCurrentElement } from '@vueuse/core' |  | ||||||
| // import { setupDropdown } from '~/utils/fomantic' |  | ||||||
| import { useRoute } from 'vue-router' |  | ||||||
| import { useStore } from '~/store' |  | ||||||
| import { useI18n } from 'vue-i18n' |  | ||||||
| 
 |  | ||||||
| import Modal from '~/components/ui/Modal.vue' |  | ||||||
| import UserModal from '~/components/common/UserModal.vue' |  | ||||||
| import SearchBar from '~/components/audio/SearchBar.vue' |  | ||||||
| import UserMenu from '~/components/common/UserMenu.vue' |  | ||||||
| import Logo from '~/components/Logo.vue' |  | ||||||
| 
 |  | ||||||
| import useThemeList from '~/composables/useThemeList' |  | ||||||
| import useTheme from '~/composables/useTheme' |  | ||||||
| import { isTauri as checkTauri } from '~/composables/tauri' |  | ||||||
| 
 |  | ||||||
| interface Props { |  | ||||||
|   width: number |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| defineProps<Props>() |  | ||||||
| 
 |  | ||||||
| const store = useStore() |  | ||||||
| const { theme } = useTheme() |  | ||||||
| const themes = useThemeList() |  | ||||||
| const { t, locale: i18nLocale } = useI18n() |  | ||||||
| 
 |  | ||||||
| const route = useRoute() |  | ||||||
| const isCollapsed = ref(true) |  | ||||||
| watch(() => route.path, () => (isCollapsed.value = true)) |  | ||||||
| 
 |  | ||||||
| const additionalNotifications = computed(() => store.getters['ui/additionalNotifications']) |  | ||||||
| const logoUrl = computed(() => store.state.auth.authenticated ? 'library.index' : 'index') |  | ||||||
| 
 |  | ||||||
| const labels = computed(() => ({ |  | ||||||
|   mainMenu: t('components.Sidebar.label.main'), |  | ||||||
|   selectTrack: t('components.Sidebar.label.play'), |  | ||||||
|   pendingFollows: t('components.Sidebar.label.follows'), |  | ||||||
|   pendingReviewEdits: t('components.Sidebar.label.edits'), |  | ||||||
|   pendingReviewReports: t('components.Sidebar.label.reports'), |  | ||||||
|   language: t('components.Sidebar.label.language'), |  | ||||||
|   theme: t('components.Sidebar.label.theme'), |  | ||||||
|   addContent: t('components.Sidebar.label.add'), |  | ||||||
|   administration: t('components.Sidebar.label.administration') |  | ||||||
| })) |  | ||||||
| 
 |  | ||||||
| type SidebarMenuTabs = 'explore' | 'myLibrary' |  | ||||||
| const expanded = ref<SidebarMenuTabs>('explore') |  | ||||||
| 
 |  | ||||||
| const ROUTE_MAPPINGS: Record<SidebarMenuTabs, RouteRecordName[]> = { |  | ||||||
|   explore: [ |  | ||||||
|     'search', |  | ||||||
|     'library.index', |  | ||||||
|     'library.podcasts.browse', |  | ||||||
|     'library.albums.browse', |  | ||||||
|     'library.albums.detail', |  | ||||||
|     'library.artists.browse', |  | ||||||
|     'library.artists.detail', |  | ||||||
|     'library.tracks.detail', |  | ||||||
|     'library.playlists.browse', |  | ||||||
|     'library.playlists.detail', |  | ||||||
|     'library.radios.browse', |  | ||||||
|     'library.radios.detail' |  | ||||||
|   ], |  | ||||||
|   myLibrary: [ |  | ||||||
|     'library.me', |  | ||||||
|     'library.albums.me', |  | ||||||
|     'library.artists.me', |  | ||||||
|     'library.playlists.me', |  | ||||||
|     'library.radios.me', |  | ||||||
|     'favorites' |  | ||||||
|   ] |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| watchEffect(() => { |  | ||||||
|   if (ROUTE_MAPPINGS.explore.includes(route.name as RouteRecordName)) { |  | ||||||
|     expanded.value = 'explore' |  | ||||||
|     return |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   if (ROUTE_MAPPINGS.myLibrary.includes(route.name as RouteRecordName)) { |  | ||||||
|     expanded.value = 'myLibrary' |  | ||||||
|     return |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   expanded.value = store.state.auth.authenticated ? 'myLibrary' : 'explore' |  | ||||||
| }) |  | ||||||
| 
 |  | ||||||
| const moderationNotifications = computed(() => |  | ||||||
|   store.state.ui.notifications.pendingReviewEdits |  | ||||||
|     + store.state.ui.notifications.pendingReviewReports |  | ||||||
|     + store.state.ui.notifications.pendingReviewRequests |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| const isLanguageModalOpen = ref(false) |  | ||||||
| const locale = ref(i18nLocale.value) |  | ||||||
| watch(locale, (locale) => { |  | ||||||
|   setI18nLanguage(locale) |  | ||||||
| }) |  | ||||||
| 
 |  | ||||||
| const isProduction = import.meta.env.PROD |  | ||||||
| const isTauri = checkTauri() |  | ||||||
| 
 |  | ||||||
| const isUserModalOpen = ref(false) |  | ||||||
| const isThemeModalOpen = ref(false) |  | ||||||
| 
 |  | ||||||
| // const el = useCurrentElement() |  | ||||||
| // watchEffect(() => { |  | ||||||
| //   if (store.state.auth.authenticated) { |  | ||||||
| //     setupDropdown('.admin-dropdown', el.value) |  | ||||||
| //   } |  | ||||||
| 
 |  | ||||||
| //   setupDropdown('.user-dropdown', el.value) |  | ||||||
| // }) |  | ||||||
| 
 |  | ||||||
| onMounted(() => { |  | ||||||
|   document.getElementById('fake-sidebar')?.classList.add('loaded') |  | ||||||
| }) |  | ||||||
| </script> |  | ||||||
| 
 |  | ||||||
| <template> |  | ||||||
|   <aside :class="['ui', 'vertical', 'left', 'visible', 'wide', {'collapsed': isCollapsed}, 'sidebar', 'component-sidebar']"> |  | ||||||
|     <header class="ui basic segment header-wrapper"> |  | ||||||
|       <router-link |  | ||||||
|         :title="'Funkwhale'" |  | ||||||
|         :to="{name: logoUrl}" |  | ||||||
|       > |  | ||||||
|         <i class="logo bordered inverted vibrant big icon"> |  | ||||||
|           <logo class="logo" /> |  | ||||||
|           <span class="visually-hidden">{{ t('components.Sidebar.link.home') }}</span> |  | ||||||
|         </i> |  | ||||||
|       </router-link> |  | ||||||
|       <nav class="top ui compact right aligned inverted text menu"> |  | ||||||
|         <div class="right menu"> |  | ||||||
|           <div |  | ||||||
|             v-if="store.state.auth.availablePermissions['settings'] || store.state.auth.availablePermissions['moderation']" |  | ||||||
|             class="item" |  | ||||||
|             :title="labels.administration" |  | ||||||
|           > |  | ||||||
|             <div class="item ui inline admin-dropdown dropdown"> |  | ||||||
|               <i class="wrench icon" /> |  | ||||||
|               <div |  | ||||||
|                 v-if="moderationNotifications > 0" |  | ||||||
|                 :class="['ui', 'accent', 'mini', 'bottom floating', 'circular', 'label']" |  | ||||||
|               > |  | ||||||
|                 {{ moderationNotifications }} |  | ||||||
|               </div> |  | ||||||
|               <div class="menu"> |  | ||||||
|                 <h3 class="header"> |  | ||||||
|                   {{ t('components.Sidebar.header.administration') }} |  | ||||||
|                 </h3> |  | ||||||
|                 <div class="divider" /> |  | ||||||
|                 <router-link |  | ||||||
|                   v-if="store.state.auth.availablePermissions['library']" |  | ||||||
|                   class="item" |  | ||||||
|                   :to="{name: 'manage.library.edits', query: {q: 'is_approved:null'}}" |  | ||||||
|                 > |  | ||||||
|                   <div |  | ||||||
|                     v-if="store.state.ui.notifications.pendingReviewEdits > 0" |  | ||||||
|                     :title="labels.pendingReviewEdits" |  | ||||||
|                     :class="['ui', 'circular', 'mini', 'right floated', 'accent', 'label']" |  | ||||||
|                   > |  | ||||||
|                     {{ store.state.ui.notifications.pendingReviewEdits }} |  | ||||||
|                   </div> |  | ||||||
|                   {{ t('components.Sidebar.link.library') }} |  | ||||||
|                 </router-link> |  | ||||||
|                 <router-link |  | ||||||
|                   v-if="store.state.auth.availablePermissions['moderation']" |  | ||||||
|                   class="item" |  | ||||||
|                   :to="{name: 'manage.moderation.reports.list', query: {q: 'resolved:no'}}" |  | ||||||
|                 > |  | ||||||
|                   <div |  | ||||||
|                     v-if="store.state.ui.notifications.pendingReviewReports + store.state.ui.notifications.pendingReviewRequests > 0" |  | ||||||
|                     :title="labels.pendingReviewReports" |  | ||||||
|                     :class="['ui', 'circular', 'mini', 'right floated', 'accent', 'label']" |  | ||||||
|                   > |  | ||||||
|                     {{ store.state.ui.notifications.pendingReviewReports + store.state.ui.notifications.pendingReviewRequests }} |  | ||||||
|                   </div> |  | ||||||
|                   {{ t('components.Sidebar.link.moderation') }} |  | ||||||
|                 </router-link> |  | ||||||
|                 <router-link |  | ||||||
|                   v-if="store.state.auth.availablePermissions['settings']" |  | ||||||
|                   class="item" |  | ||||||
|                   :to="{name: 'manage.users.users.list'}" |  | ||||||
|                 > |  | ||||||
|                   {{ t('components.Sidebar.link.users') }} |  | ||||||
|                 </router-link> |  | ||||||
|                 <router-link |  | ||||||
|                   v-if="store.state.auth.availablePermissions['settings']" |  | ||||||
|                   class="item" |  | ||||||
|                   :to="{path: '/manage/settings'}" |  | ||||||
|                 > |  | ||||||
|                   {{ t('components.Sidebar.link.settings') }} |  | ||||||
|                 </router-link> |  | ||||||
|               </div> |  | ||||||
|             </div> |  | ||||||
|           </div> |  | ||||||
|         </div> |  | ||||||
|         <router-link |  | ||||||
|           v-if="store.state.auth.authenticated" |  | ||||||
|           class="item" |  | ||||||
|           :to="{name: 'content.index'}" |  | ||||||
|         > |  | ||||||
|           <i class="upload icon" /> |  | ||||||
|           <span class="visually-hidden">{{ labels.addContent }}</span> |  | ||||||
|         </router-link> |  | ||||||
|         <template v-if="width > 768"> |  | ||||||
|           <div class="item"> |  | ||||||
|             <div class="ui user-dropdown dropdown"> |  | ||||||
|               <img |  | ||||||
|                 v-if="store.state.auth.authenticated && store.state.auth.profile?.avatar && store.state.auth.profile?.avatar.urls.small_square_crop" |  | ||||||
|                 class="ui avatar image" |  | ||||||
|                 alt="" |  | ||||||
|                 :src="store.getters['instance/absoluteUrl'](store.state.auth.profile?.avatar.urls.small_square_crop)" |  | ||||||
|               > |  | ||||||
|               <actor-avatar |  | ||||||
|                 v-else-if="store.state.auth.authenticated" |  | ||||||
|                 :actor="{preferred_username: store.state.auth.username, full_username: store.state.auth.username,}" |  | ||||||
|               /> |  | ||||||
|               <i |  | ||||||
|                 v-else |  | ||||||
|                 class="cog icon" |  | ||||||
|               /> |  | ||||||
|               <div |  | ||||||
|                 v-if="store.state.ui.notifications.inbox + additionalNotifications > 0" |  | ||||||
|                 :class="['ui', 'accent', 'mini', 'bottom floating', 'circular', 'label']" |  | ||||||
|               > |  | ||||||
|                 {{ store.state.ui.notifications.inbox + additionalNotifications }} |  | ||||||
|               </div> |  | ||||||
|               <user-menu |  | ||||||
|                 v-bind="$attrs" |  | ||||||
|                 :width="width" |  | ||||||
|               /> |  | ||||||
|             </div> |  | ||||||
|           </div> |  | ||||||
|         </template> |  | ||||||
|         <template v-else> |  | ||||||
|           <a |  | ||||||
|             href="" |  | ||||||
|             class="item" |  | ||||||
|             @click.prevent.exact="isUserModalOpen = !isUserModalOpen" |  | ||||||
|           > |  | ||||||
|             <img |  | ||||||
|               v-if="store.state.auth.authenticated && store.state.auth.profile?.avatar?.urls.small_square_crop" |  | ||||||
|               class="ui avatar image" |  | ||||||
|               alt="" |  | ||||||
|               :src="store.getters['instance/absoluteUrl'](store.state.auth.profile?.avatar.urls.small_square_crop)" |  | ||||||
|             > |  | ||||||
|             <actor-avatar |  | ||||||
|               v-else-if="store.state.auth.authenticated" |  | ||||||
|               :actor="{preferred_username: store.state.auth.username, full_username: store.state.auth.username,}" |  | ||||||
|             /> |  | ||||||
|             <i |  | ||||||
|               v-else |  | ||||||
|               class="cog icon" |  | ||||||
|             /> |  | ||||||
|             <div |  | ||||||
|               v-if="store.state.ui.notifications.inbox + additionalNotifications > 0" |  | ||||||
|               :class="['ui', 'accent', 'mini', 'bottom floating', 'circular', 'label']" |  | ||||||
|             > |  | ||||||
|               {{ store.state.ui.notifications.inbox + additionalNotifications }} |  | ||||||
|             </div> |  | ||||||
|           </a> |  | ||||||
|         </template> |  | ||||||
|         <user-modal |  | ||||||
|           v-model:show="isUserModalOpen" |  | ||||||
|           @show-theme-modal-event="isThemeModalOpen=true" |  | ||||||
|           @show-language-modal-event="isLanguageModalOpen=true" |  | ||||||
|         /> |  | ||||||
|         <Modal |  | ||||||
|           ref="languageModal" |  | ||||||
|           v-model="isLanguageModalOpen" |  | ||||||
|           :title="labels.language" |  | ||||||
|           :fullscreen="false" |  | ||||||
|         > |  | ||||||
|           <!-- TODO: Is this actually a popover menu, not a modal? --> |  | ||||||
|           <i |  | ||||||
|             role="button" |  | ||||||
|             class="left chevron back inside icon" |  | ||||||
|             @click.prevent.exact="isUserModalOpen = !isUserModalOpen" |  | ||||||
|           /> |  | ||||||
|           <div class="header"> |  | ||||||
|             <h3 class="title"> |  | ||||||
|               {{ labels.language }} |  | ||||||
|             </h3> |  | ||||||
|           </div> |  | ||||||
|           <div class="content"> |  | ||||||
|             <fieldset |  | ||||||
|               v-for="(language, key) in SUPPORTED_LOCALES" |  | ||||||
|               :key="key" |  | ||||||
|             > |  | ||||||
|               <input |  | ||||||
|                 :id="`${key}`" |  | ||||||
|                 v-model="locale" |  | ||||||
|                 type="radio" |  | ||||||
|                 name="language" |  | ||||||
|                 :value="key" |  | ||||||
|               > |  | ||||||
|               <label :for="`${key}`">{{ language }}</label> |  | ||||||
|             </fieldset> |  | ||||||
|           </div> |  | ||||||
|         </Modal> |  | ||||||
|         <Modal |  | ||||||
|           ref="themeModal" |  | ||||||
|           v-model:show="isThemeModalOpen" |  | ||||||
|           :title="labels.theme" |  | ||||||
|           :fullscreen="false" |  | ||||||
|         > |  | ||||||
|           <i |  | ||||||
|             role="button" |  | ||||||
|             class="left chevron back inside icon" |  | ||||||
|             @click.prevent.exact="isUserModalOpen = !isUserModalOpen" |  | ||||||
|           /> |  | ||||||
|           <div class="header"> |  | ||||||
|             <h3 class="title"> |  | ||||||
|               {{ labels.theme }} |  | ||||||
|             </h3> |  | ||||||
|           </div> |  | ||||||
|           <div class="content"> |  | ||||||
|             <fieldset |  | ||||||
|               v-for="th in themes" |  | ||||||
|               :key="th.key" |  | ||||||
|             > |  | ||||||
|               <input |  | ||||||
|                 :id="th.key" |  | ||||||
|                 v-model="theme" |  | ||||||
|                 type="radio" |  | ||||||
|                 name="theme" |  | ||||||
|                 :value="th.key" |  | ||||||
|               > |  | ||||||
|               <label :for="th.key">{{ th.name }}</label> |  | ||||||
|             </fieldset> |  | ||||||
|           </div> |  | ||||||
|         </Modal> |  | ||||||
|         <div class="item collapse-button-wrapper"> |  | ||||||
|           <button |  | ||||||
|             :class="['ui', 'basic', 'big', {'vibrant': !isCollapsed}, 'inverted icon', 'collapse', 'button']" |  | ||||||
|             @click="isCollapsed = !isCollapsed" |  | ||||||
|           > |  | ||||||
|             <i class="sidebar icon" /> |  | ||||||
|           </button> |  | ||||||
|         </div> |  | ||||||
|       </nav> |  | ||||||
|     </header> |  | ||||||
|     <div class="ui basic search-wrapper segment"> |  | ||||||
|       <search-bar @search="isCollapsed = false" /> |  | ||||||
|     </div> |  | ||||||
|     <div |  | ||||||
|       v-if="!store.state.auth.authenticated" |  | ||||||
|       class="ui basic signup segment" |  | ||||||
|     > |  | ||||||
|       <router-link |  | ||||||
|         class="ui fluid tiny primary button" |  | ||||||
|         :to="{name: 'login'}" |  | ||||||
|       > |  | ||||||
|         {{ t('components.Sidebar.link.login') }} |  | ||||||
|       </router-link> |  | ||||||
|       <div class="ui small hidden divider" /> |  | ||||||
|       <router-link |  | ||||||
|         class="ui fluid tiny button" |  | ||||||
|         :to="{path: '/signup'}" |  | ||||||
|       > |  | ||||||
|         {{ t('components.Sidebar.link.createAccount') }} |  | ||||||
|       </router-link> |  | ||||||
|     </div> |  | ||||||
|     <nav |  | ||||||
|       class="secondary" |  | ||||||
|       role="navigation" |  | ||||||
|       aria-labelledby="navigation-label" |  | ||||||
|     > |  | ||||||
|       <h1 |  | ||||||
|         id="navigation-label" |  | ||||||
|         class="visually-hidden" |  | ||||||
|       > |  | ||||||
|         {{ t('components.Sidebar.header.main') }} |  | ||||||
|       </h1> |  | ||||||
|       <div class="ui small hidden divider" /> |  | ||||||
|       <section |  | ||||||
|         :aria-label="labels.mainMenu" |  | ||||||
|         class="ui bottom attached active tab" |  | ||||||
|       > |  | ||||||
|         <nav |  | ||||||
|           class="ui vertical large fluid inverted menu" |  | ||||||
|           role="navigation" |  | ||||||
|           :aria-label="labels.mainMenu" |  | ||||||
|         > |  | ||||||
|           <div :class="[{ collapsed: expanded !== 'explore' }, 'collapsible item']"> |  | ||||||
|             <h2 |  | ||||||
|               class="header" |  | ||||||
|               role="button" |  | ||||||
|               tabindex="0" |  | ||||||
|               @click="expanded = 'explore'" |  | ||||||
|               @focus="expanded = 'explore'" |  | ||||||
|             > |  | ||||||
|               {{ t('components.Sidebar.header.explore') }} |  | ||||||
|               <i |  | ||||||
|                 v-if="expanded !== 'explore'" |  | ||||||
|                 class="angle right icon" |  | ||||||
|               /> |  | ||||||
|             </h2> |  | ||||||
|             <div class="menu"> |  | ||||||
|               <router-link |  | ||||||
|                 class="item" |  | ||||||
|                 :to="{name: 'search'}" |  | ||||||
|               > |  | ||||||
|                 <i class="search icon" /> |  | ||||||
|                 {{ t('components.Sidebar.link.search') }} |  | ||||||
|               </router-link> |  | ||||||
|               <router-link |  | ||||||
|                 class="item" |  | ||||||
|                 :to="{name: 'library.index'}" |  | ||||||
|                 active-class="_active" |  | ||||||
|               > |  | ||||||
|                 <i class="music icon" /> |  | ||||||
|                 {{ t('components.Sidebar.link.browse') }} |  | ||||||
|               </router-link> |  | ||||||
|               <router-link |  | ||||||
|                 class="item" |  | ||||||
|                 :to="{name: 'library.podcasts.browse'}" |  | ||||||
|               > |  | ||||||
|                 <i class="podcast icon" /> |  | ||||||
|                 {{ t('components.Sidebar.link.podcasts') }} |  | ||||||
|               </router-link> |  | ||||||
|               <router-link |  | ||||||
|                 class="item" |  | ||||||
|                 :to="{name: 'library.albums.browse'}" |  | ||||||
|               > |  | ||||||
|                 <i class="compact disc icon" /> |  | ||||||
|                 {{ t('components.Sidebar.link.albums') }} |  | ||||||
|               </router-link> |  | ||||||
|               <router-link |  | ||||||
|                 class="item" |  | ||||||
|                 :to="{name: 'library.artists.browse'}" |  | ||||||
|               > |  | ||||||
|                 <i class="user icon" /> |  | ||||||
|                 {{ t('components.Sidebar.link.artists') }} |  | ||||||
|               </router-link> |  | ||||||
|               <router-link |  | ||||||
|                 class="item" |  | ||||||
|                 :to="{name: 'library.playlists.browse'}" |  | ||||||
|               > |  | ||||||
|                 <i class="list icon" /> |  | ||||||
|                 {{ t('components.Sidebar.link.playlists') }} |  | ||||||
|               </router-link> |  | ||||||
|               <router-link |  | ||||||
|                 class="item" |  | ||||||
|                 :to="{name: 'library.radios.browse'}" |  | ||||||
|               > |  | ||||||
|                 <i class="feed icon" /> |  | ||||||
|                 {{ t('components.Sidebar.link.radios') }} |  | ||||||
|               </router-link> |  | ||||||
|             </div> |  | ||||||
|           </div> |  | ||||||
|           <div |  | ||||||
|             v-if="store.state.auth.authenticated" |  | ||||||
|             :class="[{ collapsed: expanded !== 'myLibrary' }, 'collapsible item']" |  | ||||||
|           > |  | ||||||
|             <h3 |  | ||||||
|               class="header" |  | ||||||
|               role="button" |  | ||||||
|               tabindex="0" |  | ||||||
|               @click="expanded = 'myLibrary'" |  | ||||||
|               @focus="expanded = 'myLibrary'" |  | ||||||
|             > |  | ||||||
|               {{ t('components.Sidebar.header.library') }} |  | ||||||
|               <i |  | ||||||
|                 v-if="expanded !== 'myLibrary'" |  | ||||||
|                 class="angle right icon" |  | ||||||
|               /> |  | ||||||
|             </h3> |  | ||||||
|             <div class="menu"> |  | ||||||
|               <router-link |  | ||||||
|                 class="item" |  | ||||||
|                 :to="{name: 'library.me'}" |  | ||||||
|               > |  | ||||||
|                 <i class="music icon" /> |  | ||||||
|                 {{ t('components.Sidebar.link.browse') }} |  | ||||||
|               </router-link> |  | ||||||
|               <router-link |  | ||||||
|                 class="item" |  | ||||||
|                 :to="{name: 'library.albums.me'}" |  | ||||||
|               > |  | ||||||
|                 <i class="compact disc icon" /> |  | ||||||
|                 {{ t('components.Sidebar.link.albums') }} |  | ||||||
|               </router-link> |  | ||||||
|               <router-link |  | ||||||
|                 class="item" |  | ||||||
|                 :to="{name: 'library.artists.me'}" |  | ||||||
|               > |  | ||||||
|                 <i class="user icon" /> |  | ||||||
|                 {{ t('components.Sidebar.link.artists') }} |  | ||||||
|               </router-link> |  | ||||||
|               <router-link |  | ||||||
|                 class="item" |  | ||||||
|                 :to="{name: 'library.playlists.me'}" |  | ||||||
|               > |  | ||||||
|                 <i class="list icon" /> |  | ||||||
|                 {{ t('components.Sidebar.link.playlists') }} |  | ||||||
|               </router-link> |  | ||||||
|               <router-link |  | ||||||
|                 class="item" |  | ||||||
|                 :to="{name: 'library.radios.me'}" |  | ||||||
|               > |  | ||||||
|                 <i class="feed icon" /> |  | ||||||
|                 {{ t('components.Sidebar.link.radios') }} |  | ||||||
|               </router-link> |  | ||||||
|               <router-link |  | ||||||
|                 class="item" |  | ||||||
|                 :to="{name: 'favorites'}" |  | ||||||
|               > |  | ||||||
|                 <i class="heart icon" /> |  | ||||||
|                 {{ t('components.Sidebar.link.favorites') }} |  | ||||||
|               </router-link> |  | ||||||
|             </div> |  | ||||||
|           </div> |  | ||||||
|           <router-link |  | ||||||
|             v-if="store.state.auth.authenticated" |  | ||||||
|             class="header item" |  | ||||||
|             :to="{name: 'subscriptions'}" |  | ||||||
|           > |  | ||||||
|             {{ t('components.Sidebar.link.channels') }} |  | ||||||
|           </router-link> |  | ||||||
|           <div class="item"> |  | ||||||
|             <h3 class="header"> |  | ||||||
|               {{ t('components.Sidebar.header.more') }} |  | ||||||
|             </h3> |  | ||||||
|             <div class="menu"> |  | ||||||
|               <router-link |  | ||||||
|                 class="item" |  | ||||||
|                 to="/about" |  | ||||||
|                 active-class="router-link-exact-active active" |  | ||||||
|               > |  | ||||||
|                 <i class="info icon" /> |  | ||||||
|                 {{ t('components.Sidebar.link.about') }} |  | ||||||
|               </router-link> |  | ||||||
|             </div> |  | ||||||
|           </div> |  | ||||||
|           <div |  | ||||||
|             v-if="!isProduction || isTauri" |  | ||||||
|             class="item" |  | ||||||
|           > |  | ||||||
|             <router-link |  | ||||||
|               to="/instance-chooser" |  | ||||||
|               class="link item" |  | ||||||
|             > |  | ||||||
|               {{ t('components.Sidebar.link.switchInstance') }} |  | ||||||
|             </router-link> |  | ||||||
|           </div> |  | ||||||
|         </nav> |  | ||||||
|       </section> |  | ||||||
|     </nav> |  | ||||||
|   </aside> |  | ||||||
| </template> |  | ||||||
| 
 |  | ||||||
| <style> |  | ||||||
| [type="radio"] { |  | ||||||
|   position: absolute; |  | ||||||
|   opacity: 0; |  | ||||||
|   cursor: pointer; |  | ||||||
|   height: 0; |  | ||||||
|   width: 0; |  | ||||||
| } |  | ||||||
| [type="radio"] + label::after { |  | ||||||
|   content: ""; |  | ||||||
|   font-size: 1.4em; |  | ||||||
| } |  | ||||||
| [type="radio"]:checked + label::after { |  | ||||||
|   margin-left: 10px; |  | ||||||
|   content: "\2713"; /* Checkmark */ |  | ||||||
|   font-size: 1.4em; |  | ||||||
| } |  | ||||||
| [type="radio"]:checked + label { |  | ||||||
|   font-weight: bold; |  | ||||||
| } |  | ||||||
| fieldset { |  | ||||||
|   border: none; |  | ||||||
| } |  | ||||||
| .back { |  | ||||||
|   font-size: 1.25em !important; |  | ||||||
|   position: absolute; |  | ||||||
|   top: 0.5rem; |  | ||||||
|   left: 0.5rem; |  | ||||||
|   width: 2.25rem !important; |  | ||||||
|   height: 2.25rem !important; |  | ||||||
|   padding: 0.625rem 0 0 0; |  | ||||||
| } |  | ||||||
| </style> |  | ||||||
|  | @ -46,11 +46,6 @@ const newValues = reactive({ | ||||||
|   metadata: { ...(props.object?.metadata ?? {}) } as Channel['metadata'] |   metadata: { ...(props.object?.metadata ?? {}) } as Channel['metadata'] | ||||||
| }) | }) | ||||||
| 
 | 
 | ||||||
| const tagList = computed(() => ({ |  | ||||||
|   currents: newValues.tags.map(tag => ({ type: 'custom' as const, label: tag })), |  | ||||||
|   others: [].map(tag => ({ type: 'custom' as const, label: tag })) |  | ||||||
| })) |  | ||||||
| 
 |  | ||||||
| // If props has an object, then this form edits, else it creates | // If props has an object, then this form edits, else it creates | ||||||
| // TODO: rename to `process : 'creating' | 'editing'` | // TODO: rename to `process : 'creating' | 'editing'` | ||||||
| const creating = computed(() => props.object === null) | const creating = computed(() => props.object === null) | ||||||
|  | @ -257,7 +252,11 @@ defineExpose({ | ||||||
|           </attachment-input> |           </attachment-input> | ||||||
|         </div> |         </div> | ||||||
|         <Pills |         <Pills | ||||||
|           v-model="tagList" |           :get="model => { newValues.tags = model.currents.map(({ label }) => label) }" | ||||||
|  |           :set="model => ({ | ||||||
|  |             currents: newValues.tags.map(tag => ({ type: 'custom' as const, label: tag })), | ||||||
|  |             others: [].map(tag => ({ type: 'custom' as const, label: tag })) | ||||||
|  |           })" | ||||||
|           :label="t('components.audio.ChannelForm.label.tags')" |           :label="t('components.audio.ChannelForm.label.tags')" | ||||||
|         /> |         /> | ||||||
|         <div |         <div | ||||||
|  |  | ||||||
|  | @ -3,9 +3,8 @@ import type { BackendError, Application, PrivacyLevel } from '~/types' | ||||||
| import type { $ElementType } from 'utility-types' | import type { $ElementType } from 'utility-types' | ||||||
| 
 | 
 | ||||||
| import axios from 'axios' | import axios from 'axios' | ||||||
| import $ from 'jquery' |  | ||||||
| 
 | 
 | ||||||
| import { computed, reactive, ref, onMounted } from 'vue' | import { computed, reactive, ref } from 'vue' | ||||||
| import { useI18n } from 'vue-i18n' | import { useI18n } from 'vue-i18n' | ||||||
| import { useRouter } from 'vue-router' | import { useRouter } from 'vue-router' | ||||||
| import { useStore } from '~/store' | import { useStore } from '~/store' | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
| import type { Actor } from '~/types' | import type { components } from '~/generated/types' | ||||||
| import { computed } from 'vue' | import { computed } from 'vue' | ||||||
| 
 | 
 | ||||||
| import { useI18n } from 'vue-i18n' | import { useI18n } from 'vue-i18n' | ||||||
|  | @ -15,7 +15,7 @@ interface Events { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| interface Props { | interface Props { | ||||||
|   actor: Actor |   actor: components['schemas']['FullActor'] | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const emit = defineEmits<Events>() | const emit = defineEmits<Events>() | ||||||
|  |  | ||||||
|  | @ -64,6 +64,7 @@ onMounted(() => { | ||||||
|     for="dropdown" |     for="dropdown" | ||||||
|   > |   > | ||||||
|     <!-- Label --> |     <!-- Label --> | ||||||
|  | 
 | ||||||
|     <span |     <span | ||||||
|       v-if="$slots['label']" |       v-if="$slots['label']" | ||||||
|       :class="$style.label" |       :class="$style.label" | ||||||
|  |  | ||||||
|  | @ -26,8 +26,9 @@ const currentIndex = computed(() => | ||||||
|   tabs.findIndex(({ title }) => title === actualCurrentTitle.value) |   tabs.findIndex(({ title }) => title === actualCurrentTitle.value) | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| watch(() => tabs.length, (_, from) => { | // select first tab | ||||||
|   if (from === 0) { | watch(tabs, () => { | ||||||
|  |   if (tabs.length === 1) { | ||||||
|     currentTitle.value = tabs[0].title |     currentTitle.value = tabs[0].title | ||||||
|   } |   } | ||||||
| }) | }) | ||||||
|  | @ -43,7 +44,7 @@ watch(() => tabs.length, (_, from) => { | ||||||
|         ghost |         ghost | ||||||
|         :class="{ 'is-active': actualCurrentTitle === tab.title }" |         :class="{ 'is-active': actualCurrentTitle === tab.title }" | ||||||
|         v-bind="tab" |         v-bind="tab" | ||||||
|         :on-click="() => { currentTitle = tab.title }" |         :on-click="'to' in tab ? undefined : () => { currentTitle = tab.title }" | ||||||
|         class="tabs-item" |         class="tabs-item" | ||||||
|         @keydown.left="currentTitle = tabs[(currentIndex - 1 + tabs.length) % tabs.length].title" |         @keydown.left="currentTitle = tabs[(currentIndex - 1 + tabs.length) % tabs.length].title" | ||||||
|         @keydown.right="currentTitle = tabs[(currentIndex + 1) % tabs.length].title" |         @keydown.right="currentTitle = tabs[(currentIndex + 1) % tabs.length].title" | ||||||
|  |  | ||||||
|  | @ -10,7 +10,7 @@ const play = defineEmits(['play']) | ||||||
|     class="play-button" |     class="play-button" | ||||||
|     shadow |     shadow | ||||||
|     round |     round | ||||||
|     @click="play()" |     @click="$emit('play')" | ||||||
|   /> |   /> | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -8437,6 +8437,7 @@ export interface components { | ||||||
|             channel?: string; |             channel?: string; | ||||||
|             /** @default pending */ |             /** @default pending */ | ||||||
|             import_status: components["schemas"]["ImportStatusEnum"]; |             import_status: components["schemas"]["ImportStatusEnum"]; | ||||||
|  |             privacy_level?: components["schemas"]["LibraryPrivacyLevelEnum"]; | ||||||
|             import_metadata?: unknown; |             import_metadata?: unknown; | ||||||
|             import_reference?: string; |             import_reference?: string; | ||||||
|             source?: string | null; |             source?: string | null; | ||||||
|  | @ -8827,6 +8828,11 @@ export interface components { | ||||||
|             extension: string; |             extension: string; | ||||||
|             readonly is_local: boolean; |             readonly is_local: boolean; | ||||||
|         }; |         }; | ||||||
|  |         UploadBulkUpdateRequest: { | ||||||
|  |             /** Format: uuid */ | ||||||
|  |             uuid: string; | ||||||
|  |             privacy_level: components["schemas"]["LibraryPrivacyLevelEnum"]; | ||||||
|  |         }; | ||||||
|         UploadForOwner: { |         UploadForOwner: { | ||||||
|             /** Format: uuid */ |             /** Format: uuid */ | ||||||
|             readonly uuid: string; |             readonly uuid: string; | ||||||
|  | @ -8844,6 +8850,7 @@ export interface components { | ||||||
|             readonly import_date: string | null; |             readonly import_date: string | null; | ||||||
|             /** @default pending */ |             /** @default pending */ | ||||||
|             import_status: components["schemas"]["ImportStatusEnum"]; |             import_status: components["schemas"]["ImportStatusEnum"]; | ||||||
|  |             privacy_level?: components["schemas"]["LibraryPrivacyLevelEnum"]; | ||||||
|             readonly import_details: unknown; |             readonly import_details: unknown; | ||||||
|             import_metadata?: unknown; |             import_metadata?: unknown; | ||||||
|             import_reference?: string; |             import_reference?: string; | ||||||
|  | @ -8857,6 +8864,7 @@ export interface components { | ||||||
|             channel?: string; |             channel?: string; | ||||||
|             /** @default pending */ |             /** @default pending */ | ||||||
|             import_status: components["schemas"]["ImportStatusEnum"]; |             import_status: components["schemas"]["ImportStatusEnum"]; | ||||||
|  |             privacy_level: components["schemas"]["LibraryPrivacyLevelEnum"]; | ||||||
|             import_metadata?: unknown; |             import_metadata?: unknown; | ||||||
|             import_reference?: string; |             import_reference?: string; | ||||||
|             source?: string | null; |             source?: string | null; | ||||||
|  | @ -15694,12 +15702,12 @@ export interface operations { | ||||||
|             path?: never; |             path?: never; | ||||||
|             cookie?: never; |             cookie?: never; | ||||||
|         }; |         }; | ||||||
|         requestBody?: { |         requestBody: { | ||||||
|             content: { |             content: { | ||||||
|                 "application/json": components["schemas"]["PatchedUploadForOwnerRequest"]; |                 "application/json": components["schemas"]["UploadBulkUpdateRequest"][]; | ||||||
|                 "application/x-www-form-urlencoded": components["schemas"]["PatchedUploadForOwnerRequest"]; |                 "application/x-www-form-urlencoded": components["schemas"]["UploadBulkUpdateRequest"][]; | ||||||
|                 "multipart/form-data": components["schemas"]["PatchedUploadForOwnerRequest"]; |                 "multipart/form-data": components["schemas"]["UploadBulkUpdateRequest"][]; | ||||||
|                 "application/activity+json": components["schemas"]["PatchedUploadForOwnerRequest"]; |                 "application/activity+json": components["schemas"]["UploadBulkUpdateRequest"][]; | ||||||
|             }; |             }; | ||||||
|         }; |         }; | ||||||
|         responses: { |         responses: { | ||||||
|  | @ -22863,12 +22871,12 @@ export interface operations { | ||||||
|             path?: never; |             path?: never; | ||||||
|             cookie?: never; |             cookie?: never; | ||||||
|         }; |         }; | ||||||
|         requestBody?: { |         requestBody: { | ||||||
|             content: { |             content: { | ||||||
|                 "application/json": components["schemas"]["PatchedUploadForOwnerRequest"]; |                 "application/json": components["schemas"]["UploadBulkUpdateRequest"][]; | ||||||
|                 "application/x-www-form-urlencoded": components["schemas"]["PatchedUploadForOwnerRequest"]; |                 "application/x-www-form-urlencoded": components["schemas"]["UploadBulkUpdateRequest"][]; | ||||||
|                 "multipart/form-data": components["schemas"]["PatchedUploadForOwnerRequest"]; |                 "multipart/form-data": components["schemas"]["UploadBulkUpdateRequest"][]; | ||||||
|                 "application/activity+json": components["schemas"]["PatchedUploadForOwnerRequest"]; |                 "application/activity+json": components["schemas"]["UploadBulkUpdateRequest"][]; | ||||||
|             }; |             }; | ||||||
|         }; |         }; | ||||||
|         responses: { |         responses: { | ||||||
|  |  | ||||||
|  | @ -1,8 +1,9 @@ | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
| import type { Actor } from '~/types' | import type { components } from '~/generated/types' | ||||||
| 
 | 
 | ||||||
| import { onBeforeRouteUpdate } from 'vue-router' | import { onBeforeRouteUpdate } from 'vue-router' | ||||||
| import { computed, ref, watch } from 'vue' | import { computed, ref, watch } from 'vue' | ||||||
|  | import { useClipboard } from '@vueuse/core' | ||||||
| import { useI18n } from 'vue-i18n' | import { useI18n } from 'vue-i18n' | ||||||
| import { useStore } from '~/store' | import { useStore } from '~/store' | ||||||
| import { hashCode, intToRGB } from '~/utils/color' | import { hashCode, intToRGB } from '~/utils/color' | ||||||
|  | @ -15,11 +16,13 @@ import useReport from '~/composables/moderation/useReport' | ||||||
| import RenderedDescription from '~/components/common/RenderedDescription.vue' | import RenderedDescription from '~/components/common/RenderedDescription.vue' | ||||||
| 
 | 
 | ||||||
| import Layout from '~/components/ui/Layout.vue' | import Layout from '~/components/ui/Layout.vue' | ||||||
| import Section from '~/components/ui/Section.vue' | import Header from '~/components/ui/Header.vue' | ||||||
|  | import Button from '~/components/ui/Button.vue' | ||||||
| import Nav from '~/components/ui/Nav.vue' | import Nav from '~/components/ui/Nav.vue' | ||||||
|  | import Alert from '~/components/ui/Alert.vue' | ||||||
| 
 | 
 | ||||||
| interface Events { | interface Events { | ||||||
|   (e: 'updated', value: Actor): void |   (e: 'updated', value: components['schemas']['FullActor']): void | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| interface Props { | interface Props { | ||||||
|  | @ -35,8 +38,7 @@ const props = withDefaults(defineProps<Props>(), { | ||||||
| const { report, getReportableObjects } = useReport() | const { report, getReportableObjects } = useReport() | ||||||
| const store = useStore() | const store = useStore() | ||||||
| 
 | 
 | ||||||
| // We are working either with an Actor or null | const object = ref<components['schemas']['FullActor'] | null>(null) | ||||||
| const object = ref<Actor | null>(null) |  | ||||||
| 
 | 
 | ||||||
| const actorColor = computed(() => intToRGB(hashCode(object.value?.full_username))) | const actorColor = computed(() => intToRGB(hashCode(object.value?.full_username))) | ||||||
| const defaultAvatarStyle = computed(() => ({ backgroundColor: `#${actorColor.value}` })) | const defaultAvatarStyle = computed(() => ({ backgroundColor: `#${actorColor.value}` })) | ||||||
|  | @ -79,6 +81,8 @@ const fetchData = async () => { | ||||||
| watch(props, fetchData, { immediate: true }) | watch(props, fetchData, { immediate: true }) | ||||||
| const recentActivity = ref(0) | const recentActivity = ref(0) | ||||||
| 
 | 
 | ||||||
|  | const { copy, copied, isSupported } = useClipboard() | ||||||
|  | 
 | ||||||
| const tabs = ref([{ | const tabs = ref([{ | ||||||
|   title:  t('views.auth.ProfileBase.link.overview') , |   title:  t('views.auth.ProfileBase.link.overview') , | ||||||
|   to:  { name: 'profile.overview', params: routerParams } |   to:  { name: 'profile.overview', params: routerParams } | ||||||
|  | @ -86,7 +90,7 @@ const tabs = ref([{ | ||||||
|   title:  t('views.auth.ProfileBase.link.activity') , |   title:  t('views.auth.ProfileBase.link.activity') , | ||||||
|   to:  { name: 'profile.activity', params: routerParams } |   to:  { name: 'profile.activity', params: routerParams } | ||||||
| }, ...( | }, ...( | ||||||
|   store.state.auth.authenticated && object.value && object.value.full_username === store.state.auth.fullUsername |   store.state.auth.authenticated && object.value?.full_username === store.state.auth.fullUsername | ||||||
|     ? [{ |     ? [{ | ||||||
|         title: t('views.auth.ProfileBase.link.manageUploads') , |         title: t('views.auth.ProfileBase.link.manageUploads') , | ||||||
|         to: { name: 'profile.manageUploads', params: routerParams } |         to: { name: 'profile.manageUploads', params: routerParams } | ||||||
|  | @ -101,54 +105,84 @@ const tabs = ref([{ | ||||||
|     stack |     stack | ||||||
|     main |     main | ||||||
|   > |   > | ||||||
|     <Layout flex> |     <!-- TODO: Translate Edit Link --> | ||||||
|       <!-- Profile Picture --> |     <Header | ||||||
|       <img |       :h1="props.username" | ||||||
|         v-if="object?.icon" |       :action="{ | ||||||
|         v-lazy="store.getters['instance/absoluteUrl'](object.icon.urls.large_square_crop)" |         text:'Edit profile', | ||||||
|         alt="" |         to:'/settings', | ||||||
|         class="avatar" |         primary: true, | ||||||
|       > |         solid: true, | ||||||
|       <span |         icon: 'bi-pencil-fill' | ||||||
|         v-else |       }" | ||||||
|         :style="defaultAvatarStyle" |       style="margin-top: 58px;" | ||||||
|         class="ui avatar circular label" |       page-heading | ||||||
|       > |     > | ||||||
|         {{ store.state.auth.profile?.full_username?.[0] || "" }} |       <template #image> | ||||||
|       </span> |         <img | ||||||
|       <!-- TODO: Translate Edit Link --> |           v-if="object?.user.avatar" | ||||||
|       <Section |           v-lazy="store.getters['instance/absoluteUrl'](object.user.avatar.urls.large_square_crop)" | ||||||
|         :h1="props.username" |           alt="" | ||||||
|         :action="{ |           class="avatar" | ||||||
|           text:'Edit profile', |         > | ||||||
|           to:'/settings', |         <span | ||||||
|           primary: true, |           v-else | ||||||
|           solid: true, |           :style="defaultAvatarStyle" | ||||||
|           icon: 'bi-pencil-fill' |           class="ui avatar circular label" | ||||||
|         }" |         > | ||||||
|         style="margin-top: 58px;" |           {{ store.state.auth.profile?.full_username?.[0] || "" }} | ||||||
|       > |         </span> | ||||||
|  |       </template> | ||||||
|  |       <Layout flex> | ||||||
|         <span style="grid-column: 1 / -1"> |         <span style="grid-column: 1 / -1"> | ||||||
|           {{ object?.full_username }} |           {{ object?.full_username }} | ||||||
|           <i |           <Button | ||||||
|             class="bi bi-copy" |             icon="bi-copy" | ||||||
|             style="margin-left: 8px;" |             style="margin-left: 8px;" | ||||||
|  |             :aria-label="t('views.auth.ProfileBase.copyUsername')" | ||||||
|  |             :title="t('components.common.CopyInput.button.copy')" | ||||||
|  |             ghost | ||||||
|  |             secondary | ||||||
|  |             @click="copy(fullUsername)" | ||||||
|           /> |           /> | ||||||
|         </span> |         </span> | ||||||
|  |         <Alert | ||||||
|  |           v-if="isSupported && copied" | ||||||
|  |           green | ||||||
|  |         > | ||||||
|  |           <Layout flex> | ||||||
|  |             <i class="bi bi-check" /> | ||||||
|  |             {{ t('components.common.CopyInput.message.success') }} | ||||||
|  |           </Layout> | ||||||
|  |         </Alert> | ||||||
|  |         <Alert | ||||||
|  |           v-else-if="!isSupported && copied" | ||||||
|  |           red | ||||||
|  |         > | ||||||
|  |           <Layout flex> | ||||||
|  |             <i class="bi bi-x" /> | ||||||
|  |             {{ t('components.common.CopyInput.message.fail') }} | ||||||
|  |           </Layout> | ||||||
|  |         </Alert> | ||||||
|  |       </Layout> | ||||||
|  |       <Layout | ||||||
|  |         flex | ||||||
|  |         no-gap | ||||||
|  |       > | ||||||
|         <RenderedDescription |         <RenderedDescription | ||||||
|           style="grid-column: 1 / -1" |           :content="{ html: object?.summary.html || '' }" | ||||||
|           :content="object?.summary? {text: object.summary} : null" |  | ||||||
|           :field-name="'summary'" |           :field-name="'summary'" | ||||||
|           :update-url="`users/${store.state.auth.username}/`" |           :update-url="`users/${store.state.auth.username}/`" | ||||||
|           :can-update="store.state.auth.authenticated && object?.full_username === store.state.auth.fullUsername" |           :can-update="store.state.auth.authenticated && object?.full_username === store.state.auth.fullUsername" | ||||||
|  |           :truncate-length="100" | ||||||
|           @updated="emit('updated', $event)" |           @updated="emit('updated', $event)" | ||||||
|         /> |         /> | ||||||
|         <UserFollowButton |       </Layout> | ||||||
|           v-if="store.state.auth.authenticated && object && object.full_username !== store.state.auth.fullUsername" |       <UserFollowButton | ||||||
|           :actor="object" |         v-if="store.state.auth.authenticated && object && object.full_username !== store.state.auth.fullUsername" | ||||||
|         /> |         :actor="object" | ||||||
|       </Section> |       /> | ||||||
|     </Layout> |     </Header> | ||||||
|     <Nav v-model="tabs" /> |     <Nav v-model="tabs" /> | ||||||
| 
 | 
 | ||||||
|     <router-view |     <router-view | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	 upsiflu
						upsiflu