Compare commits
54 Commits
Author | SHA1 | Date |
---|---|---|
Michel Roux | eb427681fb | |
Michel Roux | 473d181807 | |
Michel Roux | 176760cf3b | |
Michel Roux | 5c117de552 | |
Michel Roux | a747286341 | |
Michel Roux | c46f4f1c61 | |
Michel Roux | d64746be0b | |
Michel Roux | aa3c782005 | |
Michel Roux | 41c577f5cb | |
Renovate Bot | 176874f979 | |
Renovate Bot | c6ab2ff692 | |
Michel Roux | 0825598c36 | |
Michel Roux | 2c078d32ae | |
Renovate Bot | 402f167206 | |
Michel Roux | ef064f0eb3 | |
Renovate Bot | bd905cae1c | |
Michel Roux | d0142b01dd | |
Michel Roux | fc8c8f23c2 | |
Renovate Bot | cc149c2b4f | |
Renovate Bot | 3af343910e | |
Michel Roux | 5d20fb5dd3 | |
Michel Roux | 3da2da7e98 | |
Michel Roux | c26599cdf7 | |
Renovate Bot | dae875140d | |
Michel Roux | 601f77d6a3 | |
Michel Roux | 25bddc5b31 | |
Renovate Bot | 3bd282a0c0 | |
Michel Roux | bac5a5114f | |
Renovate Bot | 0e16892874 | |
Michel Roux | 9bf5b9c653 | |
Renovate Bot | 2b7f43fb5c | |
Michel Roux | b0a0414fd4 | |
Michel Roux | cbc9654af8 | |
Michel Roux | e6621fee04 | |
Renovate Bot | e55dc689f6 | |
Renovate Bot | dbd6816504 | |
Michel Roux | 7b5330e146 | |
Renovate Bot | 7492baad4e | |
Michel Roux | af25bc191c | |
Renovate Bot | b1df2759d3 | |
Michel Roux | e25b384108 | |
Renovate Bot | b11999dab8 | |
Michel Roux | b55fe53304 | |
Michel Roux | 8a421ebc8f | |
Renovate Bot | 742ca212ef | |
Renovate Bot | e96c494421 | |
Michel Roux | e7e03ca42d | |
Renovate Bot | 4a3da45d03 | |
Michel Roux | ee22ac2726 | |
Renovate Bot | f389c225c4 | |
Michel Roux | 5a6427f8a1 | |
Renovate Bot | 8058f328bf | |
Michel Roux | e5beb6ef73 | |
Michel Roux | 9178670194 |
|
@ -1,9 +1,11 @@
|
|||
module.exports = {
|
||||
extends: [
|
||||
'@nextcloud',
|
||||
'plugin:prettier/recommended',
|
||||
],
|
||||
rules: {
|
||||
'sort-imports': 'error',
|
||||
'vue/attributes-order': ['error', { alphabetical: true }],
|
||||
'vue/first-attribute-linebreak': 'off',
|
||||
},
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ jobs:
|
|||
|
||||
php:
|
||||
runs-on: ubuntu-latest
|
||||
container: nextcloud:28
|
||||
container: nextcloud:29
|
||||
steps:
|
||||
- run: apt-get update
|
||||
- run: apt-get install -y git nodejs
|
||||
|
@ -47,7 +47,7 @@ jobs:
|
|||
release:
|
||||
if: gitea.ref_type == 'tag'
|
||||
runs-on: ubuntu-latest
|
||||
container: nextcloud:28
|
||||
container: nextcloud:29
|
||||
steps:
|
||||
- run: apt-get update
|
||||
- run: apt-get install -y git nodejs
|
||||
|
|
18
CHANGELOG.md
18
CHANGELOG.md
|
@ -1,3 +1,20 @@
|
|||
## 2.2.1 - 2024-05-18
|
||||
|
||||
### Removed
|
||||
- ♻️ Rollback: Hide unreadable episodes because of insecure sources
|
||||
|
||||
## 2.2.0 - 2024-05-18
|
||||
|
||||
### Added
|
||||
- 🚨 Linting the code with ESLint
|
||||
- 🎨 Prettierify the code
|
||||
|
||||
### Changed
|
||||
- ⬆️ Update all dependancies
|
||||
|
||||
### Fixed
|
||||
- 🔓 Hide unreadable episodes because of insecure sources
|
||||
|
||||
## 2.1.0 - 2024-03-16
|
||||
|
||||
### Added
|
||||
|
@ -10,6 +27,7 @@
|
|||
|
||||
### Fixed
|
||||
- 🔒 App wasn't working for non admin users
|
||||
[#76](https://git.crystalyx.net/Xefir/repod/issues/76) reported by @devasservice
|
||||
|
||||
## 2.0.0 - 2024-03-05
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
FROM nextcloud:28
|
||||
FROM nextcloud:29
|
||||
|
||||
ENV NEXTCLOUD_UPDATE 1
|
||||
ENV NEXTCLOUD_ADMIN_USER repod
|
||||
|
|
17
README.md
17
README.md
|
@ -14,6 +14,23 @@ You need to have [GPodderSync](https://apps.nextcloud.com/apps/gpoddersync) inst
|
|||
- Unified search integration
|
||||
- Interface in multiple languages
|
||||
|
||||
## Comparaison with similar apps for Nextcloud
|
||||
|
||||
| | [RePod](https://apps.nextcloud.com/apps/repod) | [NextPod](https://apps.nextcloud.com/apps/nextpod) | [Music](https://apps.nextcloud.com/apps/music) | [Podcast](https://apps.nextcloud.com/apps/podcast) |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Actively maintened | ✅ | ✅ | ✅ | ❌ |
|
||||
| Play your local music files | ❌ | ❌ | ✅ | ❌ |
|
||||
| Sync with [GPodder clients](#clients-supporting-sync-of-gpoddersync) | ✅ | ✅ | ❌ | ❌ |
|
||||
| Add and manage subscriptions | ✅ | ❌ | ✅ | ✅ |
|
||||
| Listen synced episodes by another clients | ✅ | ✅ | ❌ | ❌ |
|
||||
| Fetch and listen new epidodes | ✅ | ❌ | ✅ | ✅ |
|
||||
| Keep track of listened episodes | ✅ | ✅ | ❌ | ✅ |
|
||||
| Import and export subscriptions | ✅ | ❌ | ❌ | ❌ |
|
||||
| Search and discover new podcasts | ✅ | ❌ | ❌ | ✅ |
|
||||
| Integrate with Nextcloud search engine | ✅ | ❌ | ❌ | ✅ |
|
||||
| Mobile friendly interface | ✅ | ❌ | ✅ | ✅ |
|
||||
| Support chapters | ❌ | ❌ | ❌ | ✅ |
|
||||
|
||||
## Screenshots
|
||||
|
||||
### Homepage
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
## Requirements
|
||||
You need to have [GPodderSync](https://apps.nextcloud.com/apps/gpoddersync) installed to use this app!]]></description>
|
||||
<version>2.1.0</version>
|
||||
<version>2.2.1</version>
|
||||
<licence>agpl</licence>
|
||||
<author mail="xefir@crystalyx.net" homepage="https://crystalyx.net">Michel Roux</author>
|
||||
<namespace>RePod</namespace>
|
||||
|
|
|
@ -3,10 +3,10 @@
|
|||
"description": "🔊 Browse, manage and listen to podcasts",
|
||||
"type": "project",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"version": "2.1.0",
|
||||
"version": "2.2.1",
|
||||
"require-dev": {
|
||||
"nextcloud/ocp": "^28.0.3",
|
||||
"psalm/phar": "^5.23.1",
|
||||
"nextcloud/ocp": "^29.0.0",
|
||||
"psalm/phar": "^5.24.0",
|
||||
"nextcloud/coding-standard": "^1.2.1"
|
||||
},
|
||||
"scripts": {
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "8978cce206009a6890740e731ec61d98",
|
||||
"content-hash": "1f79e7311d583b49dec7b7ed5e0cffb9",
|
||||
"packages": [],
|
||||
"packages-dev": [
|
||||
{
|
||||
|
@ -50,16 +50,16 @@
|
|||
},
|
||||
{
|
||||
"name": "nextcloud/ocp",
|
||||
"version": "v28.0.3",
|
||||
"version": "v29.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nextcloud-deps/ocp.git",
|
||||
"reference": "ab6acaabffc6a58f2b703fc10f425ec7e99383ce"
|
||||
"reference": "9fd8cd85394dc7c2c000d6bf2def918c81aab703"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/nextcloud-deps/ocp/zipball/ab6acaabffc6a58f2b703fc10f425ec7e99383ce",
|
||||
"reference": "ab6acaabffc6a58f2b703fc10f425ec7e99383ce",
|
||||
"url": "https://api.github.com/repos/nextcloud-deps/ocp/zipball/9fd8cd85394dc7c2c000d6bf2def918c81aab703",
|
||||
"reference": "9fd8cd85394dc7c2c000d6bf2def918c81aab703",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -72,7 +72,7 @@
|
|||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-stable28": "28.0.0-dev"
|
||||
"dev-stable29": "29.0.0-dev"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
|
@ -88,22 +88,22 @@
|
|||
"description": "Composer package containing Nextcloud's public API (classes, interfaces)",
|
||||
"support": {
|
||||
"issues": "https://github.com/nextcloud-deps/ocp/issues",
|
||||
"source": "https://github.com/nextcloud-deps/ocp/tree/v28.0.3"
|
||||
"source": "https://github.com/nextcloud-deps/ocp/tree/v29.0.0"
|
||||
},
|
||||
"time": "2024-02-04T00:34:59+00:00"
|
||||
"time": "2024-04-09T00:32:49+00:00"
|
||||
},
|
||||
{
|
||||
"name": "php-cs-fixer/shim",
|
||||
"version": "v3.51.0",
|
||||
"version": "v3.57.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/PHP-CS-Fixer/shim.git",
|
||||
"reference": "a792394f7f3934f75a4df9dca544796c6f503027"
|
||||
"reference": "5f5c7c0e18b6251517c9c3908d1a5420f17d42a5"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/a792394f7f3934f75a4df9dca544796c6f503027",
|
||||
"reference": "a792394f7f3934f75a4df9dca544796c6f503027",
|
||||
"url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/5f5c7c0e18b6251517c9c3908d1a5420f17d42a5",
|
||||
"reference": "5f5c7c0e18b6251517c9c3908d1a5420f17d42a5",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -140,22 +140,22 @@
|
|||
"description": "A tool to automatically fix PHP code style",
|
||||
"support": {
|
||||
"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.57.1"
|
||||
},
|
||||
"time": "2024-02-28T19:51:07+00:00"
|
||||
"time": "2024-05-15T22:01:33+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psalm/phar",
|
||||
"version": "5.23.1",
|
||||
"version": "5.24.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/psalm/phar.git",
|
||||
"reference": "07bb50acefdaf7b663087186f86d47542a9b1622"
|
||||
"reference": "6ca1cbe47bbda0759b22ffe555594b547ff8351b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/psalm/phar/zipball/07bb50acefdaf7b663087186f86d47542a9b1622",
|
||||
"reference": "07bb50acefdaf7b663087186f86d47542a9b1622",
|
||||
"url": "https://api.github.com/repos/psalm/phar/zipball/6ca1cbe47bbda0759b22ffe555594b547ff8351b",
|
||||
"reference": "6ca1cbe47bbda0759b22ffe555594b547ff8351b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -175,9 +175,9 @@
|
|||
"description": "Composer-based Psalm Phar",
|
||||
"support": {
|
||||
"issues": "https://github.com/psalm/phar/issues",
|
||||
"source": "https://github.com/psalm/phar/tree/5.23.1"
|
||||
"source": "https://github.com/psalm/phar/tree/5.24.0"
|
||||
},
|
||||
"time": "2024-03-11T20:43:33+00:00"
|
||||
"time": "2024-05-01T20:28:41+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/clock",
|
||||
|
|
File diff suppressed because it is too large
Load Diff
32
package.json
32
package.json
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "repod",
|
||||
"description": "🔊 Browse, manage and listen to podcasts",
|
||||
"version": "2.1.0",
|
||||
"version": "2.2.1",
|
||||
"bugs": {
|
||||
"url": "https://git.crystalyx.net/Xefir/repod/issues"
|
||||
},
|
||||
|
@ -18,12 +18,12 @@
|
|||
"stylelint:fix": "stylelint css/*.css css/*.scss src/**/*.scss src/**/*.vue --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nextcloud/axios": "^2.4.0",
|
||||
"@nextcloud/dialogs": "^5.2.0",
|
||||
"@nextcloud/initial-state": "^2.1.0",
|
||||
"@nextcloud/l10n": "^2.2.0",
|
||||
"@nextcloud/router": "^3.0.0",
|
||||
"@nextcloud/vue": "^8.11.0",
|
||||
"@nextcloud/axios": "^2.5.0",
|
||||
"@nextcloud/dialogs": "^5.3.1",
|
||||
"@nextcloud/initial-state": "^2.2.0",
|
||||
"@nextcloud/l10n": "^3.1.0",
|
||||
"@nextcloud/router": "^3.0.1",
|
||||
"@nextcloud/vue": "^8.11.3",
|
||||
"vue": "^2",
|
||||
"vue-material-design-icons": "^5.3.0",
|
||||
"vue-router": "^3",
|
||||
|
@ -32,15 +32,15 @@
|
|||
"browserslist": [
|
||||
"extends @nextcloud/browserslist-config"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.0.0",
|
||||
"npm": "^9.0.0"
|
||||
},
|
||||
"prettier": "@nextcloud/prettier-config",
|
||||
"devDependencies": {
|
||||
"@nextcloud/babel-config": "^1.0.0",
|
||||
"@nextcloud/browserslist-config": "^3.0.0",
|
||||
"@nextcloud/eslint-config": "^8.3.0",
|
||||
"@nextcloud/stylelint-config": "^2.4.0",
|
||||
"@nextcloud/webpack-vue-config": "^6.0.1"
|
||||
"@nextcloud/babel-config": "^1.2.0",
|
||||
"@nextcloud/browserslist-config": "^3.0.1",
|
||||
"@nextcloud/eslint-config": "^8.4.1",
|
||||
"@nextcloud/prettier-config": "^1.1.0",
|
||||
"@nextcloud/stylelint-config": "^3.0.1",
|
||||
"@nextcloud/webpack-vue-config": "^6.0.1",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-prettier": "^5.1.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<style scoped>
|
||||
.padding {
|
||||
padding-bottom: 6rem;
|
||||
}
|
||||
.padding {
|
||||
padding-bottom: 6rem;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -27,7 +27,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<style scoped>
|
||||
.padding {
|
||||
padding-bottom: 6rem;
|
||||
}
|
||||
.padding {
|
||||
padding-bottom: 6rem;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -14,7 +14,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<style scoped>
|
||||
.loading {
|
||||
margin: 2rem 0;
|
||||
}
|
||||
.loading {
|
||||
margin: 2rem 0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,24 +1,17 @@
|
|||
<!-- eslint-disable vue/no-v-html -->
|
||||
<template>
|
||||
<div>
|
||||
<NcAvatar :display-name="name"
|
||||
:is-no-user="true"
|
||||
:size="256"
|
||||
:url="image" />
|
||||
<NcAvatar :display-name="name" :is-no-user="true" :size="256" :url="image" />
|
||||
<h2>{{ name }}</h2>
|
||||
<p v-html="strippedDescription" />
|
||||
<div>
|
||||
<NcButton v-if="link"
|
||||
:href="link"
|
||||
target="_blank">
|
||||
<NcButton v-if="link" :href="link" target="_blank">
|
||||
<template #icon>
|
||||
<OpenInNewIcon :size="20" />
|
||||
</template>
|
||||
{{ title }}
|
||||
</NcButton>
|
||||
<NcButton v-if="url"
|
||||
:href="url"
|
||||
target="_blank">
|
||||
<NcButton v-if="url" :href="url" target="_blank">
|
||||
<template #icon>
|
||||
<DownloadIcon :size="20" />
|
||||
</template>
|
||||
|
@ -85,11 +78,11 @@ export default {
|
|||
</script>
|
||||
|
||||
<style scoped>
|
||||
div {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
margin: 2rem;
|
||||
}
|
||||
div {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
margin: 2rem;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
<template>
|
||||
<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>
|
||||
<PlusIcon :size="20" />
|
||||
</template>
|
||||
|
@ -29,7 +31,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<style scoped>
|
||||
ul {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
ul {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -2,13 +2,15 @@
|
|||
<div>
|
||||
<Loading v-if="loading" />
|
||||
<ul v-if="!loading">
|
||||
<NcListItem v-for="feed in feeds"
|
||||
<NcListItem
|
||||
v-for="feed in feeds"
|
||||
:key="feed.link"
|
||||
:details="formatLocaleDate(new Date(feed.fetchedAtUnix*1000))"
|
||||
:details="formatLocaleDate(new Date(feed.fetchedAtUnix * 1000))"
|
||||
:name="feed.title"
|
||||
:to="toUrl(feed.link)">
|
||||
<template #icon>
|
||||
<NcAvatar :display-name="feed.author"
|
||||
<NcAvatar
|
||||
:display-name="feed.author"
|
||||
:is-no-user="true"
|
||||
:url="feed.imageUrl" />
|
||||
</template>
|
||||
|
@ -61,9 +63,15 @@ export default {
|
|||
try {
|
||||
this.loading = true
|
||||
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) {
|
||||
this.feeds = [...feeds.data].sort((a, b) => b.fetchedAtUnix - a.fetchedAtUnix)
|
||||
this.feeds = [...feeds.data].sort(
|
||||
(a, b) => b.fetchedAtUnix - a.fetchedAtUnix,
|
||||
)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<ul v-if="!loading">
|
||||
<li v-for="top in tops" :key="top.link">
|
||||
<router-link :to="toUrl(top.link)">
|
||||
<img :src="top.imageUrl" :title="top.author">
|
||||
<img :src="top.imageUrl" :title="top.author" />
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -39,19 +39,21 @@ export default {
|
|||
computed: {
|
||||
title() {
|
||||
switch (this.type) {
|
||||
case 'new':
|
||||
return t('repod', 'New podcasts')
|
||||
case 'hot':
|
||||
return t('repod', 'Hot podcasts')
|
||||
default:
|
||||
return this.type
|
||||
case 'new':
|
||||
return t('repod', 'New podcasts')
|
||||
case 'hot':
|
||||
return t('repod', 'Hot podcasts')
|
||||
default:
|
||||
return this.type
|
||||
}
|
||||
},
|
||||
},
|
||||
async mounted() {
|
||||
try {
|
||||
this.loading = true
|
||||
const tops = await axios.get(generateUrl(`/apps/repod/toplist/${this.type}`))
|
||||
const tops = await axios.get(
|
||||
generateUrl(`/apps/repod/toplist/${this.type}`),
|
||||
)
|
||||
this.tops = tops.data
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
|
@ -67,24 +69,24 @@ export default {
|
|||
</script>
|
||||
|
||||
<style scoped>
|
||||
h2 {
|
||||
margin: 1rem 0;
|
||||
}
|
||||
h2 {
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
img {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
img {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
li {
|
||||
flex-basis: 10rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
li {
|
||||
flex-basis: 10rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
overflow: scroll hidden;
|
||||
padding-bottom: .5rem;
|
||||
}
|
||||
ul {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
overflow: scroll hidden;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
<!-- eslint-disable vue/no-v-html -->
|
||||
<template>
|
||||
<div class="header">
|
||||
<img class="background" :src="imageUrl">
|
||||
<img class="background" :src="imageUrl" />
|
||||
<div class="content">
|
||||
<div>
|
||||
<NcAvatar :display-name="author || title"
|
||||
<NcAvatar
|
||||
:display-name="author || title"
|
||||
:is-no-user="true"
|
||||
:size="128"
|
||||
:url="imageUrl" />
|
||||
|
@ -19,12 +20,13 @@
|
|||
<a :href="link" target="_blank">
|
||||
<i>{{ author }}</i>
|
||||
</a>
|
||||
<br><br>
|
||||
<br /><br />
|
||||
<p>
|
||||
<small v-html="strippedDescription" />
|
||||
</p>
|
||||
</div>
|
||||
<NcAppNavigationNew v-if="!isSubscribed"
|
||||
<NcAppNavigationNew
|
||||
v-if="!isSubscribed"
|
||||
:text="t('repod', 'Subscribe')"
|
||||
@click="addSubscription">
|
||||
<template #icon>
|
||||
|
@ -90,7 +92,13 @@ export default {
|
|||
methods: {
|
||||
async addSubscription() {
|
||||
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) {
|
||||
console.error(e)
|
||||
showError(t('repod', 'Error while adding the feed'))
|
||||
|
@ -100,55 +108,55 @@ export default {
|
|||
},
|
||||
copyFeed() {
|
||||
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>
|
||||
|
||||
<style scoped>
|
||||
.background {
|
||||
filter: blur(1rem) brightness(50%);
|
||||
height: auto;
|
||||
left: 0;
|
||||
opacity: .4;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
z-index: -1;
|
||||
}
|
||||
.background {
|
||||
filter: blur(1rem) brightness(50%);
|
||||
height: auto;
|
||||
left: 0;
|
||||
opacity: 0.4;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
height: 10rem;
|
||||
position: relative;
|
||||
}
|
||||
.content {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
height: 10rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.feed {
|
||||
display: flex;
|
||||
gap: .2rem;
|
||||
margin: .5rem;
|
||||
}
|
||||
.feed {
|
||||
display: flex;
|
||||
gap: 0.2rem;
|
||||
margin: 0.5rem;
|
||||
}
|
||||
|
||||
.header {
|
||||
height: 14rem;
|
||||
overflow: hidden;
|
||||
padding: 2rem;
|
||||
position: relative;
|
||||
}
|
||||
.header {
|
||||
height: 14rem;
|
||||
overflow: hidden;
|
||||
padding: 2rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.infos {
|
||||
overflow: auto;
|
||||
}
|
||||
.infos {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.inner {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
.inner {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
.inner {
|
||||
flex-direction: column;
|
||||
}
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -2,17 +2,19 @@
|
|||
<div>
|
||||
<Loading v-if="loading" />
|
||||
<ul v-if="!loading">
|
||||
<NcListItem v-for="episode in filteredEpisodes"
|
||||
<NcListItem
|
||||
v-for="episode in filteredEpisodes"
|
||||
:key="episode.guid"
|
||||
:active="isCurrentEpisode(episode)"
|
||||
:class="hasEnded(episode) ? 'ended': ''"
|
||||
:class="hasEnded(episode) ? 'ended' : ''"
|
||||
:details="formatLocaleDate(new Date(episode.pubDate?.date))"
|
||||
:force-display-actions="true"
|
||||
:name="episode.name"
|
||||
:title="episode.description"
|
||||
@click="modalEpisode = episode">
|
||||
<template #actions>
|
||||
<NcActionButton v-if="!isCurrentEpisode(episode)"
|
||||
<NcActionButton
|
||||
v-if="!isCurrentEpisode(episode)"
|
||||
:aria-label="t('repod', 'Play')"
|
||||
:name="t('repod', 'Play')"
|
||||
:title="t('repod', 'Play')"
|
||||
|
@ -21,7 +23,8 @@
|
|||
<PlayIcon :size="20" />
|
||||
</template>
|
||||
</NcActionButton>
|
||||
<NcActionButton v-if="isCurrentEpisode(episode)"
|
||||
<NcActionButton
|
||||
v-if="isCurrentEpisode(episode)"
|
||||
:aria-label="t('repod', 'Stop')"
|
||||
:name="t('repod', 'Stop')"
|
||||
:title="t('repod', 'Stop')"
|
||||
|
@ -33,7 +36,8 @@
|
|||
</template>
|
||||
<template #extra>
|
||||
<NcActions>
|
||||
<NcActionButton v-if="episode.duration && !hasEnded(episode)"
|
||||
<NcActionButton
|
||||
v-if="episode.duration && !hasEnded(episode)"
|
||||
:aria-label="t('repod', 'Mark as read')"
|
||||
:disabled="loadingAction"
|
||||
:name="t('repod', 'Mark as read')"
|
||||
|
@ -43,7 +47,8 @@
|
|||
<PlaylistPlayIcon :size="20" />
|
||||
</template>
|
||||
</NcActionButton>
|
||||
<NcActionButton v-if="episode.duration && hasEnded(episode)"
|
||||
<NcActionButton
|
||||
v-if="episode.duration && hasEnded(episode)"
|
||||
:aria-label="t('repod', 'Mark as unread')"
|
||||
:disabled="loadingAction"
|
||||
:name="t('repod', 'Mark as unread')"
|
||||
|
@ -53,7 +58,8 @@
|
|||
<PlaylistRemoveIcon :size="20" />
|
||||
</template>
|
||||
</NcActionButton>
|
||||
<NcActionLink v-if="episode.link"
|
||||
<NcActionLink
|
||||
v-if="episode.link"
|
||||
:href="episode.link"
|
||||
:name="t('repod', 'Open website')"
|
||||
target="_blank"
|
||||
|
@ -62,7 +68,8 @@
|
|||
<OpenInNewIcon :size="20" />
|
||||
</template>
|
||||
</NcActionLink>
|
||||
<NcActionLink v-if="episode.url"
|
||||
<NcActionLink
|
||||
v-if="episode.url"
|
||||
:href="episode.url"
|
||||
:name="t('repod', 'Download')"
|
||||
target="_blank"
|
||||
|
@ -74,14 +81,18 @@
|
|||
</NcActions>
|
||||
</template>
|
||||
<template #icon>
|
||||
<NcAvatar :display-name="episode.name"
|
||||
<NcAvatar
|
||||
:display-name="episode.name"
|
||||
:is-no-user="true"
|
||||
:url="episode.image" />
|
||||
</template>
|
||||
<template #indicator>
|
||||
<NcProgressBar v-if="isListening(episode)"
|
||||
<NcProgressBar
|
||||
v-if="isListening(episode)"
|
||||
class="progress"
|
||||
:value="episode.action.position * 100 / episode.action.total" />
|
||||
:value="
|
||||
(episode.action.position * 100) / episode.action.total
|
||||
" />
|
||||
</template>
|
||||
<template #subname>
|
||||
{{ episode.duration }}
|
||||
|
@ -89,7 +100,8 @@
|
|||
</NcListItem>
|
||||
</ul>
|
||||
<NcModal v-if="modalEpisode" @close="modalEpisode = null">
|
||||
<Modal :description="modalEpisode.description"
|
||||
<Modal
|
||||
:description="modalEpisode.description"
|
||||
:image="modalEpisode.image"
|
||||
:link="modalEpisode.link"
|
||||
:name="modalEpisode.name"
|
||||
|
@ -101,8 +113,20 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { NcActionButton, NcActionLink, NcActions, NcAvatar, NcListItem, NcModal, NcProgressBar } from '@nextcloud/vue'
|
||||
import { durationToSeconds, formatEpisodeTimestamp, formatLocaleDate } from '../../utils/time.js'
|
||||
import {
|
||||
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 { EventBus } from '../../store/bus.js'
|
||||
import Loading from '../Atoms/Loading.vue'
|
||||
|
@ -175,8 +199,14 @@ export default {
|
|||
async mounted() {
|
||||
try {
|
||||
this.loading = true
|
||||
const episodes = await axios.get(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))
|
||||
const episodes = await axios.get(
|
||||
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)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
|
@ -191,18 +221,22 @@ export default {
|
|||
methods: {
|
||||
formatLocaleDate,
|
||||
hasEnded(episode) {
|
||||
return episode.action
|
||||
&& episode.action.position > 0
|
||||
&& episode.action.total > 0
|
||||
&& episode.action.position >= episode.action.total
|
||||
return (
|
||||
episode.action &&
|
||||
episode.action.position > 0 &&
|
||||
episode.action.total > 0 &&
|
||||
episode.action.position >= episode.action.total
|
||||
)
|
||||
},
|
||||
isCurrentEpisode(episode) {
|
||||
return this.currentEpisode && this.currentEpisode.url === episode.url
|
||||
},
|
||||
isListening(episode) {
|
||||
return episode.action
|
||||
&& episode.action.action.toLowerCase() === 'play'
|
||||
&& !this.hasEnded(episode)
|
||||
return (
|
||||
episode.action &&
|
||||
episode.action.action.toLowerCase() === 'play' &&
|
||||
!this.hasEnded(episode)
|
||||
)
|
||||
},
|
||||
load(episode) {
|
||||
this.$store.dispatch('player/load', episode)
|
||||
|
@ -220,7 +254,10 @@ export default {
|
|||
position: read ? durationToSeconds(episode.duration) : 0,
|
||||
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)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
|
@ -230,18 +267,20 @@ export default {
|
|||
}
|
||||
},
|
||||
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>
|
||||
|
||||
<style scoped>
|
||||
.ended {
|
||||
opacity: .4;
|
||||
}
|
||||
.ended {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.progress {
|
||||
margin-top: .4rem;
|
||||
}
|
||||
.progress {
|
||||
margin-top: 0.4rem;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<template>
|
||||
<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" />
|
||||
<ProgressBar v-if="player.loaded" />
|
||||
<div v-if="player.loaded" class="player">
|
||||
<img :src="player.episode.image">
|
||||
<img :src="player.episode.image" />
|
||||
<Infos class="infos" />
|
||||
<Controls class="controls" />
|
||||
<Timer class="timer" />
|
||||
|
@ -40,49 +40,50 @@ export default {
|
|||
</script>
|
||||
|
||||
<style scoped>
|
||||
.background {
|
||||
filter: blur(1rem) brightness(50%);
|
||||
height: auto;
|
||||
left: 0;
|
||||
opacity: .4;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.footer {
|
||||
background-color: var(--color-main-background);
|
||||
bottom: 0;
|
||||
height: 6rem;
|
||||
right: 0;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
z-index: 2000;
|
||||
}
|
||||
|
||||
.player {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
height: 6rem;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.timer {
|
||||
width: 30%;
|
||||
.background {
|
||||
filter: blur(1rem) brightness(50%);
|
||||
height: auto;
|
||||
left: 0;
|
||||
opacity: 0.4;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.footer {
|
||||
background-color: var(--color-main-background);
|
||||
bottom: 0;
|
||||
height: 6rem;
|
||||
right: 0;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
z-index: 2000;
|
||||
}
|
||||
|
||||
.player {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
height: 6rem;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.timer {
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
.volume {
|
||||
width: 10%;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
.infos {
|
||||
flex: 2;
|
||||
}
|
||||
|
||||
.timer,
|
||||
.volume {
|
||||
width: 10%;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
.infos {
|
||||
flex: 2;
|
||||
}
|
||||
|
||||
.timer, .volume {
|
||||
display: none;
|
||||
}
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
<template>
|
||||
<div class="controls">
|
||||
<PauseIcon v-if="!player.paused"
|
||||
<PauseIcon
|
||||
v-if="!player.paused"
|
||||
class="pointer"
|
||||
:size="50"
|
||||
@click="$store.dispatch('player/pause')" />
|
||||
<PlayIcon v-if="player.paused"
|
||||
<PlayIcon
|
||||
v-if="player.paused"
|
||||
class="pointer"
|
||||
:size="50"
|
||||
@click="$store.dispatch('player/play')" />
|
||||
|
@ -30,11 +32,11 @@ export default {
|
|||
</script>
|
||||
|
||||
<style scoped>
|
||||
.controls {
|
||||
display: flex;
|
||||
}
|
||||
.controls {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
<i>{{ player.episode.title }}</i>
|
||||
</router-link>
|
||||
<NcModal v-if="modal" @close="modal = false">
|
||||
<Modal :description="player.episode.description"
|
||||
<Modal
|
||||
:description="player.episode.description"
|
||||
:image="player.episode.image"
|
||||
:link="player.episode.link"
|
||||
:name="player.episode.name"
|
||||
|
@ -46,14 +47,14 @@ export default {
|
|||
</script>
|
||||
|
||||
<style scoped>
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
width: 40%;
|
||||
}
|
||||
.root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
width: 40%;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
<template>
|
||||
<input class="progress"
|
||||
<input
|
||||
class="progress"
|
||||
:max="player.duration"
|
||||
min="0"
|
||||
type="range"
|
||||
:value="player.currentTime"
|
||||
@change="(event) => $store.dispatch('player/seek', event.target.value)">
|
||||
@change="(event) => $store.dispatch('player/seek', event.target.value)" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
@ -19,11 +20,11 @@ export default {
|
|||
</script>
|
||||
|
||||
<style scoped>
|
||||
.progress {
|
||||
height: 4px;
|
||||
min-height: 4px;
|
||||
position: absolute;
|
||||
top: -2px;
|
||||
width: 99%;
|
||||
}
|
||||
.progress {
|
||||
height: 4px;
|
||||
min-height: 4px;
|
||||
position: absolute;
|
||||
top: -2px;
|
||||
width: 99%;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<template>
|
||||
<div>
|
||||
<span>{{ formatTimer(new Date(player.currentTime*1000)) }}</span>
|
||||
<span>{{ formatTimer(new Date(player.currentTime * 1000)) }}</span>
|
||||
<span>/</span>
|
||||
<span>{{ formatTimer(new Date(player.duration*1000)) }}</span>
|
||||
<span>{{ formatTimer(new Date(player.duration * 1000)) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -23,10 +23,10 @@ export default {
|
|||
</script>
|
||||
|
||||
<style scoped>
|
||||
div {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
justify-content: center;
|
||||
}
|
||||
div {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,27 +1,34 @@
|
|||
<template>
|
||||
<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"
|
||||
<VolumeLowIcon
|
||||
v-if="player.volume > 0 && player.volume <= 0.3"
|
||||
class="pointer"
|
||||
:size="30"
|
||||
@click="mute" />
|
||||
<VolumeMediumIcon v-if="player.volume > 0.3 && player.volume <= 0.7"
|
||||
<VolumeMediumIcon
|
||||
v-if="player.volume > 0.3 && player.volume <= 0.7"
|
||||
class="pointer"
|
||||
:size="30"
|
||||
@click="mute" />
|
||||
<VolumeMuteIcon v-if="player.volume === 0"
|
||||
<VolumeMuteIcon
|
||||
v-if="player.volume === 0"
|
||||
class="pointer"
|
||||
:size="30"
|
||||
@click="$store.dispatch('player/volume', volumeMuted)" />
|
||||
<input max="1"
|
||||
<input
|
||||
max="1"
|
||||
min="0"
|
||||
step="0.1"
|
||||
type="range"
|
||||
:value="player.volume"
|
||||
@change="(event) => $store.dispatch('player/volume', event.target.value)">
|
||||
@change="
|
||||
(event) => $store.dispatch('player/volume', event.target.value)
|
||||
" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -59,19 +66,19 @@ export default {
|
|||
</script>
|
||||
|
||||
<style scoped>
|
||||
div {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
div {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
input {
|
||||
transform: rotate(270deg);
|
||||
width: 4rem;
|
||||
}
|
||||
input {
|
||||
transform: rotate(270deg);
|
||||
width: 4rem;
|
||||
}
|
||||
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<template>
|
||||
<NcAppNavigationItem :href="generateUrl('/apps/repod/opml/export')"
|
||||
<NcAppNavigationItem
|
||||
:href="generateUrl('/apps/repod/opml/export')"
|
||||
:name="t('repod', 'Export subscriptions')">
|
||||
<template #icon>
|
||||
<ExportIcon :size="20" />
|
||||
|
|
|
@ -1,23 +1,44 @@
|
|||
<template>
|
||||
<NcAppNavigationItem :allow-collapse="true"
|
||||
<NcAppNavigationItem
|
||||
:allow-collapse="true"
|
||||
menu-placement="top"
|
||||
:name="t('repod', 'Filtering episodes')">
|
||||
<template #actions>
|
||||
<NcActionCheckbox :checked="all"
|
||||
<NcActionCheckbox
|
||||
:checked="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') }}
|
||||
</NcActionCheckbox>
|
||||
<NcActionCheckbox :checked="filters.listened"
|
||||
@update:checked="(checked) => $store.commit('settings/filters', { listened: checked })">
|
||||
<NcActionCheckbox
|
||||
:checked="filters.listened"
|
||||
@update:checked="
|
||||
(checked) =>
|
||||
$store.commit('settings/filters', { listened: checked })
|
||||
">
|
||||
{{ t('repod', 'Listened') }}
|
||||
</NcActionCheckbox>
|
||||
<NcActionCheckbox :checked="filters.listening"
|
||||
@update:checked="(checked) => $store.commit('settings/filters', { listening: checked })">
|
||||
<NcActionCheckbox
|
||||
:checked="filters.listening"
|
||||
@update:checked="
|
||||
(checked) =>
|
||||
$store.commit('settings/filters', { listening: checked })
|
||||
">
|
||||
{{ t('repod', 'Listening') }}
|
||||
</NcActionCheckbox>
|
||||
<NcActionCheckbox :checked="filters.unlistened"
|
||||
@update:checked="(checked) => $store.commit('settings/filters', { unlistened: checked })">
|
||||
<NcActionCheckbox
|
||||
:checked="filters.unlistened"
|
||||
@update:checked="
|
||||
(checked) =>
|
||||
$store.commit('settings/filters', { unlistened: checked })
|
||||
">
|
||||
{{ t('repod', 'Unlistened') }}
|
||||
</NcActionCheckbox>
|
||||
</template>
|
||||
|
@ -43,7 +64,11 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
all() {
|
||||
return this.filters.listened && this.filters.listening && this.filters.unlistened
|
||||
return (
|
||||
this.filters.listened &&
|
||||
this.filters.listening &&
|
||||
this.filters.unlistened
|
||||
)
|
||||
},
|
||||
filters() {
|
||||
return this.$store.state.settings.filters
|
||||
|
|
|
@ -1,19 +1,23 @@
|
|||
<template>
|
||||
<NcAppNavigationItem :name="t('repod', 'Import subscriptions')" @click="modal = true">
|
||||
<NcAppNavigationItem
|
||||
:name="t('repod', 'Import subscriptions')"
|
||||
@click="modal = true">
|
||||
<template #extra>
|
||||
<NcModal v-if="modal" @close="modal = false">
|
||||
<div class="modal">
|
||||
<h2>{{ t('repod', 'Import OPML file') }}</h2>
|
||||
<form v-if="!loading"
|
||||
<form
|
||||
v-if="!loading"
|
||||
:action="generateUrl('/apps/repod/opml/import')"
|
||||
enctype="multipart/form-data"
|
||||
method="post"
|
||||
@submit.prevent="importOpml">
|
||||
<input accept="application/xml,.opml"
|
||||
<input
|
||||
accept="application/xml,.opml"
|
||||
name="import"
|
||||
required
|
||||
type="file">
|
||||
<input type="submit">
|
||||
type="file" />
|
||||
<input type="submit" />
|
||||
</form>
|
||||
<Loading v-if="loading" />
|
||||
</div>
|
||||
|
@ -64,10 +68,10 @@ export default {
|
|||
</script>
|
||||
|
||||
<style scoped>
|
||||
.modal {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 2rem;
|
||||
}
|
||||
.modal {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 2rem;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<template>
|
||||
<NcAppNavigationItem href="https://apps.nextcloud.com/apps/repod#comments"
|
||||
<NcAppNavigationItem
|
||||
href="https://apps.nextcloud.com/apps/repod#comments"
|
||||
:name="t('repod', 'Rate RePod ❤️')">
|
||||
<template #icon>
|
||||
<StarIcon :size="20" />
|
||||
|
|
|
@ -2,15 +2,11 @@
|
|||
<NcAppNavigationItem :name="t('repod', 'Playback speed')">
|
||||
<template #extra>
|
||||
<div class="extra">
|
||||
<MinusIcon class="pointer"
|
||||
:size="20"
|
||||
@click="changeRate(-.1)" />
|
||||
<MinusIcon class="pointer" :size="20" @click="changeRate(-0.1)" />
|
||||
<NcCounterBubble class="counter">
|
||||
x{{ player.rate }}
|
||||
</NcCounterBubble>
|
||||
<PlusIcon class="pointer"
|
||||
:size="20"
|
||||
@click="changeRate(.1)" />
|
||||
<PlusIcon class="pointer" :size="20" @click="changeRate(0.1)" />
|
||||
</div>
|
||||
</template>
|
||||
<template #icon>
|
||||
|
@ -48,24 +44,27 @@ export default {
|
|||
methods: {
|
||||
changeRate(diff) {
|
||||
const newRate = (this.player.rate + diff).toPrecision(2)
|
||||
this.$store.dispatch('player/rate', newRate > 0 ? newRate : this.player.rate)
|
||||
this.$store.dispatch(
|
||||
'player/rate',
|
||||
newRate > 0 ? newRate : this.player.rate,
|
||||
)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.counter {
|
||||
height: 20px;
|
||||
}
|
||||
.counter {
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.extra {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: .5rem;
|
||||
}
|
||||
.extra {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
<template>
|
||||
<NcAppNavigationItem :loading="loading"
|
||||
<NcAppNavigationItem
|
||||
:loading="loading"
|
||||
:name="feed ? feed.title : url"
|
||||
:to="hash">
|
||||
<template #actions>
|
||||
<NcActionButton :aria-label="t(`core`, 'Delete')"
|
||||
<NcActionButton
|
||||
:aria-label="t(`core`, 'Delete')"
|
||||
:name="t(`core`, 'Delete')"
|
||||
:title="t(`core`, 'Delete')"
|
||||
@click="deleteSubscription">
|
||||
|
@ -13,7 +15,8 @@
|
|||
</NcActionButton>
|
||||
</template>
|
||||
<template #icon>
|
||||
<NcAvatar v-if="feed"
|
||||
<NcAvatar
|
||||
v-if="feed"
|
||||
:display-name="feed.author || feed.title"
|
||||
:is-no-user="true"
|
||||
:url="feed.imageUrl" />
|
||||
|
@ -60,7 +63,14 @@ export default {
|
|||
},
|
||||
async mounted() {
|
||||
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
|
||||
} catch (e) {
|
||||
this.failed = true
|
||||
|
@ -71,10 +81,17 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
async deleteSubscription() {
|
||||
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 {
|
||||
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) {
|
||||
console.error(e)
|
||||
showError(t('repod', 'Error while removing the feed'))
|
||||
|
|
|
@ -11,7 +11,8 @@
|
|||
</router-link>
|
||||
<Loading v-if="loading" />
|
||||
<NcAppNavigationList v-if="!loading">
|
||||
<Item v-for="subscriptionUrl of subscriptions"
|
||||
<Item
|
||||
v-for="subscriptionUrl of subscriptions"
|
||||
:key="subscriptionUrl"
|
||||
:url="subscriptionUrl" />
|
||||
</NcAppNavigationList>
|
||||
|
@ -24,7 +25,11 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { NcAppContentList, NcAppNavigationList, NcAppNavigationNew } from '@nextcloud/vue'
|
||||
import {
|
||||
NcAppContentList,
|
||||
NcAppNavigationList,
|
||||
NcAppNavigationNew,
|
||||
} from '@nextcloud/vue'
|
||||
import AppNavigation from '../Atoms/AppNavigation.vue'
|
||||
import Item from './Item.vue'
|
||||
import Loading from '../Atoms/Loading.vue'
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { translatePlural as n, translate as t } from '@nextcloud/l10n'
|
||||
import { n, t } from '@nextcloud/l10n'
|
||||
import App from './App.vue'
|
||||
import Vue from 'vue'
|
||||
import { generateFilePath } from '@nextcloud/router'
|
||||
|
@ -14,5 +14,5 @@ export default new Vue({
|
|||
el: '#content',
|
||||
router,
|
||||
store,
|
||||
render: h => h(App),
|
||||
render: (h) => h(App),
|
||||
})
|
||||
|
|
|
@ -54,7 +54,11 @@ export const player = {
|
|||
audio.load()
|
||||
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
|
||||
state.started = audio.currentTime
|
||||
}
|
||||
|
@ -84,7 +88,11 @@ export const player = {
|
|||
load: async (context, episode) => {
|
||||
context.commit('episode', episode)
|
||||
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)
|
||||
} catch {}
|
||||
},
|
||||
|
@ -118,7 +126,9 @@ export const player = {
|
|||
position: Math.round(audio.currentTime),
|
||||
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)
|
||||
},
|
||||
volume: (_, volume) => {
|
||||
|
|
|
@ -13,9 +13,16 @@ export const subscriptions = {
|
|||
},
|
||||
actions: {
|
||||
fetch: async (context) => {
|
||||
const metrics = await axios.get(generateUrl('/apps/gpoddersync/personal_settings/metrics'))
|
||||
const subs = [...metrics.data.subscriptions].sort((a, b) => b.listenedSeconds - a.listenedSeconds)
|
||||
context.commit('set', subs.map(sub => sub.url))
|
||||
const metrics = await axios.get(
|
||||
generateUrl('/apps/gpoddersync/personal_settings/metrics'),
|
||||
)
|
||||
const subs = [...metrics.data.subscriptions].sort(
|
||||
(a, b) => b.listenedSeconds - a.listenedSeconds,
|
||||
)
|
||||
context.commit(
|
||||
'set',
|
||||
subs.map((sub) => sub.url),
|
||||
)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -6,9 +6,7 @@
|
|||
*/
|
||||
export const getCookie = (name) => {
|
||||
const cookies = document.cookie.split('; ')
|
||||
const value = cookies
|
||||
.find(c => c.startsWith(name + '='))
|
||||
?.split('=')[1]
|
||||
const value = cookies.find((c) => c.startsWith(name + '='))?.split('=')[1]
|
||||
if (value === undefined) {
|
||||
return null
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
// https://stackoverflow.com/a/53486112
|
||||
export const debounce = (fn, delay) => {
|
||||
let timeoutID = null
|
||||
return function() {
|
||||
return function () {
|
||||
clearTimeout(timeoutID)
|
||||
const args = arguments
|
||||
const that = this
|
||||
timeoutID = setTimeout(function() {
|
||||
timeoutID = setTimeout(function () {
|
||||
fn.apply(that, args)
|
||||
}, delay)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
// https://stackoverflow.com/a/20732091
|
||||
export const humanFileSize = (size) => {
|
||||
const i = size === 0 ? 0 : Math.floor(Math.log(size) / Math.log(1024))
|
||||
return (size / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i]
|
||||
return (
|
||||
(size / Math.pow(1024, i)).toFixed(2) * 1 +
|
||||
' ' +
|
||||
['B', 'kB', 'MB', 'GB', 'TB'][i]
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// https://stackoverflow.com/a/5002618
|
||||
export const cleanHtml = (text) => {
|
||||
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 || ''
|
||||
return strippedText.replace(/\n/mg, '<br>')
|
||||
return strippedText.replace(/\n/gm, '<br>')
|
||||
}
|
||||
|
|
|
@ -36,7 +36,8 @@ export const formatEpisodeTimestamp = (date) => {
|
|||
* @param {Date} date The date
|
||||
* @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
|
||||
|
@ -46,7 +47,7 @@ export const formatLocaleDate = (date) => date.toLocaleDateString(undefined, { d
|
|||
export const durationToSeconds = (duration) => {
|
||||
const splitDuration = duration.split(':').reverse()
|
||||
let seconds = parseInt(splitDuration[0])
|
||||
seconds += (splitDuration.length > 1) ? parseInt(splitDuration[1]) * 60 : 0
|
||||
seconds += (splitDuration.length > 2) ? parseInt(splitDuration[2]) * 60 * 60 : 0
|
||||
seconds += splitDuration.length > 1 ? parseInt(splitDuration[1]) * 60 : 0
|
||||
seconds += splitDuration.length > 2 ? parseInt(splitDuration[2]) * 60 * 60 : 0
|
||||
return seconds
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<style scoped>
|
||||
.main {
|
||||
padding: 15px 51px;
|
||||
}
|
||||
.main {
|
||||
padding: 15px 51px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
<template>
|
||||
<AppContent>
|
||||
<Loading v-if="loading" />
|
||||
<NcEmptyContent v-if="failed"
|
||||
<NcEmptyContent
|
||||
v-if="failed"
|
||||
class="error"
|
||||
:name="t('repod', 'Error loading feed')">
|
||||
<template #icon>
|
||||
<Alert />
|
||||
</template>
|
||||
</NcEmptyContent>
|
||||
<Banner v-if="feed"
|
||||
<Banner
|
||||
v-if="feed"
|
||||
:author="feed.author"
|
||||
:description="feed.description"
|
||||
:image-url="feed.imageUrl"
|
||||
|
@ -53,7 +55,9 @@ export default {
|
|||
},
|
||||
async mounted() {
|
||||
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
|
||||
} catch (e) {
|
||||
this.failed = true
|
||||
|
@ -66,7 +70,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<style scoped>
|
||||
.error {
|
||||
margin: 2rem;
|
||||
}
|
||||
.error {
|
||||
margin: 2rem;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -14,11 +14,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
NcAppContent,
|
||||
NcButton,
|
||||
NcEmptyContent,
|
||||
} from '@nextcloud/vue'
|
||||
import { NcAppContent, NcButton, NcEmptyContent } from '@nextcloud/vue'
|
||||
import Alert from 'vue-material-design-icons/Alert.vue'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
|
||||
|
|
Loading…
Reference in New Issue