typescript #149 #152
@ -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('*');
|
||||||
|
@ -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"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
@ -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'
|
||||||
|
@ -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) {
|
||||||
|
@ -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,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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 {
|
||||||
|
@ -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>
|
||||||
|
@ -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'
|
||||||
|
@ -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)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -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,17 +73,17 @@ 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 =
|
||||||
|
await axios.get<PersonalSettingsPodcastDataInterface>(
|
||||||
generateUrl(
|
generateUrl(
|
||||||
'/apps/gpoddersync/personal_settings/podcast_data?url={url}',
|
'/apps/gpoddersync/personal_settings/podcast_data?url={url}',
|
||||||
{
|
{
|
||||||
@ -90,8 +91,7 @@ export default {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
this.feed = podcastData.data.data
|
this.addFavoriteData(podcastData.data.data)
|
||||||
this.editFavoriteData(this.url, 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)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
@ -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,
|
||||||
}),
|
}),
|
||||||
|
@ -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)
|
||||||
},
|
},
|
||||||
|
@ -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]
|
||||||
|
@ -47,8 +47,6 @@ export interface PodcastDataInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface PodcastMetricsInterface {
|
export interface PodcastMetricsInterface {
|
||||||
subscriptions: [
|
|
||||||
{
|
|
||||||
url: string
|
url: string
|
||||||
listenedSeconds: number
|
listenedSeconds: number
|
||||||
actionCounts: {
|
actionCounts: {
|
||||||
@ -58,12 +56,18 @@ export interface PodcastMetricsInterface {
|
|||||||
new: number
|
new: number
|
||||||
play: 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
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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: {
|
||||||
|
Loading…
Reference in New Issue
Block a user