refactor: ♻️ typescript ok, need tests
Some checks failed
repod / xml (push) Successful in 26s
repod / php (push) Failing after 40s
repod / nodejs (push) Successful in 1m18s
repod / release (push) Has been skipped

This commit is contained in:
Michel Roux 2024-09-13 16:33:48 +02:00
parent 0466d49007
commit 943e2cf407
26 changed files with 157 additions and 160 deletions

View File

@ -26,7 +26,7 @@ class PageController extends Controller
* @NoCSRFRequired * @NoCSRFRequired
*/ */
public function index(): TemplateResponse { public function index(): TemplateResponse {
Util::addScript(Application::APP_ID, 'main'); Util::addScript(Application::APP_ID, Application::APP_ID . '-main');
$csp = new ContentSecurityPolicy(); $csp = new ContentSecurityPolicy();
$csp->addAllowedImageDomain('*'); $csp->addAllowedImageDomain('*');

View File

@ -1,6 +1,7 @@
{ {
"name": "repod", "name": "repod",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"type": "module",
"scripts": { "scripts": {
"build": "vue-tsc && vite build --mode production", "build": "vue-tsc && vite build --mode production",
"dev": "vite build --mode development", "dev": "vite build --mode development",
@ -11,9 +12,6 @@
"stylelint": "stylelint src/**/*.vue src/**/*.scss src/**/*.css", "stylelint": "stylelint src/**/*.vue src/**/*.scss src/**/*.css",
"stylelint:fix": "stylelint src/**/*.vue src/**/*.scss src/**/*.css --fix" "stylelint:fix": "stylelint src/**/*.vue src/**/*.scss src/**/*.css --fix"
}, },
"browserslist": [
"extends @nextcloud/browserslist-config"
],
"prettier": "@nextcloud/prettier-config", "prettier": "@nextcloud/prettier-config",
"dependencies": { "dependencies": {
"@nextcloud/axios": "^2.5.0", "@nextcloud/axios": "^2.5.0",
@ -48,5 +46,8 @@
"typescript": "5.5.4", "typescript": "5.5.4",
"vue-eslint-parser": "^9.4.3", "vue-eslint-parser": "^9.4.3",
"vue-tsc": "^2.1.6" "vue-tsc": "^2.1.6"
} },
"browserslist": [
"extends @nextcloud/browserslist-config"
]
} }

View File

@ -7,7 +7,7 @@
</NcContent> </NcContent>
</template> </template>
<script> <script lang="ts">
import 'toastify-js/src/toastify.css' import 'toastify-js/src/toastify.css'
import { mapActions, mapState } from 'pinia' import { mapActions, mapState } from 'pinia'
import Bar from './components/Player/Bar.vue' import Bar from './components/Player/Bar.vue'

View File

@ -51,8 +51,8 @@ export default {
async mounted() { async mounted() {
try { try {
this.loading = true this.loading = true
const tops = await axios.get( const tops = await axios.get<PodcastDataInterface[]>(
generateUrl(`/apps/repod/toplist/${this.type}`), generateUrl('/apps/repod/toplist/{type}', { type: this.type }),
) )
this.tops = tops.data this.tops = tops.data
} catch (e) { } catch (e) {

View File

@ -1,13 +1,13 @@
<template> <template>
<div class="header"> <div class="header">
<img class="background" :src="imageUrl" /> <img class="background" :src="feed.imageUrl" />
<div class="content"> <div class="content">
<div> <div>
<NcAvatar <NcAvatar
:display-name="author || title" :display-name="feed.author || feed.title"
:is-no-user="true" :is-no-user="true"
:size="128" :size="128"
:url="imageUrl" /> :url="feed.imageUrl" />
<a class="feed" :href="url" @click.prevent="copyFeed"> <a class="feed" :href="url" @click.prevent="copyFeed">
<RssIcon :size="20" /> <RssIcon :size="20" />
<i>{{ t('repod', 'Copy feed') }}</i> <i>{{ t('repod', 'Copy feed') }}</i>
@ -15,12 +15,12 @@
</div> </div>
<div class="inner"> <div class="inner">
<div class="infos"> <div class="infos">
<h2>{{ title }}</h2> <h2>{{ feed.title }}</h2>
<a :href="link" target="_blank"> <a :href="feed.link" target="_blank">
<i>{{ author }}</i> <i>{{ feed.author }}</i>
</a> </a>
<br /><br /> <br /><br />
<SafeHtml :source="description" /> <SafeHtml :source="feed.description || ''" />
</div> </div>
<NcAppNavigationNew <NcAppNavigationNew
v-if="!getSubByUrl(url)" v-if="!getSubByUrl(url)"
@ -40,6 +40,7 @@ import { NcAppNavigationNew, NcAvatar } from '@nextcloud/vue'
import { mapActions, mapState } from 'pinia' import { mapActions, mapState } from 'pinia'
import { showError, showSuccess } from '../../utils/toast.ts' import { showError, showSuccess } from '../../utils/toast.ts'
import PlusIcon from 'vue-material-design-icons/Plus.vue' import PlusIcon from 'vue-material-design-icons/Plus.vue'
import type { PodcastDataInterface } from '../../utils/types.ts'
import RssIcon from 'vue-material-design-icons/Rss.vue' import RssIcon from 'vue-material-design-icons/Rss.vue'
import SafeHtml from '../Atoms/SafeHtml.vue' import SafeHtml from '../Atoms/SafeHtml.vue'
import axios from '@nextcloud/axios' import axios from '@nextcloud/axios'
@ -58,24 +59,8 @@ export default {
SafeHtml, SafeHtml,
}, },
props: { props: {
author: { feed: {
type: String, type: Object as () => PodcastDataInterface,
required: true,
},
description: {
type: String,
required: true,
},
imageUrl: {
type: String,
required: true,
},
link: {
type: String,
required: true,
},
title: {
type: String,
required: true, required: true,
}, },
}, },

View File

@ -71,15 +71,15 @@ export default {
async mounted() { async mounted() {
try { try {
this.loading = true this.loading = true
const episodes = await axios.get( const episodes = await axios.get<EpisodeInterface[]>(
generateUrl('/apps/repod/episodes/list?url={url}', { generateUrl('/apps/repod/episodes/list?url={url}', {
url: this.url, url: this.url,
}), }),
) )
this.episodes = [...episodes.data].sort( this.episodes = [...episodes.data].sort(
(a, b) => (a, b) =>
new Date(b.pubDate?.date).getTime() - new Date(b.pubDate?.date || '').getTime() -
new Date(a.pubDate?.date).getTime(), new Date(a.pubDate?.date || '').getTime(),
) )
} catch (e) { } catch (e) {
console.error(e) console.error(e)

View File

@ -1,15 +1,15 @@
<template> <template>
<NcGuestContent class="guest"> <NcGuestContent class="guest">
<Loading v-if="!currentFavoriteData" /> <Loading v-if="!feed.data" />
<NcAvatar <NcAvatar
v-if="currentFavoriteData" v-if="feed.data"
class="avatar" class="avatar"
:display-name="currentFavoriteData.author || currentFavoriteData.title" :display-name="feed.data.author || feed.data.title"
:is-no-user="true" :is-no-user="true"
:size="222" :size="222"
:url="currentFavoriteData.imageUrl" /> :url="feed.data.imageUrl" />
<div v-if="currentFavoriteData" class="list"> <div v-if="feed.data" class="list">
<h2 class="title">{{ currentFavoriteData.title }}</h2> <h2 class="title">{{ feed.data.title }}</h2>
<Loading v-if="loading" /> <Loading v-if="loading" />
<ul v-if="!loading"> <ul v-if="!loading">
<Episode <Episode
@ -17,24 +17,22 @@
:key="episode.guid" :key="episode.guid"
:episode="episode" :episode="episode"
:one-line="true" :one-line="true"
:url="url" /> :url="feed.metrics.url" />
</ul> </ul>
</div> </div>
</NcGuestContent> </NcGuestContent>
</template> </template>
<script lang="ts"> <script lang="ts">
import type { EpisodeInterface, SubscriptionInterface } from '../../utils/types.ts'
import { NcAvatar, NcGuestContent } from '@nextcloud/vue' import { NcAvatar, NcGuestContent } from '@nextcloud/vue'
import Episode from './Episode.vue' import Episode from './Episode.vue'
import type { EpisodeInterface } from '../../utils/types.ts'
import Loading from '../Atoms/Loading.vue' import Loading from '../Atoms/Loading.vue'
import axios from '@nextcloud/axios' import axios from '@nextcloud/axios'
import { generateUrl } from '@nextcloud/router' import { generateUrl } from '@nextcloud/router'
import { hasEnded } from '../../utils/status.ts' import { hasEnded } from '../../utils/status.ts'
import { mapState } from 'pinia'
import { showError } from '../../utils/toast.ts' import { showError } from '../../utils/toast.ts'
import { t } from '@nextcloud/l10n' import { t } from '@nextcloud/l10n'
import { useSubscriptions } from '../../store/subscriptions.ts'
export default { export default {
name: 'Favorite', name: 'Favorite',
@ -45,8 +43,8 @@ export default {
NcGuestContent, NcGuestContent,
}, },
props: { props: {
url: { feed: {
type: String, type: Object as () => SubscriptionInterface,
required: true, required: true,
}, },
}, },
@ -54,25 +52,19 @@ export default {
episodes: [] as EpisodeInterface[], episodes: [] as EpisodeInterface[],
loading: true, loading: true,
}), }),
computed: {
...mapState(useSubscriptions, ['getSubByUrl']),
currentFavoriteData() {
return this.getSubByUrl(this.url)?.data
},
},
async mounted() { async mounted() {
try { try {
this.loading = true this.loading = true
const episodes = await axios.get( const episodes = await axios.get<EpisodeInterface[]>(
generateUrl('/apps/repod/episodes/list?url={url}', { generateUrl('/apps/repod/episodes/list?url={url}', {
url: this.url, url: this.feed.metrics.url,
}), }),
) )
this.episodes = [...episodes.data] this.episodes = [...episodes.data]
.sort( .sort(
(a, b) => (a, b) =>
new Date(b.pubDate?.date).getTime() - new Date(b.pubDate?.date || '').getTime() -
new Date(a.pubDate?.date).getTime(), new Date(a.pubDate?.date || '').getTime(),
) )
.filter((episode) => !this.hasEnded(episode)) .filter((episode) => !this.hasEnded(episode))
.slice(0, 4) .slice(0, 4)

View File

@ -8,10 +8,11 @@
</NcAppNavigationItem> </NcAppNavigationItem>
</template> </template>
<script> <script lang="ts">
import ExportIcon from 'vue-material-design-icons/Export.vue' import ExportIcon from 'vue-material-design-icons/Export.vue'
import { NcAppNavigationItem } from '@nextcloud/vue' import { NcAppNavigationItem } from '@nextcloud/vue'
import { generateUrl } from '@nextcloud/router' import { generateUrl } from '@nextcloud/router'
import { t } from '@nextcloud/l10n'
export default { export default {
name: 'Export', name: 'Export',
@ -21,6 +22,7 @@ export default {
}, },
methods: { methods: {
generateUrl, generateUrl,
t,
}, },
} }
</script> </script>

View File

@ -39,11 +39,12 @@
</NcAppNavigationItem> </NcAppNavigationItem>
</template> </template>
<script> <script lang="ts">
import { NcActionCheckbox, NcAppNavigationItem } from '@nextcloud/vue' import { NcActionCheckbox, NcAppNavigationItem } from '@nextcloud/vue'
import { mapActions, mapState } from 'pinia' import { mapActions, mapState } from 'pinia'
import FilterIcon from 'vue-material-design-icons/Filter.vue' import FilterIcon from 'vue-material-design-icons/Filter.vue'
import FilterSettingsIcon from 'vue-material-design-icons/FilterSettings.vue' import FilterSettingsIcon from 'vue-material-design-icons/FilterSettings.vue'
import { t } from '@nextcloud/l10n'
import { useSettings } from '../../store/settings.ts' import { useSettings } from '../../store/settings.ts'
export default { export default {
@ -66,6 +67,7 @@ export default {
}, },
methods: { methods: {
...mapActions(useSettings, ['setFilters']), ...mapActions(useSettings, ['setFilters']),
t,
}, },
} }
</script> </script>

View File

@ -29,12 +29,13 @@
</NcAppNavigationItem> </NcAppNavigationItem>
</template> </template>
<script> <script lang="ts">
import { NcAppNavigationItem, NcModal } from '@nextcloud/vue' import { NcAppNavigationItem, NcModal } from '@nextcloud/vue'
import ImportIcon from 'vue-material-design-icons/Import.vue' import ImportIcon from 'vue-material-design-icons/Import.vue'
import Loading from '../Atoms/Loading.vue' import Loading from '../Atoms/Loading.vue'
import axios from '@nextcloud/axios' import axios from '@nextcloud/axios'
import { generateUrl } from '@nextcloud/router' import { generateUrl } from '@nextcloud/router'
import { t } from '@nextcloud/l10n'
export default { export default {
name: 'Import', name: 'Import',
@ -50,11 +51,13 @@ export default {
}), }),
methods: { methods: {
generateUrl, generateUrl,
async importOpml(event) { t,
async importOpml(event: Event) {
try { try {
const formData = new FormData(event.target) const target = event.target as HTMLFormElement
this.importLoading = true const formData = new FormData(target)
await axios.post(event.target.action, formData) this.loading = true
await axios.post(target.action, formData)
} catch (e) { } catch (e) {
console.error(e) console.error(e)
} finally { } finally {

View File

@ -8,9 +8,10 @@
</NcAppNavigationItem> </NcAppNavigationItem>
</template> </template>
<script> <script lang="ts">
import { NcAppNavigationItem } from '@nextcloud/vue' import { NcAppNavigationItem } from '@nextcloud/vue'
import StarIcon from 'vue-material-design-icons/Star.vue' import StarIcon from 'vue-material-design-icons/Star.vue'
import { t } from '@nextcloud/l10n'
export default { export default {
name: 'Rate', name: 'Rate',
@ -18,5 +19,8 @@ export default {
NcAppNavigationItem, NcAppNavigationItem,
StarIcon, StarIcon,
}, },
methods: {
t,
},
} }
</script> </script>

View File

@ -8,7 +8,7 @@
</NcAppNavigationSettings> </NcAppNavigationSettings>
</template> </template>
<script> <script lang="ts">
import Export from './Export.vue' import Export from './Export.vue'
import Filters from './Filters.vue' import Filters from './Filters.vue'
import Import from './Import.vue' import Import from './Import.vue'

View File

@ -15,7 +15,7 @@
</NcAppNavigationItem> </NcAppNavigationItem>
</template> </template>
<script> <script lang="ts">
import { NcAppNavigationItem, NcCounterBubble } from '@nextcloud/vue' import { NcAppNavigationItem, NcCounterBubble } from '@nextcloud/vue'
import { mapActions, mapState } from 'pinia' import { mapActions, mapState } from 'pinia'
import MinusIcon from 'vue-material-design-icons/Minus.vue' import MinusIcon from 'vue-material-design-icons/Minus.vue'
@ -23,6 +23,7 @@ import PlusIcon from 'vue-material-design-icons/Plus.vue'
import SpeedometerIcon from 'vue-material-design-icons/Speedometer.vue' import SpeedometerIcon from 'vue-material-design-icons/Speedometer.vue'
import SpeedometerMediumIcon from 'vue-material-design-icons/SpeedometerMedium.vue' import SpeedometerMediumIcon from 'vue-material-design-icons/SpeedometerMedium.vue'
import SpeedometerSlowIcon from 'vue-material-design-icons/SpeedometerSlow.vue' import SpeedometerSlowIcon from 'vue-material-design-icons/SpeedometerSlow.vue'
import { t } from '@nextcloud/l10n'
import { usePlayer } from '../../store/player.ts' import { usePlayer } from '../../store/player.ts'
export default { export default {
@ -41,8 +42,9 @@ export default {
}, },
methods: { methods: {
...mapActions(usePlayer, ['setRate']), ...mapActions(usePlayer, ['setRate']),
changeRate(diff) { t,
const newRate = (this.rate + diff).toPrecision(2) changeRate(diff: number) {
const newRate = parseFloat((this.rate + diff).toPrecision(2))
this.setRate(newRate > 0 ? newRate : this.rate) this.setRate(newRate > 0 ? newRate : this.rate)
}, },
}, },

View File

@ -1,18 +1,18 @@
<template> <template>
<NcAppNavigationItem <NcAppNavigationItem
:loading="loading" :loading="loading"
:name="feed ? feed.title : url" :name="feed?.data?.title || url"
:to="toFeedUrl(url)"> :to="toFeedUrl(url)">
<template #actions> <template #actions>
<NcActionButton <NcActionButton
:aria-label="t('repod', 'Favorite')" :aria-label="t('repod', 'Favorite')"
:model-value="isFavorite" :model-value="feed?.isFavorite"
:name="t('repod', 'Favorite')" :name="t('repod', 'Favorite')"
:title="t('repod', 'Favorite')" :title="t('repod', 'Favorite')"
@update:modelValue="switchFavorite($event)"> @update:modelValue="switchFavorite($event)">
<template #icon> <template #icon>
<StarPlusIcon v-if="!isFavorite" :size="20" /> <StarPlusIcon v-if="!feed?.isFavorite" :size="20" />
<StarRemoveIcon v-if="isFavorite" :size="20" /> <StarRemoveIcon v-if="feed?.isFavorite" :size="20" />
</template> </template>
</NcActionButton> </NcActionButton>
<NcActionButton <NcActionButton
@ -27,27 +27,28 @@
</template> </template>
<template #icon> <template #icon>
<NcAvatar <NcAvatar
v-if="feed" :display-name="feed?.data?.author || feed?.data?.title"
:display-name="feed.author || feed.title"
:is-no-user="true" :is-no-user="true"
:url="feed.imageUrl" /> :url="feed?.data?.imageUrl" />
<StarIcon v-if="feed && isFavorite" class="star" :size="20" /> <StarIcon v-if="feed?.isFavorite" class="star" :size="20" />
<AlertIcon v-if="failed" /> <AlertIcon v-if="failed" />
</template> </template>
</NcAppNavigationItem> </NcAppNavigationItem>
</template> </template>
<script> <script lang="ts">
import { NcActionButton, NcAppNavigationItem, NcAvatar } from '@nextcloud/vue' import { NcActionButton, NcAppNavigationItem, NcAvatar } from '@nextcloud/vue'
import { mapActions, mapState } from 'pinia' import { mapActions, mapState } from 'pinia'
import AlertIcon from 'vue-material-design-icons/Alert.vue' import AlertIcon from 'vue-material-design-icons/Alert.vue'
import DeleteIcon from 'vue-material-design-icons/Delete.vue' import DeleteIcon from 'vue-material-design-icons/Delete.vue'
import type { PersonalSettingsPodcastDataInterface } from '../../utils/types.ts'
import StarIcon from 'vue-material-design-icons/Star.vue' import StarIcon from 'vue-material-design-icons/Star.vue'
import StarPlusIcon from 'vue-material-design-icons/StarPlus.vue' import StarPlusIcon from 'vue-material-design-icons/StarPlus.vue'
import StarRemoveIcon from 'vue-material-design-icons/StarRemove.vue' import StarRemoveIcon from 'vue-material-design-icons/StarRemove.vue'
import axios from '@nextcloud/axios' import axios from '@nextcloud/axios'
import { generateUrl } from '@nextcloud/router' import { generateUrl } from '@nextcloud/router'
import { showError } from '../../utils/toast.ts' import { showError } from '../../utils/toast.ts'
import { t } from '@nextcloud/l10n'
import { toFeedUrl } from '../../utils/url.ts' import { toFeedUrl } from '../../utils/url.ts'
import { useSubscriptions } from '../../store/subscriptions.ts' import { useSubscriptions } from '../../store/subscriptions.ts'
@ -72,26 +73,25 @@ export default {
data: () => ({ data: () => ({
failed: false, failed: false,
loading: true, loading: true,
feed: null,
}), }),
computed: { computed: {
...mapState(useSubscriptions, ['getFavorites']), ...mapState(useSubscriptions, ['subs']),
isFavorite() { feed() {
return this.getFavorites.map((fav) => fav.url).includes(this.url) return this.subs.find((sub) => sub.metrics.url === this.url)
}, },
}, },
async mounted() { async mounted() {
try { try {
const podcastData = await axios.get( const podcastData =
generateUrl( await axios.get<PersonalSettingsPodcastDataInterface>(
'/apps/gpoddersync/personal_settings/podcast_data?url={url}', generateUrl(
{ '/apps/gpoddersync/personal_settings/podcast_data?url={url}',
url: this.url, {
}, url: this.url,
), },
) ),
this.feed = podcastData.data.data )
this.editFavoriteData(this.url, podcastData.data.data) this.addFavoriteData(podcastData.data.data)
} catch (e) { } catch (e) {
this.failed = true this.failed = true
console.error(e) console.error(e)
@ -100,12 +100,8 @@ export default {
} }
}, },
methods: { methods: {
...mapActions(useSubscriptions, [ ...mapActions(useSubscriptions, ['fetch', 'addFavoriteData', 'setFavorite']),
'fetch', t,
'addFavorite',
'editFavoriteData',
'removeFavorite',
]),
toFeedUrl, toFeedUrl,
async deleteSubscription() { async deleteSubscription() {
if ( if (
@ -123,23 +119,20 @@ export default {
console.error(e) console.error(e)
showError(t('repod', 'Error while removing the feed')) showError(t('repod', 'Error while removing the feed'))
} finally { } finally {
this.removeFavorite(this.url) this.setFavorite(this.url, false)
this.loading = false this.loading = false
this.fetch() this.fetch()
} }
} }
}, },
switchFavorite(value) { switchFavorite(value: boolean) {
if (value) { if (value) {
if (this.getFavorites.length >= 10) { if (this.subs.filter((sub) => sub.isFavorite).length >= 10) {
showError(t('repod', 'You can only have 10 favorites')) showError(t('repod', 'You can only have 10 favorites'))
return return
} }
this.addFavorite(this.url)
} else {
this.removeFavorite(this.url)
} }
this.setFavorite(this.url, value)
}, },
}, },
} }

View File

@ -12,16 +12,13 @@
<Loading v-if="loading" /> <Loading v-if="loading" />
<NcAppNavigationList v-if="!loading"> <NcAppNavigationList v-if="!loading">
<Subscription <Subscription
v-for="url of getFavorites.map((fav) => fav.url)" v-for="sub of subs.filter((sub) => sub.isFavorite)"
:key="url" :key="sub.metrics.url"
:url="url" /> :url="sub.metrics.url" />
<Subscription <Subscription
v-for="url of getSubscriptions.filter( v-for="sub of subs.filter((sub) => !sub.isFavorite)"
(sub) => :key="sub.metrics.url"
!getFavorites.map((fav) => fav.url).includes(sub), :url="sub.metrics.url" />
)"
:key="url"
:url="url" />
</NcAppNavigationList> </NcAppNavigationList>
</NcAppContentList> </NcAppContentList>
</template> </template>
@ -31,7 +28,7 @@
</AppNavigation> </AppNavigation>
</template> </template>
<script> <script lang="ts">
import { import {
NcAppContentList, NcAppContentList,
NcAppNavigationList, NcAppNavigationList,
@ -44,6 +41,7 @@ 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 Subscription from './Subscription.vue'
import { showError } from '../../utils/toast.ts' import { showError } from '../../utils/toast.ts'
import { t } from '@nextcloud/l10n'
import { useSubscriptions } from '../../store/subscriptions.ts' import { useSubscriptions } from '../../store/subscriptions.ts'
export default { export default {
@ -62,7 +60,7 @@ export default {
loading: true, loading: true,
}), }),
computed: { computed: {
...mapState(useSubscriptions, ['getSubscriptions', 'getFavorites']), ...mapState(useSubscriptions, ['subs']),
}, },
async mounted() { async mounted() {
try { try {
@ -76,6 +74,7 @@ export default {
}, },
methods: { methods: {
...mapActions(useSubscriptions, ['fetch']), ...mapActions(useSubscriptions, ['fetch']),
t,
}, },
} }
</script> </script>

View File

@ -1,4 +1,4 @@
import type { EpisodeInterface } from '../utils/types' import type { EpisodeActionInterface, EpisodeInterface } from '../utils/types'
import axios from '@nextcloud/axios' import axios from '@nextcloud/axios'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { formatEpisodeTimestamp } from '../utils/time' import { formatEpisodeTimestamp } from '../utils/time'
@ -39,7 +39,7 @@ export const usePlayer = defineStore('player', {
audio.load() audio.load()
try { try {
const action = await axios.get( const action = await axios.get<EpisodeActionInterface>(
generateUrl('/apps/repod/episodes/action?url={url}', { generateUrl('/apps/repod/episodes/action?url={url}', {
url: this.episode.url, url: this.episode.url,
}), }),

View File

@ -24,7 +24,7 @@ export const useSettings = defineStore('settings', {
} }
}, },
actions: { actions: {
setFilters(filters: FiltersInterface) { setFilters(filters: Partial<FiltersInterface>) {
this.filters = { ...this.filters, ...filters } this.filters = { ...this.filters, ...filters }
setCookie('repod.filters', JSON.stringify(this.filters), 365) setCookie('repod.filters', JSON.stringify(this.filters), 365)
}, },

View File

@ -1,6 +1,6 @@
import type { import type {
PersonalSettingsMetricsInterface,
PodcastDataInterface, PodcastDataInterface,
PodcastMetricsInterface,
SubscriptionInterface, SubscriptionInterface,
} from '../utils/types' } from '../utils/types'
import { getCookie, setCookie } from '../utils/cookies' import { getCookie, setCookie } from '../utils/cookies'
@ -23,7 +23,7 @@ export const useSubscriptions = defineStore('subscriptions', {
favorites = JSON.parse(getCookie('repod.favorites') || '[]') || [] favorites = JSON.parse(getCookie('repod.favorites') || '[]') || []
} catch {} } catch {}
const metrics = await axios.get<PodcastMetricsInterface>( const metrics = await axios.get<PersonalSettingsMetricsInterface>(
generateUrl('/apps/gpoddersync/personal_settings/metrics'), generateUrl('/apps/gpoddersync/personal_settings/metrics'),
) )
this.subs = [...metrics.data.subscriptions] this.subs = [...metrics.data.subscriptions]

View File

@ -47,23 +47,27 @@ export interface PodcastDataInterface {
} }
export interface PodcastMetricsInterface { export interface PodcastMetricsInterface {
subscriptions: [ url: string
{ listenedSeconds: number
url: string actionCounts: {
listenedSeconds: number delete: number
actionCounts: { download: number
delete: number flattr: number
download: number new: number
flattr: number play: number
new: number }
play: number
}
},
]
} }
export interface SubscriptionInterface { export interface SubscriptionInterface {
data?: PodcastDataInterface data?: PodcastDataInterface
isFavorite: boolean isFavorite: boolean
metrics: PodcastMetricsInterface['subscriptions'][0] metrics: PodcastMetricsInterface
}
export interface PersonalSettingsMetricsInterface {
subscriptions: PodcastMetricsInterface[]
}
export interface PersonalSettingsPodcastDataInterface {
data: PodcastDataInterface
} }

View File

@ -12,13 +12,14 @@
</AppContent> </AppContent>
</template> </template>
<script> <script lang="ts">
import AddRss from '../components/Discover/AddRss.vue' import AddRss from '../components/Discover/AddRss.vue'
import AppContent from '../components/Atoms/AppContent.vue' import AppContent from '../components/Atoms/AppContent.vue'
import Magnify from 'vue-material-design-icons/Magnify.vue' import Magnify from 'vue-material-design-icons/Magnify.vue'
import { NcTextField } from '@nextcloud/vue' import { NcTextField } from '@nextcloud/vue'
import Search from '../components/Discover/Search.vue' import Search from '../components/Discover/Search.vue'
import Toplist from '../components/Discover/Toplist.vue' import Toplist from '../components/Discover/Toplist.vue'
import { t } from '@nextcloud/l10n'
export default { export default {
name: 'Discover', name: 'Discover',
@ -33,6 +34,9 @@ export default {
data: () => ({ data: () => ({
search: '', search: '',
}), }),
methods: {
t,
},
} }
</script> </script>

View File

@ -6,27 +6,23 @@
<Alert /> <Alert />
</template> </template>
</EmptyContent> </EmptyContent>
<Banner <Banner v-if="feed" :feed="feed" />
v-if="feed"
:author="feed.author"
:description="feed.description"
:image-url="feed.imageUrl"
:link="feed.link"
:title="feed.title" />
<Episodes v-if="feed" /> <Episodes v-if="feed" />
</AppContent> </AppContent>
</template> </template>
<script> <script lang="ts">
import Alert from 'vue-material-design-icons/Alert.vue' import Alert from 'vue-material-design-icons/Alert.vue'
import AppContent from '../components/Atoms/AppContent.vue' import AppContent from '../components/Atoms/AppContent.vue'
import Banner from '../components/Feed/Banner.vue' import Banner from '../components/Feed/Banner.vue'
import EmptyContent from '../components/Atoms/EmptyContent.vue' import EmptyContent from '../components/Atoms/EmptyContent.vue'
import Episodes from '../components/Feed/Episodes.vue' import Episodes from '../components/Feed/Episodes.vue'
import Loading from '../components/Atoms/Loading.vue' import Loading from '../components/Atoms/Loading.vue'
import type { PodcastDataInterface } from '../utils/types.ts'
import axios from '@nextcloud/axios' import axios from '@nextcloud/axios'
import { decodeUrl } from '../utils/url.ts' import { decodeUrl } from '../utils/url.ts'
import { generateUrl } from '@nextcloud/router' import { generateUrl } from '@nextcloud/router'
import { t } from '@nextcloud/l10n'
export default { export default {
name: 'Feed', name: 'Feed',
@ -41,16 +37,17 @@ export default {
data: () => ({ data: () => ({
failed: false, failed: false,
loading: true, loading: true,
feed: null, feed: null as PodcastDataInterface | null,
}), }),
computed: { computed: {
url() { url() {
return decodeUrl(this.$route.params.url) return decodeUrl(this.$route.params.url as string)
}, },
}, },
async mounted() { async mounted() {
try { try {
const podcastData = await axios.get( this.loading = true
const podcastData = await axios.get<PodcastDataInterface>(
generateUrl('/apps/repod/podcast?url={url}', { url: this.url }), generateUrl('/apps/repod/podcast?url={url}', { url: this.url }),
) )
this.feed = podcastData.data this.feed = podcastData.data
@ -61,5 +58,8 @@ export default {
this.loading = false this.loading = false
} }
}, },
methods: {
t,
},
} }
</script> </script>

View File

@ -13,12 +13,13 @@
</AppContent> </AppContent>
</template> </template>
<script> <script lang="ts">
import Alert from 'vue-material-design-icons/Alert.vue' import Alert from 'vue-material-design-icons/Alert.vue'
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 { NcButton } from '@nextcloud/vue' import { NcButton } from '@nextcloud/vue'
import { generateUrl } from '@nextcloud/router' import { generateUrl } from '@nextcloud/router'
import { t } from '@nextcloud/l10n'
export default { export default {
name: 'GPodder', name: 'GPodder',
@ -33,5 +34,8 @@ export default {
return generateUrl('/settings/apps/installed/gpoddersync') return generateUrl('/settings/apps/installed/gpoddersync')
}, },
}, },
methods: {
t,
},
} }
</script> </script>

View File

@ -1,7 +1,7 @@
<template> <template>
<AppContent> <AppContent>
<EmptyContent <EmptyContent
v-if="!getFavorites.length" v-if="!favorites.length"
:description=" :description="
t('repod', 'Pin some subscriptions to see their latest updates') t('repod', 'Pin some subscriptions to see their latest updates')
" "
@ -10,20 +10,21 @@
<StarOffIcon /> <StarOffIcon />
</template> </template>
</EmptyContent> </EmptyContent>
<ul v-if="getFavorites.length"> <ul v-if="favorites.length">
<li v-for="url in getFavorites.map((fav) => fav.url)" :key="url"> <li v-for="favorite in favorites" :key="favorite.metrics.url">
<Favorite :url="url" /> <Favorite :feed="favorite" />
</li> </li>
</ul> </ul>
</AppContent> </AppContent>
</template> </template>
<script> <script lang="ts">
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 Favorite from '../components/Feed/Favorite.vue' import Favorite from '../components/Feed/Favorite.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 { t } from '@nextcloud/l10n'
import { useSubscriptions } from '../store/subscriptions.ts' import { useSubscriptions } from '../store/subscriptions.ts'
export default { export default {
@ -35,7 +36,13 @@ export default {
StarOffIcon, StarOffIcon,
}, },
computed: { computed: {
...mapState(useSubscriptions, ['getFavorites']), ...mapState(useSubscriptions, ['subs']),
favorites() {
return this.subs.filter((sub) => sub.isFavorite)
},
},
methods: {
t,
}, },
} }
</script> </script>

View File

@ -4,11 +4,6 @@ import vueDevTools from 'vite-plugin-vue-devtools'
const config = defineConfig(({ mode }) => ({ const config = defineConfig(({ mode }) => ({
build: { build: {
rollupOptions: {
output: {
entryFileNames: 'js/[name].js',
},
},
sourcemap: mode !== 'production', sourcemap: mode !== 'production',
}, },
define: { define: {