refactor: 🚧 still working on typescript conversion
Some checks failed
repod / xml (push) Successful in 20s
repod / php (push) Successful in 55s
repod / nodejs (push) Failing after 51s
repod / release (push) Has been skipped

This commit is contained in:
Michel Roux 2024-09-13 12:35:08 +02:00
parent fdee813bce
commit 0466d49007
18 changed files with 203 additions and 162 deletions

View File

@ -1,6 +1,7 @@
module.exports = {
extends: [
'@nextcloud',
'@vue/eslint-config-typescript',
'plugin:pinia/recommended',
'plugin:prettier/recommended',
],

55
package-lock.json generated
View File

@ -18,11 +18,11 @@
"petite-utils": "^0.0.5-3",
"pinia": "^2.2.2",
"toastify-js": "^1.12.0",
"vite": "^5.4.4",
"vite": "^5.4.5",
"vite-plugin-vue-devtools": "^7.4.5",
"vue": "^3.5.4",
"vue-material-design-icons": "^5.3.0",
"vue-router": "^4.4.4"
"vue-router": "^4.4.5"
},
"devDependencies": {
"@nextcloud/browserslist-config": "^3.0.1",
@ -30,12 +30,13 @@
"@nextcloud/prettier-config": "^1.1.0",
"@nextcloud/stylelint-config": "^3.0.1",
"@types/toastify-js": "^1.12.3",
"@vue/eslint-config-typescript": "^13.0.0",
"@vue/tsconfig": "^0.5.1",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-pinia": "^0.4.1",
"eslint-plugin-prettier": "^5.2.1",
"ts-node": "^10.9.2",
"typescript": "^5.6.2",
"typescript": "5.5.4",
"vue-eslint-parser": "^9.4.3",
"vue-tsc": "^2.1.6"
}
@ -1033,7 +1034,6 @@
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
"integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==",
"dev": true,
"peer": true,
"dependencies": {
"eslint-visitor-keys": "^3.3.0"
},
@ -1049,7 +1049,6 @@
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
"integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
"dev": true,
"peer": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
@ -1062,7 +1061,6 @@
"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz",
"integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==",
"dev": true,
"peer": true,
"engines": {
"node": "^12.0.0 || ^14.0.0 || >=16.0.0"
}
@ -2314,7 +2312,6 @@
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz",
"integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==",
"dev": true,
"peer": true,
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "7.18.0",
@ -2348,7 +2345,6 @@
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz",
"integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==",
"dev": true,
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "7.18.0",
"@typescript-eslint/types": "7.18.0",
@ -2377,7 +2373,6 @@
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz",
"integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==",
"dev": true,
"peer": true,
"dependencies": {
"@typescript-eslint/types": "7.18.0",
"@typescript-eslint/visitor-keys": "7.18.0"
@ -2395,7 +2390,6 @@
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz",
"integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==",
"dev": true,
"peer": true,
"dependencies": {
"@typescript-eslint/typescript-estree": "7.18.0",
"@typescript-eslint/utils": "7.18.0",
@ -2423,7 +2417,6 @@
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz",
"integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==",
"dev": true,
"peer": true,
"engines": {
"node": "^18.18.0 || >=20.0.0"
},
@ -2437,7 +2430,6 @@
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz",
"integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==",
"dev": true,
"peer": true,
"dependencies": {
"@typescript-eslint/types": "7.18.0",
"@typescript-eslint/visitor-keys": "7.18.0",
@ -2466,7 +2458,6 @@
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
"dev": true,
"peer": true,
"bin": {
"semver": "bin/semver.js"
},
@ -2479,7 +2470,6 @@
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz",
"integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==",
"dev": true,
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"@typescript-eslint/scope-manager": "7.18.0",
@ -2502,7 +2492,6 @@
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz",
"integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==",
"dev": true,
"peer": true,
"dependencies": {
"@typescript-eslint/types": "7.18.0",
"eslint-visitor-keys": "^3.4.3"
@ -2520,7 +2509,6 @@
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
"integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
"dev": true,
"peer": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
@ -2736,7 +2724,6 @@
"resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-13.0.0.tgz",
"integrity": "sha512-MHh9SncG/sfqjVqjcuFLOLD6Ed4dRAis4HNt0dXASeAuLqIAx4YMB1/m2o4pUKK1vCt8fUvYG8KKX2Ot3BVZTg==",
"dev": true,
"peer": true,
"dependencies": {
"@typescript-eslint/eslint-plugin": "^7.1.1",
"@typescript-eslint/parser": "^7.1.1",
@ -3104,7 +3091,6 @@
"resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
"integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
"dev": true,
"peer": true,
"engines": {
"node": ">=8"
}
@ -4406,9 +4392,9 @@
}
},
"node_modules/electron-to-chromium": {
"version": "1.5.20",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.20.tgz",
"integrity": "sha512-74mdl6Fs1HHzK9SUX4CKFxAtAe3nUns48y79TskHNAG6fGOlLfyKA4j855x+0b5u8rWJIrlaG9tcTPstMlwjIw=="
"version": "1.5.22",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.22.tgz",
"integrity": "sha512-tKYm5YHPU1djz0O+CGJ+oJIvimtsCcwR2Z9w7Skh08lUdyzXY5djods3q+z2JkWdb7tCcmM//eVavSRAiaPRNg=="
},
"node_modules/elliptic": {
"version": "6.5.7",
@ -6148,7 +6134,6 @@
"resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
"integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
"dev": true,
"peer": true,
"dependencies": {
"array-union": "^2.1.0",
"dir-glob": "^3.0.1",
@ -6191,8 +6176,7 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
"integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
"dev": true,
"peer": true
"dev": true
},
"node_modules/has-bigints": {
"version": "1.0.2",
@ -8338,8 +8322,7 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
"dev": true,
"peer": true
"dev": true
},
"node_modules/node-gettext": {
"version": "3.0.0",
@ -9809,7 +9792,6 @@
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
"dev": true,
"peer": true,
"engines": {
"node": ">=8"
}
@ -10794,7 +10776,6 @@
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz",
"integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==",
"dev": true,
"peer": true,
"engines": {
"node": ">=16"
},
@ -10986,9 +10967,9 @@
}
},
"node_modules/typescript": {
"version": "5.6.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz",
"integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==",
"version": "5.5.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
"integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@ -11231,9 +11212,9 @@
}
},
"node_modules/vite": {
"version": "5.4.4",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.4.tgz",
"integrity": "sha512-RHFCkULitycHVTtelJ6jQLd+KSAAzOgEYorV32R2q++M6COBjKJR6BxqClwp5sf0XaBDjVMuJ9wnNfyAJwjMkA==",
"version": "5.4.5",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.5.tgz",
"integrity": "sha512-pXqR0qtb2bTwLkev4SE3r4abCNioP3GkjvIDLlzziPpXtHgiJIjuKl+1GN6ESOT3wMjG3JTeARopj2SwYaHTOA==",
"dependencies": {
"esbuild": "^0.21.3",
"postcss": "^8.4.43",
@ -11997,9 +11978,9 @@
}
},
"node_modules/vue-router": {
"version": "4.4.4",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.4.4.tgz",
"integrity": "sha512-3MlnDqwRwZwCQVbtVfpsU+nrNymNjnXSsQtXName5925NVC1+326VVfYH9vSrA0N13teGEo8z5x7gbRnGjCDiQ==",
"version": "4.4.5",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.4.5.tgz",
"integrity": "sha512-4fKZygS8cH1yCyuabAXGUAsyi1b2/o/OKgu/RUb+znIYOxPRxdkytJEx+0wGcpBE1pX6vUgh5jwWOKRGvuA/7Q==",
"dependencies": {
"@vue/devtools-api": "^6.6.4"
},

View File

@ -27,11 +27,11 @@
"petite-utils": "^0.0.5-3",
"pinia": "^2.2.2",
"toastify-js": "^1.12.0",
"vite": "^5.4.4",
"vite": "^5.4.5",
"vite-plugin-vue-devtools": "^7.4.5",
"vue": "^3.5.4",
"vue-material-design-icons": "^5.3.0",
"vue-router": "^4.4.4"
"vue-router": "^4.4.5"
},
"devDependencies": {
"@nextcloud/browserslist-config": "^3.0.1",
@ -39,12 +39,13 @@
"@nextcloud/prettier-config": "^1.1.0",
"@nextcloud/stylelint-config": "^3.0.1",
"@types/toastify-js": "^1.12.3",
"@vue/eslint-config-typescript": "^13.0.0",
"@vue/tsconfig": "^0.5.1",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-pinia": "^0.4.1",
"eslint-plugin-prettier": "^5.2.1",
"ts-node": "^10.9.2",
"typescript": "^5.6.2",
"typescript": "5.5.4",
"vue-eslint-parser": "^9.4.3",
"vue-tsc": "^2.1.6"
}

View File

@ -19,7 +19,7 @@
</template>
<template #actions>
<NcActionButton
v-if="!getSubscriptions.includes(feed.link)"
v-if="!getSubByUrl(feed.link)"
:aria-label="t('repod', 'Subscribe')"
:name="t('repod', 'Subscribe')"
:title="t('repod', 'Subscribe')"
@ -39,6 +39,7 @@ import { NcActionButton, NcAvatar, NcListItem } from '@nextcloud/vue'
import { mapActions, mapState } from 'pinia'
import Loading from '../Atoms/Loading.vue'
import PlusIcon from 'vue-material-design-icons/Plus.vue'
import type { PodcastDataInterface } from '../../utils/types.ts'
import axios from '@nextcloud/axios'
import { debounce } from 'petite-utils'
import { formatLocaleDate } from '../../utils/time.ts'
@ -64,15 +65,38 @@ export default {
},
},
data: () => ({
feeds: [],
feeds: [] as PodcastDataInterface[],
loading: false,
}),
computed: {
...mapState(useSubscriptions, ['getSubscriptions']),
...mapState(useSubscriptions, ['getSubByUrl']),
},
watch: {
value() {
this.search()
const that = this
debounce(async function () {
try {
that.loading = true
const currentSearch = that.value
const feeds = await axios.get<PodcastDataInterface[]>(
generateUrl('/apps/repod/search?q={value}', {
value: currentSearch,
}),
)
if (currentSearch === that.value) {
that.feeds = [...feeds.data].sort(
(a, b) => b.fetchedAtUnix - a.fetchedAtUnix,
)
}
} catch (e) {
console.error(e)
showError(t('repod', 'Could not fetch search results'))
} finally {
if (that.feeds) {
that.loading = false
}
}
}, 200)
},
},
methods: {
@ -80,7 +104,7 @@ export default {
formatLocaleDate,
t,
toFeedUrl,
addSubscription: async (url) => {
async addSubscription(url: string) {
try {
await axios.post(
generateUrl('/apps/gpoddersync/subscription_change/create'),
@ -96,29 +120,6 @@ export default {
this.fetch()
},
search: debounce(async function value() {
try {
this.loading = true
const currentSearch = this.value
const feeds = await axios.get(
generateUrl('/apps/repod/search?q={value}', {
value: currentSearch,
}),
)
if (currentSearch === this.value) {
this.feeds = [...feeds.data].sort(
(a, b) => b.fetchedAtUnix - a.fetchedAtUnix,
)
}
} catch (e) {
console.error(e)
showError(t('repod', 'Could not fetch search results'))
} finally {
if (this.feeds) {
this.loading = false
}
}
}, 200),
},
}
</script>

View File

@ -12,11 +12,13 @@
</div>
</template>
<script>
<script lang="ts">
import Loading from '../Atoms/Loading.vue'
import type { PodcastDataInterface } from '../../utils/types.ts'
import axios from '@nextcloud/axios'
import { generateUrl } from '@nextcloud/router'
import { showError } from '../../utils/toast.ts'
import { t } from '@nextcloud/l10n'
import { toFeedUrl } from '../../utils/url.ts'
export default {
@ -32,7 +34,7 @@ export default {
},
data: () => ({
loading: true,
tops: [],
tops: [] as PodcastDataInterface[],
}),
computed: {
title() {

View File

@ -23,7 +23,7 @@
<SafeHtml :source="description" />
</div>
<NcAppNavigationNew
v-if="!getSubscriptions.includes(url)"
v-if="!getSubByUrl(url)"
:text="t('repod', 'Subscribe')"
@click="addSubscription">
<template #icon>
@ -35,7 +35,7 @@
</div>
</template>
<script>
<script lang="ts">
import { NcAppNavigationNew, NcAvatar } from '@nextcloud/vue'
import { mapActions, mapState } from 'pinia'
import { showError, showSuccess } from '../../utils/toast.ts'
@ -45,6 +45,7 @@ import SafeHtml from '../Atoms/SafeHtml.vue'
import axios from '@nextcloud/axios'
import { decodeUrl } from '../../utils/url.ts'
import { generateUrl } from '@nextcloud/router'
import { t } from '@nextcloud/l10n'
import { useSubscriptions } from '../../store/subscriptions.ts'
export default {
@ -79,9 +80,9 @@ export default {
},
},
computed: {
...mapState(useSubscriptions, ['getSubscriptions']),
...mapState(useSubscriptions, ['getSubByUrl']),
url() {
return decodeUrl(this.$route.params.url)
return decodeUrl(this.$route.params.url as string)
},
},
methods: {
@ -106,6 +107,7 @@ export default {
window.navigator.clipboard.writeText(this.url)
showSuccess(t('repod', 'Link copied to the clipboard'))
},
t,
},
}
</script>

View File

@ -2,7 +2,11 @@
<NcListItem
:active="isCurrentEpisode(episode)"
class="episode"
:details="!oneLine ? formatLocaleDate(new Date(episode.pubDate?.date)) : ''"
:details="
!oneLine && episode.pubDate
? formatLocaleDate(new Date(episode.pubDate?.date))
: ''
"
:force-display-actions="true"
:name="episode.name"
:one-line="oneLine"
@ -78,7 +82,7 @@
</template>
<template #indicator>
<NcProgressBar
v-if="isListening(episode) && !oneLine"
v-if="episode.action && isListening(episode) && !oneLine"
class="progress"
:value="(episode.action.position * 100) / episode.action.total" />
</template>
@ -88,7 +92,7 @@
</NcListItem>
</template>
<script>
<script lang="ts">
import {
NcActionButton,
NcActionLink,
@ -106,6 +110,7 @@ import {
import { hasEnded, isListening } from '../../utils/status.ts'
import { mapActions, mapState } from 'pinia'
import DownloadIcon from 'vue-material-design-icons/Download.vue'
import type { EpisodeInterface } from '../../utils/types.ts'
import Modal from '../Atoms/Modal.vue'
import OpenInNewIcon from 'vue-material-design-icons/OpenInNew.vue'
import PlayIcon from 'vue-material-design-icons/Play.vue'
@ -116,6 +121,7 @@ import axios from '@nextcloud/axios'
import { filenameFromUrl } from '../../utils/url.ts'
import { generateUrl } from '@nextcloud/router'
import { showError } from '../../utils/toast.ts'
import { t } from '@nextcloud/l10n'
import { usePlayer } from '../../store/player.ts'
export default {
@ -138,7 +144,7 @@ export default {
},
props: {
episode: {
type: Object,
type: Object as () => EpisodeInterface,
required: true,
},
oneLine: {
@ -152,7 +158,7 @@ export default {
},
data: () => ({
loading: false,
modalEpisode: null,
modalEpisode: null as EpisodeInterface | null,
}),
computed: {
...mapState(usePlayer, { playerEpisode: 'episode' }),
@ -163,10 +169,11 @@ export default {
hasEnded,
isListening,
filenameFromUrl,
isCurrentEpisode(episode) {
t,
isCurrentEpisode(episode: EpisodeInterface) {
return this.playerEpisode?.url === episode.url
},
async markAs(episode, read) {
async markAs(episode: EpisodeInterface, read: boolean) {
try {
this.loading = true
episode.action = {
@ -176,8 +183,8 @@ export default {
action: 'play',
timestamp: formatEpisodeTimestamp(new Date()),
started: episode.action?.started || 0,
position: read ? durationToSeconds(episode.duration) : 0,
total: durationToSeconds(episode.duration),
position: read ? durationToSeconds(episode.duration || '') : 0,
total: durationToSeconds(episode.duration || ''),
}
await axios.post(
generateUrl('/apps/gpoddersync/episode_action/create'),

View File

@ -11,15 +11,17 @@
</div>
</template>
<script>
<script lang="ts">
import { hasEnded, isListening } from '../../utils/status.ts'
import Episode from './Episode.vue'
import type { EpisodeInterface } from '../../utils/types.ts'
import Loading from '../Atoms/Loading.vue'
import axios from '@nextcloud/axios'
import { decodeUrl } from '../../utils/url.ts'
import { generateUrl } from '@nextcloud/router'
import { mapState } from 'pinia'
import { showError } from '../../utils/toast.ts'
import { t } from '@nextcloud/l10n'
import { usePlayer } from '../../store/player.ts'
import { useSettings } from '../../store/settings.ts'
@ -30,7 +32,7 @@ export default {
Loading,
},
data: () => ({
episodes: [],
episodes: [] as EpisodeInterface[],
loading: true,
}),
computed: {
@ -54,14 +56,14 @@ export default {
})
},
url() {
return decodeUrl(this.$route.params.url)
return decodeUrl(this.$route.params.url as string)
},
},
watch: {
episode() {
if (this.episode) {
this.episodes = this.episodes.map((e) =>
e.url === this.episode.url ? this.episode : e,
e.url === this.episode?.url ? this.episode : e,
)
}
},
@ -75,7 +77,9 @@ export default {
}),
)
this.episodes = [...episodes.data].sort(
(a, b) => new Date(b.pubDate?.date) - new Date(a.pubDate?.date),
(a, b) =>
new Date(b.pubDate?.date).getTime() -
new Date(a.pubDate?.date).getTime(),
)
} catch (e) {
console.error(e)

View File

@ -8,7 +8,7 @@
:is-no-user="true"
:size="222"
:url="currentFavoriteData.imageUrl" />
<div class="list">
<div v-if="currentFavoriteData" class="list">
<h2 class="title">{{ currentFavoriteData.title }}</h2>
<Loading v-if="loading" />
<ul v-if="!loading">
@ -23,15 +23,17 @@
</NcGuestContent>
</template>
<script>
<script lang="ts">
import { NcAvatar, NcGuestContent } from '@nextcloud/vue'
import Episode from './Episode.vue'
import type { EpisodeInterface } from '../../utils/types.ts'
import Loading from '../Atoms/Loading.vue'
import axios from '@nextcloud/axios'
import { generateUrl } from '@nextcloud/router'
import { hasEnded } from '../../utils/status.ts'
import { mapState } from 'pinia'
import { showError } from '../../utils/toast.ts'
import { t } from '@nextcloud/l10n'
import { useSubscriptions } from '../../store/subscriptions.ts'
export default {
@ -49,13 +51,13 @@ export default {
},
},
data: () => ({
episodes: [],
episodes: [] as EpisodeInterface[],
loading: true,
}),
computed: {
...mapState(useSubscriptions, ['getFavorites']),
...mapState(useSubscriptions, ['getSubByUrl']),
currentFavoriteData() {
return this.getFavorites.find((fav) => fav.url === this.url)
return this.getSubByUrl(this.url)?.data
},
},
async mounted() {
@ -68,7 +70,9 @@ export default {
)
this.episodes = [...episodes.data]
.sort(
(a, b) => new Date(b.pubDate?.date) - new Date(a.pubDate?.date),
(a, b) =>
new Date(b.pubDate?.date).getTime() -
new Date(a.pubDate?.date).getTime(),
)
.filter((episode) => !this.hasEnded(episode))
.slice(0, 4)

View File

@ -13,7 +13,7 @@
</div>
</template>
<script>
<script lang="ts">
import Controls from './Controls.vue'
import Infos from './Infos.vue'
import Loading from '../Atoms/Loading.vue'

View File

@ -5,7 +5,7 @@
</div>
</template>
<script>
<script lang="ts">
import { mapActions, mapState } from 'pinia'
import PauseIcon from 'vue-material-design-icons/Pause.vue'
import PlayIcon from 'vue-material-design-icons/Play.vue'

View File

@ -1,5 +1,5 @@
<template>
<div class="root">
<div v-if="episode && podcastUrl" class="root">
<strong class="pointer" @click="modal = true">
{{ episode.name }}
</strong>
@ -12,7 +12,7 @@
</div>
</template>
<script>
<script lang="ts">
import Modal from '../Atoms/Modal.vue'
import { NcModal } from '@nextcloud/vue'
import { mapState } from 'pinia'

View File

@ -1,14 +1,17 @@
<template>
<input
v-if="duration"
class="progress"
:max="duration"
min="0"
type="range"
:value="currentTime"
@change="(event) => seek(event.target.value)" />
@change="
(event) => seek(parseInt((event.target as HTMLInputElement).value))
" />
</template>
<script>
<script lang="ts">
import { mapActions, mapState } from 'pinia'
import { usePlayer } from '../../store/player.ts'

View File

@ -1,12 +1,12 @@
<template>
<div class="root">
<div v-if="currentTime && duration" class="root">
<span>{{ formatTimer(new Date(currentTime * 1000)) }}</span>
<span>/</span>
<span>{{ formatTimer(new Date(duration * 1000)) }}</span>
</div>
</template>
<script>
<script lang="ts">
import { formatTimer } from '../../utils/time.ts'
import { mapState } from 'pinia'
import { usePlayer } from '../../store/player.ts'

View File

@ -26,11 +26,14 @@
step="0.1"
type="range"
:value="volume"
@change="(event) => setVolume(event.target.value)" />
@change="
(event) =>
setVolume(parseInt((event.target as HTMLInputElement).value))
" />
</div>
</template>
<script>
<script lang="ts">
import { mapActions, mapState } from 'pinia'
import VolumeHighIcon from 'vue-material-design-icons/VolumeHigh.vue'
import VolumeLowIcon from 'vue-material-design-icons/VolumeLow.vue'

View File

@ -4,27 +4,23 @@ import { defineStore } from 'pinia'
export const useSettings = defineStore('settings', {
state: () => {
const cookie = getCookie('repod.filters')
if (cookie) {
try {
const filters = JSON.parse(cookie)
return {
filters: {
listened: filters.listened,
listening: filters.listening,
unlistened: filters.unlistened,
},
}
} catch {}
}
return {
filters: {
listened: true,
listening: true,
unlistened: true,
},
try {
const filters = JSON.parse(getCookie('repod.filters') || '{}') || {}
return {
filters: {
listened: filters.listened,
listening: filters.listening,
unlistened: filters.unlistened,
},
}
} catch {
return {
filters: {
listened: true,
listening: true,
unlistened: true,
},
}
}
},
actions: {

View File

@ -1,3 +1,8 @@
import type {
PodcastDataInterface,
PodcastMetricsInterface,
SubscriptionInterface,
} from '../utils/types'
import { getCookie, setCookie } from '../utils/cookies'
import axios from '@nextcloud/axios'
import { defineStore } from 'pinia'
@ -5,52 +10,47 @@ import { generateUrl } from '@nextcloud/router'
export const useSubscriptions = defineStore('subscriptions', {
state: () => ({
subs: [],
favs: [],
subs: [] as SubscriptionInterface[],
}),
getters: {
getSubscriptions: (state) => {
return state.subs
},
getFavorites: (state) => {
return state.favs
.filter((fav) => state.subs.includes(fav.url))
.sort((fav) => fav.lastPub)
},
getSubByUrl: (state) => (url: string) =>
state.subs.find((sub) => sub.metrics.url === url),
},
actions: {
async fetch() {
const metrics = await axios.get(
let favorites: string[] = []
try {
favorites = JSON.parse(getCookie('repod.favorites') || '[]') || []
} catch {}
const metrics = await axios.get<PodcastMetricsInterface>(
generateUrl('/apps/gpoddersync/personal_settings/metrics'),
)
const subs = [...metrics.data.subscriptions].sort(
(a, b) => b.listenedSeconds - a.listenedSeconds,
this.subs = [...metrics.data.subscriptions]
.sort((a, b) => b.listenedSeconds - a.listenedSeconds)
.map((sub) => ({
metrics: sub,
isFavorite: favorites.includes(sub.url),
data: this.subs.find((s) => s.metrics.url === sub.url)?.data,
}))
},
addFavoriteData(data: PodcastDataInterface) {
this.subs = this.subs.map((sub) =>
sub.metrics.url === data.link ? { ...sub, data } : sub,
)
},
setFavorite(link: string, isFavorite: boolean) {
this.subs.map((sub) =>
sub.metrics.url === link ? { ...sub, isFavorite } : sub,
)
this.subs = subs.map((sub) => sub.url)
try {
const favs = JSON.parse(getCookie('repod.favorites')) || []
this.favs = favs.map((url) => ({ url }))
} catch {}
},
addFavorite(url) {
this.favs.push({ url })
setCookie(
'repod.favorites',
JSON.stringify(this.favs.map((fav) => fav.url)),
365,
)
},
editFavoriteData(url, data) {
this.favs = this.favs.map((fav) =>
fav.url === url ? { ...fav, ...data } : fav,
)
},
removeFavorite(url) {
this.favs = this.favs.filter((fav) => fav.url !== url)
setCookie(
'repod.favorites',
JSON.stringify(this.favs.map((fav) => fav.url)),
JSON.stringify(
this.subs
.filter((sub) => sub.isFavorite)
.map((sub) => sub.metrics.url),
),
365,
)
},

View File

@ -12,7 +12,7 @@ export interface EpisodeActionInterface {
export interface EpisodeInterface {
title: string
url?: string
url: string
name: string
link?: string
image?: string
@ -21,7 +21,11 @@ export interface EpisodeInterface {
guid: string
type?: string
size?: number
pubDate?: Date
pubDate?: {
date: string
timezone_type: number
timezone: string
}
duration?: string
action?: EpisodeActionInterface
}
@ -31,3 +35,35 @@ export interface FiltersInterface {
listening: boolean
unlistened: boolean
}
export interface PodcastDataInterface {
title: string
author?: string
link: string
description?: string
imageUrl?: string
fetchedAtUnix: number
imageBlob?: string | null
}
export interface PodcastMetricsInterface {
subscriptions: [
{
url: string
listenedSeconds: number
actionCounts: {
delete: number
download: number
flattr: number
new: number
play: number
}
},
]
}
export interface SubscriptionInterface {
data?: PodcastDataInterface
isFavorite: boolean
metrics: PodcastMetricsInterface['subscriptions'][0]
}