@ -1,25 +1,29 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<NcAvatar :display-name="name" :is-no-user="true" :size="256" :url="image" />
|
<NcAvatar
|
||||||
<h2>{{ name }}</h2>
|
:display-name="episode.name"
|
||||||
<SafeHtml :source="description" />
|
:is-no-user="true"
|
||||||
|
:size="256"
|
||||||
|
:url="episode.image" />
|
||||||
|
<h2>{{ episode.name }}</h2>
|
||||||
|
<SafeHtml :source="episode.description" />
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<NcButton v-if="link" :href="link" target="_blank">
|
<NcButton v-if="episode.link" :href="episode.link" target="_blank">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<OpenInNewIcon :size="20" />
|
<OpenInNewIcon :size="20" />
|
||||||
</template>
|
</template>
|
||||||
{{ title }}
|
{{ episode.title }}
|
||||||
</NcButton>
|
</NcButton>
|
||||||
<NcButton
|
<NcButton
|
||||||
v-if="url"
|
v-if="episode.url"
|
||||||
:download="filenameFromUrl(url)"
|
:download="filenameFromUrl(episode.url)"
|
||||||
:href="url"
|
:href="episode.url"
|
||||||
target="_blank">
|
target="_blank">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<DownloadIcon :size="20" />
|
<DownloadIcon :size="20" />
|
||||||
</template>
|
</template>
|
||||||
{{ t('repod', 'Download') }}
|
{{ t('repod', 'Download') }}
|
||||||
{{ size ? `(${humanFileSize(size)})` : '' }}
|
{{ episode.size ? `(${humanFileSize(episode.size)})` : '' }}
|
||||||
</NcButton>
|
</NcButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -43,32 +47,8 @@ export default {
|
|||||||
SafeHtml,
|
SafeHtml,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
description: {
|
episode: {
|
||||||
type: String,
|
type: Object,
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
image: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
link: {
|
|
||||||
type: String,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
size: {
|
|
||||||
type: Number,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
url: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
203
src/components/Feed/Episode.vue
Normal file
203
src/components/Feed/Episode.vue
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
<template>
|
||||||
|
<NcListItem
|
||||||
|
:active="isCurrentEpisode(episode)"
|
||||||
|
:details="!oneLine ? formatLocaleDate(new Date(episode.pubDate?.date)) : ''"
|
||||||
|
:force-display-actions="true"
|
||||||
|
:name="episode.name"
|
||||||
|
:one-line="oneLine"
|
||||||
|
:style="{ opacity: hasEnded(episode) ? 0.4 : 1 }"
|
||||||
|
:title="episode.description"
|
||||||
|
@click="modalEpisode = episode">
|
||||||
|
<template #actions>
|
||||||
|
<NcActionButton
|
||||||
|
v-if="!isCurrentEpisode(episode)"
|
||||||
|
:aria-label="t('repod', 'Play')"
|
||||||
|
:title="t('repod', 'Play')"
|
||||||
|
@click="load(episode, url)">
|
||||||
|
<template #icon>
|
||||||
|
<PlayIcon :size="20" />
|
||||||
|
</template>
|
||||||
|
</NcActionButton>
|
||||||
|
<NcActionButton
|
||||||
|
v-if="isCurrentEpisode(episode)"
|
||||||
|
:aria-label="t('repod', 'Stop')"
|
||||||
|
:title="t('repod', 'Stop')"
|
||||||
|
@click="load(null)">
|
||||||
|
<template #icon>
|
||||||
|
<StopIcon :size="20" />
|
||||||
|
</template>
|
||||||
|
</NcActionButton>
|
||||||
|
</template>
|
||||||
|
<template #extra>
|
||||||
|
<NcActions>
|
||||||
|
<NcActionButton
|
||||||
|
v-if="episode.duration"
|
||||||
|
:aria-label="t('repod', 'Read')"
|
||||||
|
:disabled="loading"
|
||||||
|
:model-value="hasEnded(episode)"
|
||||||
|
:name="t('repod', 'Read')"
|
||||||
|
:title="t('repod', 'Read')"
|
||||||
|
@click="markAs(episode, !hasEnded(episode))">
|
||||||
|
<template #icon>
|
||||||
|
<PlaylistPlayIcon v-if="!hasEnded(episode)" :size="20" />
|
||||||
|
<PlaylistRemoveIcon v-if="hasEnded(episode)" :size="20" />
|
||||||
|
</template>
|
||||||
|
</NcActionButton>
|
||||||
|
<NcActionLink
|
||||||
|
v-if="episode.link"
|
||||||
|
:href="episode.link"
|
||||||
|
:name="t('repod', 'Open website')"
|
||||||
|
target="_blank"
|
||||||
|
:title="t('repod', 'Open website')">
|
||||||
|
<template #icon>
|
||||||
|
<OpenInNewIcon :size="20" />
|
||||||
|
</template>
|
||||||
|
</NcActionLink>
|
||||||
|
<NcActionLink
|
||||||
|
v-if="episode.url"
|
||||||
|
:download="filenameFromUrl(episode.url)"
|
||||||
|
:href="episode.url"
|
||||||
|
:name="t('repod', 'Download')"
|
||||||
|
target="_blank"
|
||||||
|
:title="t('repod', 'Download')">
|
||||||
|
<template #icon>
|
||||||
|
<DownloadIcon :size="20" />
|
||||||
|
</template>
|
||||||
|
</NcActionLink>
|
||||||
|
</NcActions>
|
||||||
|
<NcModal v-if="modalEpisode" @close="modalEpisode = null">
|
||||||
|
<Modal :episode="episode" />
|
||||||
|
</NcModal>
|
||||||
|
</template>
|
||||||
|
<template #icon>
|
||||||
|
<NcAvatar
|
||||||
|
:display-name="episode.name"
|
||||||
|
:is-no-user="true"
|
||||||
|
:url="episode.image" />
|
||||||
|
</template>
|
||||||
|
<template #indicator>
|
||||||
|
<NcProgressBar
|
||||||
|
v-if="isListening(episode) && !oneLine"
|
||||||
|
class="progress"
|
||||||
|
:value="(episode.action.position * 100) / episode.action.total" />
|
||||||
|
</template>
|
||||||
|
<template v-if="!oneLine" #subname>
|
||||||
|
{{ episode.duration }}
|
||||||
|
</template>
|
||||||
|
</NcListItem>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
NcActionButton,
|
||||||
|
NcActionLink,
|
||||||
|
NcActions,
|
||||||
|
NcAvatar,
|
||||||
|
NcListItem,
|
||||||
|
NcModal,
|
||||||
|
NcProgressBar,
|
||||||
|
} from '@nextcloud/vue'
|
||||||
|
import {
|
||||||
|
durationToSeconds,
|
||||||
|
formatEpisodeTimestamp,
|
||||||
|
formatLocaleDate,
|
||||||
|
} from '../../utils/time.js'
|
||||||
|
import { hasEnded, isListening } from '../../utils/status.js'
|
||||||
|
import { mapActions, mapState } from 'pinia'
|
||||||
|
import DownloadIcon from 'vue-material-design-icons/Download.vue'
|
||||||
|
import Modal from '../Atoms/Modal.vue'
|
||||||
|
import OpenInNewIcon from 'vue-material-design-icons/OpenInNew.vue'
|
||||||
|
import PlayIcon from 'vue-material-design-icons/Play.vue'
|
||||||
|
import PlaylistPlayIcon from 'vue-material-design-icons/PlaylistPlay.vue'
|
||||||
|
import PlaylistRemoveIcon from 'vue-material-design-icons/PlaylistRemove.vue'
|
||||||
|
import StopIcon from 'vue-material-design-icons/Stop.vue'
|
||||||
|
import axios from '@nextcloud/axios'
|
||||||
|
import { filenameFromUrl } from '../../utils/url.js'
|
||||||
|
import { generateUrl } from '@nextcloud/router'
|
||||||
|
import { showError } from '../../utils/toast.js'
|
||||||
|
import { usePlayer } from '../../store/player.js'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Episode',
|
||||||
|
components: {
|
||||||
|
DownloadIcon,
|
||||||
|
Modal,
|
||||||
|
NcActionButton,
|
||||||
|
NcActionLink,
|
||||||
|
NcActions,
|
||||||
|
NcAvatar,
|
||||||
|
NcListItem,
|
||||||
|
NcModal,
|
||||||
|
NcProgressBar,
|
||||||
|
OpenInNewIcon,
|
||||||
|
PlayIcon,
|
||||||
|
PlaylistPlayIcon,
|
||||||
|
PlaylistRemoveIcon,
|
||||||
|
StopIcon,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
episode: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
oneLine: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
url: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data: () => ({
|
||||||
|
loading: false,
|
||||||
|
modalEpisode: null,
|
||||||
|
}),
|
||||||
|
computed: {
|
||||||
|
...mapState(usePlayer, { playerEpisode: 'episode' }),
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions(usePlayer, ['load']),
|
||||||
|
formatLocaleDate,
|
||||||
|
hasEnded,
|
||||||
|
isListening,
|
||||||
|
filenameFromUrl,
|
||||||
|
isCurrentEpisode(episode) {
|
||||||
|
return this.playerEpisode?.url === episode.url
|
||||||
|
},
|
||||||
|
async markAs(episode, read) {
|
||||||
|
try {
|
||||||
|
this.loading = true
|
||||||
|
episode.action = {
|
||||||
|
podcast: this.url,
|
||||||
|
episode: episode.url,
|
||||||
|
guid: episode.guid,
|
||||||
|
action: 'play',
|
||||||
|
timestamp: formatEpisodeTimestamp(new Date()),
|
||||||
|
started: episode.action?.started || 0,
|
||||||
|
position: read ? durationToSeconds(episode.duration) : 0,
|
||||||
|
total: durationToSeconds(episode.duration),
|
||||||
|
}
|
||||||
|
await axios.post(
|
||||||
|
generateUrl('/apps/gpoddersync/episode_action/create'),
|
||||||
|
[episode.action],
|
||||||
|
)
|
||||||
|
if (read && this.isCurrentEpisode(episode)) {
|
||||||
|
this.load(null)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
showError(t('repod', 'Could not change the status of the episode'))
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.progress {
|
||||||
|
margin-top: 0.4rem;
|
||||||
|
}
|
||||||
|
</style>
|
@ -2,138 +2,23 @@
|
|||||||
<div>
|
<div>
|
||||||
<Loading v-if="loading" />
|
<Loading v-if="loading" />
|
||||||
<ul v-if="!loading">
|
<ul v-if="!loading">
|
||||||
<NcListItem
|
<Episode
|
||||||
v-for="episode in filteredEpisodes"
|
v-for="episode in filteredEpisodes"
|
||||||
:key="episode.guid"
|
:key="episode.guid"
|
||||||
:active="isCurrentEpisode(episode)"
|
:episode="episode"
|
||||||
:details="formatLocaleDate(new Date(episode.pubDate?.date))"
|
:url="url" />
|
||||||
:force-display-actions="true"
|
|
||||||
:name="episode.name"
|
|
||||||
:style="{ opacity: hasEnded(episode) ? 0.4 : 1 }"
|
|
||||||
target="_self"
|
|
||||||
:title="episode.description"
|
|
||||||
@click="modalEpisode = episode">
|
|
||||||
<template #actions>
|
|
||||||
<NcActionButton
|
|
||||||
v-if="!isCurrentEpisode(episode)"
|
|
||||||
:aria-label="t('repod', 'Play')"
|
|
||||||
:title="t('repod', 'Play')"
|
|
||||||
@click="load(episode, url)">
|
|
||||||
<template #icon>
|
|
||||||
<PlayIcon :size="20" />
|
|
||||||
</template>
|
|
||||||
</NcActionButton>
|
|
||||||
<NcActionButton
|
|
||||||
v-if="isCurrentEpisode(episode)"
|
|
||||||
:aria-label="t('repod', 'Stop')"
|
|
||||||
:title="t('repod', 'Stop')"
|
|
||||||
@click="load(null)">
|
|
||||||
<template #icon>
|
|
||||||
<StopIcon :size="20" />
|
|
||||||
</template>
|
|
||||||
</NcActionButton>
|
|
||||||
</template>
|
|
||||||
<template #extra>
|
|
||||||
<NcActions>
|
|
||||||
<NcActionButton
|
|
||||||
v-if="episode.duration"
|
|
||||||
:aria-label="t('repod', 'Read')"
|
|
||||||
:disabled="loadingAction"
|
|
||||||
:model-value="hasEnded(episode)"
|
|
||||||
:name="t('repod', 'Read')"
|
|
||||||
:title="t('repod', 'Read')"
|
|
||||||
@update:modelValue="markAs(episode, !hasEnded(episode))">
|
|
||||||
<template #icon>
|
|
||||||
<PlaylistPlayIcon
|
|
||||||
v-if="!hasEnded(episode)"
|
|
||||||
:size="20" />
|
|
||||||
<PlaylistRemoveIcon
|
|
||||||
v-if="hasEnded(episode)"
|
|
||||||
:size="20" />
|
|
||||||
</template>
|
|
||||||
</NcActionButton>
|
|
||||||
<NcActionLink
|
|
||||||
v-if="episode.link"
|
|
||||||
:href="episode.link"
|
|
||||||
:name="t('repod', 'Open website')"
|
|
||||||
target="_blank"
|
|
||||||
:title="t('repod', 'Open website')">
|
|
||||||
<template #icon>
|
|
||||||
<OpenInNewIcon :size="20" />
|
|
||||||
</template>
|
|
||||||
</NcActionLink>
|
|
||||||
<NcActionLink
|
|
||||||
v-if="episode.url"
|
|
||||||
:download="filenameFromUrl(episode.url)"
|
|
||||||
:href="episode.url"
|
|
||||||
:name="t('repod', 'Download')"
|
|
||||||
target="_blank"
|
|
||||||
:title="t('repod', 'Download')">
|
|
||||||
<template #icon>
|
|
||||||
<DownloadIcon :size="20" />
|
|
||||||
</template>
|
|
||||||
</NcActionLink>
|
|
||||||
</NcActions>
|
|
||||||
</template>
|
|
||||||
<template #icon>
|
|
||||||
<NcAvatar
|
|
||||||
:display-name="episode.name"
|
|
||||||
:is-no-user="true"
|
|
||||||
:url="episode.image" />
|
|
||||||
</template>
|
|
||||||
<template #indicator>
|
|
||||||
<NcProgressBar
|
|
||||||
v-if="isListening(episode)"
|
|
||||||
class="progress"
|
|
||||||
:value="
|
|
||||||
(episode.action.position * 100) / episode.action.total
|
|
||||||
" />
|
|
||||||
</template>
|
|
||||||
<template #subname>
|
|
||||||
{{ episode.duration }}
|
|
||||||
</template>
|
|
||||||
</NcListItem>
|
|
||||||
</ul>
|
</ul>
|
||||||
<NcModal v-if="modalEpisode" @close="modalEpisode = null">
|
|
||||||
<Modal
|
|
||||||
:description="modalEpisode.description"
|
|
||||||
:image="modalEpisode.image"
|
|
||||||
:link="modalEpisode.link"
|
|
||||||
:name="modalEpisode.name"
|
|
||||||
:size="modalEpisode.size"
|
|
||||||
:title="modalEpisode.title"
|
|
||||||
:url="modalEpisode.url" />
|
|
||||||
</NcModal>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {
|
import { hasEnded, isListening } from '../../utils/status.js'
|
||||||
NcActionButton,
|
import Episode from './Episode.vue'
|
||||||
NcActionLink,
|
|
||||||
NcActions,
|
|
||||||
NcAvatar,
|
|
||||||
NcListItem,
|
|
||||||
NcModal,
|
|
||||||
NcProgressBar,
|
|
||||||
} from '@nextcloud/vue'
|
|
||||||
import { decodeUrl, filenameFromUrl } from '../../utils/url.js'
|
|
||||||
import {
|
|
||||||
durationToSeconds,
|
|
||||||
formatEpisodeTimestamp,
|
|
||||||
formatLocaleDate,
|
|
||||||
} from '../../utils/time.js'
|
|
||||||
import { mapActions, mapState } from 'pinia'
|
|
||||||
import DownloadIcon from 'vue-material-design-icons/Download.vue'
|
|
||||||
import Loading from '../Atoms/Loading.vue'
|
import Loading from '../Atoms/Loading.vue'
|
||||||
import Modal from '../Atoms/Modal.vue'
|
|
||||||
import OpenInNewIcon from 'vue-material-design-icons/OpenInNew.vue'
|
|
||||||
import PlayIcon from 'vue-material-design-icons/Play.vue'
|
|
||||||
import PlaylistPlayIcon from 'vue-material-design-icons/PlaylistPlay.vue'
|
|
||||||
import PlaylistRemoveIcon from 'vue-material-design-icons/PlaylistRemove.vue'
|
|
||||||
import StopIcon from 'vue-material-design-icons/Stop.vue'
|
|
||||||
import axios from '@nextcloud/axios'
|
import axios from '@nextcloud/axios'
|
||||||
|
import { decodeUrl } from '../../utils/url.js'
|
||||||
import { generateUrl } from '@nextcloud/router'
|
import { generateUrl } from '@nextcloud/router'
|
||||||
|
import { mapState } from 'pinia'
|
||||||
import { showError } from '../../utils/toast.js'
|
import { showError } from '../../utils/toast.js'
|
||||||
import { usePlayer } from '../../store/player.js'
|
import { usePlayer } from '../../store/player.js'
|
||||||
import { useSettings } from '../../store/settings.js'
|
import { useSettings } from '../../store/settings.js'
|
||||||
@ -141,27 +26,12 @@ import { useSettings } from '../../store/settings.js'
|
|||||||
export default {
|
export default {
|
||||||
name: 'Episodes',
|
name: 'Episodes',
|
||||||
components: {
|
components: {
|
||||||
DownloadIcon,
|
Episode,
|
||||||
Loading,
|
Loading,
|
||||||
Modal,
|
|
||||||
NcActionButton,
|
|
||||||
NcActionLink,
|
|
||||||
NcActions,
|
|
||||||
NcAvatar,
|
|
||||||
NcListItem,
|
|
||||||
NcModal,
|
|
||||||
NcProgressBar,
|
|
||||||
OpenInNewIcon,
|
|
||||||
PlayIcon,
|
|
||||||
PlaylistPlayIcon,
|
|
||||||
PlaylistRemoveIcon,
|
|
||||||
StopIcon,
|
|
||||||
},
|
},
|
||||||
data: () => ({
|
data: () => ({
|
||||||
episodes: [],
|
episodes: [],
|
||||||
loading: true,
|
loading: true,
|
||||||
loadingAction: false,
|
|
||||||
modalEpisode: null,
|
|
||||||
}),
|
}),
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(usePlayer, ['episode']),
|
...mapState(usePlayer, ['episode']),
|
||||||
@ -215,63 +85,8 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(usePlayer, ['load']),
|
hasEnded,
|
||||||
filenameFromUrl,
|
isListening,
|
||||||
formatLocaleDate,
|
|
||||||
hasEnded(episode) {
|
|
||||||
return (
|
|
||||||
episode.action &&
|
|
||||||
(episode.action.action === 'DELETE' ||
|
|
||||||
(episode.action.position > 0 &&
|
|
||||||
episode.action.total > 0 &&
|
|
||||||
episode.action.position >= episode.action.total))
|
|
||||||
)
|
|
||||||
},
|
|
||||||
isCurrentEpisode(episode) {
|
|
||||||
return this.episode && this.episode.url === episode.url
|
|
||||||
},
|
|
||||||
isListening(episode) {
|
|
||||||
return (
|
|
||||||
episode.action &&
|
|
||||||
episode.action.action &&
|
|
||||||
episode.action.action.toLowerCase() === 'play' &&
|
|
||||||
episode.action.position > 0 &&
|
|
||||||
!this.hasEnded(episode)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
async markAs(episode, read) {
|
|
||||||
try {
|
|
||||||
this.loadingAction = true
|
|
||||||
episode.action = {
|
|
||||||
podcast: this.url,
|
|
||||||
episode: episode.url,
|
|
||||||
guid: episode.guid,
|
|
||||||
action: 'play',
|
|
||||||
timestamp: formatEpisodeTimestamp(new Date()),
|
|
||||||
started: episode.action ? episode.action.started : 0,
|
|
||||||
position: read ? durationToSeconds(episode.duration) : 0,
|
|
||||||
total: durationToSeconds(episode.duration),
|
|
||||||
}
|
|
||||||
await axios.post(
|
|
||||||
generateUrl('/apps/gpoddersync/episode_action/create'),
|
|
||||||
[episode.action],
|
|
||||||
)
|
|
||||||
if (read && this.episode && episode.url === this.episode.url) {
|
|
||||||
this.load(null)
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e)
|
|
||||||
showError(t('repod', 'Could not change the status of the episode'))
|
|
||||||
} finally {
|
|
||||||
this.loadingAction = false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.progress {
|
|
||||||
margin-top: 0.4rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
97
src/components/Feed/Favorites.vue
Normal file
97
src/components/Feed/Favorites.vue
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
<template>
|
||||||
|
<NcGuestContent class="guest">
|
||||||
|
<Loading v-if="!currentFavoriteData" />
|
||||||
|
<NcAvatar
|
||||||
|
v-if="currentFavoriteData"
|
||||||
|
:display-name="currentFavoriteData.author || currentFavoriteData.title"
|
||||||
|
:is-no-user="true"
|
||||||
|
:size="222"
|
||||||
|
:url="currentFavoriteData.imageUrl" />
|
||||||
|
<div class="list">
|
||||||
|
<h2>{{ currentFavoriteData.title }}</h2>
|
||||||
|
<Loading v-if="loading" />
|
||||||
|
<ul v-if="!loading">
|
||||||
|
<Episode
|
||||||
|
v-for="episode in episodes"
|
||||||
|
:key="episode.guid"
|
||||||
|
:episode="episode"
|
||||||
|
:one-line="true"
|
||||||
|
:url="url" />
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</NcGuestContent>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { NcAvatar, NcGuestContent } from '@nextcloud/vue'
|
||||||
|
import Episode from './Episode.vue'
|
||||||
|
import Loading from '../Atoms/Loading.vue'
|
||||||
|
import axios from '@nextcloud/axios'
|
||||||
|
import { generateUrl } from '@nextcloud/router'
|
||||||
|
import { hasEnded } from '../../utils/status.js'
|
||||||
|
import { mapState } from 'pinia'
|
||||||
|
import { showError } from '../../utils/toast.js'
|
||||||
|
import { useSubscriptions } from '../../store/subscriptions.js'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Favorites',
|
||||||
|
components: {
|
||||||
|
Episode,
|
||||||
|
Loading,
|
||||||
|
NcAvatar,
|
||||||
|
NcGuestContent,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
url: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data: () => ({
|
||||||
|
episodes: [],
|
||||||
|
loading: true,
|
||||||
|
}),
|
||||||
|
computed: {
|
||||||
|
...mapState(useSubscriptions, ['favs']),
|
||||||
|
currentFavoriteData() {
|
||||||
|
return this.favs.find((fav) => fav.url === this.url)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
try {
|
||||||
|
this.loading = true
|
||||||
|
const episodes = await axios.get(
|
||||||
|
generateUrl('/apps/repod/episodes/list?url={url}', {
|
||||||
|
url: this.url,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
this.episodes = [...episodes.data]
|
||||||
|
.sort(
|
||||||
|
(a, b) => new Date(b.pubDate?.date) - new Date(a.pubDate?.date),
|
||||||
|
)
|
||||||
|
.filter((episode) => !this.hasEnded(episode))
|
||||||
|
.slice(0, 4)
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
showError(t('repod', 'Could not fetch episodes'))
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
hasEnded,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.guest {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,39 +0,0 @@
|
|||||||
<template>
|
|
||||||
<NcGuestContent>
|
|
||||||
<Loading v-if="!currentFavoriteData" class="loading" />
|
|
||||||
<NcAvatar
|
|
||||||
v-if="currentFavoriteData"
|
|
||||||
:display-name="currentFavoriteData.author || currentFavoriteData.title"
|
|
||||||
:is-no-user="true"
|
|
||||||
:size="222"
|
|
||||||
:url="currentFavoriteData.imageUrl" />
|
|
||||||
</NcGuestContent>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { NcAvatar, NcGuestContent } from '@nextcloud/vue'
|
|
||||||
import Loading from '../Atoms/Loading.vue'
|
|
||||||
import { mapState } from 'pinia'
|
|
||||||
import { useSubscriptions } from '../../store/subscriptions.js'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'Favorites',
|
|
||||||
components: {
|
|
||||||
Loading,
|
|
||||||
NcAvatar,
|
|
||||||
NcGuestContent,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
url: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapState(useSubscriptions, ['favs']),
|
|
||||||
currentFavoriteData() {
|
|
||||||
return this.favs.find((fav) => fav.url === this.url)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
@ -7,14 +7,7 @@
|
|||||||
<i>{{ episode.title }}</i>
|
<i>{{ episode.title }}</i>
|
||||||
</router-link>
|
</router-link>
|
||||||
<NcModal v-if="modal" @close="modal = false">
|
<NcModal v-if="modal" @close="modal = false">
|
||||||
<Modal
|
<Modal :episode="episode" />
|
||||||
:description="episode.description"
|
|
||||||
:image="episode.image"
|
|
||||||
:link="episode.link"
|
|
||||||
:name="episode.name"
|
|
||||||
:size="episode.size"
|
|
||||||
:title="episode.title"
|
|
||||||
:url="episode.url" />
|
|
||||||
</NcModal>
|
</NcModal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -52,7 +52,7 @@ import { toFeedUrl } from '../../utils/url.js'
|
|||||||
import { useSubscriptions } from '../../store/subscriptions.js'
|
import { useSubscriptions } from '../../store/subscriptions.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Item',
|
name: 'Subscription',
|
||||||
components: {
|
components: {
|
||||||
AlertIcon,
|
AlertIcon,
|
||||||
DeleteIcon,
|
DeleteIcon,
|
@ -11,13 +11,14 @@
|
|||||||
</router-link>
|
</router-link>
|
||||||
<Loading v-if="loading" />
|
<Loading v-if="loading" />
|
||||||
<NcAppNavigationList v-if="!loading">
|
<NcAppNavigationList v-if="!loading">
|
||||||
<Item
|
<Subscription
|
||||||
v-for="url of favs
|
v-for="url of favs
|
||||||
.sort((fav) => fav.lastPub)
|
.sort((fav) => fav.lastPub)
|
||||||
.map((fav) => fav.url)"
|
.map((fav) => fav.url)
|
||||||
|
.filter((url) => subs.includes(url))"
|
||||||
:key="url"
|
:key="url"
|
||||||
:url="url" />
|
:url="url" />
|
||||||
<Item
|
<Subscription
|
||||||
v-for="url of subs.filter(
|
v-for="url of subs.filter(
|
||||||
(sub) => !favs.map((fav) => fav.url).includes(sub),
|
(sub) => !favs.map((fav) => fav.url).includes(sub),
|
||||||
)"
|
)"
|
||||||
@ -40,10 +41,10 @@ import {
|
|||||||
} from '@nextcloud/vue'
|
} from '@nextcloud/vue'
|
||||||
import { mapActions, mapState } from 'pinia'
|
import { mapActions, mapState } from 'pinia'
|
||||||
import AppNavigation from '../Atoms/AppNavigation.vue'
|
import AppNavigation from '../Atoms/AppNavigation.vue'
|
||||||
import Item from './Item.vue'
|
|
||||||
import Loading from '../Atoms/Loading.vue'
|
import Loading from '../Atoms/Loading.vue'
|
||||||
import PlusIcon from 'vue-material-design-icons/Plus.vue'
|
import PlusIcon from 'vue-material-design-icons/Plus.vue'
|
||||||
import Settings from '../Settings/Settings.vue'
|
import Settings from '../Settings/Settings.vue'
|
||||||
|
import Subscription from './Subscription.vue'
|
||||||
import { showError } from '../../utils/toast.js'
|
import { showError } from '../../utils/toast.js'
|
||||||
import { useSubscriptions } from '../../store/subscriptions.js'
|
import { useSubscriptions } from '../../store/subscriptions.js'
|
||||||
|
|
||||||
@ -51,13 +52,13 @@ export default {
|
|||||||
name: 'Subscriptions',
|
name: 'Subscriptions',
|
||||||
components: {
|
components: {
|
||||||
AppNavigation,
|
AppNavigation,
|
||||||
Item,
|
|
||||||
Loading,
|
Loading,
|
||||||
NcAppContentList,
|
NcAppContentList,
|
||||||
NcAppNavigationList,
|
NcAppNavigationList,
|
||||||
NcAppNavigationNew,
|
NcAppNavigationNew,
|
||||||
PlusIcon,
|
PlusIcon,
|
||||||
Settings,
|
Settings,
|
||||||
|
Subscription,
|
||||||
},
|
},
|
||||||
data: () => ({
|
data: () => ({
|
||||||
loading: true,
|
loading: true,
|
||||||
|
@ -48,8 +48,7 @@ export const usePlayer = defineStore('player', {
|
|||||||
} catch {}
|
} catch {}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this.episode.action &&
|
this.episode.action?.position &&
|
||||||
this.episode.action.position &&
|
|
||||||
this.episode.action.position < this.episode.action.total
|
this.episode.action.position < this.episode.action.total
|
||||||
) {
|
) {
|
||||||
audio.currentTime = this.episode.action.position
|
audio.currentTime = this.episode.action.position
|
||||||
|
14
src/utils/status.js
Normal file
14
src/utils/status.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
export const hasEnded = (episode) =>
|
||||||
|
episode.action &&
|
||||||
|
episode.action.action &&
|
||||||
|
(episode.action.action.toLowerCase() === 'delete' ||
|
||||||
|
(episode.action.position > 0 &&
|
||||||
|
episode.action.total > 0 &&
|
||||||
|
episode.action.position >= episode.action.total))
|
||||||
|
|
||||||
|
export const isListening = (episode) =>
|
||||||
|
episode.action &&
|
||||||
|
episode.action.action &&
|
||||||
|
episode.action.action.toLowerCase() === 'play' &&
|
||||||
|
episode.action.position > 0 &&
|
||||||
|
!hasEnded(episode)
|
@ -24,7 +24,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import AppContent from '../components/Atoms/AppContent.vue'
|
import AppContent from '../components/Atoms/AppContent.vue'
|
||||||
import EmptyContent from '../components/Atoms/EmptyContent.vue'
|
import EmptyContent from '../components/Atoms/EmptyContent.vue'
|
||||||
import Favorites from '../components/Home/Favorites.vue'
|
import Favorites from '../components/Feed/Favorites.vue'
|
||||||
import StarOffIcon from 'vue-material-design-icons/StarOff.vue'
|
import StarOffIcon from 'vue-material-design-icons/StarOff.vue'
|
||||||
import { mapState } from 'pinia'
|
import { mapState } from 'pinia'
|
||||||
import { useSubscriptions } from '../store/subscriptions.js'
|
import { useSubscriptions } from '../store/subscriptions.js'
|
||||||
|
Loading…
Reference in New Issue
Block a user