Fix auto-fixable linter issues
This commit is contained in:
		
							parent
							
								
									73b1dc8f30
								
							
						
					
					
						commit
						bbdb3dcb9d
					
				| 
						 | 
				
			
			@ -73,6 +73,7 @@
 | 
			
		|||
    "eslint-config-standard": "17.0.0",
 | 
			
		||||
    "eslint-plugin-html": "6.2.0",
 | 
			
		||||
    "eslint-plugin-import": "2.26.0",
 | 
			
		||||
    "eslint-plugin-n": "^15.2.4",
 | 
			
		||||
    "eslint-plugin-node": "11.1.0",
 | 
			
		||||
    "eslint-plugin-promise": "6.0.0",
 | 
			
		||||
    "eslint-plugin-vue": "9.2.0",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -23,7 +23,7 @@ const shortDescription = computed(() => get(nodeinfo.value, 'metadata.shortDescr
 | 
			
		|||
const stats = computed(() => {
 | 
			
		||||
  const users = get(nodeinfo.value, 'usage.users.activeMonth', null)
 | 
			
		||||
  const hours = get(nodeinfo.value, 'metadata.library.music.hours', 0)
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  if (users === null) {
 | 
			
		||||
    return null
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			@ -39,8 +39,8 @@ const headerStyle = computed(() => {
 | 
			
		|||
    return ''
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return { 
 | 
			
		||||
    backgroundImage: `url(${store.getters['instance/absoluteUrl'](banner.value)})` 
 | 
			
		||||
  return {
 | 
			
		||||
    backgroundImage: `url(${store.getters['instance/absoluteUrl'](banner.value)})`
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -53,8 +53,8 @@ const headerStyle = computed(() => {
 | 
			
		|||
    return ''
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return { 
 | 
			
		||||
    backgroundImage: `url(${store.getters['instance/absoluteUrl'](banner.value)})` 
 | 
			
		||||
  return {
 | 
			
		||||
    backgroundImage: `url(${store.getters['instance/absoluteUrl'](banner.value)})`
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			@ -138,7 +138,7 @@ const headerStyle = computed(() => {
 | 
			
		|||
                  About this pod
 | 
			
		||||
                </translate>
 | 
			
		||||
              </h2>
 | 
			
		||||
              <sanitized-html 
 | 
			
		||||
              <sanitized-html
 | 
			
		||||
                v-if="longDescription"
 | 
			
		||||
                :html="markdown.makeHtml(longDescription)"
 | 
			
		||||
              />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -35,7 +35,7 @@ const defaultUploadQuota = computed(() => get(nodeinfo.value, 'metadata.defaultU
 | 
			
		|||
const stats = computed(() => {
 | 
			
		||||
  const users = get(nodeinfo.value, 'usage.users.activeMonth', null)
 | 
			
		||||
  const hours = get(nodeinfo.value, 'metadata.library.music.hours', 0)
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  if (users === null) {
 | 
			
		||||
    return null
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			@ -48,8 +48,8 @@ const headerStyle = computed(() => {
 | 
			
		|||
    return ''
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return { 
 | 
			
		||||
    backgroundImage: `url(${store.getters['instance/absoluteUrl'](banner.value)})` 
 | 
			
		||||
  return {
 | 
			
		||||
    backgroundImage: `url(${store.getters['instance/absoluteUrl'](banner.value)})`
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,7 +5,7 @@ import { useGettext } from 'vue3-gettext'
 | 
			
		|||
const path = window.location.href
 | 
			
		||||
 | 
			
		||||
const { $pgettext } = useGettext()
 | 
			
		||||
const labels = computed(() => ({ 
 | 
			
		||||
const labels = computed(() => ({
 | 
			
		||||
  title: $pgettext('Head/*/Title', 'Page Not Found')
 | 
			
		||||
}))
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,7 +8,7 @@ import TrackFavoriteIcon from '~/components/favorites/TrackFavoriteIcon.vue'
 | 
			
		|||
import TrackPlaylistIcon from '~/components/playlists/TrackPlaylistIcon.vue'
 | 
			
		||||
import Draggable from 'vuedraggable'
 | 
			
		||||
import { whenever, useTimeoutFn, useWindowScroll, useWindowSize } from '@vueuse/core'
 | 
			
		||||
import { useGettext } from "vue3-gettext"
 | 
			
		||||
import { useGettext } from 'vue3-gettext'
 | 
			
		||||
import useQueue from '~/composables/audio/useQueue'
 | 
			
		||||
import usePlayer from '~/composables/audio/usePlayer'
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -42,8 +42,8 @@ onMounted(async () => {
 | 
			
		|||
 | 
			
		||||
const { $pgettext } = useGettext()
 | 
			
		||||
 | 
			
		||||
const { 
 | 
			
		||||
  playing, 
 | 
			
		||||
const {
 | 
			
		||||
  playing,
 | 
			
		||||
  loading: isLoadingAudio,
 | 
			
		||||
  errored,
 | 
			
		||||
  focused: playerFocused,
 | 
			
		||||
| 
						 | 
				
			
			@ -54,10 +54,10 @@ const {
 | 
			
		|||
  bufferProgress,
 | 
			
		||||
  currentTime,
 | 
			
		||||
  pause,
 | 
			
		||||
  resume,
 | 
			
		||||
  resume
 | 
			
		||||
} = usePlayer()
 | 
			
		||||
 | 
			
		||||
const { 
 | 
			
		||||
const {
 | 
			
		||||
  focused: queueFocused,
 | 
			
		||||
  currentTrack,
 | 
			
		||||
  hasNext,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -29,16 +29,15 @@ const type = ref(props.initialType)
 | 
			
		|||
const id = ref(props.initialId)
 | 
			
		||||
const errors = ref([] as string[])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const { $pgettext } = useGettext()
 | 
			
		||||
const labels = computed(() => ({
 | 
			
		||||
  title: type.value === 'rss' 
 | 
			
		||||
  title: type.value === 'rss'
 | 
			
		||||
    ? $pgettext('Head/Fetch/Title', 'Subscribe to a podcast RSS feed')
 | 
			
		||||
    : $pgettext('Head/Fetch/Title', 'Subscribe to a podcast hosted on the Fediverse'),
 | 
			
		||||
  fieldLabel: type.value === 'rss' 
 | 
			
		||||
  fieldLabel: type.value === 'rss'
 | 
			
		||||
    ? $pgettext('*/*/*', 'RSS feed location')
 | 
			
		||||
    : $pgettext('*/*/*', 'Fediverse object'),
 | 
			
		||||
  fieldPlaceholder: type.value === 'rss' 
 | 
			
		||||
  fieldPlaceholder: type.value === 'rss'
 | 
			
		||||
    ? $pgettext('Head/Fetch/Field.Placeholder', 'https://website.example.com/rss.xml')
 | 
			
		||||
    : $pgettext('Head/Fetch/Field.Placeholder', '@username@example.com')
 | 
			
		||||
}))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,7 +8,7 @@ interface Props {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
const props = withDefaults(defineProps<Props>(), {
 | 
			
		||||
  tag: 'div' 
 | 
			
		||||
  tag: 'div'
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const html = computed(() => DOMPurify.sanitize(props.html))
 | 
			
		||||
| 
						 | 
				
			
			@ -17,4 +17,4 @@ const root = () => h(props.tag, { innerHTML: html.value })
 | 
			
		|||
 | 
			
		||||
<template>
 | 
			
		||||
  <root />
 | 
			
		||||
</template>
 | 
			
		||||
</template>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -39,7 +39,7 @@ const labels = computed(() => ({
 | 
			
		|||
  selectTrack: $pgettext('Sidebar/Player/Hidden text', 'Play this track'),
 | 
			
		||||
  pendingFollows: $pgettext('Sidebar/Notifications/Hidden text', 'Pending follow requests'),
 | 
			
		||||
  pendingReviewEdits: $pgettext('Sidebar/Moderation/Hidden text', 'Pending review edits'),
 | 
			
		||||
  pendingReviewReports:  $pgettext('Sidebar/Moderation/Hidden text', 'Pending review reports'),
 | 
			
		||||
  pendingReviewReports: $pgettext('Sidebar/Moderation/Hidden text', 'Pending review reports'),
 | 
			
		||||
  language: $pgettext('Sidebar/Settings/Dropdown.Label/Short, Verb', 'Language'),
 | 
			
		||||
  theme: $pgettext('Sidebar/Settings/Dropdown.Label/Short, Verb', 'Theme'),
 | 
			
		||||
  addContent: $pgettext('*/Library/*/Verb', 'Add content'),
 | 
			
		||||
| 
						 | 
				
			
			@ -51,11 +51,11 @@ const expanded = ref<SidebarMenuTabs>('explore')
 | 
			
		|||
 | 
			
		||||
const ROUTE_MAPPINGS: Record<SidebarMenuTabs, RouteRecordName[]> = {
 | 
			
		||||
  explore: [
 | 
			
		||||
    'search', 
 | 
			
		||||
    'library.index', 
 | 
			
		||||
    'library.podcasts.browse', 
 | 
			
		||||
    'library.albums.browse', 
 | 
			
		||||
    'library.albums.detail', 
 | 
			
		||||
    'search',
 | 
			
		||||
    'library.index',
 | 
			
		||||
    'library.podcasts.browse',
 | 
			
		||||
    'library.albums.browse',
 | 
			
		||||
    'library.albums.detail',
 | 
			
		||||
    'library.artists.browse',
 | 
			
		||||
    'library.artists.detail',
 | 
			
		||||
    'library.tracks.detail',
 | 
			
		||||
| 
						 | 
				
			
			@ -88,10 +88,10 @@ watchEffect(() => {
 | 
			
		|||
  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 moderationNotifications = computed(() =>
 | 
			
		||||
  store.state.ui.notifications.pendingReviewEdits +
 | 
			
		||||
    store.state.ui.notifications.pendingReviewReports +
 | 
			
		||||
    store.state.ui.notifications.pendingReviewRequests
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
Promise.resolve().then(async () => {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -53,7 +53,7 @@ const save = async () => {
 | 
			
		|||
  let contentType = 'application/json'
 | 
			
		||||
 | 
			
		||||
  if (fileSettings.value.length > 0) {
 | 
			
		||||
    const fileSettingsIDs = fileSettings.value.map((setting) =>  setting.identifier)
 | 
			
		||||
    const fileSettingsIDs = fileSettings.value.map((setting) => setting.identifier)
 | 
			
		||||
    const data = settings.value.reduce((data, setting) => {
 | 
			
		||||
      if (fileSettingsIDs.includes(setting.identifier)) {
 | 
			
		||||
        const input = fileRefs[setting.identifier]
 | 
			
		||||
| 
						 | 
				
			
			@ -69,7 +69,7 @@ const save = async () => {
 | 
			
		|||
      }
 | 
			
		||||
 | 
			
		||||
      return data
 | 
			
		||||
    }, {} as Record<string, string | File>) 
 | 
			
		||||
    }, {} as Record<string, string | File>)
 | 
			
		||||
 | 
			
		||||
    contentType = 'multipart/form-data'
 | 
			
		||||
    postData = useFormData(data)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,7 +7,6 @@ import useQueue from '~/composables/audio/useQueue'
 | 
			
		|||
import usePlayer from '~/composables/audio/usePlayer'
 | 
			
		||||
import { computed } from 'vue'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
interface Props {
 | 
			
		||||
  // TODO (wvffle): Is it correct type?
 | 
			
		||||
  entry: Track
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -90,7 +90,7 @@ export default {
 | 
			
		|||
      const params = clone(this.filters)
 | 
			
		||||
      params.page_size = this.limit
 | 
			
		||||
      params.include_channels = true
 | 
			
		||||
      axios.get(url, { params: params }).then((response) => {
 | 
			
		||||
      axios.get(url, { params }).then((response) => {
 | 
			
		||||
        self.nextPage = response.data.next
 | 
			
		||||
        self.isLoading = false
 | 
			
		||||
        self.objects = self.objects.concat(response.data.results)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -71,7 +71,7 @@ export default {
 | 
			
		|||
      const params = clone(this.filters)
 | 
			
		||||
      params.page_size = this.limit
 | 
			
		||||
      params.include_channels = true
 | 
			
		||||
      axios.get(url, { params: params }).then((response) => {
 | 
			
		||||
      axios.get(url, { params }).then((response) => {
 | 
			
		||||
        self.nextPage = response.data.next
 | 
			
		||||
        self.isLoading = false
 | 
			
		||||
        self.objects = self.objects.concat(response.data.results)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -32,7 +32,7 @@ const iframeSrc = computed(() => {
 | 
			
		|||
  const bParam = !window.location.href.startsWith(instanceUrl)
 | 
			
		||||
    ? `&b=${instanceUrl}`
 | 
			
		||||
    : ''
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  return `${base}embed.html?&type=${props.type}&id=${props.id}${bParam}`
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -51,10 +51,10 @@ const props = withDefaults(defineProps<Props>(), {
 | 
			
		|||
  paused: () => false
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const { 
 | 
			
		||||
  playable, 
 | 
			
		||||
  filterableArtist, 
 | 
			
		||||
  filterArtist, 
 | 
			
		||||
const {
 | 
			
		||||
  playable,
 | 
			
		||||
  filterableArtist,
 | 
			
		||||
  filterArtist,
 | 
			
		||||
  enqueue,
 | 
			
		||||
  enqueueNext,
 | 
			
		||||
  replacePlay,
 | 
			
		||||
| 
						 | 
				
			
			@ -75,14 +75,14 @@ const labels = computed(() => ({
 | 
			
		|||
  addToPlaylist: $pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Add to playlist…'),
 | 
			
		||||
  hideArtist: $pgettext('*/Queue/Dropdown/Button/Label/Short', 'Hide content from this artist'),
 | 
			
		||||
  replacePlay: props.track
 | 
			
		||||
  ? $pgettext('*/Queue/Dropdown/Button/Title', 'Play track')
 | 
			
		||||
  : props.album
 | 
			
		||||
    ? $pgettext('*/Queue/Dropdown/Button/Title', 'Play album')
 | 
			
		||||
    : props.artist
 | 
			
		||||
      ? $pgettext('*/Queue/Dropdown/Button/Title', 'Play artist')
 | 
			
		||||
      : props.playlist
 | 
			
		||||
        ? $pgettext('*/Queue/Dropdown/Button/Title', 'Play playlist')
 | 
			
		||||
        : $pgettext('*/Queue/Dropdown/Button/Title', 'Play tracks')
 | 
			
		||||
    ? $pgettext('*/Queue/Dropdown/Button/Title', 'Play track')
 | 
			
		||||
    : props.album
 | 
			
		||||
      ? $pgettext('*/Queue/Dropdown/Button/Title', 'Play album')
 | 
			
		||||
      : props.artist
 | 
			
		||||
        ? $pgettext('*/Queue/Dropdown/Button/Title', 'Play artist')
 | 
			
		||||
        : props.playlist
 | 
			
		||||
          ? $pgettext('*/Queue/Dropdown/Button/Title', 'Play playlist')
 | 
			
		||||
          : $pgettext('*/Queue/Dropdown/Button/Title', 'Play tracks')
 | 
			
		||||
}))
 | 
			
		||||
 | 
			
		||||
const title = computed(() => {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -284,7 +284,7 @@ const switchTab = () => {
 | 
			
		|||
              >
 | 
			
		||||
                {{ currentTimeFormatted }}
 | 
			
		||||
              </span>
 | 
			
		||||
              | 
 | 
			
		||||
              |
 | 
			
		||||
              <span class="total">{{ durationFormatted }}</span>
 | 
			
		||||
            </template>
 | 
			
		||||
          </div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -125,7 +125,7 @@ export default {
 | 
			
		|||
        query: this.query
 | 
			
		||||
      }
 | 
			
		||||
      axios.get('search', {
 | 
			
		||||
        params: params
 | 
			
		||||
        params
 | 
			
		||||
      }).then((response) => {
 | 
			
		||||
        self.results = self.castResults(response.data)
 | 
			
		||||
        self.isLoading = false
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,7 +17,7 @@ const sliderVolume = computed({
 | 
			
		|||
})
 | 
			
		||||
 | 
			
		||||
const { $pgettext } = useGettext()
 | 
			
		||||
const labels = computed(() => ({ 
 | 
			
		||||
const labels = computed(() => ({
 | 
			
		||||
  unmute: $pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Unmute'),
 | 
			
		||||
  mute: $pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Mute'),
 | 
			
		||||
  slider: $pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Adjust volume')
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -107,7 +107,7 @@ export default {
 | 
			
		|||
      const params = { q: this.query, ...this.filters }
 | 
			
		||||
      params.page_size = this.limit
 | 
			
		||||
      params.offset = this.offset
 | 
			
		||||
      axios.get(url, { params: params }).then((response) => {
 | 
			
		||||
      axios.get(url, { params }).then((response) => {
 | 
			
		||||
        self.previousPage = response.data.previous
 | 
			
		||||
        self.nextPage = response.data.next
 | 
			
		||||
        self.isLoading = false
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -103,7 +103,7 @@ export default {
 | 
			
		|||
      const params = { q: this.query, ...this.filters }
 | 
			
		||||
      params.page_size = this.limit
 | 
			
		||||
      params.offset = this.offset
 | 
			
		||||
      axios.get(url, { params: params }).then((response) => {
 | 
			
		||||
      axios.get(url, { params }).then((response) => {
 | 
			
		||||
        self.previousPage = response.data.previous
 | 
			
		||||
        self.nextPage = response.data.next
 | 
			
		||||
        self.isLoading = false
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -47,22 +47,22 @@ const store = useStore()
 | 
			
		|||
const isFavorite = computed(() => store.getters['favorites/isFavorite'](props.track.id))
 | 
			
		||||
 | 
			
		||||
const { $pgettext } = useGettext()
 | 
			
		||||
const favoriteButton = computed(() => isFavorite.value 
 | 
			
		||||
const favoriteButton = computed(() => isFavorite.value
 | 
			
		||||
  ? $pgettext('Content/Track/Icon.Tooltip/Verb', 'Remove from favorites')
 | 
			
		||||
  : $pgettext('Content/Track/*/Verb', 'Add to favorites')
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const trackDetailsButton = computed(() => props.track.artist?.content_category === 'podcast' 
 | 
			
		||||
const trackDetailsButton = computed(() => props.track.artist?.content_category === 'podcast'
 | 
			
		||||
  ? $pgettext('*/Queue/Dropdown/Button/Label/Short', 'Episode details')
 | 
			
		||||
  : $pgettext('*/Queue/Dropdown/Button/Label/Short', 'Track details')
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const albumDetailsButton = computed(() => props.track.artist?.content_category === 'podcast' 
 | 
			
		||||
const albumDetailsButton = computed(() => props.track.artist?.content_category === 'podcast'
 | 
			
		||||
  ? $pgettext('*/Queue/Dropdown/Button/Label/Short', 'View series')
 | 
			
		||||
  : $pgettext('*/Queue/Dropdown/Button/Label/Short', 'View album')
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const artistDetailsButton = computed(() => props.track.artist?.content_category === 'podcast' 
 | 
			
		||||
const artistDetailsButton = computed(() => props.track.artist?.content_category === 'podcast'
 | 
			
		||||
  ? $pgettext('*/Queue/Dropdown/Button/Label/Short', 'View channel')
 | 
			
		||||
  : $pgettext('*/Queue/Dropdown/Button/Label/Short', 'View artist')
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,6 @@
 | 
			
		|||
<script setup lang="ts">
 | 
			
		||||
import type { Track, Artist, Album, Playlist, Library, Channel, Actor } from '~/types'
 | 
			
		||||
import type { Track, Artist, Album, Playlist, Library, Channel, Actor, /* Track, */ Cover } from '~/types'
 | 
			
		||||
import type { PlayOptionsProps } from '~/composables/audio/usePlayOptions'
 | 
			
		||||
import type { /* Track, */ Cover } from '~/types'
 | 
			
		||||
 | 
			
		||||
import axios from 'axios'
 | 
			
		||||
import PlayButton from '~/components/audio/PlayButton.vue'
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -73,7 +73,6 @@ withDefaults(defineProps<Props>(), {
 | 
			
		|||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div :class="['track-table', 'ui', 'unstackable', 'grid', 'tablet-and-below']">
 | 
			
		||||
 | 
			
		||||
      <!-- For each item, build a row -->
 | 
			
		||||
 | 
			
		||||
      <track-mobile-row
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -47,22 +47,22 @@ const store = useStore()
 | 
			
		|||
const isFavorite = computed(() => store.getters['favorites/isFavorite'](props.track.id))
 | 
			
		||||
 | 
			
		||||
const { $pgettext } = useGettext()
 | 
			
		||||
const favoriteButton = computed(() => isFavorite.value 
 | 
			
		||||
const favoriteButton = computed(() => isFavorite.value
 | 
			
		||||
  ? $pgettext('Content/Track/Icon.Tooltip/Verb', 'Remove from favorites')
 | 
			
		||||
  : $pgettext('Content/Track/*/Verb', 'Add to favorites')
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const trackDetailsButton = computed(() => props.track.artist?.content_category === 'podcast' 
 | 
			
		||||
const trackDetailsButton = computed(() => props.track.artist?.content_category === 'podcast'
 | 
			
		||||
  ? $pgettext('*/Queue/Dropdown/Button/Label/Short', 'Episode details')
 | 
			
		||||
  : $pgettext('*/Queue/Dropdown/Button/Label/Short', 'Track details')
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const albumDetailsButton = computed(() => props.track.artist?.content_category === 'podcast' 
 | 
			
		||||
const albumDetailsButton = computed(() => props.track.artist?.content_category === 'podcast'
 | 
			
		||||
  ? $pgettext('*/Queue/Dropdown/Button/Label/Short', 'View series')
 | 
			
		||||
  : $pgettext('*/Queue/Dropdown/Button/Label/Short', 'View album')
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const artistDetailsButton = computed(() => props.track.artist?.content_category === 'podcast' 
 | 
			
		||||
const artistDetailsButton = computed(() => props.track.artist?.content_category === 'podcast'
 | 
			
		||||
  ? $pgettext('*/Queue/Dropdown/Button/Label/Short', 'View channel')
 | 
			
		||||
  : $pgettext('*/Queue/Dropdown/Button/Label/Short', 'View artist')
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -69,9 +69,9 @@ const active = computed(() => props.track.id === currentTrack.value?.id && props
 | 
			
		|||
            !hover
 | 
			
		||||
        "
 | 
			
		||||
      />
 | 
			
		||||
      <button 
 | 
			
		||||
      <button
 | 
			
		||||
        v-else-if="
 | 
			
		||||
            !playing &&
 | 
			
		||||
          !playing &&
 | 
			
		||||
            active &&
 | 
			
		||||
            !hover
 | 
			
		||||
        "
 | 
			
		||||
| 
						 | 
				
			
			@ -79,9 +79,9 @@ const active = computed(() => props.track.id === currentTrack.value?.id && props
 | 
			
		|||
      >
 | 
			
		||||
        <i class="play icon" />
 | 
			
		||||
      </button>
 | 
			
		||||
      <button 
 | 
			
		||||
      <button
 | 
			
		||||
        v-else-if="
 | 
			
		||||
            playing &&
 | 
			
		||||
          playing &&
 | 
			
		||||
            active &&
 | 
			
		||||
            hover
 | 
			
		||||
        "
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -24,7 +24,7 @@ interface Props {
 | 
			
		|||
  isAlbum?: boolean
 | 
			
		||||
  isPodcast?: boolean
 | 
			
		||||
 | 
			
		||||
  // TODO (wvffle): Find correct type 
 | 
			
		||||
  // TODO (wvffle): Find correct type
 | 
			
		||||
  filters?: object
 | 
			
		||||
 | 
			
		||||
  nextUrl?: string | null
 | 
			
		||||
| 
						 | 
				
			
			@ -227,9 +227,9 @@ const updatePage = (page: number) => {
 | 
			
		|||
 | 
			
		||||
        <track-row
 | 
			
		||||
          v-for="(track, index) in allTracks"
 | 
			
		||||
          :key="track.id + track.position"
 | 
			
		||||
          :data-track-id="track.id"
 | 
			
		||||
          :data-track-position="track.position"
 | 
			
		||||
          :key="track.id + track.position"
 | 
			
		||||
          :track="track"
 | 
			
		||||
          :index="index"
 | 
			
		||||
          :tracks="allTracks"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -45,7 +45,7 @@ const fetchData = async (url = props.url) => {
 | 
			
		|||
    nextPage.value = response.data.next
 | 
			
		||||
    count.value = response.data.count
 | 
			
		||||
 | 
			
		||||
    const newObjects = !props.isActivity 
 | 
			
		||||
    const newObjects = !props.isActivity
 | 
			
		||||
      ? response.data.results.map((track: Track) => { track })
 | 
			
		||||
      : response.data.results
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -127,7 +127,10 @@ if (props.websocketHandlers.includes('Listen')) {
 | 
			
		|||
                  {{ object.track.title }}
 | 
			
		||||
                </router-link>
 | 
			
		||||
              </div>
 | 
			
		||||
              <div v-if="object.track.artist" class="meta ellipsis">
 | 
			
		||||
              <div
 | 
			
		||||
                v-if="object.track.artist"
 | 
			
		||||
                class="meta ellipsis"
 | 
			
		||||
              >
 | 
			
		||||
                <span>
 | 
			
		||||
                  <router-link
 | 
			
		||||
                    class="discrete link"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,7 +18,7 @@ const props = withDefaults(defineProps<Props>(), {
 | 
			
		|||
const defaults = reactive({
 | 
			
		||||
  name: props.name,
 | 
			
		||||
  scopes: props.scopes,
 | 
			
		||||
  redirectUris: props.redirectUris,
 | 
			
		||||
  redirectUris: props.redirectUris
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const { $pgettext } = useGettext()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -219,7 +219,7 @@ export default {
 | 
			
		|||
      return this.knownScopes.map(s => {
 | 
			
		||||
        const id = s.id
 | 
			
		||||
        return {
 | 
			
		||||
          id: id,
 | 
			
		||||
          id,
 | 
			
		||||
          icon: s.icon,
 | 
			
		||||
          label: self.sharedLabels.scopes[s.id].label,
 | 
			
		||||
          description: self.sharedLabels.scopes[s.id].description,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -73,7 +73,10 @@
 | 
			
		|||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <template v-if="plugin.conf?.length > 0">
 | 
			
		||||
      <template v-for="(field, key) in plugin.conf" :key="key">
 | 
			
		||||
      <template
 | 
			
		||||
        v-for="(field, key) in plugin.conf"
 | 
			
		||||
        :key="key"
 | 
			
		||||
      >
 | 
			
		||||
        <div
 | 
			
		||||
          v-if="field.type === 'text'"
 | 
			
		||||
          class="field"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -601,7 +601,7 @@ export default {
 | 
			
		|||
    async fetchDraftUploads (channel) {
 | 
			
		||||
      const self = this
 | 
			
		||||
      this.draftUploads = null
 | 
			
		||||
      const response = await axios.get('uploads', { params: { import_status: 'draft', channel: channel } })
 | 
			
		||||
      const response = await axios.get('uploads', { params: { import_status: 'draft', channel } })
 | 
			
		||||
      this.draftUploads = response.data.results
 | 
			
		||||
      this.draftUploads.forEach((u) => {
 | 
			
		||||
        self.uploadImportData[u.uuid] = u.import_metadata
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -33,4 +33,4 @@ withDefaults(defineProps<Props>(), {
 | 
			
		|||
      </button>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
</template>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,4 +8,4 @@ defineProps<Props>()
 | 
			
		|||
 | 
			
		||||
<template>
 | 
			
		||||
  <span>{{ username }}</span>
 | 
			
		||||
</template>
 | 
			
		||||
</template>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,6 @@
 | 
			
		|||
<script setup lang="ts">
 | 
			
		||||
import type { RouteWithPreferences } from '~/store/ui'
 | 
			
		||||
import type { RouteWithPreferences, OrderingField } from '~/store/ui'
 | 
			
		||||
import type { Track } from '~/types'
 | 
			
		||||
import type { OrderingField } from '~/store/ui'
 | 
			
		||||
import type { OrderingProps } from '~/composables/useOrdering'
 | 
			
		||||
 | 
			
		||||
import axios from 'axios'
 | 
			
		||||
| 
						 | 
				
			
			@ -77,7 +76,7 @@ const fetchFavorites = async () => {
 | 
			
		|||
 | 
			
		||||
  try {
 | 
			
		||||
    logger.time('Loading user favorites')
 | 
			
		||||
    const response = await axios.get('tracks/', { params: params })
 | 
			
		||||
    const response = await axios.get('tracks/', { params })
 | 
			
		||||
 | 
			
		||||
    results.length = 0
 | 
			
		||||
    results.push(...response.data.results)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -89,7 +89,7 @@ export default {
 | 
			
		|||
      const params = clone({})
 | 
			
		||||
      params.page_size = this.limit
 | 
			
		||||
      params.offset = this.offset
 | 
			
		||||
      axios.get(url, { params: params }).then((response) => {
 | 
			
		||||
      axios.get(url, { params }).then((response) => {
 | 
			
		||||
        self.previousPage = response.data.previous
 | 
			
		||||
        self.nextPage = response.data.next
 | 
			
		||||
        self.isLoading = false
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -44,14 +44,14 @@ const fetchData = async () => {
 | 
			
		|||
  const albumResponse = await axios.get(`albums/${props.id}/`, { params: { refresh: 'true' } })
 | 
			
		||||
  const [artistResponse, tracksResponse] = await Promise.all([
 | 
			
		||||
    axios.get(`artists/${albumResponse.data.artist.id}/`),
 | 
			
		||||
    axios.get('tracks/', { 
 | 
			
		||||
      params: { 
 | 
			
		||||
        ordering: 'disc_number,position', 
 | 
			
		||||
        album: props.id, 
 | 
			
		||||
        page_size: paginateBy.value, 
 | 
			
		||||
        page: page.value, 
 | 
			
		||||
        include_channels: true 
 | 
			
		||||
      } 
 | 
			
		||||
    axios.get('tracks/', {
 | 
			
		||||
      params: {
 | 
			
		||||
        ordering: 'disc_number,position',
 | 
			
		||||
        album: props.id,
 | 
			
		||||
        page_size: paginateBy.value,
 | 
			
		||||
        page: page.value,
 | 
			
		||||
        include_channels: true
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
  ])
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -60,7 +60,6 @@ const fetchData = async () => {
 | 
			
		|||
    artist.value.channel.artist = artist.value
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  object.value = albumResponse.data
 | 
			
		||||
  if (object.value) {
 | 
			
		||||
    object.value.tracks = tracksResponse.data.results
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -180,4 +180,4 @@ const remove = () => emit('remove')
 | 
			
		|||
      </div>
 | 
			
		||||
    </button>
 | 
			
		||||
  </span>
 | 
			
		||||
</template>
 | 
			
		||||
</template>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -80,7 +80,6 @@ const fetchData = async () => {
 | 
			
		|||
  nextTracksUrl.value = tracksResponse.data.next
 | 
			
		||||
  totalTracks.value = tracksResponse.data.count
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  nextAlbumsUrl.value = albumsResponse.data.next
 | 
			
		||||
  totalAlbums.value = albumsResponse.data.count
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -86,7 +86,7 @@ export default {
 | 
			
		|||
      const self = this
 | 
			
		||||
      const params = clone(this.filters)
 | 
			
		||||
      params.page_size = this.limit
 | 
			
		||||
      axios.get(url, { params: params }).then((response) => {
 | 
			
		||||
      axios.get(url, { params }).then((response) => {
 | 
			
		||||
        self.previousPage = response.data.previous
 | 
			
		||||
        self.nextPage = response.data.next
 | 
			
		||||
        self.isLoading = false
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -36,7 +36,7 @@ const fetchData = async () => {
 | 
			
		|||
  }
 | 
			
		||||
 | 
			
		||||
  try {
 | 
			
		||||
    const response = await axios.get('artists/', { params: params })
 | 
			
		||||
    const response = await axios.get('artists/', { params })
 | 
			
		||||
    artists.value = response.data.results
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    // TODO (wvffle): Handle error
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -58,9 +58,9 @@ const escapeHtml = (unsafe: string) => document.createTextNode(unsafe).textConte
 | 
			
		|||
const subtitle = computed(() => {
 | 
			
		||||
  if (track.value?.attributed_to) {
 | 
			
		||||
    return $pgettext(
 | 
			
		||||
      'Content/Track/Paragraph', 
 | 
			
		||||
      'Content/Track/Paragraph',
 | 
			
		||||
      'Uploaded by <a class="internal" href="%{ uploaderUrl }">%{ uploader }</a> on <time title="%{ date }" datetime="%{ date }">%{ prettyDate }</time>',
 | 
			
		||||
      { 
 | 
			
		||||
      {
 | 
			
		||||
        uploaderUrl: attributedToUrl.value,
 | 
			
		||||
        uploader: escapeHtml(`@${track.value.attributed_to.full_username}`),
 | 
			
		||||
        date: escapeHtml(track.value.creation_date),
 | 
			
		||||
| 
						 | 
				
			
			@ -70,7 +70,7 @@ const subtitle = computed(() => {
 | 
			
		|||
  }
 | 
			
		||||
 | 
			
		||||
  return $pgettext(
 | 
			
		||||
    'Content/Track/Paragraph', 
 | 
			
		||||
    'Content/Track/Paragraph',
 | 
			
		||||
    'Uploaded on <time title="%{ date }" datetime="%{ date }">%{ prettyDate }</time>',
 | 
			
		||||
    {
 | 
			
		||||
      date: escapeHtml(track.value?.creation_date ?? ''),
 | 
			
		||||
| 
						 | 
				
			
			@ -79,7 +79,6 @@ const subtitle = computed(() => {
 | 
			
		|||
  )
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const { $pgettext } = useGettext()
 | 
			
		||||
const labels = computed(() => ({
 | 
			
		||||
  title: $pgettext('*/*/*/Noun', 'Track'),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,12 +19,12 @@ interface Props {
 | 
			
		|||
  filter: {
 | 
			
		||||
    type: string
 | 
			
		||||
    label: string
 | 
			
		||||
    fields: { 
 | 
			
		||||
    fields: {
 | 
			
		||||
      name: string
 | 
			
		||||
      placeholder: string
 | 
			
		||||
      type: 'list'
 | 
			
		||||
      subtype: 'number'
 | 
			
		||||
      autocomplete?: string 
 | 
			
		||||
      autocomplete?: string
 | 
			
		||||
      autocomplete_qs: string
 | 
			
		||||
      autocomplete_fields: {
 | 
			
		||||
        remoteValues?: unknown
 | 
			
		||||
| 
						 | 
				
			
			@ -103,7 +103,7 @@ const fetchCandidates = async () => {
 | 
			
		|||
  const params = {
 | 
			
		||||
    filters: [{
 | 
			
		||||
      ...clone(props.config),
 | 
			
		||||
      type: props.filter.type,
 | 
			
		||||
      type: props.filter.type
 | 
			
		||||
    }]
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,6 @@
 | 
			
		|||
<script setup lang="ts">
 | 
			
		||||
import type { PrivacyLevel } from '~/types'
 | 
			
		||||
import type { RouteWithPreferences } from '~/store/ui'
 | 
			
		||||
import type { OrderingField } from '~/store/ui'
 | 
			
		||||
import type { RouteWithPreferences, OrderingField } from '~/store/ui'
 | 
			
		||||
import type { SmartSearchProps } from '~/composables/useSmartSearch'
 | 
			
		||||
import type { OrderingProps } from '~/composables/useOrdering'
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,5 @@
 | 
			
		|||
<script setup lang="ts">
 | 
			
		||||
import type { RouteWithPreferences } from '~/store/ui'
 | 
			
		||||
import type { OrderingField } from '~/store/ui'
 | 
			
		||||
import type { RouteWithPreferences, OrderingField } from '~/store/ui'
 | 
			
		||||
import type { SmartSearchProps } from '~/composables/useSmartSearch'
 | 
			
		||||
import type { OrderingProps } from '~/composables/useOrdering'
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,8 +1,6 @@
 | 
			
		|||
<script setup lang="ts">
 | 
			
		||||
import type { ImportStatus, PrivacyLevel } from '~/types'
 | 
			
		||||
import type { RouteWithPreferences } from '~/store/ui'
 | 
			
		||||
import type { OrderingField } from '~/store/ui'
 | 
			
		||||
import type { Upload } from '~/types'
 | 
			
		||||
import type { ImportStatus, PrivacyLevel, Upload } from '~/types'
 | 
			
		||||
import type { RouteWithPreferences, OrderingField } from '~/store/ui'
 | 
			
		||||
import type { OrderingProps } from '~/composables/useOrdering'
 | 
			
		||||
import type { SmartSearchProps } from '~/composables/useSmartSearch'
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -39,7 +39,7 @@ const fetchData = async () => {
 | 
			
		|||
 | 
			
		||||
    target_account_domain: props.type === 'actor'
 | 
			
		||||
      ? domain
 | 
			
		||||
      : undefined,
 | 
			
		||||
      : undefined
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  isLoading.value = true
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,7 +10,6 @@ import { useStore } from '~/store'
 | 
			
		|||
import SemanticModal from '~/components/semantic/Modal.vue'
 | 
			
		||||
import useLogger from '~/composables/useLogger'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const logger = useLogger()
 | 
			
		||||
const { $pgettext } = useGettext()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,9 +16,9 @@
 | 
			
		|||
        <sanitized-html
 | 
			
		||||
          tag="span"
 | 
			
		||||
          class="link"
 | 
			
		||||
          :html="notificationData.message"
 | 
			
		||||
          @click="navigate"
 | 
			
		||||
          @keypress.enter="navigate"
 | 
			
		||||
          :html="notificationData.message"
 | 
			
		||||
        />
 | 
			
		||||
      </router-link>
 | 
			
		||||
      <sanitized-html
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,7 +16,7 @@ const store = useStore()
 | 
			
		|||
const images = computed(() => {
 | 
			
		||||
  const urls = props.playlist.album_covers.slice(0, 4).map(url => store.getters['instance/absoluteUrl'](url))
 | 
			
		||||
 | 
			
		||||
  while (urls.length < 4) { 
 | 
			
		||||
  while (urls.length < 4) {
 | 
			
		||||
    urls.push(defaultCover)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -152,8 +152,8 @@ const insertMany = async (insertedTracks: Track[], allowDuplicates: boolean) =>
 | 
			
		|||
<template>
 | 
			
		||||
  <div class="ui text container component-playlist-editor">
 | 
			
		||||
    <playlist-form
 | 
			
		||||
      :title="false"
 | 
			
		||||
      v-model:playlist="playlist"
 | 
			
		||||
      :title="false"
 | 
			
		||||
    />
 | 
			
		||||
    <h3 class="ui top attached header">
 | 
			
		||||
      <translate translate-context="Content/Playlist/Title">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -69,7 +69,7 @@ const submit = async () => {
 | 
			
		|||
 | 
			
		||||
  try {
 | 
			
		||||
    const url = props.create ? 'playlists/' : `playlists/${playlist.value!.id}/`
 | 
			
		||||
    const method = props.create ? 'post' : 'patch' 
 | 
			
		||||
    const method = props.create ? 'post' : 'patch'
 | 
			
		||||
 | 
			
		||||
    const data = {
 | 
			
		||||
      name: name.value,
 | 
			
		||||
| 
						 | 
				
			
			@ -94,7 +94,6 @@ const submit = async () => {
 | 
			
		|||
  isLoading.value = false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,7 +26,7 @@ const playlists = computed(() => store.state.playlists.playlists)
 | 
			
		|||
const track = computed(() => store.state.playlists.modalTrack)
 | 
			
		||||
 | 
			
		||||
const { $pgettext } = useGettext()
 | 
			
		||||
const labels = computed(() => ({ 
 | 
			
		||||
const labels = computed(() => ({
 | 
			
		||||
  addToPlaylist: $pgettext('Popup/Playlist/Table.Button.Tooltip/Verb', 'Add to this playlist'),
 | 
			
		||||
  filterPlaylistField: $pgettext('Popup/Playlist/Form/Placeholder', 'Enter playlist name')
 | 
			
		||||
}))
 | 
			
		||||
| 
						 | 
				
			
			@ -51,7 +51,7 @@ const duplicateTrackAddInfo = ref({} as { playlist_name?: string })
 | 
			
		|||
const addToPlaylist = async (playlistId: number, allowDuplicates: boolean) => {
 | 
			
		||||
  lastSelectedPlaylist.value = playlistId
 | 
			
		||||
 | 
			
		||||
  try { 
 | 
			
		||||
  try {
 | 
			
		||||
    await axios.post(`playlists/${playlistId}/add/`, {
 | 
			
		||||
      tracks: [track.value?.id].filter(i => i),
 | 
			
		||||
      allow_duplicates: allowDuplicates
 | 
			
		||||
| 
						 | 
				
			
			@ -106,7 +106,10 @@ store.dispatch('playlists/fetchOwn')
 | 
			
		|||
      </translate>
 | 
			
		||||
    </h4>
 | 
			
		||||
    <div class="scrolling content">
 | 
			
		||||
      <playlist-form :create="true" :key="formKey" />
 | 
			
		||||
      <playlist-form
 | 
			
		||||
        :key="formKey"
 | 
			
		||||
        :create="true"
 | 
			
		||||
      />
 | 
			
		||||
      <div class="ui divider" />
 | 
			
		||||
      <div v-if="playlists.length > 0">
 | 
			
		||||
        <div
 | 
			
		||||
| 
						 | 
				
			
			@ -249,9 +252,9 @@ store.dispatch('playlists/fetchOwn')
 | 
			
		|||
          </div>
 | 
			
		||||
        </template>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div 
 | 
			
		||||
      v-else 
 | 
			
		||||
      class="ui placeholder segment"
 | 
			
		||||
      <div
 | 
			
		||||
        v-else
 | 
			
		||||
        class="ui placeholder segment"
 | 
			
		||||
      >
 | 
			
		||||
        <div class="ui icon header">
 | 
			
		||||
          <i class="list icon" />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -107,7 +107,7 @@ export default {
 | 
			
		|||
      const params = clone(this.filters)
 | 
			
		||||
      params.page_size = this.limit
 | 
			
		||||
      params.offset = this.offset
 | 
			
		||||
      axios.get(url, { params: params }).then((response) => {
 | 
			
		||||
      axios.get(url, { params }).then((response) => {
 | 
			
		||||
        self.previousPage = response.data.previous
 | 
			
		||||
        self.nextPage = response.data.next
 | 
			
		||||
        self.isLoading = false
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -27,12 +27,12 @@ const running = computed(() => {
 | 
			
		|||
    return false
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return store.state.radios.current?.type === props.type
 | 
			
		||||
    && store.state.radios.current?.customRadioId === props.customRadioId
 | 
			
		||||
    && (
 | 
			
		||||
        typeof props.objectId === 'string'
 | 
			
		||||
          || store.state.radios.current?.objectId.fullUsername === props.objectId?.fullUsername
 | 
			
		||||
      )
 | 
			
		||||
  return store.state.radios.current?.type === props.type &&
 | 
			
		||||
    store.state.radios.current?.customRadioId === props.customRadioId &&
 | 
			
		||||
    (
 | 
			
		||||
      typeof props.objectId === 'string' ||
 | 
			
		||||
          store.state.radios.current?.objectId.fullUsername === props.objectId?.fullUsername
 | 
			
		||||
    )
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const { $pgettext } = useGettext()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,7 +3,7 @@ import type { ContentFilter } from '~/store/moderation'
 | 
			
		|||
 | 
			
		||||
import { useStore } from '~/store'
 | 
			
		||||
import { useGettext } from 'vue3-gettext'
 | 
			
		||||
import { computed, ref } from "vue"
 | 
			
		||||
import { computed, ref } from 'vue'
 | 
			
		||||
import axios from 'axios'
 | 
			
		||||
import usePlayer from '~/composables/audio/usePlayer'
 | 
			
		||||
import useQueue from '~/composables/audio/useQueue'
 | 
			
		||||
| 
						 | 
				
			
			@ -36,8 +36,8 @@ export default (props: PlayOptionsProps) => {
 | 
			
		|||
    if (props.track) {
 | 
			
		||||
      return props.track.uploads?.length > 0
 | 
			
		||||
    } else if (props.artist) {
 | 
			
		||||
      return props.artist.tracks_count > 0 
 | 
			
		||||
        || props.artist.albums.some((album) => album.is_playable === true)
 | 
			
		||||
      return props.artist.tracks_count > 0 ||
 | 
			
		||||
        props.artist.albums.some((album) => album.is_playable === true)
 | 
			
		||||
    } else if (props.tracks) {
 | 
			
		||||
      return props.tracks.some((track) => (track.uploads?.length ?? 0) > 0)
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -55,7 +55,7 @@ export default (props: PlayOptionsProps) => {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    store.commit('ui/addMessage', {
 | 
			
		||||
      content: $npgettext('*/Queue/Message', '%{ count } track was added to your queue', '%{ count } tracks were added to your queue', tracks.length, { 
 | 
			
		||||
      content: $npgettext('*/Queue/Message', '%{ count } track was added to your queue', '%{ count } tracks were added to your queue', tracks.length, {
 | 
			
		||||
        count: tracks.length.toString()
 | 
			
		||||
      }),
 | 
			
		||||
      date: new Date()
 | 
			
		||||
| 
						 | 
				
			
			@ -70,8 +70,8 @@ export default (props: PlayOptionsProps) => {
 | 
			
		|||
 | 
			
		||||
    // when fetching artists/or album tracks, sometimes, we may have to fetch
 | 
			
		||||
    // multiple pages
 | 
			
		||||
    const response = await axios.get('tracks/', { 
 | 
			
		||||
      params: { 
 | 
			
		||||
    const response = await axios.get('tracks/', {
 | 
			
		||||
      params: {
 | 
			
		||||
        ...params,
 | 
			
		||||
        page_size: 100,
 | 
			
		||||
        page,
 | 
			
		||||
| 
						 | 
				
			
			@ -136,7 +136,7 @@ export default (props: PlayOptionsProps) => {
 | 
			
		|||
    jQuery(el.value).find('.ui.dropdown').dropdown('hide')
 | 
			
		||||
 | 
			
		||||
    const tracks = await getPlayableTracks()
 | 
			
		||||
    store.dispatch('queue/appendMany', { tracks: tracks }).then(() => addMessage(tracks))
 | 
			
		||||
    store.dispatch('queue/appendMany', { tracks }).then(() => addMessage(tracks))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const enqueueNext = async (next = false) => {
 | 
			
		||||
| 
						 | 
				
			
			@ -188,7 +188,7 @@ export default (props: PlayOptionsProps) => {
 | 
			
		|||
    replacePlay()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return { 
 | 
			
		||||
  return {
 | 
			
		||||
    playable,
 | 
			
		||||
    filterableArtist,
 | 
			
		||||
    filterArtist,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,11 +1,11 @@
 | 
			
		|||
import type { Track } from "~/types"
 | 
			
		||||
import type { Track } from '~/types'
 | 
			
		||||
 | 
			
		||||
import { useTimeoutFn, useThrottleFn, useTimeAgo, useNow, whenever } from '@vueuse/core'
 | 
			
		||||
import { Howler } from 'howler'
 | 
			
		||||
import { gettext } from '~/init/locale'
 | 
			
		||||
import { ref, computed } from "vue"
 | 
			
		||||
import { ref, computed } from 'vue'
 | 
			
		||||
import { sum } from 'lodash-es'
 | 
			
		||||
import store from "~/store"
 | 
			
		||||
import store from '~/store'
 | 
			
		||||
 | 
			
		||||
const { $pgettext } = gettext
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -25,7 +25,7 @@ const previous = () => store.dispatch('queue/previous')
 | 
			
		|||
 | 
			
		||||
const focused = computed(() => store.state.ui.queueFocused === 'queue')
 | 
			
		||||
 | 
			
		||||
// 
 | 
			
		||||
//
 | 
			
		||||
// Track list
 | 
			
		||||
//
 | 
			
		||||
const tracksChangeBuffer = ref<Track[] | null>(null)
 | 
			
		||||
| 
						 | 
				
			
			@ -44,7 +44,7 @@ const reorder = (oldIndex: number, newIndex: number) => {
 | 
			
		|||
  tracksChangeBuffer.value = null
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 
 | 
			
		||||
//
 | 
			
		||||
// Shuffle
 | 
			
		||||
//
 | 
			
		||||
const isShuffling = ref(false)
 | 
			
		||||
| 
						 | 
				
			
			@ -88,12 +88,12 @@ const endsIn = useTimeAgo(computed(() => {
 | 
			
		|||
}))
 | 
			
		||||
 | 
			
		||||
export default () => {
 | 
			
		||||
  return { 
 | 
			
		||||
  return {
 | 
			
		||||
    currentTrack,
 | 
			
		||||
    currentIndex,
 | 
			
		||||
    hasNext,
 | 
			
		||||
    hasPrevious,
 | 
			
		||||
    isEmpty, 
 | 
			
		||||
    isEmpty,
 | 
			
		||||
    isShuffling,
 | 
			
		||||
 | 
			
		||||
    removeTrack,
 | 
			
		||||
| 
						 | 
				
			
			@ -110,4 +110,4 @@ export default () => {
 | 
			
		|||
    endsIn,
 | 
			
		||||
    focused
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,12 +1,12 @@
 | 
			
		|||
import type { Track } from '~/types'
 | 
			
		||||
 | 
			
		||||
import { ref, computed } from "vue"
 | 
			
		||||
import { ref, computed } from 'vue'
 | 
			
		||||
import { Howl } from 'howler'
 | 
			
		||||
import useTrackSources from '~/composables/audio/useTrackSources'
 | 
			
		||||
import useSoundCache from '~/composables/audio/useSoundCache'
 | 
			
		||||
import usePlayer from '~/composables/audio/usePlayer'
 | 
			
		||||
import store from "~/store"
 | 
			
		||||
import { createEventHook } from "@vueuse/core"
 | 
			
		||||
import store from '~/store'
 | 
			
		||||
import { createEventHook } from '@vueuse/core'
 | 
			
		||||
 | 
			
		||||
interface Sound {
 | 
			
		||||
  id?: number
 | 
			
		||||
| 
						 | 
				
			
			@ -152,4 +152,4 @@ export default () => {
 | 
			
		|||
    currentSound,
 | 
			
		||||
    onSoundProgress: soundProgress.on
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,7 @@
 | 
			
		|||
import type { Howl } from "howler"
 | 
			
		||||
import type { Howl } from 'howler'
 | 
			
		||||
 | 
			
		||||
import { sortBy } from "lodash-es"
 | 
			
		||||
import { reactive, watchEffect, ref } from "vue"
 | 
			
		||||
import { sortBy } from 'lodash-es'
 | 
			
		||||
import { reactive, watchEffect, ref } from 'vue'
 | 
			
		||||
 | 
			
		||||
const MAX_PRELOADED = 3
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -15,7 +15,7 @@ const soundCache = reactive(new Map<string, CachedSound>())
 | 
			
		|||
const cleaningCache = ref(false)
 | 
			
		||||
 | 
			
		||||
watchEffect(() => {
 | 
			
		||||
  let toRemove = soundCache.size - MAX_PRELOADED
 | 
			
		||||
  const toRemove = soundCache.size - MAX_PRELOADED
 | 
			
		||||
 | 
			
		||||
  if (toRemove > 0 && !cleaningCache.value) {
 | 
			
		||||
    cleaningCache.value = true
 | 
			
		||||
| 
						 | 
				
			
			@ -33,7 +33,6 @@ watchEffect(() => {
 | 
			
		|||
  }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export default () => {
 | 
			
		||||
  return soundCache
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,10 +1,10 @@
 | 
			
		|||
import type { Track } from "~/types"
 | 
			
		||||
import type { Track } from '~/types'
 | 
			
		||||
 | 
			
		||||
import store from '~/store'
 | 
			
		||||
import updateQueryString from '~/composables/updateQueryString'
 | 
			
		||||
 | 
			
		||||
export interface TrackSource {
 | 
			
		||||
  url: string 
 | 
			
		||||
  url: string
 | 
			
		||||
  type: string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -29,23 +29,23 @@ export default (trackData: Track): TrackSource[] => {
 | 
			
		|||
  sources.push({
 | 
			
		||||
    type: 'mp3',
 | 
			
		||||
    url: updateQueryString(
 | 
			
		||||
      store.getters['instance/absoluteUrl'](trackData.listen_url), 
 | 
			
		||||
      'to', 
 | 
			
		||||
      store.getters['instance/absoluteUrl'](trackData.listen_url),
 | 
			
		||||
      'to',
 | 
			
		||||
      'mp3'
 | 
			
		||||
    )
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  const token = store.state.auth.scopedTokens.listen
 | 
			
		||||
  if (store.state.auth.authenticated && token !== null) {
 | 
			
		||||
      // we need to send the token directly in url
 | 
			
		||||
      // so authentication can be checked by the backend
 | 
			
		||||
      // because for audio files we cannot use the regular Authentication
 | 
			
		||||
      // header
 | 
			
		||||
      return sources.map(source => ({
 | 
			
		||||
        ...source,
 | 
			
		||||
        url: updateQueryString(source.url, 'token', token)
 | 
			
		||||
      }))
 | 
			
		||||
    // we need to send the token directly in url
 | 
			
		||||
    // so authentication can be checked by the backend
 | 
			
		||||
    // because for audio files we cannot use the regular Authentication
 | 
			
		||||
    // header
 | 
			
		||||
    return sources.map(source => ({
 | 
			
		||||
      ...source,
 | 
			
		||||
      url: updateQueryString(source.url, 'token', token)
 | 
			
		||||
    }))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return sources
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -134,7 +134,7 @@ const report = (obj: ReportableObject) => {
 | 
			
		|||
  store.dispatch('moderation/report', obj.target)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default () => ({ 
 | 
			
		||||
export default () => ({
 | 
			
		||||
  getReportableObjects,
 | 
			
		||||
  report
 | 
			
		||||
})
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -83,7 +83,7 @@ export const install: InitModule = ({ store, router }) => {
 | 
			
		|||
      case 500:
 | 
			
		||||
        error.backendErrors.push('A server error occurred')
 | 
			
		||||
        break
 | 
			
		||||
      
 | 
			
		||||
 | 
			
		||||
      default:
 | 
			
		||||
        if (error.response?.data as object) {
 | 
			
		||||
          const data = error.response?.data as Record<string, unknown>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,4 +11,4 @@ export const hasPermissions = (permission: Permission) => (to: RouteLocationNorm
 | 
			
		|||
 | 
			
		||||
  console.log('Not authenticated. Redirecting to library.')
 | 
			
		||||
  next({ name: 'library.index' })
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -61,5 +61,5 @@ export default [
 | 
			
		|||
    path: '/logout',
 | 
			
		||||
    name: 'logout',
 | 
			
		||||
    component: () => import('~/components/auth/Logout.vue')
 | 
			
		||||
  },
 | 
			
		||||
] as RouteRecordRaw[]
 | 
			
		||||
  }
 | 
			
		||||
] as RouteRecordRaw[]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -37,5 +37,5 @@ export default [
 | 
			
		|||
      name: 'content.remote.index',
 | 
			
		||||
      component: () => import('~/views/content/remote/Home.vue')
 | 
			
		||||
    }]
 | 
			
		||||
  },
 | 
			
		||||
] as RouteRecordRaw[]
 | 
			
		||||
  }
 | 
			
		||||
] as RouteRecordRaw[]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -104,4 +104,4 @@ export default [
 | 
			
		|||
    name: '404',
 | 
			
		||||
    component: () => import('~/components/PageNotFound.vue')
 | 
			
		||||
  }
 | 
			
		||||
] as RouteRecordRaw[]
 | 
			
		||||
] as RouteRecordRaw[]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -178,5 +178,5 @@ export default [
 | 
			
		|||
        props: true
 | 
			
		||||
      }
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  }
 | 
			
		||||
] as RouteRecordRaw[]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,5 +26,5 @@ export default [
 | 
			
		|||
    name: 'settings.applications.edit',
 | 
			
		||||
    component: () => import('~/components/auth/ApplicationEdit.vue'),
 | 
			
		||||
    props: true
 | 
			
		||||
  },
 | 
			
		||||
] as RouteRecordRaw[]
 | 
			
		||||
  }
 | 
			
		||||
] as RouteRecordRaw[]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,7 +3,7 @@ import type { RouteRecordRaw } from 'vue-router'
 | 
			
		|||
import store from '~/store'
 | 
			
		||||
 | 
			
		||||
export default [
 | 
			
		||||
  { suffix: '.full', path: '/@:username@:domain' }, 
 | 
			
		||||
  { suffix: '.full', path: '/@:username@:domain' },
 | 
			
		||||
  { suffix: '', path: '/@:username' }
 | 
			
		||||
].map((route) => {
 | 
			
		||||
  return {
 | 
			
		||||
| 
						 | 
				
			
			@ -31,4 +31,4 @@ export default [
 | 
			
		|||
      }
 | 
			
		||||
    ]
 | 
			
		||||
  }
 | 
			
		||||
}) as RouteRecordRaw[]
 | 
			
		||||
}) as RouteRecordRaw[]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -71,7 +71,7 @@ const store: Module<State, RootState> = {
 | 
			
		|||
        page_size: 50,
 | 
			
		||||
        ordering: '-creation_date'
 | 
			
		||||
      }
 | 
			
		||||
      const promise = axios.get('favorites/tracks/all/', { params: params })
 | 
			
		||||
      const promise = axios.get('favorites/tracks/all/', { params })
 | 
			
		||||
      return promise.then((response) => {
 | 
			
		||||
        logger.info('Fetched a batch of ' + response.data.results.length + ' favorites')
 | 
			
		||||
        response.data.results.forEach((result: { track: string }) => {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -110,7 +110,7 @@ export default createStore<RootState>({
 | 
			
		|||
                mbid: track.mbid,
 | 
			
		||||
                uploads: track.uploads,
 | 
			
		||||
                listen_url: track.listen_url,
 | 
			
		||||
                artist: artist,
 | 
			
		||||
                artist,
 | 
			
		||||
                album: {}
 | 
			
		||||
              }
 | 
			
		||||
              if (track.album) {
 | 
			
		||||
| 
						 | 
				
			
			@ -119,7 +119,7 @@ export default createStore<RootState>({
 | 
			
		|||
                  title: track.album.title,
 | 
			
		||||
                  mbid: track.album.mbid,
 | 
			
		||||
                  cover: track.album.cover,
 | 
			
		||||
                  artist: artist
 | 
			
		||||
                  artist
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
              return data
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -60,7 +60,7 @@ const store: Module<State, RootState> = {
 | 
			
		|||
          commit('follows', { library: uuid, follow: null })
 | 
			
		||||
        }, () => {
 | 
			
		||||
          logger.info('Error while unsubscribing from library')
 | 
			
		||||
          commit('follows', { library: uuid, follow: follow })
 | 
			
		||||
          commit('follows', { library: uuid, follow })
 | 
			
		||||
        })
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -139,7 +139,7 @@ const store: Module<State, RootState> = {
 | 
			
		|||
          page_size: 100,
 | 
			
		||||
          ordering: '-creation_date'
 | 
			
		||||
        }
 | 
			
		||||
        promise = axios.get('moderation/content-filters/', { params: params })
 | 
			
		||||
        promise = axios.get('moderation/content-filters/', { params })
 | 
			
		||||
      }
 | 
			
		||||
      return promise.then((response) => {
 | 
			
		||||
        logger.info('Fetched a batch of ' + response.data.results.length + ' filters')
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -34,7 +34,7 @@ const store: Module<State, RootState> = {
 | 
			
		|||
    currentTime: 0,
 | 
			
		||||
    errored: false,
 | 
			
		||||
    bufferProgress: 0,
 | 
			
		||||
    looping: 0 
 | 
			
		||||
    looping: 0
 | 
			
		||||
  },
 | 
			
		||||
  mutations: {
 | 
			
		||||
    reset (state) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -96,7 +96,7 @@ const store: Module<State, RootState> = {
 | 
			
		|||
 | 
			
		||||
      const total = tracks.length
 | 
			
		||||
      tracks.forEach((track: Track, i: number) => {
 | 
			
		||||
        const promise = dispatch('append', { track: track, index: index })
 | 
			
		||||
        const promise = dispatch('append', { track, index })
 | 
			
		||||
        index += 1
 | 
			
		||||
 | 
			
		||||
        if (callback && i + 1 === total) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -86,7 +86,7 @@ const store: Module<State, RootState> = {
 | 
			
		|||
        radio_type: type,
 | 
			
		||||
        related_object_id: objectId,
 | 
			
		||||
        custom_radio: customRadioId,
 | 
			
		||||
        config: config
 | 
			
		||||
        config
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (clientOnly) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,8 @@ import type { Store } from 'vuex'
 | 
			
		|||
import type { Router } from 'vue-router'
 | 
			
		||||
import type { AxiosError } from 'axios'
 | 
			
		||||
import type { RootState } from '~/store'
 | 
			
		||||
 | 
			
		||||
// eslint-disable-next-line
 | 
			
		||||
import type { ComponentPublicInstance } from '@vue/runtime-core'
 | 
			
		||||
 | 
			
		||||
export type FunctionRef = Element | ComponentPublicInstance | null
 | 
			
		||||
| 
						 | 
				
			
			@ -100,7 +102,6 @@ export interface Track {
 | 
			
		|||
  is_local: boolean
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export interface Channel {
 | 
			
		||||
  id: string
 | 
			
		||||
  uuid: string
 | 
			
		||||
| 
						 | 
				
			
			@ -195,6 +196,7 @@ export interface Listening {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
// API stuff
 | 
			
		||||
// eslint-disable-next-line
 | 
			
		||||
export interface APIErrorResponse extends Record<string, APIErrorResponse | string[] | { code: string }[]> {}
 | 
			
		||||
 | 
			
		||||
export interface BackendError extends AxiosError {
 | 
			
		||||
| 
						 | 
				
			
			@ -360,4 +362,4 @@ export interface Note {
 | 
			
		|||
  author: Actor // TODO (wvffle): Check if is valid
 | 
			
		||||
  summary: string
 | 
			
		||||
  creation_date: string
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,7 +3,6 @@
 | 
			
		|||
import $ from 'jquery'
 | 
			
		||||
import { tryOnMounted, useCurrentElement } from '@vueuse/core'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export const getDropdown = (selector = '.ui.dropdown'): JQuery => {
 | 
			
		||||
  const el = useCurrentElement()
 | 
			
		||||
  return $(el.value).find(selector)
 | 
			
		||||
| 
						 | 
				
			
			@ -11,7 +10,7 @@ export const getDropdown = (selector = '.ui.dropdown'): JQuery => {
 | 
			
		|||
 | 
			
		||||
export const setupDropdown = (selector: string | HTMLElement = '.ui.dropdown') => tryOnMounted(() => {
 | 
			
		||||
  const el = useCurrentElement()
 | 
			
		||||
  const $dropdown = typeof selector === 'string' 
 | 
			
		||||
  const $dropdown = typeof selector === 'string'
 | 
			
		||||
    ? $(el.value).find(selector)
 | 
			
		||||
    : $(selector)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -30,7 +30,7 @@ export function parseAPIErrors (responseData: APIErrorResponse, parentField?: st
 | 
			
		|||
      }))
 | 
			
		||||
 | 
			
		||||
      continue
 | 
			
		||||
    } 
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Handle nested errors
 | 
			
		||||
    errors.push(...parseAPIErrors(value, fieldName))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -286,7 +286,7 @@ export default {
 | 
			
		|||
    },
 | 
			
		||||
    fetch (params) {
 | 
			
		||||
      this.isLoading = true
 | 
			
		||||
      axios.get('federation/inbox/', { params: params }).then(response => {
 | 
			
		||||
      axios.get('federation/inbox/', { params }).then(response => {
 | 
			
		||||
        this.isLoading = false
 | 
			
		||||
        this.notifications = response.data
 | 
			
		||||
      })
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -201,9 +201,9 @@
 | 
			
		|||
                        Description
 | 
			
		||||
                      </translate>
 | 
			
		||||
                    </td>
 | 
			
		||||
                    <sanitized-html 
 | 
			
		||||
                    <sanitized-html
 | 
			
		||||
                      tag="td"
 | 
			
		||||
                      :html="object.artist.description.html" 
 | 
			
		||||
                      :html="object.artist.description.html"
 | 
			
		||||
                    />
 | 
			
		||||
                  </tr>
 | 
			
		||||
                  <tr v-if="object.actor.url">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -74,7 +74,7 @@ const title = computed(() => labels.value[props.type])
 | 
			
		|||
        :default-query="defaultQuery"
 | 
			
		||||
        :ordering-config-name="null"
 | 
			
		||||
      />
 | 
			
		||||
      <invitations-table 
 | 
			
		||||
      <invitations-table
 | 
			
		||||
        v-else-if="type === 'invitations'"
 | 
			
		||||
        :ordering-config-name="null"
 | 
			
		||||
      />
 | 
			
		||||
| 
						 | 
				
			
			@ -102,7 +102,7 @@ const title = computed(() => labels.value[props.type])
 | 
			
		|||
        :default-query="defaultQuery"
 | 
			
		||||
        :ordering-config-name="null"
 | 
			
		||||
      />
 | 
			
		||||
      <users-table 
 | 
			
		||||
      <users-table
 | 
			
		||||
        v-else-if="type === 'users'"
 | 
			
		||||
        :ordering-config-name="null"
 | 
			
		||||
      />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,7 +4,6 @@ import type { SettingsGroup as SettingsGroupType } from '~/types'
 | 
			
		|||
import axios from 'axios'
 | 
			
		||||
import $ from 'jquery'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
import SettingsGroup from '~/components/admin/SettingsGroup.vue'
 | 
			
		||||
 | 
			
		||||
import { useCurrentElement } from '@vueuse/core'
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -212,9 +212,9 @@
 | 
			
		|||
                        Description
 | 
			
		||||
                      </translate>
 | 
			
		||||
                    </td>
 | 
			
		||||
                    <sanitized-html 
 | 
			
		||||
                    <sanitized-html
 | 
			
		||||
                      tag="td"
 | 
			
		||||
                      :html="object.description.html" 
 | 
			
		||||
                      :html="object.description.html"
 | 
			
		||||
                    />
 | 
			
		||||
                  </tr>
 | 
			
		||||
                </tbody>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -211,9 +211,9 @@
 | 
			
		|||
                        Description
 | 
			
		||||
                      </translate>
 | 
			
		||||
                    </td>
 | 
			
		||||
                    <sanitized-html 
 | 
			
		||||
                    <sanitized-html
 | 
			
		||||
                      tag="td"
 | 
			
		||||
                      :html="object.description.html" 
 | 
			
		||||
                      :html="object.description.html"
 | 
			
		||||
                    />
 | 
			
		||||
                  </tr>
 | 
			
		||||
                </tbody>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -348,9 +348,9 @@ const getQuery = (field: string, value: string) => `${field}:"${value}"`
 | 
			
		|||
                        Description
 | 
			
		||||
                      </translate>
 | 
			
		||||
                    </td>
 | 
			
		||||
                    <sanitized-html 
 | 
			
		||||
                    <sanitized-html
 | 
			
		||||
                      tag="td"
 | 
			
		||||
                      :html="track.description.html" 
 | 
			
		||||
                      :html="track.description.html"
 | 
			
		||||
                    />
 | 
			
		||||
                  </tr>
 | 
			
		||||
                </tbody>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -73,7 +73,6 @@ const fetchData = async () => {
 | 
			
		|||
      } else {
 | 
			
		||||
        await router.replace({ name: 'channels.detail', params: { id: actor.full_username } })
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    // TODO (wvffle): Handle error
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,6 @@
 | 
			
		|||
<script setup lang="ts">
 | 
			
		||||
import type { ImportStatus } from '~/types'
 | 
			
		||||
import type { RouteWithPreferences } from '~/store/ui'
 | 
			
		||||
import type { OrderingField } from '~/store/ui'
 | 
			
		||||
import type { RouteWithPreferences, OrderingField } from '~/store/ui'
 | 
			
		||||
import type { OrderingProps } from '~/composables/useOrdering'
 | 
			
		||||
import type { SmartSearchProps } from '~/composables/useSmartSearch'
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -34,13 +34,13 @@ const scanProgress = computed(() => Math.min(latestScan.value.processed_files *
 | 
			
		|||
const scanStatus = computed(() => latestScan.value?.status ?? 'unknown')
 | 
			
		||||
const canLaunchScan = computed(() => scanStatus.value !== 'pending' && scanStatus.value !== 'scanning')
 | 
			
		||||
const radioPlayable = computed(() => (
 | 
			
		||||
  (library.value.actor.is_local || scanStatus.value === 'finished')
 | 
			
		||||
  && (library.value.privacy_level === 'everyone' || library.value.follow?.approved)
 | 
			
		||||
  (library.value.actor.is_local || scanStatus.value === 'finished') &&
 | 
			
		||||
  (library.value.privacy_level === 'everyone' || library.value.follow?.approved)
 | 
			
		||||
))
 | 
			
		||||
 | 
			
		||||
const { $pgettext } = useGettext()
 | 
			
		||||
const labels = computed(() => ({
 | 
			
		||||
  tooltips: { 
 | 
			
		||||
  tooltips: {
 | 
			
		||||
    me: $pgettext('Content/Library/Card.Help text', 'This library is private and your approval from its owner is needed to access its content'),
 | 
			
		||||
    everyone: $pgettext('Content/Library/Card.Help text', 'This library is public and you can access its content freely')
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			@ -364,4 +364,4 @@ watch(showScan, (shouldShow) => {
 | 
			
		|||
      </template>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
</template>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,10 +25,10 @@ const object = ref<Library | null>(null)
 | 
			
		|||
 | 
			
		||||
const isOwner = computed(() => store.state.auth.authenticated && object.value?.actor.full_username === store.state.auth.fullUsername)
 | 
			
		||||
const isPlayable = computed(() => (object.value?.uploads_count ?? 0) > 0 && (
 | 
			
		||||
  isOwner.value 
 | 
			
		||||
    || object.value?.privacy_level === 'everyone' 
 | 
			
		||||
    || (object.value?.privacy_level === 'instance' && store.state.auth.authenticated && object.value.actor.domain === store.getters['instance/domain']) 
 | 
			
		||||
    || (store.getters['libraries/follow'](object.value?.uuid) || {}).approved === true
 | 
			
		||||
  isOwner.value ||
 | 
			
		||||
    object.value?.privacy_level === 'everyone' ||
 | 
			
		||||
    (object.value?.privacy_level === 'instance' && store.state.auth.authenticated && object.value.actor.domain === store.getters['instance/domain']) ||
 | 
			
		||||
    (store.getters['libraries/follow'](object.value?.uuid) || {}).approved === true
 | 
			
		||||
))
 | 
			
		||||
 | 
			
		||||
const { $pgettext } = useGettext()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -59,7 +59,10 @@ const updateApproved = async (follow: LibraryFollow, approved: boolean) => {
 | 
			
		|||
        Library contents
 | 
			
		||||
      </translate>
 | 
			
		||||
    </h2>
 | 
			
		||||
    <library-files-table :filters="{library: object.uuid}" :ordering-config-name="null" />
 | 
			
		||||
    <library-files-table
 | 
			
		||||
      :filters="{library: object.uuid}"
 | 
			
		||||
      :ordering-config-name="null"
 | 
			
		||||
    />
 | 
			
		||||
 | 
			
		||||
    <div class="ui hidden divider" />
 | 
			
		||||
    <h2 class="ui header">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -44,7 +44,7 @@ const fetchData = async () => {
 | 
			
		|||
  try {
 | 
			
		||||
    const [playlistResponse, tracksResponse] = await Promise.all([
 | 
			
		||||
      axios.get(`playlists/${props.id}/`),
 | 
			
		||||
      axios.get(`playlists/${props.id}/tracks/`),
 | 
			
		||||
      axios.get(`playlists/${props.id}/tracks/`)
 | 
			
		||||
    ])
 | 
			
		||||
 | 
			
		||||
    playlist.value = playlistResponse.data
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
<script setup lang="ts">
 | 
			
		||||
import type { Track, Radio } from "~/types"
 | 
			
		||||
import type { Track, Radio } from '~/types'
 | 
			
		||||
 | 
			
		||||
import axios from 'axios'
 | 
			
		||||
import TrackTable from '~/components/audio/track/Table.vue'
 | 
			
		||||
| 
						 | 
				
			
			@ -35,7 +35,7 @@ const fetchData = async () => {
 | 
			
		|||
    const radioResponse = await axios.get(url)
 | 
			
		||||
    radio.value = radioResponse.data
 | 
			
		||||
 | 
			
		||||
    const tracksResponse = await axios.get(url + 'tracks/', { params: { page: page.value }})
 | 
			
		||||
    const tracksResponse = await axios.get(url + 'tracks/', { params: { page: page.value } })
 | 
			
		||||
    totalTracks.value = tracksResponse.data.count
 | 
			
		||||
    tracks.value = tracksResponse.data.results
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2616,6 +2616,13 @@ builtin-modules@^3.1.0:
 | 
			
		|||
  resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6"
 | 
			
		||||
  integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==
 | 
			
		||||
 | 
			
		||||
builtins@^5.0.1:
 | 
			
		||||
  version "5.0.1"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/builtins/-/builtins-5.0.1.tgz#87f6db9ab0458be728564fa81d876d8d74552fa9"
 | 
			
		||||
  integrity sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    semver "^7.0.0"
 | 
			
		||||
 | 
			
		||||
buntis@0.2.1:
 | 
			
		||||
  version "0.2.1"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/buntis/-/buntis-0.2.1.tgz#a043aabc7d64f2243bfaa53e34e999c2dd790e82"
 | 
			
		||||
| 
						 | 
				
			
			@ -3448,6 +3455,14 @@ eslint-plugin-es@^3.0.0:
 | 
			
		|||
    eslint-utils "^2.0.0"
 | 
			
		||||
    regexpp "^3.0.0"
 | 
			
		||||
 | 
			
		||||
eslint-plugin-es@^4.1.0:
 | 
			
		||||
  version "4.1.0"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-4.1.0.tgz#f0822f0c18a535a97c3e714e89f88586a7641ec9"
 | 
			
		||||
  integrity sha512-GILhQTnjYE2WorX5Jyi5i4dz5ALWxBIdQECVQavL6s7cI76IZTDWleTHkxz/QT3kvcs2QlGHvKLYsSlPOlPXnQ==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    eslint-utils "^2.0.0"
 | 
			
		||||
    regexpp "^3.0.0"
 | 
			
		||||
 | 
			
		||||
eslint-plugin-html@6.2.0:
 | 
			
		||||
  version "6.2.0"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/eslint-plugin-html/-/eslint-plugin-html-6.2.0.tgz#715bc00b50bbd0d996e28f953c289a5ebec69d43"
 | 
			
		||||
| 
						 | 
				
			
			@ -3474,6 +3489,20 @@ eslint-plugin-import@2.26.0:
 | 
			
		|||
    resolve "^1.22.0"
 | 
			
		||||
    tsconfig-paths "^3.14.1"
 | 
			
		||||
 | 
			
		||||
eslint-plugin-n@^15.2.4:
 | 
			
		||||
  version "15.2.4"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/eslint-plugin-n/-/eslint-plugin-n-15.2.4.tgz#d62021a0821ae650701ed459756aaf478a9b6056"
 | 
			
		||||
  integrity sha512-tjnVMv2fiXYMnuiIFI8QMtyUFI42SckEEWvi8h68SWGWshfqO6SSCASy24dGMGAiy7NUk6DZt90DM0iNUsmQ5w==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    builtins "^5.0.1"
 | 
			
		||||
    eslint-plugin-es "^4.1.0"
 | 
			
		||||
    eslint-utils "^3.0.0"
 | 
			
		||||
    ignore "^5.1.1"
 | 
			
		||||
    is-core-module "^2.9.0"
 | 
			
		||||
    minimatch "^3.1.2"
 | 
			
		||||
    resolve "^1.10.1"
 | 
			
		||||
    semver "^7.3.7"
 | 
			
		||||
 | 
			
		||||
eslint-plugin-node@11.1.0:
 | 
			
		||||
  version "11.1.0"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz#c95544416ee4ada26740a30474eefc5402dc671d"
 | 
			
		||||
| 
						 | 
				
			
			@ -5806,7 +5835,7 @@ semver@7.0.0:
 | 
			
		|||
  resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e"
 | 
			
		||||
  integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==
 | 
			
		||||
 | 
			
		||||
semver@7.x, semver@^7.3.5, semver@^7.3.6, semver@^7.3.7:
 | 
			
		||||
semver@7.x, semver@^7.0.0, semver@^7.3.5, semver@^7.3.6, semver@^7.3.7:
 | 
			
		||||
  version "7.3.7"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f"
 | 
			
		||||
  integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue