refactor: 🎨 add prettier and update deps
All checks were successful
repod / xml (push) Successful in 1m25s
repod / php (push) Successful in 45s
repod / nodejs (push) Successful in 1m30s
repod / release (push) Has been skipped

This commit is contained in:
Michel Roux 2024-04-30 00:48:47 +02:00
parent cbc9654af8
commit b0a0414fd4
36 changed files with 2351 additions and 5720 deletions

View File

@ -1,9 +1,11 @@
module.exports = { module.exports = {
extends: [ extends: [
'@nextcloud', '@nextcloud',
'plugin:prettier/recommended',
], ],
rules: { rules: {
'sort-imports': 'error', 'sort-imports': 'error',
'vue/attributes-order': ['error', { alphabetical: true }], 'vue/attributes-order': ['error', { alphabetical: true }],
'vue/first-attribute-linebreak': 'off',
}, },
} }

12
composer.lock generated
View File

@ -94,16 +94,16 @@
}, },
{ {
"name": "php-cs-fixer/shim", "name": "php-cs-fixer/shim",
"version": "v3.51.0", "version": "v3.54.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/PHP-CS-Fixer/shim.git", "url": "https://github.com/PHP-CS-Fixer/shim.git",
"reference": "a792394f7f3934f75a4df9dca544796c6f503027" "reference": "887c350fccbadb2b84278fdb963c25a0c304ac9c"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/a792394f7f3934f75a4df9dca544796c6f503027", "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/887c350fccbadb2b84278fdb963c25a0c304ac9c",
"reference": "a792394f7f3934f75a4df9dca544796c6f503027", "reference": "887c350fccbadb2b84278fdb963c25a0c304ac9c",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -140,9 +140,9 @@
"description": "A tool to automatically fix PHP code style", "description": "A tool to automatically fix PHP code style",
"support": { "support": {
"issues": "https://github.com/PHP-CS-Fixer/shim/issues", "issues": "https://github.com/PHP-CS-Fixer/shim/issues",
"source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.51.0" "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.54.0"
}, },
"time": "2024-02-28T19:51:07+00:00" "time": "2024-04-17T08:23:10+00:00"
}, },
{ {
"name": "psalm/phar", "name": "psalm/phar",

7326
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -19,11 +19,11 @@
}, },
"dependencies": { "dependencies": {
"@nextcloud/axios": "^2.4.0", "@nextcloud/axios": "^2.4.0",
"@nextcloud/dialogs": "^5.2.0", "@nextcloud/dialogs": "^5.3.1",
"@nextcloud/initial-state": "^2.1.0", "@nextcloud/initial-state": "^2.2.0",
"@nextcloud/l10n": "^2.2.0", "@nextcloud/l10n": "^2.2.0",
"@nextcloud/router": "^3.0.0", "@nextcloud/router": "^3.0.1",
"@nextcloud/vue": "^8.11.0", "@nextcloud/vue": "^8.11.2",
"vue": "^2", "vue": "^2",
"vue-material-design-icons": "^5.3.0", "vue-material-design-icons": "^5.3.0",
"vue-router": "^3", "vue-router": "^3",
@ -36,11 +36,15 @@
"node": "^20.0.0", "node": "^20.0.0",
"npm": "^9.0.0" "npm": "^9.0.0"
}, },
"prettier": "@nextcloud/prettier-config",
"devDependencies": { "devDependencies": {
"@nextcloud/babel-config": "^1.0.0", "@nextcloud/babel-config": "^1.1.1",
"@nextcloud/browserslist-config": "^3.0.0", "@nextcloud/browserslist-config": "^3.0.1",
"@nextcloud/eslint-config": "^8.3.0", "@nextcloud/eslint-config": "^8.3.0",
"@nextcloud/stylelint-config": "^2.4.0", "@nextcloud/prettier-config": "^1.0.0",
"@nextcloud/webpack-vue-config": "^6.0.1" "@nextcloud/stylelint-config": "^3.0.0",
"@nextcloud/webpack-vue-config": "^6.0.1",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3"
} }
} }

View File

@ -21,7 +21,7 @@ export default {
</script> </script>
<style scoped> <style scoped>
.padding { .padding {
padding-bottom: 6rem; padding-bottom: 6rem;
} }
</style> </style>

View File

@ -27,7 +27,7 @@ export default {
</script> </script>
<style scoped> <style scoped>
.padding { .padding {
padding-bottom: 6rem; padding-bottom: 6rem;
} }
</style> </style>

View File

@ -14,7 +14,7 @@ export default {
</script> </script>
<style scoped> <style scoped>
.loading { .loading {
margin: 2rem 0; margin: 2rem 0;
} }
</style> </style>

View File

@ -1,24 +1,17 @@
<!-- eslint-disable vue/no-v-html --> <!-- eslint-disable vue/no-v-html -->
<template> <template>
<div> <div>
<NcAvatar :display-name="name" <NcAvatar :display-name="name" :is-no-user="true" :size="256" :url="image" />
:is-no-user="true"
:size="256"
:url="image" />
<h2>{{ name }}</h2> <h2>{{ name }}</h2>
<p v-html="strippedDescription" /> <p v-html="strippedDescription" />
<div> <div>
<NcButton v-if="link" <NcButton v-if="link" :href="link" target="_blank">
:href="link"
target="_blank">
<template #icon> <template #icon>
<OpenInNewIcon :size="20" /> <OpenInNewIcon :size="20" />
</template> </template>
{{ title }} {{ title }}
</NcButton> </NcButton>
<NcButton v-if="url" <NcButton v-if="url" :href="url" target="_blank">
:href="url"
target="_blank">
<template #icon> <template #icon>
<DownloadIcon :size="20" /> <DownloadIcon :size="20" />
</template> </template>
@ -85,11 +78,11 @@ export default {
</script> </script>
<style scoped> <style scoped>
div { div {
align-items: center; align-items: center;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 1rem; gap: 1rem;
margin: 2rem; margin: 2rem;
} }
</style> </style>

View File

@ -1,6 +1,8 @@
<template> <template>
<NcAppNavigationList> <NcAppNavigationList>
<NcAppNavigationNewItem :name="t('repod', 'Add a RSS link')" @new-item="addSubscription"> <NcAppNavigationNewItem
:name="t('repod', 'Add a RSS link')"
@new-item="addSubscription">
<template #icon> <template #icon>
<PlusIcon :size="20" /> <PlusIcon :size="20" />
</template> </template>
@ -29,7 +31,7 @@ export default {
</script> </script>
<style scoped> <style scoped>
ul { ul {
margin-top: 2rem; margin-top: 2rem;
} }
</style> </style>

View File

@ -2,13 +2,15 @@
<div> <div>
<Loading v-if="loading" /> <Loading v-if="loading" />
<ul v-if="!loading"> <ul v-if="!loading">
<NcListItem v-for="feed in feeds" <NcListItem
v-for="feed in feeds"
:key="feed.link" :key="feed.link"
:details="formatLocaleDate(new Date(feed.fetchedAtUnix*1000))" :details="formatLocaleDate(new Date(feed.fetchedAtUnix * 1000))"
:name="feed.title" :name="feed.title"
:to="toUrl(feed.link)"> :to="toUrl(feed.link)">
<template #icon> <template #icon>
<NcAvatar :display-name="feed.author" <NcAvatar
:display-name="feed.author"
:is-no-user="true" :is-no-user="true"
:url="feed.imageUrl" /> :url="feed.imageUrl" />
</template> </template>
@ -61,9 +63,13 @@ export default {
try { try {
this.loading = true this.loading = true
const currentSearch = this.value const currentSearch = this.value
const feeds = await axios.get(generateUrl('/apps/repod/search?q={value}', { value: currentSearch })) const feeds = await axios.get(
generateUrl('/apps/repod/search?q={value}', { value: currentSearch }),
)
if (currentSearch === this.value) { if (currentSearch === this.value) {
this.feeds = [...feeds.data].sort((a, b) => b.fetchedAtUnix - a.fetchedAtUnix) this.feeds = [...feeds.data].sort(
(a, b) => b.fetchedAtUnix - a.fetchedAtUnix,
)
} }
} catch (e) { } catch (e) {
console.error(e) console.error(e)

View File

@ -5,7 +5,7 @@
<ul v-if="!loading"> <ul v-if="!loading">
<li v-for="top in tops" :key="top.link"> <li v-for="top in tops" :key="top.link">
<router-link :to="toUrl(top.link)"> <router-link :to="toUrl(top.link)">
<img :src="top.imageUrl" :title="top.author"> <img :src="top.imageUrl" :title="top.author" />
</router-link> </router-link>
</li> </li>
</ul> </ul>
@ -67,24 +67,24 @@ export default {
</script> </script>
<style scoped> <style scoped>
h2 { h2 {
margin: 1rem 0; margin: 1rem 0;
} }
img { img {
height: 100%; height: 100%;
width: 100%; width: 100%;
} }
li { li {
flex-basis: 10rem; flex-basis: 10rem;
flex-shrink: 0; flex-shrink: 0;
} }
ul { ul {
display: flex; display: flex;
gap: 2rem; gap: 2rem;
overflow: scroll hidden; overflow: scroll hidden;
padding-bottom: .5rem; padding-bottom: 0.5rem;
} }
</style> </style>

View File

@ -1,10 +1,11 @@
<!-- eslint-disable vue/no-v-html --> <!-- eslint-disable vue/no-v-html -->
<template> <template>
<div class="header"> <div class="header">
<img class="background" :src="imageUrl"> <img class="background" :src="imageUrl" />
<div class="content"> <div class="content">
<div> <div>
<NcAvatar :display-name="author || title" <NcAvatar
:display-name="author || title"
:is-no-user="true" :is-no-user="true"
:size="128" :size="128"
:url="imageUrl" /> :url="imageUrl" />
@ -19,12 +20,13 @@
<a :href="link" target="_blank"> <a :href="link" target="_blank">
<i>{{ author }}</i> <i>{{ author }}</i>
</a> </a>
<br><br> <br /><br />
<p> <p>
<small v-html="strippedDescription" /> <small v-html="strippedDescription" />
</p> </p>
</div> </div>
<NcAppNavigationNew v-if="!isSubscribed" <NcAppNavigationNew
v-if="!isSubscribed"
:text="t('repod', 'Subscribe')" :text="t('repod', 'Subscribe')"
@click="addSubscription"> @click="addSubscription">
<template #icon> <template #icon>
@ -90,7 +92,10 @@ export default {
methods: { methods: {
async addSubscription() { async addSubscription() {
try { try {
await axios.post(generateUrl('/apps/gpoddersync/subscription_change/create'), { add: [this.url], remove: [] }) await axios.post(generateUrl('/apps/gpoddersync/subscription_change/create'), {
add: [this.url],
remove: [],
})
} catch (e) { } catch (e) {
console.error(e) console.error(e)
showError(t('repod', 'Error while adding the feed')) showError(t('repod', 'Error while adding the feed'))
@ -100,55 +105,55 @@ export default {
}, },
copyFeed() { copyFeed() {
window.navigator.clipboard.writeText(this.url) window.navigator.clipboard.writeText(this.url)
showSuccess(t('repod', 'Feed\'s link copied to the clipboard')) showSuccess(t('repod', "Feed's link copied to the clipboard"))
}, },
}, },
} }
</script> </script>
<style scoped> <style scoped>
.background { .background {
filter: blur(1rem) brightness(50%); filter: blur(1rem) brightness(50%);
height: auto; height: auto;
left: 0; left: 0;
opacity: .4; opacity: 0.4;
position: absolute; position: absolute;
top: 0; top: 0;
width: 100%; width: 100%;
z-index: -1; z-index: -1;
} }
.content { .content {
display: flex; display: flex;
gap: 1rem; gap: 1rem;
height: 10rem; height: 10rem;
position: relative; position: relative;
} }
.feed { .feed {
display: flex; display: flex;
gap: .2rem; gap: 0.2rem;
margin: .5rem; margin: 0.5rem;
} }
.header { .header {
height: 14rem; height: 14rem;
overflow: hidden; overflow: hidden;
padding: 2rem; padding: 2rem;
position: relative; position: relative;
} }
.infos { .infos {
overflow: auto; overflow: auto;
} }
.inner { .inner {
display: flex; display: flex;
} }
@media only screen and (max-width: 768px) { @media only screen and (max-width: 768px) {
.inner { .inner {
flex-direction: column; flex-direction: column;
} }
} }
</style> </style>

View File

@ -2,17 +2,19 @@
<div> <div>
<Loading v-if="loading" /> <Loading v-if="loading" />
<ul v-if="!loading"> <ul v-if="!loading">
<NcListItem v-for="episode in filteredEpisodes" <NcListItem
v-for="episode in filteredEpisodes"
:key="episode.guid" :key="episode.guid"
:active="isCurrentEpisode(episode)" :active="isCurrentEpisode(episode)"
:class="hasEnded(episode) ? 'ended': ''" :class="hasEnded(episode) ? 'ended' : ''"
:details="formatLocaleDate(new Date(episode.pubDate?.date))" :details="formatLocaleDate(new Date(episode.pubDate?.date))"
:force-display-actions="true" :force-display-actions="true"
:name="episode.name" :name="episode.name"
:title="episode.description" :title="episode.description"
@click="modalEpisode = episode"> @click="modalEpisode = episode">
<template #actions> <template #actions>
<NcActionButton v-if="!isCurrentEpisode(episode)" <NcActionButton
v-if="!isCurrentEpisode(episode)"
:aria-label="t('repod', 'Play')" :aria-label="t('repod', 'Play')"
:name="t('repod', 'Play')" :name="t('repod', 'Play')"
:title="t('repod', 'Play')" :title="t('repod', 'Play')"
@ -21,7 +23,8 @@
<PlayIcon :size="20" /> <PlayIcon :size="20" />
</template> </template>
</NcActionButton> </NcActionButton>
<NcActionButton v-if="isCurrentEpisode(episode)" <NcActionButton
v-if="isCurrentEpisode(episode)"
:aria-label="t('repod', 'Stop')" :aria-label="t('repod', 'Stop')"
:name="t('repod', 'Stop')" :name="t('repod', 'Stop')"
:title="t('repod', 'Stop')" :title="t('repod', 'Stop')"
@ -33,7 +36,8 @@
</template> </template>
<template #extra> <template #extra>
<NcActions> <NcActions>
<NcActionButton v-if="episode.duration && !hasEnded(episode)" <NcActionButton
v-if="episode.duration && !hasEnded(episode)"
:aria-label="t('repod', 'Mark as read')" :aria-label="t('repod', 'Mark as read')"
:disabled="loadingAction" :disabled="loadingAction"
:name="t('repod', 'Mark as read')" :name="t('repod', 'Mark as read')"
@ -43,7 +47,8 @@
<PlaylistPlayIcon :size="20" /> <PlaylistPlayIcon :size="20" />
</template> </template>
</NcActionButton> </NcActionButton>
<NcActionButton v-if="episode.duration && hasEnded(episode)" <NcActionButton
v-if="episode.duration && hasEnded(episode)"
:aria-label="t('repod', 'Mark as unread')" :aria-label="t('repod', 'Mark as unread')"
:disabled="loadingAction" :disabled="loadingAction"
:name="t('repod', 'Mark as unread')" :name="t('repod', 'Mark as unread')"
@ -53,7 +58,8 @@
<PlaylistRemoveIcon :size="20" /> <PlaylistRemoveIcon :size="20" />
</template> </template>
</NcActionButton> </NcActionButton>
<NcActionLink v-if="episode.link" <NcActionLink
v-if="episode.link"
:href="episode.link" :href="episode.link"
:name="t('repod', 'Open website')" :name="t('repod', 'Open website')"
target="_blank" target="_blank"
@ -62,7 +68,8 @@
<OpenInNewIcon :size="20" /> <OpenInNewIcon :size="20" />
</template> </template>
</NcActionLink> </NcActionLink>
<NcActionLink v-if="episode.url" <NcActionLink
v-if="episode.url"
:href="episode.url" :href="episode.url"
:name="t('repod', 'Download')" :name="t('repod', 'Download')"
target="_blank" target="_blank"
@ -74,14 +81,16 @@
</NcActions> </NcActions>
</template> </template>
<template #icon> <template #icon>
<NcAvatar :display-name="episode.name" <NcAvatar
:display-name="episode.name"
:is-no-user="true" :is-no-user="true"
:url="episode.image" /> :url="episode.image" />
</template> </template>
<template #indicator> <template #indicator>
<NcProgressBar v-if="isListening(episode)" <NcProgressBar
v-if="isListening(episode)"
class="progress" class="progress"
:value="episode.action.position * 100 / episode.action.total" /> :value="(episode.action.position * 100) / episode.action.total" />
</template> </template>
<template #subname> <template #subname>
{{ episode.duration }} {{ episode.duration }}
@ -89,7 +98,8 @@
</NcListItem> </NcListItem>
</ul> </ul>
<NcModal v-if="modalEpisode" @close="modalEpisode = null"> <NcModal v-if="modalEpisode" @close="modalEpisode = null">
<Modal :description="modalEpisode.description" <Modal
:description="modalEpisode.description"
:image="modalEpisode.image" :image="modalEpisode.image"
:link="modalEpisode.link" :link="modalEpisode.link"
:name="modalEpisode.name" :name="modalEpisode.name"
@ -101,8 +111,20 @@
</template> </template>
<script> <script>
import { NcActionButton, NcActionLink, NcActions, NcAvatar, NcListItem, NcModal, NcProgressBar } from '@nextcloud/vue' import {
import { durationToSeconds, formatEpisodeTimestamp, formatLocaleDate } from '../../utils/time.js' NcActionButton,
NcActionLink,
NcActions,
NcAvatar,
NcListItem,
NcModal,
NcProgressBar,
} from '@nextcloud/vue'
import {
durationToSeconds,
formatEpisodeTimestamp,
formatLocaleDate,
} from '../../utils/time.js'
import DownloadIcon from 'vue-material-design-icons/Download.vue' import DownloadIcon from 'vue-material-design-icons/Download.vue'
import { EventBus } from '../../store/bus.js' import { EventBus } from '../../store/bus.js'
import Loading from '../Atoms/Loading.vue' import Loading from '../Atoms/Loading.vue'
@ -175,8 +197,12 @@ export default {
async mounted() { async mounted() {
try { try {
this.loading = true this.loading = true
const episodes = await axios.get(generateUrl('/apps/repod/episodes/list?url={url}', { url: this.url })) const episodes = await axios.get(
this.episodes = [...episodes.data].sort((a, b) => new Date(b.pubDate?.date) - new Date(a.pubDate?.date)) 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),
)
EventBus.$on('updateEpisodesList', this.updateList) EventBus.$on('updateEpisodesList', this.updateList)
} catch (e) { } catch (e) {
console.error(e) console.error(e)
@ -191,18 +217,22 @@ export default {
methods: { methods: {
formatLocaleDate, formatLocaleDate,
hasEnded(episode) { hasEnded(episode) {
return episode.action return (
&& episode.action.position > 0 episode.action &&
&& episode.action.total > 0 episode.action.position > 0 &&
&& episode.action.position >= episode.action.total episode.action.total > 0 &&
episode.action.position >= episode.action.total
)
}, },
isCurrentEpisode(episode) { isCurrentEpisode(episode) {
return this.currentEpisode && this.currentEpisode.url === episode.url return this.currentEpisode && this.currentEpisode.url === episode.url
}, },
isListening(episode) { isListening(episode) {
return episode.action return (
&& episode.action.action.toLowerCase() === 'play' episode.action &&
&& !this.hasEnded(episode) episode.action.action.toLowerCase() === 'play' &&
!this.hasEnded(episode)
)
}, },
load(episode) { load(episode) {
this.$store.dispatch('player/load', episode) this.$store.dispatch('player/load', episode)
@ -220,7 +250,9 @@ export default {
position: read ? durationToSeconds(episode.duration) : 0, position: read ? durationToSeconds(episode.duration) : 0,
total: durationToSeconds(episode.duration), total: durationToSeconds(episode.duration),
} }
await axios.post(generateUrl('/apps/gpoddersync/episode_action/create'), [episode.action]) await axios.post(generateUrl('/apps/gpoddersync/episode_action/create'), [
episode.action,
])
this.updateList(episode) this.updateList(episode)
} catch (e) { } catch (e) {
console.error(e) console.error(e)
@ -230,18 +262,18 @@ export default {
} }
}, },
updateList(episode) { updateList(episode) {
this.episodes = this.episodes.map((e) => e.url === episode.url ? episode : e) this.episodes = this.episodes.map((e) => (e.url === episode.url ? episode : e))
}, },
}, },
} }
</script> </script>
<style scoped> <style scoped>
.ended { .ended {
opacity: .4; opacity: 0.4;
} }
.progress { .progress {
margin-top: .4rem; margin-top: 0.4rem;
} }
</style> </style>

View File

@ -1,10 +1,10 @@
<template> <template>
<div v-if="player.episode" class="footer"> <div v-if="player.episode" class="footer">
<img class="background" :src="player.episode.image"> <img class="background" :src="player.episode.image" />
<Loading v-if="!player.loaded" /> <Loading v-if="!player.loaded" />
<ProgressBar v-if="player.loaded" /> <ProgressBar v-if="player.loaded" />
<div v-if="player.loaded" class="player"> <div v-if="player.loaded" class="player">
<img :src="player.episode.image"> <img :src="player.episode.image" />
<Infos class="infos" /> <Infos class="infos" />
<Controls class="controls" /> <Controls class="controls" />
<Timer class="timer" /> <Timer class="timer" />
@ -40,18 +40,18 @@ export default {
</script> </script>
<style scoped> <style scoped>
.background { .background {
filter: blur(1rem) brightness(50%); filter: blur(1rem) brightness(50%);
height: auto; height: auto;
left: 0; left: 0;
opacity: .4; opacity: 0.4;
position: absolute; position: absolute;
top: 0; top: 0;
width: 100%; width: 100%;
z-index: -1; z-index: -1;
} }
.footer { .footer {
background-color: var(--color-main-background); background-color: var(--color-main-background);
bottom: 0; bottom: 0;
height: 6rem; height: 6rem;
@ -59,30 +59,31 @@ export default {
position: absolute; position: absolute;
width: 100%; width: 100%;
z-index: 2000; z-index: 2000;
} }
.player { .player {
display: flex; display: flex;
gap: 1rem; gap: 1rem;
height: 6rem; height: 6rem;
justify-content: space-between; justify-content: space-between;
} }
.timer { .timer {
width: 30%; width: 30%;
} }
.volume { .volume {
width: 10%; width: 10%;
} }
@media only screen and (max-width: 768px) { @media only screen and (max-width: 768px) {
.infos { .infos {
flex: 2; flex: 2;
} }
.timer, .volume { .timer,
.volume {
display: none; display: none;
} }
} }
</style> </style>

View File

@ -1,10 +1,12 @@
<template> <template>
<div class="controls"> <div class="controls">
<PauseIcon v-if="!player.paused" <PauseIcon
v-if="!player.paused"
class="pointer" class="pointer"
:size="50" :size="50"
@click="$store.dispatch('player/pause')" /> @click="$store.dispatch('player/pause')" />
<PlayIcon v-if="player.paused" <PlayIcon
v-if="player.paused"
class="pointer" class="pointer"
:size="50" :size="50"
@click="$store.dispatch('player/play')" /> @click="$store.dispatch('player/play')" />
@ -30,11 +32,11 @@ export default {
</script> </script>
<style scoped> <style scoped>
.controls { .controls {
display: flex; display: flex;
} }
.pointer { .pointer {
cursor: pointer; cursor: pointer;
} }
</style> </style>

View File

@ -7,7 +7,8 @@
<i>{{ player.episode.title }}</i> <i>{{ player.episode.title }}</i>
</router-link> </router-link>
<NcModal v-if="modal" @close="modal = false"> <NcModal v-if="modal" @close="modal = false">
<Modal :description="player.episode.description" <Modal
:description="player.episode.description"
:image="player.episode.image" :image="player.episode.image"
:link="player.episode.link" :link="player.episode.link"
:name="player.episode.name" :name="player.episode.name"
@ -46,14 +47,14 @@ export default {
</script> </script>
<style scoped> <style scoped>
.pointer { .pointer {
cursor: pointer; cursor: pointer;
} }
.root { .root {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
width: 40%; width: 40%;
} }
</style> </style>

View File

@ -1,10 +1,11 @@
<template> <template>
<input class="progress" <input
class="progress"
:max="player.duration" :max="player.duration"
min="0" min="0"
type="range" type="range"
:value="player.currentTime" :value="player.currentTime"
@change="(event) => $store.dispatch('player/seek', event.target.value)"> @change="(event) => $store.dispatch('player/seek', event.target.value)" />
</template> </template>
<script> <script>
@ -19,11 +20,11 @@ export default {
</script> </script>
<style scoped> <style scoped>
.progress { .progress {
height: 4px; height: 4px;
min-height: 4px; min-height: 4px;
position: absolute; position: absolute;
top: -2px; top: -2px;
width: 99%; width: 99%;
} }
</style> </style>

View File

@ -1,8 +1,8 @@
<template> <template>
<div> <div>
<span>{{ formatTimer(new Date(player.currentTime*1000)) }}</span> <span>{{ formatTimer(new Date(player.currentTime * 1000)) }}</span>
<span>/</span> <span>/</span>
<span>{{ formatTimer(new Date(player.duration*1000)) }}</span> <span>{{ formatTimer(new Date(player.duration * 1000)) }}</span>
</div> </div>
</template> </template>
@ -23,10 +23,10 @@ export default {
</script> </script>
<style scoped> <style scoped>
div { div {
align-items: center; align-items: center;
display: flex; display: flex;
gap: 5px; gap: 5px;
justify-content: center; justify-content: center;
} }
</style> </style>

View File

@ -1,27 +1,28 @@
<template> <template>
<div> <div>
<VolumeHighIcon v-if="player.volume > 0.7" <VolumeHighIcon v-if="player.volume > 0.7" class="pointer" :size="30" @click="mute" />
<VolumeLowIcon
v-if="player.volume > 0 && player.volume <= 0.3"
class="pointer" class="pointer"
:size="30" :size="30"
@click="mute" /> @click="mute" />
<VolumeLowIcon v-if="player.volume > 0 && player.volume <= 0.3" <VolumeMediumIcon
v-if="player.volume > 0.3 && player.volume <= 0.7"
class="pointer" class="pointer"
:size="30" :size="30"
@click="mute" /> @click="mute" />
<VolumeMediumIcon v-if="player.volume > 0.3 && player.volume <= 0.7" <VolumeMuteIcon
class="pointer" v-if="player.volume === 0"
:size="30"
@click="mute" />
<VolumeMuteIcon v-if="player.volume === 0"
class="pointer" class="pointer"
:size="30" :size="30"
@click="$store.dispatch('player/volume', volumeMuted)" /> @click="$store.dispatch('player/volume', volumeMuted)" />
<input max="1" <input
max="1"
min="0" min="0"
step="0.1" step="0.1"
type="range" type="range"
:value="player.volume" :value="player.volume"
@change="(event) => $store.dispatch('player/volume', event.target.value)"> @change="(event) => $store.dispatch('player/volume', event.target.value)" />
</div> </div>
</template> </template>
@ -59,19 +60,19 @@ export default {
</script> </script>
<style scoped> <style scoped>
div { div {
align-items: center; align-items: center;
display: flex; display: flex;
gap: 5px; gap: 5px;
justify-content: flex-end; justify-content: flex-end;
} }
input { input {
transform: rotate(270deg); transform: rotate(270deg);
width: 4rem; width: 4rem;
} }
.pointer { .pointer {
cursor: pointer; cursor: pointer;
} }
</style> </style>

View File

@ -1,5 +1,6 @@
<template> <template>
<NcAppNavigationItem :href="generateUrl('/apps/repod/opml/export')" <NcAppNavigationItem
:href="generateUrl('/apps/repod/opml/export')"
:name="t('repod', 'Export subscriptions')"> :name="t('repod', 'Export subscriptions')">
<template #icon> <template #icon>
<ExportIcon :size="20" /> <ExportIcon :size="20" />

View File

@ -1,23 +1,41 @@
<template> <template>
<NcAppNavigationItem :allow-collapse="true" <NcAppNavigationItem
:allow-collapse="true"
menu-placement="top" menu-placement="top"
:name="t('repod', 'Filtering episodes')"> :name="t('repod', 'Filtering episodes')">
<template #actions> <template #actions>
<NcActionCheckbox :checked="all" <NcActionCheckbox
:checked="all"
:disabled="all" :disabled="all"
@update:checked="(checked) => $store.commit('settings/filters', { listened: checked, listening: checked, unlistened: checked })"> @update:checked="
(checked) =>
$store.commit('settings/filters', {
listened: checked,
listening: checked,
unlistened: checked,
})
">
{{ t('repod', 'Show all') }} {{ t('repod', 'Show all') }}
</NcActionCheckbox> </NcActionCheckbox>
<NcActionCheckbox :checked="filters.listened" <NcActionCheckbox
@update:checked="(checked) => $store.commit('settings/filters', { listened: checked })"> :checked="filters.listened"
@update:checked="
(checked) => $store.commit('settings/filters', { listened: checked })
">
{{ t('repod', 'Listened') }} {{ t('repod', 'Listened') }}
</NcActionCheckbox> </NcActionCheckbox>
<NcActionCheckbox :checked="filters.listening" <NcActionCheckbox
@update:checked="(checked) => $store.commit('settings/filters', { listening: checked })"> :checked="filters.listening"
@update:checked="
(checked) => $store.commit('settings/filters', { listening: checked })
">
{{ t('repod', 'Listening') }} {{ t('repod', 'Listening') }}
</NcActionCheckbox> </NcActionCheckbox>
<NcActionCheckbox :checked="filters.unlistened" <NcActionCheckbox
@update:checked="(checked) => $store.commit('settings/filters', { unlistened: checked })"> :checked="filters.unlistened"
@update:checked="
(checked) => $store.commit('settings/filters', { unlistened: checked })
">
{{ t('repod', 'Unlistened') }} {{ t('repod', 'Unlistened') }}
</NcActionCheckbox> </NcActionCheckbox>
</template> </template>

View File

@ -4,16 +4,18 @@
<NcModal v-if="modal" @close="modal = false"> <NcModal v-if="modal" @close="modal = false">
<div class="modal"> <div class="modal">
<h2>{{ t('repod', 'Import OPML file') }}</h2> <h2>{{ t('repod', 'Import OPML file') }}</h2>
<form v-if="!loading" <form
v-if="!loading"
:action="generateUrl('/apps/repod/opml/import')" :action="generateUrl('/apps/repod/opml/import')"
enctype="multipart/form-data" enctype="multipart/form-data"
method="post" method="post"
@submit.prevent="importOpml"> @submit.prevent="importOpml">
<input accept="application/xml,.opml" <input
accept="application/xml,.opml"
name="import" name="import"
required required
type="file"> type="file" />
<input type="submit"> <input type="submit" />
</form> </form>
<Loading v-if="loading" /> <Loading v-if="loading" />
</div> </div>
@ -64,10 +66,10 @@ export default {
</script> </script>
<style scoped> <style scoped>
.modal { .modal {
align-items: center; align-items: center;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
margin: 2rem; margin: 2rem;
} }
</style> </style>

View File

@ -1,5 +1,6 @@
<template> <template>
<NcAppNavigationItem href="https://apps.nextcloud.com/apps/repod#comments" <NcAppNavigationItem
href="https://apps.nextcloud.com/apps/repod#comments"
:name="t('repod', 'Rate RePod ❤️')"> :name="t('repod', 'Rate RePod ❤️')">
<template #icon> <template #icon>
<StarIcon :size="20" /> <StarIcon :size="20" />

View File

@ -2,15 +2,9 @@
<NcAppNavigationItem :name="t('repod', 'Playback speed')"> <NcAppNavigationItem :name="t('repod', 'Playback speed')">
<template #extra> <template #extra>
<div class="extra"> <div class="extra">
<MinusIcon class="pointer" <MinusIcon class="pointer" :size="20" @click="changeRate(-0.1)" />
:size="20" <NcCounterBubble class="counter"> x{{ player.rate }} </NcCounterBubble>
@click="changeRate(-.1)" /> <PlusIcon class="pointer" :size="20" @click="changeRate(0.1)" />
<NcCounterBubble class="counter">
x{{ player.rate }}
</NcCounterBubble>
<PlusIcon class="pointer"
:size="20"
@click="changeRate(.1)" />
</div> </div>
</template> </template>
<template #icon> <template #icon>
@ -55,17 +49,17 @@ export default {
</script> </script>
<style scoped> <style scoped>
.counter { .counter {
height: 20px; height: 20px;
} }
.extra { .extra {
align-items: center; align-items: center;
display: flex; display: flex;
gap: .5rem; gap: 0.5rem;
} }
.pointer { .pointer {
cursor: pointer; cursor: pointer;
} }
</style> </style>

View File

@ -1,9 +1,8 @@
<template> <template>
<NcAppNavigationItem :loading="loading" <NcAppNavigationItem :loading="loading" :name="feed ? feed.title : url" :to="hash">
:name="feed ? feed.title : url"
:to="hash">
<template #actions> <template #actions>
<NcActionButton :aria-label="t(`core`, 'Delete')" <NcActionButton
:aria-label="t(`core`, 'Delete')"
:name="t(`core`, 'Delete')" :name="t(`core`, 'Delete')"
:title="t(`core`, 'Delete')" :title="t(`core`, 'Delete')"
@click="deleteSubscription"> @click="deleteSubscription">
@ -13,7 +12,8 @@
</NcActionButton> </NcActionButton>
</template> </template>
<template #icon> <template #icon>
<NcAvatar v-if="feed" <NcAvatar
v-if="feed"
:display-name="feed.author || feed.title" :display-name="feed.author || feed.title"
:is-no-user="true" :is-no-user="true"
:url="feed.imageUrl" /> :url="feed.imageUrl" />
@ -60,7 +60,11 @@ export default {
}, },
async mounted() { async mounted() {
try { try {
const podcastData = await axios.get(generateUrl('/apps/gpoddersync/personal_settings/podcast_data?url={url}', { url: this.url })) const podcastData = await axios.get(
generateUrl('/apps/gpoddersync/personal_settings/podcast_data?url={url}', {
url: this.url,
}),
)
this.feed = podcastData.data.data this.feed = podcastData.data.data
} catch (e) { } catch (e) {
this.failed = true this.failed = true
@ -74,7 +78,10 @@ export default {
if (confirm(t('repod', 'Are you sure you want to delete this subscription?'))) { if (confirm(t('repod', 'Are you sure you want to delete this subscription?'))) {
try { try {
this.loading = true this.loading = true
await axios.post(generateUrl('/apps/gpoddersync/subscription_change/create'), { add: [], remove: [this.url] }) await axios.post(
generateUrl('/apps/gpoddersync/subscription_change/create'),
{ add: [], remove: [this.url] },
)
} catch (e) { } catch (e) {
console.error(e) console.error(e)
showError(t('repod', 'Error while removing the feed')) showError(t('repod', 'Error while removing the feed'))

View File

@ -11,7 +11,8 @@
</router-link> </router-link>
<Loading v-if="loading" /> <Loading v-if="loading" />
<NcAppNavigationList v-if="!loading"> <NcAppNavigationList v-if="!loading">
<Item v-for="subscriptionUrl of subscriptions" <Item
v-for="subscriptionUrl of subscriptions"
:key="subscriptionUrl" :key="subscriptionUrl"
:url="subscriptionUrl" /> :url="subscriptionUrl" />
</NcAppNavigationList> </NcAppNavigationList>

View File

@ -14,5 +14,5 @@ export default new Vue({
el: '#content', el: '#content',
router, router,
store, store,
render: h => h(App), render: (h) => h(App),
}) })

View File

@ -54,7 +54,11 @@ export const player = {
audio.load() audio.load()
audio.play() audio.play()
if (episode.action && episode.action.position && episode.action.position < episode.action.total) { if (
episode.action &&
episode.action.position &&
episode.action.position < episode.action.total
) {
audio.currentTime = episode.action.position audio.currentTime = episode.action.position
state.started = audio.currentTime state.started = audio.currentTime
} }
@ -84,7 +88,9 @@ export const player = {
load: async (context, episode) => { load: async (context, episode) => {
context.commit('episode', episode) context.commit('episode', episode)
try { try {
const action = await axios.get(generateUrl('/apps/repod/episodes/action?url={url}', { url: episode.url })) const action = await axios.get(
generateUrl('/apps/repod/episodes/action?url={url}', { url: episode.url }),
)
context.commit('action', action.data) context.commit('action', action.data)
} catch {} } catch {}
}, },
@ -118,7 +124,9 @@ export const player = {
position: Math.round(audio.currentTime), position: Math.round(audio.currentTime),
total: Math.round(audio.duration), total: Math.round(audio.duration),
} }
axios.post(generateUrl('/apps/gpoddersync/episode_action/create'), [episode.action]) axios.post(generateUrl('/apps/gpoddersync/episode_action/create'), [
episode.action,
])
EventBus.$emit('updateEpisodesList', episode) EventBus.$emit('updateEpisodesList', episode)
}, },
volume: (_, volume) => { volume: (_, volume) => {

View File

@ -13,9 +13,16 @@ export const subscriptions = {
}, },
actions: { actions: {
fetch: async (context) => { fetch: async (context) => {
const metrics = await axios.get(generateUrl('/apps/gpoddersync/personal_settings/metrics')) const metrics = await axios.get(
const subs = [...metrics.data.subscriptions].sort((a, b) => b.listenedSeconds - a.listenedSeconds) generateUrl('/apps/gpoddersync/personal_settings/metrics'),
context.commit('set', subs.map(sub => sub.url)) )
const subs = [...metrics.data.subscriptions].sort(
(a, b) => b.listenedSeconds - a.listenedSeconds,
)
context.commit(
'set',
subs.map((sub) => sub.url),
)
}, },
}, },
} }

View File

@ -6,9 +6,7 @@
*/ */
export const getCookie = (name) => { export const getCookie = (name) => {
const cookies = document.cookie.split('; ') const cookies = document.cookie.split('; ')
const value = cookies const value = cookies.find((c) => c.startsWith(name + '='))?.split('=')[1]
.find(c => c.startsWith(name + '='))
?.split('=')[1]
if (value === undefined) { if (value === undefined) {
return null return null
} }

View File

@ -1,11 +1,11 @@
// https://stackoverflow.com/a/53486112 // https://stackoverflow.com/a/53486112
export const debounce = (fn, delay) => { export const debounce = (fn, delay) => {
let timeoutID = null let timeoutID = null
return function() { return function () {
clearTimeout(timeoutID) clearTimeout(timeoutID)
const args = arguments const args = arguments
const that = this const that = this
timeoutID = setTimeout(function() { timeoutID = setTimeout(function () {
fn.apply(that, args) fn.apply(that, args)
}, delay) }, delay)
} }

View File

@ -1,7 +1,7 @@
// https://stackoverflow.com/a/5002618 // https://stackoverflow.com/a/5002618
export const cleanHtml = (text) => { export const cleanHtml = (text) => {
const pre = document.createElement('pre') const pre = document.createElement('pre')
pre.innerHTML = text.replace(/<br\s*\/?>/mg, '\n') pre.innerHTML = text.replace(/<br\s*\/?>/gm, '\n')
const strippedText = pre.textContent || pre.innerText || '' const strippedText = pre.textContent || pre.innerText || ''
return strippedText.replace(/\n/mg, '<br>') return strippedText.replace(/\n/gm, '<br>')
} }

View File

@ -36,7 +36,8 @@ export const formatEpisodeTimestamp = (date) => {
* @param {Date} date The date * @param {Date} date The date
* @return {string} * @return {string}
*/ */
export const formatLocaleDate = (date) => date.toLocaleDateString(undefined, { dateStyle: 'medium' }) export const formatLocaleDate = (date) =>
date.toLocaleDateString(undefined, { dateStyle: 'medium' })
/** /**
* Returns the number of seconds from a duration feed's entry * Returns the number of seconds from a duration feed's entry
@ -46,7 +47,7 @@ export const formatLocaleDate = (date) => date.toLocaleDateString(undefined, { d
export const durationToSeconds = (duration) => { export const durationToSeconds = (duration) => {
const splitDuration = duration.split(':').reverse() const splitDuration = duration.split(':').reverse()
let seconds = parseInt(splitDuration[0]) let seconds = parseInt(splitDuration[0])
seconds += (splitDuration.length > 1) ? parseInt(splitDuration[1]) * 60 : 0 seconds += splitDuration.length > 1 ? parseInt(splitDuration[1]) * 60 : 0
seconds += (splitDuration.length > 2) ? parseInt(splitDuration[2]) * 60 * 60 : 0 seconds += splitDuration.length > 2 ? parseInt(splitDuration[2]) * 60 * 60 : 0
return seconds return seconds
} }

View File

@ -37,7 +37,7 @@ export default {
</script> </script>
<style scoped> <style scoped>
.main { .main {
padding: 15px 51px; padding: 15px 51px;
} }
</style> </style>

View File

@ -1,14 +1,13 @@
<template> <template>
<AppContent> <AppContent>
<Loading v-if="loading" /> <Loading v-if="loading" />
<NcEmptyContent v-if="failed" <NcEmptyContent v-if="failed" class="error" :name="t('repod', 'Error loading feed')">
class="error"
:name="t('repod', 'Error loading feed')">
<template #icon> <template #icon>
<Alert /> <Alert />
</template> </template>
</NcEmptyContent> </NcEmptyContent>
<Banner v-if="feed" <Banner
v-if="feed"
:author="feed.author" :author="feed.author"
:description="feed.description" :description="feed.description"
:image-url="feed.imageUrl" :image-url="feed.imageUrl"
@ -53,7 +52,9 @@ export default {
}, },
async mounted() { async mounted() {
try { try {
const podcastData = await axios.get(generateUrl('/apps/repod/podcast?url={url}', { url: this.url })) const podcastData = await axios.get(
generateUrl('/apps/repod/podcast?url={url}', { url: this.url }),
)
this.feed = podcastData.data this.feed = podcastData.data
} catch (e) { } catch (e) {
this.failed = true this.failed = true
@ -66,7 +67,7 @@ export default {
</script> </script>
<style scoped> <style scoped>
.error { .error {
margin: 2rem; margin: 2rem;
} }
</style> </style>

View File

@ -14,11 +14,7 @@
</template> </template>
<script> <script>
import { import { NcAppContent, NcButton, NcEmptyContent } from '@nextcloud/vue'
NcAppContent,
NcButton,
NcEmptyContent,
} from '@nextcloud/vue'
import Alert from 'vue-material-design-icons/Alert.vue' import Alert from 'vue-material-design-icons/Alert.vue'
import { generateUrl } from '@nextcloud/router' import { generateUrl } from '@nextcloud/router'