typescript #149 #152
@ -1,6 +1,7 @@
|
||||
module.exports = {
|
||||
extends: [
|
||||
'@nextcloud',
|
||||
'@vue/eslint-config-typescript',
|
||||
'plugin:pinia/recommended',
|
||||
'plugin:prettier/recommended',
|
||||
],
|
||||
|
43
package-lock.json
generated
43
package-lock.json
generated
@ -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"
|
||||
},
|
||||
@ -2740,7 +2728,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",
|
||||
@ -3113,7 +3100,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"
|
||||
}
|
||||
@ -4415,9 +4401,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",
|
||||
@ -6157,7 +6143,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",
|
||||
@ -6200,8 +6185,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",
|
||||
@ -8347,8 +8331,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",
|
||||
@ -9818,7 +9801,6 @@
|
||||
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
|
||||
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
@ -10803,7 +10785,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"
|
||||
},
|
||||
@ -10995,9 +10976,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"
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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() {
|
||||
|
@ -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>
|
||||
|
@ -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'),
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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'
|
||||
|
@ -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'
|
||||
|
@ -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'
|
||||
|
@ -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'
|
||||
|
||||
|
@ -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'
|
||||
|
@ -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'
|
||||
|
@ -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: {
|
||||
|
@ -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,
|
||||
)
|
||||
},
|
||||
|
@ -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]
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user