Merge pull request 'new homepage based on favorites (fixes #130 #59)' (#131) from favorites into main
Reviewed-on: https://git.crystalyx.net/Xefir/repod/pulls/131
This commit is contained in:
commit
094b7812cd
15
CHANGELOG.md
15
CHANGELOG.md
|
@ -1,3 +1,18 @@
|
||||||
|
## 3.1.0 - 2024-09-02
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- ⭐ You can now add favorites subscriptions !
|
||||||
|
It will show's up on the homepage instead of the recommendations witch appear only when you add a new subscription.
|
||||||
|
[#59](https://git.crystalyx.net/Xefir/repod/issues/59) suggested by @W_LL_M, @Jaunty and @Satalink
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- 💥 Use html5 routing instead of hashes. All the URLs has changed removing the `#/` part.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- 🐛 Regression on 3.0 that prevent seeking player to episode last listened position
|
||||||
|
[#136](https://git.crystalyx.net/Xefir/repod/issues/136) reported by @randomuser1967
|
||||||
|
- ⚡ Improve the detection off mis-installed or mis-enabled gpodder app
|
||||||
|
|
||||||
## 3.0.0 - 2024-08-17
|
## 3.0.0 - 2024-08-17
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
You need to have [GPodderSync](https://apps.nextcloud.com/apps/gpoddersync) installed to use this app!]]></description>
|
You need to have [GPodderSync](https://apps.nextcloud.com/apps/gpoddersync) installed to use this app!]]></description>
|
||||||
<version>3.0.0</version>
|
<version>3.1.0</version>
|
||||||
<licence>agpl</licence>
|
<licence>agpl</licence>
|
||||||
<author mail="xefir@crystalyx.net" homepage="https://crystalyx.net">Michel Roux</author>
|
<author mail="xefir@crystalyx.net" homepage="https://crystalyx.net">Michel Roux</author>
|
||||||
<namespace>RePod</namespace>
|
<namespace>RePod</namespace>
|
||||||
|
|
|
@ -13,6 +13,8 @@ declare(strict_types=1);
|
||||||
return [
|
return [
|
||||||
'routes' => [
|
'routes' => [
|
||||||
['name' => 'page#index', 'url' => '/', 'verb' => 'GET'],
|
['name' => 'page#index', 'url' => '/', 'verb' => 'GET'],
|
||||||
|
['name' => 'page#feed', 'url' => '/feed/{path}', 'verb' => 'GET', 'requirements' => ['path' => '.+']],
|
||||||
|
['name' => 'page#discover', 'url' => '/discover', 'verb' => 'GET'],
|
||||||
['name' => 'episodes#action', 'url' => '/episodes/action', 'verb' => 'GET'],
|
['name' => 'episodes#action', 'url' => '/episodes/action', 'verb' => 'GET'],
|
||||||
['name' => 'episodes#list', 'url' => '/episodes/list', 'verb' => 'GET'],
|
['name' => 'episodes#list', 'url' => '/episodes/list', 'verb' => 'GET'],
|
||||||
['name' => 'opml#export', 'url' => '/opml/export', 'verb' => 'GET'],
|
['name' => 'opml#export', 'url' => '/opml/export', 'verb' => 'GET'],
|
||||||
|
|
|
@ -15,9 +15,9 @@
|
||||||
"psalm": "psalm --threads=1 --no-cache --show-info=true"
|
"psalm": "psalm --threads=1 --no-cache --show-info=true"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"nextcloud/ocp": "^29.0.4",
|
"nextcloud/ocp": "^29.0.5",
|
||||||
"roave/security-advisories": "dev-latest",
|
"roave/security-advisories": "dev-latest",
|
||||||
"nextcloud/coding-standard": "^1.2.1",
|
"nextcloud/coding-standard": "^1.2.3",
|
||||||
"vimeo/psalm": "^5.25.0"
|
"vimeo/psalm": "^5.25.0"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "855f98aa313f86222776e061139c42ec",
|
"content-hash": "a59841dc91b50fc36ec116bab55543b0",
|
||||||
"packages": [],
|
"packages": [],
|
||||||
"packages-dev": [
|
"packages-dev": [
|
||||||
{
|
{
|
||||||
|
@ -169,26 +169,26 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "composer/pcre",
|
"name": "composer/pcre",
|
||||||
"version": "3.2.0",
|
"version": "3.3.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/composer/pcre.git",
|
"url": "https://github.com/composer/pcre.git",
|
||||||
"reference": "ea4ab6f9580a4fd221e0418f2c357cdd39102a90"
|
"reference": "1637e067347a0c40bbb1e3cd786b20dcab556a81"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/composer/pcre/zipball/ea4ab6f9580a4fd221e0418f2c357cdd39102a90",
|
"url": "https://api.github.com/repos/composer/pcre/zipball/1637e067347a0c40bbb1e3cd786b20dcab556a81",
|
||||||
"reference": "ea4ab6f9580a4fd221e0418f2c357cdd39102a90",
|
"reference": "1637e067347a0c40bbb1e3cd786b20dcab556a81",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^7.4 || ^8.0"
|
"php": "^7.4 || ^8.0"
|
||||||
},
|
},
|
||||||
"conflict": {
|
"conflict": {
|
||||||
"phpstan/phpstan": "<1.11.8"
|
"phpstan/phpstan": "<1.11.10"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpstan/phpstan": "^1.11.8",
|
"phpstan/phpstan": "^1.11.10",
|
||||||
"phpstan/phpstan-strict-rules": "^1.1",
|
"phpstan/phpstan-strict-rules": "^1.1",
|
||||||
"phpunit/phpunit": "^8 || ^9"
|
"phpunit/phpunit": "^8 || ^9"
|
||||||
},
|
},
|
||||||
|
@ -228,7 +228,7 @@
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/composer/pcre/issues",
|
"issues": "https://github.com/composer/pcre/issues",
|
||||||
"source": "https://github.com/composer/pcre/tree/3.2.0"
|
"source": "https://github.com/composer/pcre/tree/3.3.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -244,7 +244,7 @@
|
||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2024-07-25T09:36:02+00:00"
|
"time": "2024-08-19T19:43:53+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "composer/semver",
|
"name": "composer/semver",
|
||||||
|
@ -1312,12 +1312,12 @@
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/Roave/SecurityAdvisories.git",
|
"url": "https://github.com/Roave/SecurityAdvisories.git",
|
||||||
"reference": "251a4f1fefcc6e6cc90d50514fee6b6e3745cb3e"
|
"reference": "f8de2a81061775002d96aea80b12f2ab3c5eeb8d"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/251a4f1fefcc6e6cc90d50514fee6b6e3745cb3e",
|
"url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/f8de2a81061775002d96aea80b12f2ab3c5eeb8d",
|
||||||
"reference": "251a4f1fefcc6e6cc90d50514fee6b6e3745cb3e",
|
"reference": "f8de2a81061775002d96aea80b12f2ab3c5eeb8d",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"conflict": {
|
"conflict": {
|
||||||
|
@ -1355,7 +1355,7 @@
|
||||||
"athlon1600/php-proxy-app": "<=3",
|
"athlon1600/php-proxy-app": "<=3",
|
||||||
"austintoddj/canvas": "<=3.4.2",
|
"austintoddj/canvas": "<=3.4.2",
|
||||||
"auth0/wordpress": "<=4.6",
|
"auth0/wordpress": "<=4.6",
|
||||||
"automad/automad": "<=2.0.0.0-alpha5",
|
"automad/automad": "<2.0.0.0-alpha5",
|
||||||
"automattic/jetpack": "<9.8",
|
"automattic/jetpack": "<9.8",
|
||||||
"awesome-support/awesome-support": "<=6.0.7",
|
"awesome-support/awesome-support": "<=6.0.7",
|
||||||
"aws/aws-sdk-php": "<3.288.1",
|
"aws/aws-sdk-php": "<3.288.1",
|
||||||
|
@ -1528,7 +1528,7 @@
|
||||||
"friendsoftypo3/mediace": ">=7.6.2,<7.6.5",
|
"friendsoftypo3/mediace": ">=7.6.2,<7.6.5",
|
||||||
"friendsoftypo3/openid": ">=4.5,<4.5.31|>=4.7,<4.7.16|>=6,<6.0.11|>=6.1,<6.1.6",
|
"friendsoftypo3/openid": ">=4.5,<4.5.31|>=4.7,<4.7.16|>=6,<6.0.11|>=6.1,<6.1.6",
|
||||||
"froala/wysiwyg-editor": "<3.2.7|>=4.0.1,<=4.1.3",
|
"froala/wysiwyg-editor": "<3.2.7|>=4.0.1,<=4.1.3",
|
||||||
"froxlor/froxlor": "<2.1.9",
|
"froxlor/froxlor": "<=2.2.0.0-RC3",
|
||||||
"frozennode/administrator": "<=5.0.12",
|
"frozennode/administrator": "<=5.0.12",
|
||||||
"fuel/core": "<1.8.1",
|
"fuel/core": "<1.8.1",
|
||||||
"funadmin/funadmin": "<=3.2|>=3.3.2,<=3.3.3",
|
"funadmin/funadmin": "<=3.2|>=3.3.2,<=3.3.3",
|
||||||
|
@ -2121,7 +2121,7 @@
|
||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2024-08-14T19:05:08+00:00"
|
"time": "2024-08-23T19:04:38+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "sebastian/diff",
|
"name": "sebastian/diff",
|
||||||
|
|
11
l10n/de.js
11
l10n/de.js
|
@ -18,11 +18,10 @@ OC.L10N.register(
|
||||||
"Link copied to the clipboard" : "Der Link des Feeds wurde in die Zwischenablage kopiert",
|
"Link copied to the clipboard" : "Der Link des Feeds wurde in die Zwischenablage kopiert",
|
||||||
"Play" : "Abspielen",
|
"Play" : "Abspielen",
|
||||||
"Stop" : "Stopp",
|
"Stop" : "Stopp",
|
||||||
"Mark as read" : "Als gelesen markieren",
|
"Read" : "Gelesen",
|
||||||
"Mark as unread" : "Als ungelesen markieren",
|
|
||||||
"Open website" : "Webseite aufrufen",
|
"Open website" : "Webseite aufrufen",
|
||||||
"Could not fetch episodes" : "Folgen können nicht abgerufen werden",
|
|
||||||
"Could not change the status of the episode" : "Kann den Status der Folge nicht ändern",
|
"Could not change the status of the episode" : "Kann den Status der Folge nicht ändern",
|
||||||
|
"Could not fetch episodes" : "Folgen können nicht abgerufen werden",
|
||||||
"Export subscriptions" : "Abonnements exportieren",
|
"Export subscriptions" : "Abonnements exportieren",
|
||||||
"Filtering episodes" : "Folgen filtern",
|
"Filtering episodes" : "Folgen filtern",
|
||||||
"Show all" : "Zeige alles",
|
"Show all" : "Zeige alles",
|
||||||
|
@ -33,13 +32,17 @@ OC.L10N.register(
|
||||||
"Import OPML file" : "Importiere OPML-Datei",
|
"Import OPML file" : "Importiere OPML-Datei",
|
||||||
"Rate RePod ❤️" : "Bewerte RePod ❤️",
|
"Rate RePod ❤️" : "Bewerte RePod ❤️",
|
||||||
"Playback speed" : "Wiedergabegeschwindigkeit",
|
"Playback speed" : "Wiedergabegeschwindigkeit",
|
||||||
|
"Favorite" : "Favorit",
|
||||||
"Are you sure you want to delete this subscription?" : "Bist Du sicher, dass Du das Abonnement löschen möchtest?",
|
"Are you sure you want to delete this subscription?" : "Bist Du sicher, dass Du das Abonnement löschen möchtest?",
|
||||||
"Error while removing the feed" : "Fehler beim Löschen des Feeds",
|
"Error while removing the feed" : "Fehler beim Löschen des Feeds",
|
||||||
|
"You can only have 10 favorites" : "Du kannst nur 10 Favoriten haben",
|
||||||
"Add a podcast" : "Einen Podcast hinzufügen",
|
"Add a podcast" : "Einen Podcast hinzufügen",
|
||||||
"Could not fetch subscriptions" : "Abonnements können nicht abgerufen werden",
|
"Could not fetch subscriptions" : "Abonnements können nicht abgerufen werden",
|
||||||
"Find a podcast" : "Finde einen Podcast",
|
"Find a podcast" : "Finde einen Podcast",
|
||||||
"Error loading feed" : "Fehler beim Laden des Feeds",
|
"Error loading feed" : "Fehler beim Laden des Feeds",
|
||||||
"Missing required app" : "Benötigte App fehlt",
|
"Missing required app" : "Benötigte App fehlt",
|
||||||
"Install GPodder Sync" : "Installiere GPodder Sync"
|
"Install GPodder Sync" : "Installiere GPodder Sync",
|
||||||
|
"Pin some subscriptions to see their latest updates" : "Pinne einige Abonnements, um ihre neuesten Updates zu sehen",
|
||||||
|
"No favorites" : "Keine Favoriten"
|
||||||
},
|
},
|
||||||
"");
|
"");
|
||||||
|
|
11
l10n/de.json
11
l10n/de.json
|
@ -16,11 +16,10 @@
|
||||||
"Link copied to the clipboard" : "Der Link des Feeds wurde in die Zwischenablage kopiert",
|
"Link copied to the clipboard" : "Der Link des Feeds wurde in die Zwischenablage kopiert",
|
||||||
"Play" : "Abspielen",
|
"Play" : "Abspielen",
|
||||||
"Stop" : "Stopp",
|
"Stop" : "Stopp",
|
||||||
"Mark as read" : "Als gelesen markieren",
|
"Read" : "Gelesen",
|
||||||
"Mark as unread" : "Als ungelesen markieren",
|
|
||||||
"Open website" : "Webseite aufrufen",
|
"Open website" : "Webseite aufrufen",
|
||||||
"Could not fetch episodes" : "Folgen können nicht abgerufen werden",
|
|
||||||
"Could not change the status of the episode" : "Kann den Status der Folge nicht ändern",
|
"Could not change the status of the episode" : "Kann den Status der Folge nicht ändern",
|
||||||
|
"Could not fetch episodes" : "Folgen können nicht abgerufen werden",
|
||||||
"Export subscriptions" : "Abonnements exportieren",
|
"Export subscriptions" : "Abonnements exportieren",
|
||||||
"Filtering episodes" : "Folgen filtern",
|
"Filtering episodes" : "Folgen filtern",
|
||||||
"Show all" : "Zeige alles",
|
"Show all" : "Zeige alles",
|
||||||
|
@ -31,13 +30,17 @@
|
||||||
"Import OPML file" : "Importiere OPML-Datei",
|
"Import OPML file" : "Importiere OPML-Datei",
|
||||||
"Rate RePod ❤️" : "Bewerte RePod ❤️",
|
"Rate RePod ❤️" : "Bewerte RePod ❤️",
|
||||||
"Playback speed" : "Wiedergabegeschwindigkeit",
|
"Playback speed" : "Wiedergabegeschwindigkeit",
|
||||||
|
"Favorite" : "Favorit",
|
||||||
"Are you sure you want to delete this subscription?" : "Bist Du sicher, dass Du das Abonnement löschen möchtest?",
|
"Are you sure you want to delete this subscription?" : "Bist Du sicher, dass Du das Abonnement löschen möchtest?",
|
||||||
"Error while removing the feed" : "Fehler beim Löschen des Feeds",
|
"Error while removing the feed" : "Fehler beim Löschen des Feeds",
|
||||||
|
"You can only have 10 favorites" : "Du kannst nur 10 Favoriten haben",
|
||||||
"Add a podcast" : "Einen Podcast hinzufügen",
|
"Add a podcast" : "Einen Podcast hinzufügen",
|
||||||
"Could not fetch subscriptions" : "Abonnements können nicht abgerufen werden",
|
"Could not fetch subscriptions" : "Abonnements können nicht abgerufen werden",
|
||||||
"Find a podcast" : "Finde einen Podcast",
|
"Find a podcast" : "Finde einen Podcast",
|
||||||
"Error loading feed" : "Fehler beim Laden des Feeds",
|
"Error loading feed" : "Fehler beim Laden des Feeds",
|
||||||
"Missing required app" : "Benötigte App fehlt",
|
"Missing required app" : "Benötigte App fehlt",
|
||||||
"Install GPodder Sync" : "Installiere GPodder Sync"
|
"Install GPodder Sync" : "Installiere GPodder Sync",
|
||||||
|
"Pin some subscriptions to see their latest updates" : "Pinne einige Abonnements, um ihre neuesten Updates zu sehen",
|
||||||
|
"No favorites" : "Keine Favoriten"
|
||||||
},"pluralForm" :""
|
},"pluralForm" :""
|
||||||
}
|
}
|
11
l10n/fr.js
11
l10n/fr.js
|
@ -18,11 +18,10 @@ OC.L10N.register(
|
||||||
"Link copied to the clipboard" : "Lien vers le flux copié dans le presse-papiers",
|
"Link copied to the clipboard" : "Lien vers le flux copié dans le presse-papiers",
|
||||||
"Play" : "Lecture",
|
"Play" : "Lecture",
|
||||||
"Stop" : "Arrêter",
|
"Stop" : "Arrêter",
|
||||||
"Mark as read" : "Marquer comme lu",
|
"Read" : "Lu",
|
||||||
"Mark as unread" : "Marquer comme non lu",
|
|
||||||
"Open website" : "Ouvrir le site web",
|
"Open website" : "Ouvrir le site web",
|
||||||
"Could not fetch episodes" : "Impossible de récuprer les épisodes",
|
|
||||||
"Could not change the status of the episode" : "Impossible de changer le status de l'épisode",
|
"Could not change the status of the episode" : "Impossible de changer le status de l'épisode",
|
||||||
|
"Could not fetch episodes" : "Impossible de récuprer les épisodes",
|
||||||
"Export subscriptions" : "Exporter les abonnements",
|
"Export subscriptions" : "Exporter les abonnements",
|
||||||
"Filtering episodes" : "Filtrage des épisodes",
|
"Filtering episodes" : "Filtrage des épisodes",
|
||||||
"Show all" : "Montrer tout",
|
"Show all" : "Montrer tout",
|
||||||
|
@ -33,13 +32,17 @@ OC.L10N.register(
|
||||||
"Import OPML file" : "Importer un fichier OPML",
|
"Import OPML file" : "Importer un fichier OPML",
|
||||||
"Rate RePod ❤️" : "Donnez votre avis ❤️",
|
"Rate RePod ❤️" : "Donnez votre avis ❤️",
|
||||||
"Playback speed" : "Vitesse de lecture",
|
"Playback speed" : "Vitesse de lecture",
|
||||||
|
"Favorite" : "Favori",
|
||||||
"Are you sure you want to delete this subscription?" : "Êtes-vous sûr de vouloir supprimer ce flux ?",
|
"Are you sure you want to delete this subscription?" : "Êtes-vous sûr de vouloir supprimer ce flux ?",
|
||||||
"Error while removing the feed" : "Erreur lors de la suppression du flux",
|
"Error while removing the feed" : "Erreur lors de la suppression du flux",
|
||||||
|
"You can only have 10 favorites" : "Vous ne pouvez avoir que 10 favoris",
|
||||||
"Add a podcast" : "Ajouter un podcast",
|
"Add a podcast" : "Ajouter un podcast",
|
||||||
"Could not fetch subscriptions" : "Impossible de récupérer les flux",
|
"Could not fetch subscriptions" : "Impossible de récupérer les flux",
|
||||||
"Find a podcast" : "Chercher un podcast",
|
"Find a podcast" : "Chercher un podcast",
|
||||||
"Error loading feed" : "Erreur lors du chargement du flux",
|
"Error loading feed" : "Erreur lors du chargement du flux",
|
||||||
"Missing required app" : "Une application requise est manquante",
|
"Missing required app" : "Une application requise est manquante",
|
||||||
"Install GPodder Sync" : "Installer GPodder Sync"
|
"Install GPodder Sync" : "Installer GPodder Sync",
|
||||||
|
"Pin some subscriptions to see their latest updates" : "Ajoutez des abonnements en favoris pour obtenir les dernières nouvelles ici",
|
||||||
|
"No favorites" : "Aucun favoris"
|
||||||
},
|
},
|
||||||
"");
|
"");
|
||||||
|
|
11
l10n/fr.json
11
l10n/fr.json
|
@ -16,11 +16,10 @@
|
||||||
"Link copied to the clipboard" : "Lien vers le flux copié dans le presse-papiers",
|
"Link copied to the clipboard" : "Lien vers le flux copié dans le presse-papiers",
|
||||||
"Play" : "Lecture",
|
"Play" : "Lecture",
|
||||||
"Stop" : "Arrêter",
|
"Stop" : "Arrêter",
|
||||||
"Mark as read" : "Marquer comme lu",
|
"Read" : "Lu",
|
||||||
"Mark as unread" : "Marquer comme non lu",
|
|
||||||
"Open website" : "Ouvrir le site web",
|
"Open website" : "Ouvrir le site web",
|
||||||
"Could not fetch episodes" : "Impossible de récuprer les épisodes",
|
|
||||||
"Could not change the status of the episode" : "Impossible de changer le status de l'épisode",
|
"Could not change the status of the episode" : "Impossible de changer le status de l'épisode",
|
||||||
|
"Could not fetch episodes" : "Impossible de récuprer les épisodes",
|
||||||
"Export subscriptions" : "Exporter les abonnements",
|
"Export subscriptions" : "Exporter les abonnements",
|
||||||
"Filtering episodes" : "Filtrage des épisodes",
|
"Filtering episodes" : "Filtrage des épisodes",
|
||||||
"Show all" : "Montrer tout",
|
"Show all" : "Montrer tout",
|
||||||
|
@ -31,13 +30,17 @@
|
||||||
"Import OPML file" : "Importer un fichier OPML",
|
"Import OPML file" : "Importer un fichier OPML",
|
||||||
"Rate RePod ❤️" : "Donnez votre avis ❤️",
|
"Rate RePod ❤️" : "Donnez votre avis ❤️",
|
||||||
"Playback speed" : "Vitesse de lecture",
|
"Playback speed" : "Vitesse de lecture",
|
||||||
|
"Favorite" : "Favori",
|
||||||
"Are you sure you want to delete this subscription?" : "Êtes-vous sûr de vouloir supprimer ce flux ?",
|
"Are you sure you want to delete this subscription?" : "Êtes-vous sûr de vouloir supprimer ce flux ?",
|
||||||
"Error while removing the feed" : "Erreur lors de la suppression du flux",
|
"Error while removing the feed" : "Erreur lors de la suppression du flux",
|
||||||
|
"You can only have 10 favorites" : "Vous ne pouvez avoir que 10 favoris",
|
||||||
"Add a podcast" : "Ajouter un podcast",
|
"Add a podcast" : "Ajouter un podcast",
|
||||||
"Could not fetch subscriptions" : "Impossible de récupérer les flux",
|
"Could not fetch subscriptions" : "Impossible de récupérer les flux",
|
||||||
"Find a podcast" : "Chercher un podcast",
|
"Find a podcast" : "Chercher un podcast",
|
||||||
"Error loading feed" : "Erreur lors du chargement du flux",
|
"Error loading feed" : "Erreur lors du chargement du flux",
|
||||||
"Missing required app" : "Une application requise est manquante",
|
"Missing required app" : "Une application requise est manquante",
|
||||||
"Install GPodder Sync" : "Installer GPodder Sync"
|
"Install GPodder Sync" : "Installer GPodder Sync",
|
||||||
|
"Pin some subscriptions to see their latest updates" : "Ajoutez des abonnements en favoris pour obtenir les dernières nouvelles ici",
|
||||||
|
"No favorites" : "Aucun favoris"
|
||||||
},"pluralForm" :""
|
},"pluralForm" :""
|
||||||
}
|
}
|
|
@ -32,6 +32,10 @@ class Application extends App implements IBootstrap
|
||||||
/** @var IInitialState $initialState */
|
/** @var IInitialState $initialState */
|
||||||
$initialState = $appContainer->get(IInitialState::class);
|
$initialState = $appContainer->get(IInitialState::class);
|
||||||
|
|
||||||
|
if (null === $appManager->getAppInfo(self::GPODDERSYNC_ID)) {
|
||||||
|
$appManager->disableApp(self::GPODDERSYNC_ID);
|
||||||
|
}
|
||||||
|
|
||||||
$gpoddersync = $appManager->isEnabledForUser(self::GPODDERSYNC_ID);
|
$gpoddersync = $appManager->isEnabledForUser(self::GPODDERSYNC_ID);
|
||||||
if (!$gpoddersync) {
|
if (!$gpoddersync) {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -8,10 +8,19 @@ use OCA\RePod\AppInfo\Application;
|
||||||
use OCP\AppFramework\Controller;
|
use OCP\AppFramework\Controller;
|
||||||
use OCP\AppFramework\Http\ContentSecurityPolicy;
|
use OCP\AppFramework\Http\ContentSecurityPolicy;
|
||||||
use OCP\AppFramework\Http\TemplateResponse;
|
use OCP\AppFramework\Http\TemplateResponse;
|
||||||
|
use OCP\IConfig;
|
||||||
|
use OCP\IRequest;
|
||||||
use OCP\Util;
|
use OCP\Util;
|
||||||
|
|
||||||
class PageController extends Controller
|
class PageController extends Controller
|
||||||
{
|
{
|
||||||
|
public function __construct(
|
||||||
|
IRequest $request,
|
||||||
|
private IConfig $config
|
||||||
|
) {
|
||||||
|
parent::__construct(Application::APP_ID, $request);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @NoAdminRequired
|
* @NoAdminRequired
|
||||||
* @NoCSRFRequired
|
* @NoCSRFRequired
|
||||||
|
@ -23,9 +32,32 @@ class PageController extends Controller
|
||||||
$csp->addAllowedImageDomain('*');
|
$csp->addAllowedImageDomain('*');
|
||||||
$csp->addAllowedMediaDomain('*');
|
$csp->addAllowedMediaDomain('*');
|
||||||
|
|
||||||
|
if ($this->config->getSystemValueBool('debug', false)) {
|
||||||
|
/** @psalm-suppress DeprecatedMethod */
|
||||||
|
$csp->allowEvalScript();
|
||||||
|
$csp->addAllowedConnectDomain('*');
|
||||||
|
$csp->addAllowedScriptDomain('*');
|
||||||
|
}
|
||||||
|
|
||||||
$response = new TemplateResponse(Application::APP_ID, 'main');
|
$response = new TemplateResponse(Application::APP_ID, 'main');
|
||||||
$response->setContentSecurityPolicy($csp);
|
$response->setContentSecurityPolicy($csp);
|
||||||
|
|
||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
* @NoCSRFRequired
|
||||||
|
*/
|
||||||
|
public function discover(): TemplateResponse {
|
||||||
|
return $this->index();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
* @NoCSRFRequired
|
||||||
|
*/
|
||||||
|
public function feed(): TemplateResponse {
|
||||||
|
return $this->index();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -26,7 +26,7 @@
|
||||||
"linkify-html": "^4.1.3",
|
"linkify-html": "^4.1.3",
|
||||||
"pinia": "^2.2.2",
|
"pinia": "^2.2.2",
|
||||||
"toastify-js": "^1.12.0",
|
"toastify-js": "^1.12.0",
|
||||||
"vite": "^5.4.1",
|
"vite": "^5.4.2",
|
||||||
"vue": "^3.4.38",
|
"vue": "^3.4.38",
|
||||||
"vue-material-design-icons": "^5.3.0",
|
"vue-material-design-icons": "^5.3.0",
|
||||||
"vue-router": "^4.4.3"
|
"vue-router": "^4.4.3"
|
||||||
|
@ -37,7 +37,8 @@
|
||||||
"@nextcloud/prettier-config": "^1.1.0",
|
"@nextcloud/prettier-config": "^1.1.0",
|
||||||
"@nextcloud/stylelint-config": "^3.0.1",
|
"@nextcloud/stylelint-config": "^3.0.1",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-plugin-pinia": "^0.4.0",
|
"eslint-plugin-pinia": "^0.4.1",
|
||||||
"eslint-plugin-prettier": "^5.2.1"
|
"eslint-plugin-prettier": "^5.2.1",
|
||||||
|
"vite-plugin-vue-devtools": "^7.3.9"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<NcAppContent :class="{ padding: episode }">
|
<NcAppContent :class="{ episode }">
|
||||||
<slot />
|
<slot />
|
||||||
</NcAppContent>
|
</NcAppContent>
|
||||||
</template>
|
</template>
|
||||||
|
@ -21,7 +21,7 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.padding {
|
.episode {
|
||||||
padding-bottom: 6rem;
|
padding-bottom: 6rem;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
<template>
|
||||||
|
<NcEmptyContent class="empty">
|
||||||
|
<slot />
|
||||||
|
</NcEmptyContent>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { NcEmptyContent } from '@nextcloud/vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'EmptyContent',
|
||||||
|
components: {
|
||||||
|
NcEmptyContent,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.empty {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,25 +1,29 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<NcAvatar :display-name="name" :is-no-user="true" :size="256" :url="image" />
|
<NcAvatar
|
||||||
<h2>{{ name }}</h2>
|
:display-name="episode.name"
|
||||||
<SafeHtml :source="description" />
|
:is-no-user="true"
|
||||||
|
:size="256"
|
||||||
|
:url="episode.image" />
|
||||||
|
<h2>{{ episode.name }}</h2>
|
||||||
|
<SafeHtml :source="episode.description" />
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<NcButton v-if="link" :href="link" target="_blank">
|
<NcButton v-if="episode.link" :href="episode.link" target="_blank">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<OpenInNewIcon :size="20" />
|
<OpenInNewIcon :size="20" />
|
||||||
</template>
|
</template>
|
||||||
{{ title }}
|
{{ episode.title }}
|
||||||
</NcButton>
|
</NcButton>
|
||||||
<NcButton
|
<NcButton
|
||||||
v-if="url"
|
v-if="episode.url"
|
||||||
:download="filenameFromUrl(url)"
|
:download="filenameFromUrl(episode.url)"
|
||||||
:href="url"
|
:href="episode.url"
|
||||||
target="_blank">
|
target="_blank">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<DownloadIcon :size="20" />
|
<DownloadIcon :size="20" />
|
||||||
</template>
|
</template>
|
||||||
{{ t('repod', 'Download') }}
|
{{ t('repod', 'Download') }}
|
||||||
{{ size ? `(${humanFileSize(size)})` : '' }}
|
{{ episode.size ? `(${humanFileSize(episode.size)})` : '' }}
|
||||||
</NcButton>
|
</NcButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -43,32 +47,8 @@ export default {
|
||||||
SafeHtml,
|
SafeHtml,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
description: {
|
episode: {
|
||||||
type: String,
|
type: Object,
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
image: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
link: {
|
|
||||||
type: String,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
size: {
|
|
||||||
type: Number,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
url: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<NcAppNavigationList>
|
<NcAppNavigationList>
|
||||||
<NcAppNavigationNewItem
|
<NcAppNavigationNewItem
|
||||||
:name="t('repod', 'Add a RSS link')"
|
:name="t('repod', 'Add a RSS link')"
|
||||||
@new-item="addSubscription">
|
@new-item="(url) => $router.push(toFeedUrl(url))">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<PlusIcon :size="20" />
|
<PlusIcon :size="20" />
|
||||||
</template>
|
</template>
|
||||||
|
@ -13,7 +13,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { NcAppNavigationList, NcAppNavigationNewItem } from '@nextcloud/vue'
|
import { NcAppNavigationList, NcAppNavigationNewItem } from '@nextcloud/vue'
|
||||||
import PlusIcon from 'vue-material-design-icons/Plus.vue'
|
import PlusIcon from 'vue-material-design-icons/Plus.vue'
|
||||||
import { encodeUrl } from '../../utils/url.js'
|
import { toFeedUrl } from '../../utils/url.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'AddRss',
|
name: 'AddRss',
|
||||||
|
@ -23,9 +23,7 @@ export default {
|
||||||
PlusIcon,
|
PlusIcon,
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
addSubscription(feedUrl) {
|
toFeedUrl,
|
||||||
this.$router.push(encodeUrl(feedUrl))
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
:key="feed.link"
|
:key="feed.link"
|
||||||
:details="formatLocaleDate(new Date(feed.fetchedAtUnix * 1000))"
|
:details="formatLocaleDate(new Date(feed.fetchedAtUnix * 1000))"
|
||||||
:name="feed.title"
|
:name="feed.title"
|
||||||
:to="toUrl(feed.link)">
|
:to="toFeedUrl(feed.link)">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<NcAvatar
|
<NcAvatar
|
||||||
:display-name="feed.author"
|
:display-name="feed.author"
|
||||||
|
@ -19,7 +19,7 @@
|
||||||
</template>
|
</template>
|
||||||
<template #actions>
|
<template #actions>
|
||||||
<NcActionButton
|
<NcActionButton
|
||||||
v-if="!subscriptions.includes(feed.link)"
|
v-if="!getSubscriptions.includes(feed.link)"
|
||||||
:aria-label="t('repod', 'Subscribe')"
|
:aria-label="t('repod', 'Subscribe')"
|
||||||
:name="t('repod', 'Subscribe')"
|
:name="t('repod', 'Subscribe')"
|
||||||
:title="t('repod', 'Subscribe')"
|
:title="t('repod', 'Subscribe')"
|
||||||
|
@ -44,7 +44,7 @@ import { debounce } from '../../utils/debounce.js'
|
||||||
import { formatLocaleDate } from '../../utils/time.js'
|
import { formatLocaleDate } from '../../utils/time.js'
|
||||||
import { generateUrl } from '@nextcloud/router'
|
import { generateUrl } from '@nextcloud/router'
|
||||||
import { showError } from '../../utils/toast.js'
|
import { showError } from '../../utils/toast.js'
|
||||||
import { toUrl } from '../../utils/url.js'
|
import { toFeedUrl } from '../../utils/url.js'
|
||||||
import { useSubscriptions } from '../../store/subscriptions.js'
|
import { useSubscriptions } from '../../store/subscriptions.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -62,14 +62,12 @@ export default {
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data: () => ({
|
||||||
return {
|
|
||||||
feeds: [],
|
feeds: [],
|
||||||
loading: false,
|
loading: false,
|
||||||
}
|
}),
|
||||||
},
|
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(useSubscriptions, ['subscriptions']),
|
...mapState(useSubscriptions, ['getSubscriptions']),
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
value() {
|
value() {
|
||||||
|
@ -79,7 +77,7 @@ export default {
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(useSubscriptions, ['fetch']),
|
...mapActions(useSubscriptions, ['fetch']),
|
||||||
formatLocaleDate,
|
formatLocaleDate,
|
||||||
toUrl,
|
toFeedUrl,
|
||||||
async addSubscription(url) {
|
async addSubscription(url) {
|
||||||
try {
|
try {
|
||||||
await axios.post(
|
await axios.post(
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<Loading v-if="loading" />
|
<Loading v-if="loading" />
|
||||||
<ul v-if="!loading">
|
<ul v-if="!loading">
|
||||||
<li v-for="top in tops" :key="top.link">
|
<li v-for="top in tops" :key="top.link">
|
||||||
<router-link :to="toUrl(top.link)">
|
<router-link :to="toFeedUrl(top.link)">
|
||||||
<img :src="top.imageUrl" :title="top.author" />
|
<img :src="top.imageUrl" :title="top.author" />
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
|
@ -17,7 +17,7 @@ import Loading from '../Atoms/Loading.vue'
|
||||||
import axios from '@nextcloud/axios'
|
import axios from '@nextcloud/axios'
|
||||||
import { generateUrl } from '@nextcloud/router'
|
import { generateUrl } from '@nextcloud/router'
|
||||||
import { showError } from '../../utils/toast.js'
|
import { showError } from '../../utils/toast.js'
|
||||||
import { toUrl } from '../../utils/url.js'
|
import { toFeedUrl } from '../../utils/url.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Toplist',
|
name: 'Toplist',
|
||||||
|
@ -30,12 +30,10 @@ export default {
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data: () => ({
|
||||||
return {
|
|
||||||
loading: true,
|
loading: true,
|
||||||
tops: [],
|
tops: [],
|
||||||
}
|
}),
|
||||||
},
|
|
||||||
computed: {
|
computed: {
|
||||||
title() {
|
title() {
|
||||||
switch (this.type) {
|
switch (this.type) {
|
||||||
|
@ -63,7 +61,7 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
toUrl,
|
toFeedUrl,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
<SafeHtml :source="description" />
|
<SafeHtml :source="description" />
|
||||||
</div>
|
</div>
|
||||||
<NcAppNavigationNew
|
<NcAppNavigationNew
|
||||||
v-if="!subscriptions.includes(url)"
|
v-if="!getSubscriptions.includes(url)"
|
||||||
:text="t('repod', 'Subscribe')"
|
:text="t('repod', 'Subscribe')"
|
||||||
@click="addSubscription">
|
@click="addSubscription">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
|
@ -79,7 +79,7 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(useSubscriptions, ['subscriptions']),
|
...mapState(useSubscriptions, ['getSubscriptions']),
|
||||||
url() {
|
url() {
|
||||||
return decodeUrl(this.$route.params.url)
|
return decodeUrl(this.$route.params.url)
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,203 @@
|
||||||
|
<template>
|
||||||
|
<NcListItem
|
||||||
|
:active="isCurrentEpisode(episode)"
|
||||||
|
:details="!oneLine ? formatLocaleDate(new Date(episode.pubDate?.date)) : ''"
|
||||||
|
:force-display-actions="true"
|
||||||
|
:name="episode.name"
|
||||||
|
:one-line="oneLine"
|
||||||
|
:style="{ opacity: hasEnded(episode) ? 0.4 : 1 }"
|
||||||
|
:title="episode.description"
|
||||||
|
@click="modalEpisode = episode">
|
||||||
|
<template #actions>
|
||||||
|
<NcActionButton
|
||||||
|
v-if="!isCurrentEpisode(episode)"
|
||||||
|
:aria-label="t('repod', 'Play')"
|
||||||
|
:title="t('repod', 'Play')"
|
||||||
|
@click="load(episode, url)">
|
||||||
|
<template #icon>
|
||||||
|
<PlayIcon :size="20" />
|
||||||
|
</template>
|
||||||
|
</NcActionButton>
|
||||||
|
<NcActionButton
|
||||||
|
v-if="isCurrentEpisode(episode)"
|
||||||
|
:aria-label="t('repod', 'Stop')"
|
||||||
|
:title="t('repod', 'Stop')"
|
||||||
|
@click="load(null)">
|
||||||
|
<template #icon>
|
||||||
|
<StopIcon :size="20" />
|
||||||
|
</template>
|
||||||
|
</NcActionButton>
|
||||||
|
</template>
|
||||||
|
<template #extra>
|
||||||
|
<NcActions>
|
||||||
|
<NcActionButton
|
||||||
|
v-if="episode.duration"
|
||||||
|
:aria-label="t('repod', 'Read')"
|
||||||
|
:disabled="loading"
|
||||||
|
:model-value="hasEnded(episode)"
|
||||||
|
:name="t('repod', 'Read')"
|
||||||
|
:title="t('repod', 'Read')"
|
||||||
|
@click="markAs(episode, !hasEnded(episode))">
|
||||||
|
<template #icon>
|
||||||
|
<PlaylistPlayIcon v-if="!hasEnded(episode)" :size="20" />
|
||||||
|
<PlaylistRemoveIcon v-if="hasEnded(episode)" :size="20" />
|
||||||
|
</template>
|
||||||
|
</NcActionButton>
|
||||||
|
<NcActionLink
|
||||||
|
v-if="episode.link"
|
||||||
|
:href="episode.link"
|
||||||
|
:name="t('repod', 'Open website')"
|
||||||
|
target="_blank"
|
||||||
|
:title="t('repod', 'Open website')">
|
||||||
|
<template #icon>
|
||||||
|
<OpenInNewIcon :size="20" />
|
||||||
|
</template>
|
||||||
|
</NcActionLink>
|
||||||
|
<NcActionLink
|
||||||
|
v-if="episode.url"
|
||||||
|
:download="filenameFromUrl(episode.url)"
|
||||||
|
:href="episode.url"
|
||||||
|
:name="t('repod', 'Download')"
|
||||||
|
target="_blank"
|
||||||
|
:title="t('repod', 'Download')">
|
||||||
|
<template #icon>
|
||||||
|
<DownloadIcon :size="20" />
|
||||||
|
</template>
|
||||||
|
</NcActionLink>
|
||||||
|
</NcActions>
|
||||||
|
<NcModal v-if="modalEpisode" @close="modalEpisode = null">
|
||||||
|
<Modal :episode="episode" />
|
||||||
|
</NcModal>
|
||||||
|
</template>
|
||||||
|
<template #icon>
|
||||||
|
<NcAvatar
|
||||||
|
:display-name="episode.name"
|
||||||
|
:is-no-user="true"
|
||||||
|
:url="episode.image" />
|
||||||
|
</template>
|
||||||
|
<template #indicator>
|
||||||
|
<NcProgressBar
|
||||||
|
v-if="isListening(episode) && !oneLine"
|
||||||
|
class="progress"
|
||||||
|
:value="(episode.action.position * 100) / episode.action.total" />
|
||||||
|
</template>
|
||||||
|
<template v-if="!oneLine" #subname>
|
||||||
|
{{ episode.duration }}
|
||||||
|
</template>
|
||||||
|
</NcListItem>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
NcActionButton,
|
||||||
|
NcActionLink,
|
||||||
|
NcActions,
|
||||||
|
NcAvatar,
|
||||||
|
NcListItem,
|
||||||
|
NcModal,
|
||||||
|
NcProgressBar,
|
||||||
|
} from '@nextcloud/vue'
|
||||||
|
import {
|
||||||
|
durationToSeconds,
|
||||||
|
formatEpisodeTimestamp,
|
||||||
|
formatLocaleDate,
|
||||||
|
} from '../../utils/time.js'
|
||||||
|
import { hasEnded, isListening } from '../../utils/status.js'
|
||||||
|
import { mapActions, mapState } from 'pinia'
|
||||||
|
import DownloadIcon from 'vue-material-design-icons/Download.vue'
|
||||||
|
import Modal from '../Atoms/Modal.vue'
|
||||||
|
import OpenInNewIcon from 'vue-material-design-icons/OpenInNew.vue'
|
||||||
|
import PlayIcon from 'vue-material-design-icons/Play.vue'
|
||||||
|
import PlaylistPlayIcon from 'vue-material-design-icons/PlaylistPlay.vue'
|
||||||
|
import PlaylistRemoveIcon from 'vue-material-design-icons/PlaylistRemove.vue'
|
||||||
|
import StopIcon from 'vue-material-design-icons/Stop.vue'
|
||||||
|
import axios from '@nextcloud/axios'
|
||||||
|
import { filenameFromUrl } from '../../utils/url.js'
|
||||||
|
import { generateUrl } from '@nextcloud/router'
|
||||||
|
import { showError } from '../../utils/toast.js'
|
||||||
|
import { usePlayer } from '../../store/player.js'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Episode',
|
||||||
|
components: {
|
||||||
|
DownloadIcon,
|
||||||
|
Modal,
|
||||||
|
NcActionButton,
|
||||||
|
NcActionLink,
|
||||||
|
NcActions,
|
||||||
|
NcAvatar,
|
||||||
|
NcListItem,
|
||||||
|
NcModal,
|
||||||
|
NcProgressBar,
|
||||||
|
OpenInNewIcon,
|
||||||
|
PlayIcon,
|
||||||
|
PlaylistPlayIcon,
|
||||||
|
PlaylistRemoveIcon,
|
||||||
|
StopIcon,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
episode: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
oneLine: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
url: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data: () => ({
|
||||||
|
loading: false,
|
||||||
|
modalEpisode: null,
|
||||||
|
}),
|
||||||
|
computed: {
|
||||||
|
...mapState(usePlayer, { playerEpisode: 'episode' }),
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions(usePlayer, ['load']),
|
||||||
|
formatLocaleDate,
|
||||||
|
hasEnded,
|
||||||
|
isListening,
|
||||||
|
filenameFromUrl,
|
||||||
|
isCurrentEpisode(episode) {
|
||||||
|
return this.playerEpisode?.url === episode.url
|
||||||
|
},
|
||||||
|
async markAs(episode, read) {
|
||||||
|
try {
|
||||||
|
this.loading = true
|
||||||
|
episode.action = {
|
||||||
|
podcast: this.url,
|
||||||
|
episode: episode.url,
|
||||||
|
guid: episode.guid,
|
||||||
|
action: 'play',
|
||||||
|
timestamp: formatEpisodeTimestamp(new Date()),
|
||||||
|
started: episode.action?.started || 0,
|
||||||
|
position: read ? durationToSeconds(episode.duration) : 0,
|
||||||
|
total: durationToSeconds(episode.duration),
|
||||||
|
}
|
||||||
|
await axios.post(
|
||||||
|
generateUrl('/apps/gpoddersync/episode_action/create'),
|
||||||
|
[episode.action],
|
||||||
|
)
|
||||||
|
if (read && this.isCurrentEpisode(episode)) {
|
||||||
|
this.load(null)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
showError(t('repod', 'Could not change the status of the episode'))
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.progress {
|
||||||
|
margin-top: 0.4rem;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -2,144 +2,23 @@
|
||||||
<div>
|
<div>
|
||||||
<Loading v-if="loading" />
|
<Loading v-if="loading" />
|
||||||
<ul v-if="!loading">
|
<ul v-if="!loading">
|
||||||
<NcListItem
|
<Episode
|
||||||
v-for="episode in filteredEpisodes"
|
v-for="episode in filteredEpisodes"
|
||||||
:key="episode.guid"
|
:key="episode.guid"
|
||||||
:active="isCurrentEpisode(episode)"
|
:episode="episode"
|
||||||
:details="formatLocaleDate(new Date(episode.pubDate?.date))"
|
:url="url" />
|
||||||
:force-display-actions="true"
|
|
||||||
:href="$route.href"
|
|
||||||
:name="episode.name"
|
|
||||||
:style="{ opacity: hasEnded(episode) ? 0.4 : 1 }"
|
|
||||||
target="_self"
|
|
||||||
:title="episode.description"
|
|
||||||
@click="modalEpisode = episode">
|
|
||||||
<template #actions>
|
|
||||||
<NcActionButton
|
|
||||||
v-if="!isCurrentEpisode(episode)"
|
|
||||||
:aria-label="t('repod', 'Play')"
|
|
||||||
:title="t('repod', 'Play')"
|
|
||||||
@click="load(episode, url)">
|
|
||||||
<template #icon>
|
|
||||||
<PlayIcon :size="20" />
|
|
||||||
</template>
|
|
||||||
</NcActionButton>
|
|
||||||
<NcActionButton
|
|
||||||
v-if="isCurrentEpisode(episode)"
|
|
||||||
:aria-label="t('repod', 'Stop')"
|
|
||||||
:title="t('repod', 'Stop')"
|
|
||||||
@click="load(null)">
|
|
||||||
<template #icon>
|
|
||||||
<StopIcon :size="20" />
|
|
||||||
</template>
|
|
||||||
</NcActionButton>
|
|
||||||
</template>
|
|
||||||
<template #extra>
|
|
||||||
<NcActions>
|
|
||||||
<NcActionButton
|
|
||||||
v-if="episode.duration && !hasEnded(episode)"
|
|
||||||
:aria-label="t('repod', 'Mark as read')"
|
|
||||||
:disabled="loadingAction"
|
|
||||||
:name="t('repod', 'Mark as read')"
|
|
||||||
:title="t('repod', 'Mark as read')"
|
|
||||||
@click="markAs(episode, true)">
|
|
||||||
<template #icon>
|
|
||||||
<PlaylistPlayIcon :size="20" />
|
|
||||||
</template>
|
|
||||||
</NcActionButton>
|
|
||||||
<NcActionButton
|
|
||||||
v-if="episode.duration && hasEnded(episode)"
|
|
||||||
:aria-label="t('repod', 'Mark as unread')"
|
|
||||||
:disabled="loadingAction"
|
|
||||||
:name="t('repod', 'Mark as unread')"
|
|
||||||
:title="t('repod', 'Mark as unread')"
|
|
||||||
@click="markAs(episode, false)">
|
|
||||||
<template #icon>
|
|
||||||
<PlaylistRemoveIcon :size="20" />
|
|
||||||
</template>
|
|
||||||
</NcActionButton>
|
|
||||||
<NcActionLink
|
|
||||||
v-if="episode.link"
|
|
||||||
:href="episode.link"
|
|
||||||
:name="t('repod', 'Open website')"
|
|
||||||
target="_blank"
|
|
||||||
:title="t('repod', 'Open website')">
|
|
||||||
<template #icon>
|
|
||||||
<OpenInNewIcon :size="20" />
|
|
||||||
</template>
|
|
||||||
</NcActionLink>
|
|
||||||
<NcActionLink
|
|
||||||
v-if="episode.url"
|
|
||||||
:download="filenameFromUrl(episode.url)"
|
|
||||||
:href="episode.url"
|
|
||||||
:name="t('repod', 'Download')"
|
|
||||||
target="_blank"
|
|
||||||
:title="t('repod', 'Download')">
|
|
||||||
<template #icon>
|
|
||||||
<DownloadIcon :size="20" />
|
|
||||||
</template>
|
|
||||||
</NcActionLink>
|
|
||||||
</NcActions>
|
|
||||||
</template>
|
|
||||||
<template #icon>
|
|
||||||
<NcAvatar
|
|
||||||
:display-name="episode.name"
|
|
||||||
:is-no-user="true"
|
|
||||||
:url="episode.image" />
|
|
||||||
</template>
|
|
||||||
<template #indicator>
|
|
||||||
<NcProgressBar
|
|
||||||
v-if="isListening(episode)"
|
|
||||||
class="progress"
|
|
||||||
:value="
|
|
||||||
(episode.action.position * 100) / episode.action.total
|
|
||||||
" />
|
|
||||||
</template>
|
|
||||||
<template #subname>
|
|
||||||
{{ episode.duration }}
|
|
||||||
</template>
|
|
||||||
</NcListItem>
|
|
||||||
</ul>
|
</ul>
|
||||||
<NcModal v-if="modalEpisode" @close="modalEpisode = null">
|
|
||||||
<Modal
|
|
||||||
:description="modalEpisode.description"
|
|
||||||
:image="modalEpisode.image"
|
|
||||||
:link="modalEpisode.link"
|
|
||||||
:name="modalEpisode.name"
|
|
||||||
:size="modalEpisode.size"
|
|
||||||
:title="modalEpisode.title"
|
|
||||||
:url="modalEpisode.url" />
|
|
||||||
</NcModal>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {
|
import { hasEnded, isListening } from '../../utils/status.js'
|
||||||
NcActionButton,
|
import Episode from './Episode.vue'
|
||||||
NcActionLink,
|
|
||||||
NcActions,
|
|
||||||
NcAvatar,
|
|
||||||
NcListItem,
|
|
||||||
NcModal,
|
|
||||||
NcProgressBar,
|
|
||||||
} from '@nextcloud/vue'
|
|
||||||
import { decodeUrl, filenameFromUrl } from '../../utils/url.js'
|
|
||||||
import {
|
|
||||||
durationToSeconds,
|
|
||||||
formatEpisodeTimestamp,
|
|
||||||
formatLocaleDate,
|
|
||||||
} from '../../utils/time.js'
|
|
||||||
import { mapActions, mapState } from 'pinia'
|
|
||||||
import DownloadIcon from 'vue-material-design-icons/Download.vue'
|
|
||||||
import Loading from '../Atoms/Loading.vue'
|
import Loading from '../Atoms/Loading.vue'
|
||||||
import Modal from '../Atoms/Modal.vue'
|
|
||||||
import OpenInNewIcon from 'vue-material-design-icons/OpenInNew.vue'
|
|
||||||
import PlayIcon from 'vue-material-design-icons/Play.vue'
|
|
||||||
import PlaylistPlayIcon from 'vue-material-design-icons/PlaylistPlay.vue'
|
|
||||||
import PlaylistRemoveIcon from 'vue-material-design-icons/PlaylistRemove.vue'
|
|
||||||
import StopIcon from 'vue-material-design-icons/Stop.vue'
|
|
||||||
import axios from '@nextcloud/axios'
|
import axios from '@nextcloud/axios'
|
||||||
|
import { decodeUrl } from '../../utils/url.js'
|
||||||
import { generateUrl } from '@nextcloud/router'
|
import { generateUrl } from '@nextcloud/router'
|
||||||
|
import { mapState } from 'pinia'
|
||||||
import { showError } from '../../utils/toast.js'
|
import { showError } from '../../utils/toast.js'
|
||||||
import { usePlayer } from '../../store/player.js'
|
import { usePlayer } from '../../store/player.js'
|
||||||
import { useSettings } from '../../store/settings.js'
|
import { useSettings } from '../../store/settings.js'
|
||||||
|
@ -147,30 +26,13 @@ import { useSettings } from '../../store/settings.js'
|
||||||
export default {
|
export default {
|
||||||
name: 'Episodes',
|
name: 'Episodes',
|
||||||
components: {
|
components: {
|
||||||
DownloadIcon,
|
Episode,
|
||||||
Loading,
|
Loading,
|
||||||
Modal,
|
|
||||||
NcActionButton,
|
|
||||||
NcActionLink,
|
|
||||||
NcActions,
|
|
||||||
NcAvatar,
|
|
||||||
NcListItem,
|
|
||||||
NcModal,
|
|
||||||
NcProgressBar,
|
|
||||||
OpenInNewIcon,
|
|
||||||
PlayIcon,
|
|
||||||
PlaylistPlayIcon,
|
|
||||||
PlaylistRemoveIcon,
|
|
||||||
StopIcon,
|
|
||||||
},
|
},
|
||||||
data() {
|
data: () => ({
|
||||||
return {
|
|
||||||
episodes: [],
|
episodes: [],
|
||||||
loading: true,
|
loading: true,
|
||||||
loadingAction: false,
|
}),
|
||||||
modalEpisode: null,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(usePlayer, ['episode']),
|
...mapState(usePlayer, ['episode']),
|
||||||
...mapState(useSettings, ['filters']),
|
...mapState(useSettings, ['filters']),
|
||||||
|
@ -223,63 +85,8 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(usePlayer, ['load']),
|
hasEnded,
|
||||||
filenameFromUrl,
|
isListening,
|
||||||
formatLocaleDate,
|
|
||||||
hasEnded(episode) {
|
|
||||||
return (
|
|
||||||
episode.action &&
|
|
||||||
(episode.action.action === 'DELETE' ||
|
|
||||||
(episode.action.position > 0 &&
|
|
||||||
episode.action.total > 0 &&
|
|
||||||
episode.action.position >= episode.action.total))
|
|
||||||
)
|
|
||||||
},
|
|
||||||
isCurrentEpisode(episode) {
|
|
||||||
return this.episode && this.episode.url === episode.url
|
|
||||||
},
|
|
||||||
isListening(episode) {
|
|
||||||
return (
|
|
||||||
episode.action &&
|
|
||||||
episode.action.action &&
|
|
||||||
episode.action.action.toLowerCase() === 'play' &&
|
|
||||||
episode.action.position > 0 &&
|
|
||||||
!this.hasEnded(episode)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
async markAs(episode, read) {
|
|
||||||
try {
|
|
||||||
this.loadingAction = true
|
|
||||||
episode.action = {
|
|
||||||
podcast: this.url,
|
|
||||||
episode: episode.url,
|
|
||||||
guid: episode.guid,
|
|
||||||
action: 'play',
|
|
||||||
timestamp: formatEpisodeTimestamp(new Date()),
|
|
||||||
started: episode.action ? episode.action.started : 0,
|
|
||||||
position: read ? durationToSeconds(episode.duration) : 0,
|
|
||||||
total: durationToSeconds(episode.duration),
|
|
||||||
}
|
|
||||||
await axios.post(
|
|
||||||
generateUrl('/apps/gpoddersync/episode_action/create'),
|
|
||||||
[episode.action],
|
|
||||||
)
|
|
||||||
if (read && this.episode && episode.url === this.episode.url) {
|
|
||||||
this.load(null)
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e)
|
|
||||||
showError(t('repod', 'Could not change the status of the episode'))
|
|
||||||
} finally {
|
|
||||||
this.loadingAction = false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.progress {
|
|
||||||
margin-top: 0.4rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -0,0 +1,108 @@
|
||||||
|
<template>
|
||||||
|
<NcGuestContent class="guest">
|
||||||
|
<Loading v-if="!currentFavoriteData" />
|
||||||
|
<NcAvatar
|
||||||
|
v-if="currentFavoriteData"
|
||||||
|
class="avatar"
|
||||||
|
:display-name="currentFavoriteData.author || currentFavoriteData.title"
|
||||||
|
:is-no-user="true"
|
||||||
|
:size="222"
|
||||||
|
:url="currentFavoriteData.imageUrl" />
|
||||||
|
<div class="list">
|
||||||
|
<h2 class="title">{{ currentFavoriteData.title }}</h2>
|
||||||
|
<Loading v-if="loading" />
|
||||||
|
<ul v-if="!loading">
|
||||||
|
<Episode
|
||||||
|
v-for="episode in episodes"
|
||||||
|
:key="episode.guid"
|
||||||
|
:episode="episode"
|
||||||
|
:one-line="true"
|
||||||
|
:url="url" />
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</NcGuestContent>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { NcAvatar, NcGuestContent } from '@nextcloud/vue'
|
||||||
|
import Episode from './Episode.vue'
|
||||||
|
import Loading from '../Atoms/Loading.vue'
|
||||||
|
import axios from '@nextcloud/axios'
|
||||||
|
import { generateUrl } from '@nextcloud/router'
|
||||||
|
import { hasEnded } from '../../utils/status.js'
|
||||||
|
import { mapState } from 'pinia'
|
||||||
|
import { showError } from '../../utils/toast.js'
|
||||||
|
import { useSubscriptions } from '../../store/subscriptions.js'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Favorites',
|
||||||
|
components: {
|
||||||
|
Episode,
|
||||||
|
Loading,
|
||||||
|
NcAvatar,
|
||||||
|
NcGuestContent,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
url: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data: () => ({
|
||||||
|
episodes: [],
|
||||||
|
loading: true,
|
||||||
|
}),
|
||||||
|
computed: {
|
||||||
|
...mapState(useSubscriptions, ['getFavorites']),
|
||||||
|
currentFavoriteData() {
|
||||||
|
return this.getFavorites.find((fav) => fav.url === this.url)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
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),
|
||||||
|
)
|
||||||
|
.filter((episode) => !this.hasEnded(episode))
|
||||||
|
.slice(0, 4)
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
showError(t('repod', 'Could not fetch episodes'))
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
hasEnded,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.guest {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 768px) {
|
||||||
|
.avatar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -78,7 +78,7 @@ export default {
|
||||||
|
|
||||||
@media only screen and (max-width: 768px) {
|
@media only screen and (max-width: 768px) {
|
||||||
.infos {
|
.infos {
|
||||||
flex: 2;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.timer,
|
.timer,
|
||||||
|
|
|
@ -3,18 +3,11 @@
|
||||||
<strong class="pointer" @click="modal = true">
|
<strong class="pointer" @click="modal = true">
|
||||||
{{ episode.name }}
|
{{ episode.name }}
|
||||||
</strong>
|
</strong>
|
||||||
<router-link :to="hash">
|
<router-link :to="toFeedUrl(podcastUrl)">
|
||||||
<i>{{ episode.title }}</i>
|
<i>{{ episode.title }}</i>
|
||||||
</router-link>
|
</router-link>
|
||||||
<NcModal v-if="modal" @close="modal = false">
|
<NcModal v-if="modal" @close="modal = false">
|
||||||
<Modal
|
<Modal :episode="episode" />
|
||||||
:description="episode.description"
|
|
||||||
:image="episode.image"
|
|
||||||
:link="episode.link"
|
|
||||||
:name="episode.name"
|
|
||||||
:size="episode.size"
|
|
||||||
:title="episode.title"
|
|
||||||
:url="episode.url" />
|
|
||||||
</NcModal>
|
</NcModal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -23,7 +16,7 @@
|
||||||
import Modal from '../Atoms/Modal.vue'
|
import Modal from '../Atoms/Modal.vue'
|
||||||
import { NcModal } from '@nextcloud/vue'
|
import { NcModal } from '@nextcloud/vue'
|
||||||
import { mapState } from 'pinia'
|
import { mapState } from 'pinia'
|
||||||
import { toUrl } from '../../utils/url.js'
|
import { toFeedUrl } from '../../utils/url.js'
|
||||||
import { usePlayer } from '../../store/player.js'
|
import { usePlayer } from '../../store/player.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -32,16 +25,14 @@ export default {
|
||||||
Modal,
|
Modal,
|
||||||
NcModal,
|
NcModal,
|
||||||
},
|
},
|
||||||
data() {
|
data: () => ({
|
||||||
return {
|
|
||||||
modal: false,
|
modal: false,
|
||||||
}
|
}),
|
||||||
},
|
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(usePlayer, ['episode', 'podcastUrl']),
|
...mapState(usePlayer, ['episode', 'podcastUrl']),
|
||||||
hash() {
|
|
||||||
return toUrl(this.podcastUrl)
|
|
||||||
},
|
},
|
||||||
|
methods: {
|
||||||
|
toFeedUrl,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -46,11 +46,9 @@ export default {
|
||||||
VolumeMediumIcon,
|
VolumeMediumIcon,
|
||||||
VolumeMuteIcon,
|
VolumeMuteIcon,
|
||||||
},
|
},
|
||||||
data() {
|
data: () => ({
|
||||||
return {
|
|
||||||
volumeMuted: 0,
|
volumeMuted: 0,
|
||||||
}
|
}),
|
||||||
},
|
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(usePlayer, ['volume']),
|
...mapState(usePlayer, ['volume']),
|
||||||
},
|
},
|
||||||
|
|
|
@ -44,7 +44,6 @@ import { NcActionCheckbox, NcAppNavigationItem } from '@nextcloud/vue'
|
||||||
import { mapActions, mapState } from 'pinia'
|
import { mapActions, mapState } from 'pinia'
|
||||||
import FilterIcon from 'vue-material-design-icons/Filter.vue'
|
import FilterIcon from 'vue-material-design-icons/Filter.vue'
|
||||||
import FilterSettingsIcon from 'vue-material-design-icons/FilterSettings.vue'
|
import FilterSettingsIcon from 'vue-material-design-icons/FilterSettings.vue'
|
||||||
import { getCookie } from '../../utils/cookies.js'
|
|
||||||
import { useSettings } from '../../store/settings.js'
|
import { useSettings } from '../../store/settings.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -65,12 +64,6 @@ export default {
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
|
||||||
try {
|
|
||||||
const filters = getCookie('repod.filters')
|
|
||||||
this.filters = JSON.parse(filters)
|
|
||||||
} catch {}
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(useSettings, ['setFilters']),
|
...mapActions(useSettings, ['setFilters']),
|
||||||
},
|
},
|
||||||
|
|
|
@ -44,12 +44,10 @@ export default {
|
||||||
NcAppNavigationItem,
|
NcAppNavigationItem,
|
||||||
NcModal,
|
NcModal,
|
||||||
},
|
},
|
||||||
data() {
|
data: () => ({
|
||||||
return {
|
|
||||||
loading: false,
|
loading: false,
|
||||||
modal: false,
|
modal: false,
|
||||||
}
|
}),
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
generateUrl,
|
generateUrl,
|
||||||
async importOpml(event) {
|
async importOpml(event) {
|
||||||
|
|
|
@ -2,8 +2,19 @@
|
||||||
<NcAppNavigationItem
|
<NcAppNavigationItem
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:name="feed ? feed.title : url"
|
:name="feed ? feed.title : url"
|
||||||
:to="hash">
|
:to="toFeedUrl(url)">
|
||||||
<template #actions>
|
<template #actions>
|
||||||
|
<NcActionButton
|
||||||
|
:aria-label="t('repod', 'Favorite')"
|
||||||
|
:model-value="isFavorite"
|
||||||
|
:name="t('repod', 'Favorite')"
|
||||||
|
:title="t('repod', 'Favorite')"
|
||||||
|
@update:modelValue="switchFavorite($event)">
|
||||||
|
<template #icon>
|
||||||
|
<StarPlusIcon v-if="!isFavorite" :size="20" />
|
||||||
|
<StarRemoveIcon v-if="isFavorite" :size="20" />
|
||||||
|
</template>
|
||||||
|
</NcActionButton>
|
||||||
<NcActionButton
|
<NcActionButton
|
||||||
:aria-label="t(`core`, 'Delete')"
|
:aria-label="t(`core`, 'Delete')"
|
||||||
:name="t(`core`, 'Delete')"
|
:name="t(`core`, 'Delete')"
|
||||||
|
@ -20,6 +31,7 @@
|
||||||
:display-name="feed.author || feed.title"
|
:display-name="feed.author || feed.title"
|
||||||
:is-no-user="true"
|
:is-no-user="true"
|
||||||
:url="feed.imageUrl" />
|
:url="feed.imageUrl" />
|
||||||
|
<StarIcon v-if="feed && isFavorite" class="star" :size="20" />
|
||||||
<AlertIcon v-if="failed" />
|
<AlertIcon v-if="failed" />
|
||||||
</template>
|
</template>
|
||||||
</NcAppNavigationItem>
|
</NcAppNavigationItem>
|
||||||
|
@ -27,23 +39,29 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { NcActionButton, NcAppNavigationItem, NcAvatar } from '@nextcloud/vue'
|
import { NcActionButton, NcAppNavigationItem, NcAvatar } from '@nextcloud/vue'
|
||||||
|
import { mapActions, mapState } from 'pinia'
|
||||||
import AlertIcon from 'vue-material-design-icons/Alert.vue'
|
import AlertIcon from 'vue-material-design-icons/Alert.vue'
|
||||||
import DeleteIcon from 'vue-material-design-icons/Delete.vue'
|
import DeleteIcon from 'vue-material-design-icons/Delete.vue'
|
||||||
|
import StarIcon from 'vue-material-design-icons/Star.vue'
|
||||||
|
import StarPlusIcon from 'vue-material-design-icons/StarPlus.vue'
|
||||||
|
import StarRemoveIcon from 'vue-material-design-icons/StarRemove.vue'
|
||||||
import axios from '@nextcloud/axios'
|
import axios from '@nextcloud/axios'
|
||||||
import { generateUrl } from '@nextcloud/router'
|
import { generateUrl } from '@nextcloud/router'
|
||||||
import { mapActions } from 'pinia'
|
|
||||||
import { showError } from '../../utils/toast.js'
|
import { showError } from '../../utils/toast.js'
|
||||||
import { toUrl } from '../../utils/url.js'
|
import { toFeedUrl } from '../../utils/url.js'
|
||||||
import { useSubscriptions } from '../../store/subscriptions.js'
|
import { useSubscriptions } from '../../store/subscriptions.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Item',
|
name: 'Subscription',
|
||||||
components: {
|
components: {
|
||||||
AlertIcon,
|
AlertIcon,
|
||||||
DeleteIcon,
|
DeleteIcon,
|
||||||
NcActionButton,
|
NcActionButton,
|
||||||
NcAppNavigationItem,
|
NcAppNavigationItem,
|
||||||
NcAvatar,
|
NcAvatar,
|
||||||
|
StarIcon,
|
||||||
|
StarPlusIcon,
|
||||||
|
StarRemoveIcon,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
url: {
|
url: {
|
||||||
|
@ -51,16 +69,15 @@ export default {
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data: () => ({
|
||||||
return {
|
|
||||||
failed: false,
|
failed: false,
|
||||||
loading: true,
|
loading: true,
|
||||||
feed: null,
|
feed: null,
|
||||||
}
|
}),
|
||||||
},
|
|
||||||
computed: {
|
computed: {
|
||||||
hash() {
|
...mapState(useSubscriptions, ['getFavorites']),
|
||||||
return toUrl(this.url)
|
isFavorite() {
|
||||||
|
return this.getFavorites.map((fav) => fav.url).includes(this.url)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
|
@ -74,6 +91,7 @@ export default {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
this.feed = podcastData.data.data
|
this.feed = podcastData.data.data
|
||||||
|
this.editFavoriteData(this.url, podcastData.data.data)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.failed = true
|
this.failed = true
|
||||||
console.error(e)
|
console.error(e)
|
||||||
|
@ -82,7 +100,13 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(useSubscriptions, ['fetch']),
|
...mapActions(useSubscriptions, [
|
||||||
|
'fetch',
|
||||||
|
'addFavorite',
|
||||||
|
'editFavoriteData',
|
||||||
|
'removeFavorite',
|
||||||
|
]),
|
||||||
|
toFeedUrl,
|
||||||
async deleteSubscription() {
|
async deleteSubscription() {
|
||||||
if (
|
if (
|
||||||
confirm(
|
confirm(
|
||||||
|
@ -99,11 +123,33 @@ export default {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
showError(t('repod', 'Error while removing the feed'))
|
showError(t('repod', 'Error while removing the feed'))
|
||||||
} finally {
|
} finally {
|
||||||
|
this.removeFavorite(this.url)
|
||||||
this.loading = false
|
this.loading = false
|
||||||
this.fetch()
|
this.fetch()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
switchFavorite(value) {
|
||||||
|
if (value) {
|
||||||
|
if (this.getFavorites.length >= 10) {
|
||||||
|
showError(t('repod', 'You can only have 10 favorites'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.addFavorite(this.url)
|
||||||
|
} else {
|
||||||
|
this.removeFavorite(this.url)
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.star {
|
||||||
|
bottom: 2px;
|
||||||
|
color: yellow;
|
||||||
|
left: 22px;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -2,7 +2,7 @@
|
||||||
<AppNavigation>
|
<AppNavigation>
|
||||||
<template #list>
|
<template #list>
|
||||||
<NcAppContentList>
|
<NcAppContentList>
|
||||||
<router-link to="/">
|
<router-link to="/discover">
|
||||||
<NcAppNavigationNew :text="t('repod', 'Add a podcast')">
|
<NcAppNavigationNew :text="t('repod', 'Add a podcast')">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<PlusIcon :size="20" />
|
<PlusIcon :size="20" />
|
||||||
|
@ -11,10 +11,17 @@
|
||||||
</router-link>
|
</router-link>
|
||||||
<Loading v-if="loading" />
|
<Loading v-if="loading" />
|
||||||
<NcAppNavigationList v-if="!loading">
|
<NcAppNavigationList v-if="!loading">
|
||||||
<Item
|
<Subscription
|
||||||
v-for="subscriptionUrl of subscriptions"
|
v-for="url of getFavorites.map((fav) => fav.url)"
|
||||||
:key="subscriptionUrl"
|
:key="url"
|
||||||
:url="subscriptionUrl" />
|
:url="url" />
|
||||||
|
<Subscription
|
||||||
|
v-for="url of getSubscriptions.filter(
|
||||||
|
(sub) =>
|
||||||
|
!getFavorites.map((fav) => fav.url).includes(sub),
|
||||||
|
)"
|
||||||
|
:key="url"
|
||||||
|
:url="url" />
|
||||||
</NcAppNavigationList>
|
</NcAppNavigationList>
|
||||||
</NcAppContentList>
|
</NcAppContentList>
|
||||||
</template>
|
</template>
|
||||||
|
@ -32,10 +39,10 @@ import {
|
||||||
} from '@nextcloud/vue'
|
} from '@nextcloud/vue'
|
||||||
import { mapActions, mapState } from 'pinia'
|
import { mapActions, mapState } from 'pinia'
|
||||||
import AppNavigation from '../Atoms/AppNavigation.vue'
|
import AppNavigation from '../Atoms/AppNavigation.vue'
|
||||||
import Item from './Item.vue'
|
|
||||||
import Loading from '../Atoms/Loading.vue'
|
import Loading from '../Atoms/Loading.vue'
|
||||||
import PlusIcon from 'vue-material-design-icons/Plus.vue'
|
import PlusIcon from 'vue-material-design-icons/Plus.vue'
|
||||||
import Settings from '../Settings/Settings.vue'
|
import Settings from '../Settings/Settings.vue'
|
||||||
|
import Subscription from './Subscription.vue'
|
||||||
import { showError } from '../../utils/toast.js'
|
import { showError } from '../../utils/toast.js'
|
||||||
import { useSubscriptions } from '../../store/subscriptions.js'
|
import { useSubscriptions } from '../../store/subscriptions.js'
|
||||||
|
|
||||||
|
@ -43,21 +50,19 @@ export default {
|
||||||
name: 'Subscriptions',
|
name: 'Subscriptions',
|
||||||
components: {
|
components: {
|
||||||
AppNavigation,
|
AppNavigation,
|
||||||
Item,
|
|
||||||
Loading,
|
Loading,
|
||||||
NcAppContentList,
|
NcAppContentList,
|
||||||
NcAppNavigationList,
|
NcAppNavigationList,
|
||||||
NcAppNavigationNew,
|
NcAppNavigationNew,
|
||||||
PlusIcon,
|
PlusIcon,
|
||||||
Settings,
|
Settings,
|
||||||
|
Subscription,
|
||||||
},
|
},
|
||||||
data() {
|
data: () => ({
|
||||||
return {
|
|
||||||
loading: true,
|
loading: true,
|
||||||
}
|
}),
|
||||||
},
|
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(useSubscriptions, ['subscriptions']),
|
...mapState(useSubscriptions, ['getSubscriptions', 'getFavorites']),
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -1,17 +1,22 @@
|
||||||
import { createRouter, createWebHashHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
import Discover from './views/Discover.vue'
|
import Discover from './views/Discover.vue'
|
||||||
import Feed from './views/Feed.vue'
|
import Feed from './views/Feed.vue'
|
||||||
|
import Home from './views/Home.vue'
|
||||||
import { generateUrl } from '@nextcloud/router'
|
import { generateUrl } from '@nextcloud/router'
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHashHistory(generateUrl('apps/repod')),
|
history: createWebHistory(generateUrl('apps/repod')),
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
|
component: Home,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/discover',
|
||||||
component: Discover,
|
component: Discover,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/:url',
|
path: '/feed/:url',
|
||||||
component: Feed,
|
component: Feed,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -44,12 +44,11 @@ export const usePlayer = defineStore('player', {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
this.episode.action = action
|
this.episode.action = action.data
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this.episode.action &&
|
this.episode.action &&
|
||||||
this.episode.action.position &&
|
|
||||||
this.episode.action.position < this.episode.action.total
|
this.episode.action.position < this.episode.action.total
|
||||||
) {
|
) {
|
||||||
audio.currentTime = this.episode.action.position
|
audio.currentTime = this.episode.action.position
|
||||||
|
|
|
@ -1,14 +1,27 @@
|
||||||
|
import { getCookie, setCookie } from '../utils/cookies.js'
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { setCookie } from '../utils/cookies.js'
|
|
||||||
|
|
||||||
export const useSettings = defineStore('settings', {
|
export const useSettings = defineStore('settings', {
|
||||||
state: () => ({
|
state: () => {
|
||||||
|
try {
|
||||||
|
const filters = JSON.parse(getCookie('repod.filters'))
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
listened: filters.listened,
|
||||||
|
listening: filters.listening,
|
||||||
|
unlistened: filters.unlistened,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
listened: true,
|
listened: true,
|
||||||
listening: true,
|
listening: true,
|
||||||
unlistened: true,
|
unlistened: true,
|
||||||
},
|
},
|
||||||
}),
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
actions: {
|
actions: {
|
||||||
setFilters(filters) {
|
setFilters(filters) {
|
||||||
this.filters = { ...this.filters, ...filters }
|
this.filters = { ...this.filters, ...filters }
|
||||||
|
|
|
@ -1,11 +1,23 @@
|
||||||
|
import { getCookie, setCookie } from '../utils/cookies.js'
|
||||||
import axios from '@nextcloud/axios'
|
import axios from '@nextcloud/axios'
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { generateUrl } from '@nextcloud/router'
|
import { generateUrl } from '@nextcloud/router'
|
||||||
|
|
||||||
export const useSubscriptions = defineStore('subscriptions', {
|
export const useSubscriptions = defineStore('subscriptions', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
subscriptions: [],
|
subs: [],
|
||||||
|
favs: [],
|
||||||
}),
|
}),
|
||||||
|
getters: {
|
||||||
|
getSubscriptions: (state) => {
|
||||||
|
return state.subs
|
||||||
|
},
|
||||||
|
getFavorites: (state) => {
|
||||||
|
return state.favs
|
||||||
|
.filter((fav) => state.subs.includes(fav.url))
|
||||||
|
.sort((fav) => fav.lastPub)
|
||||||
|
},
|
||||||
|
},
|
||||||
actions: {
|
actions: {
|
||||||
async fetch() {
|
async fetch() {
|
||||||
const metrics = await axios.get(
|
const metrics = await axios.get(
|
||||||
|
@ -14,7 +26,33 @@ export const useSubscriptions = defineStore('subscriptions', {
|
||||||
const subs = [...metrics.data.subscriptions].sort(
|
const subs = [...metrics.data.subscriptions].sort(
|
||||||
(a, b) => b.listenedSeconds - a.listenedSeconds,
|
(a, b) => b.listenedSeconds - a.listenedSeconds,
|
||||||
)
|
)
|
||||||
this.subscriptions = subs.map((sub) => sub.url)
|
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)),
|
||||||
|
365,
|
||||||
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
export const hasEnded = (episode) =>
|
||||||
|
episode.action &&
|
||||||
|
episode.action.action &&
|
||||||
|
(episode.action.action.toLowerCase() === 'delete' ||
|
||||||
|
(episode.action.position > 0 &&
|
||||||
|
episode.action.total > 0 &&
|
||||||
|
episode.action.position >= episode.action.total))
|
||||||
|
|
||||||
|
export const isListening = (episode) =>
|
||||||
|
episode.action &&
|
||||||
|
episode.action.action &&
|
||||||
|
episode.action.action.toLowerCase() === 'play' &&
|
||||||
|
episode.action.position > 0 &&
|
||||||
|
!hasEnded(episode)
|
|
@ -1,6 +1,6 @@
|
||||||
export const encodeUrl = (url) => encodeURIComponent(btoa(url))
|
export const encodeUrl = (url) => encodeURIComponent(btoa(url))
|
||||||
export const decodeUrl = (url) => atob(decodeURIComponent(url))
|
export const decodeUrl = (url) => atob(decodeURIComponent(url))
|
||||||
export const toUrl = (url) => `/${encodeUrl(url)}`
|
export const toFeedUrl = (url) => `/feed/${encodeUrl(url)}`
|
||||||
export const filenameFromUrl = (str) => {
|
export const filenameFromUrl = (str) => {
|
||||||
const url = new URL(str)
|
const url = new URL(str)
|
||||||
return url.pathname.split('/').pop()
|
return url.pathname.split('/').pop()
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<AppContent class="main">
|
<AppContent class="padding">
|
||||||
<NcTextField v-model="search" :label="t('repod', 'Find a podcast')">
|
<NcTextField v-model="search" :label="t('repod', 'Find a podcast')">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<Magnify :size="20" />
|
<Magnify :size="20" />
|
||||||
|
@ -30,16 +30,14 @@ export default {
|
||||||
Search,
|
Search,
|
||||||
Toplist,
|
Toplist,
|
||||||
},
|
},
|
||||||
data() {
|
data: () => ({
|
||||||
return {
|
|
||||||
search: '',
|
search: '',
|
||||||
}
|
}),
|
||||||
},
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.main {
|
.padding {
|
||||||
padding: 15px 51px;
|
padding: 15px 51px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
<template>
|
<template>
|
||||||
<AppContent>
|
<AppContent>
|
||||||
<Loading v-if="loading" />
|
<Loading v-if="loading" />
|
||||||
<NcEmptyContent
|
<EmptyContent
|
||||||
v-if="failed"
|
v-if="failed"
|
||||||
class="error"
|
class="error"
|
||||||
:name="t('repod', 'Error loading feed')">
|
:name="t('repod', 'Error loading feed')">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<Alert />
|
<Alert />
|
||||||
</template>
|
</template>
|
||||||
</NcEmptyContent>
|
</EmptyContent>
|
||||||
<Banner
|
<Banner
|
||||||
v-if="feed"
|
v-if="feed"
|
||||||
:author="feed.author"
|
:author="feed.author"
|
||||||
|
@ -24,9 +24,9 @@
|
||||||
import Alert from 'vue-material-design-icons/Alert.vue'
|
import Alert from 'vue-material-design-icons/Alert.vue'
|
||||||
import AppContent from '../components/Atoms/AppContent.vue'
|
import AppContent from '../components/Atoms/AppContent.vue'
|
||||||
import Banner from '../components/Feed/Banner.vue'
|
import Banner from '../components/Feed/Banner.vue'
|
||||||
|
import EmptyContent from '../components/Atoms/EmptyContent.vue'
|
||||||
import Episodes from '../components/Feed/Episodes.vue'
|
import Episodes from '../components/Feed/Episodes.vue'
|
||||||
import Loading from '../components/Atoms/Loading.vue'
|
import Loading from '../components/Atoms/Loading.vue'
|
||||||
import { NcEmptyContent } from '@nextcloud/vue'
|
|
||||||
import axios from '@nextcloud/axios'
|
import axios from '@nextcloud/axios'
|
||||||
import { decodeUrl } from '../utils/url.js'
|
import { decodeUrl } from '../utils/url.js'
|
||||||
import { generateUrl } from '@nextcloud/router'
|
import { generateUrl } from '@nextcloud/router'
|
||||||
|
@ -37,17 +37,15 @@ export default {
|
||||||
Alert,
|
Alert,
|
||||||
AppContent,
|
AppContent,
|
||||||
Banner,
|
Banner,
|
||||||
|
EmptyContent,
|
||||||
Episodes,
|
Episodes,
|
||||||
Loading,
|
Loading,
|
||||||
NcEmptyContent,
|
|
||||||
},
|
},
|
||||||
data() {
|
data: () => ({
|
||||||
return {
|
|
||||||
failed: false,
|
failed: false,
|
||||||
loading: true,
|
loading: true,
|
||||||
feed: null,
|
feed: null,
|
||||||
}
|
}),
|
||||||
},
|
|
||||||
computed: {
|
computed: {
|
||||||
url() {
|
url() {
|
||||||
return decodeUrl(this.$route.params.url)
|
return decodeUrl(this.$route.params.url)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<NcAppContent class="content">
|
<AppContent>
|
||||||
<NcEmptyContent :name="t('repod', 'Missing required app')">
|
<EmptyContent class="empty" :name="t('repod', 'Missing required app')">
|
||||||
<template #action>
|
<template #action>
|
||||||
<NcButton :href="gPodderSyncUrl">
|
<NcButton :href="gPodderSyncUrl">
|
||||||
{{ t('repod', 'Install GPodder Sync') }}
|
{{ t('repod', 'Install GPodder Sync') }}
|
||||||
|
@ -9,22 +9,24 @@
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<Alert />
|
<Alert />
|
||||||
</template>
|
</template>
|
||||||
</NcEmptyContent>
|
</EmptyContent>
|
||||||
</NcAppContent>
|
</AppContent>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { NcAppContent, NcButton, NcEmptyContent } from '@nextcloud/vue'
|
|
||||||
import Alert from 'vue-material-design-icons/Alert.vue'
|
import Alert from 'vue-material-design-icons/Alert.vue'
|
||||||
|
import AppContent from '../components/Atoms/AppContent.vue'
|
||||||
|
import EmptyContent from '../components/Atoms/EmptyContent.vue'
|
||||||
|
import { NcButton } from '@nextcloud/vue'
|
||||||
import { generateUrl } from '@nextcloud/router'
|
import { generateUrl } from '@nextcloud/router'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'GPodder',
|
name: 'GPodder',
|
||||||
components: {
|
components: {
|
||||||
Alert,
|
Alert,
|
||||||
NcAppContent,
|
AppContent,
|
||||||
|
EmptyContent,
|
||||||
NcButton,
|
NcButton,
|
||||||
NcEmptyContent,
|
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
gPodderSyncUrl() {
|
gPodderSyncUrl() {
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
<template>
|
||||||
|
<AppContent>
|
||||||
|
<EmptyContent
|
||||||
|
v-if="!getFavorites.length"
|
||||||
|
class="empty"
|
||||||
|
:description="
|
||||||
|
t('repod', 'Pin some subscriptions to see their latest updates')
|
||||||
|
"
|
||||||
|
:name="t('repod', 'No favorites')">
|
||||||
|
<template #icon>
|
||||||
|
<StarOffIcon :size="20" />
|
||||||
|
</template>
|
||||||
|
</EmptyContent>
|
||||||
|
<ul v-if="getFavorites.length">
|
||||||
|
<li v-for="url in getFavorites.map((fav) => fav.url)" :key="url">
|
||||||
|
<Favorites :url="url" />
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</AppContent>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import AppContent from '../components/Atoms/AppContent.vue'
|
||||||
|
import EmptyContent from '../components/Atoms/EmptyContent.vue'
|
||||||
|
import Favorites from '../components/Feed/Favorites.vue'
|
||||||
|
import StarOffIcon from 'vue-material-design-icons/StarOff.vue'
|
||||||
|
import { mapState } from 'pinia'
|
||||||
|
import { useSubscriptions } from '../store/subscriptions.js'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Home',
|
||||||
|
components: {
|
||||||
|
AppContent,
|
||||||
|
EmptyContent,
|
||||||
|
Favorites,
|
||||||
|
StarOffIcon,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState(useSubscriptions, ['getFavorites']),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -83,21 +83,18 @@ msgstr "Abspielen"
|
||||||
msgid "Stop"
|
msgid "Stop"
|
||||||
msgstr "Stopp"
|
msgstr "Stopp"
|
||||||
|
|
||||||
msgid "Mark as read"
|
msgid "Read"
|
||||||
msgstr "Als gelesen markieren"
|
msgstr "Gelesen"
|
||||||
|
|
||||||
msgid "Mark as unread"
|
|
||||||
msgstr "Als ungelesen markieren"
|
|
||||||
|
|
||||||
msgid "Open website"
|
msgid "Open website"
|
||||||
msgstr "Webseite aufrufen"
|
msgstr "Webseite aufrufen"
|
||||||
|
|
||||||
msgid "Could not fetch episodes"
|
|
||||||
msgstr "Folgen können nicht abgerufen werden"
|
|
||||||
|
|
||||||
msgid "Could not change the status of the episode"
|
msgid "Could not change the status of the episode"
|
||||||
msgstr "Kann den Status der Folge nicht ändern"
|
msgstr "Kann den Status der Folge nicht ändern"
|
||||||
|
|
||||||
|
msgid "Could not fetch episodes"
|
||||||
|
msgstr "Folgen können nicht abgerufen werden"
|
||||||
|
|
||||||
msgid "Export subscriptions"
|
msgid "Export subscriptions"
|
||||||
msgstr "Abonnements exportieren"
|
msgstr "Abonnements exportieren"
|
||||||
|
|
||||||
|
@ -128,12 +125,18 @@ msgstr "Bewerte RePod ❤️"
|
||||||
msgid "Playback speed"
|
msgid "Playback speed"
|
||||||
msgstr "Wiedergabegeschwindigkeit"
|
msgstr "Wiedergabegeschwindigkeit"
|
||||||
|
|
||||||
|
msgid "Favorite"
|
||||||
|
msgstr "Favorit"
|
||||||
|
|
||||||
msgid "Are you sure you want to delete this subscription?"
|
msgid "Are you sure you want to delete this subscription?"
|
||||||
msgstr "Bist Du sicher, dass Du das Abonnement löschen möchtest?"
|
msgstr "Bist Du sicher, dass Du das Abonnement löschen möchtest?"
|
||||||
|
|
||||||
msgid "Error while removing the feed"
|
msgid "Error while removing the feed"
|
||||||
msgstr "Fehler beim Löschen des Feeds"
|
msgstr "Fehler beim Löschen des Feeds"
|
||||||
|
|
||||||
|
msgid "You can only have 10 favorites"
|
||||||
|
msgstr "Du kannst nur 10 Favoriten haben"
|
||||||
|
|
||||||
msgid "Add a podcast"
|
msgid "Add a podcast"
|
||||||
msgstr "Einen Podcast hinzufügen"
|
msgstr "Einen Podcast hinzufügen"
|
||||||
|
|
||||||
|
@ -151,3 +154,9 @@ msgstr "Benötigte App fehlt"
|
||||||
|
|
||||||
msgid "Install GPodder Sync"
|
msgid "Install GPodder Sync"
|
||||||
msgstr "Installiere GPodder Sync"
|
msgstr "Installiere GPodder Sync"
|
||||||
|
|
||||||
|
msgid "Pin some subscriptions to see their latest updates"
|
||||||
|
msgstr "Pinne einige Abonnements, um ihre neuesten Updates zu sehen"
|
||||||
|
|
||||||
|
msgid "No favorites"
|
||||||
|
msgstr "Keine Favoriten"
|
||||||
|
|
|
@ -83,21 +83,18 @@ msgstr "Lecture"
|
||||||
msgid "Stop"
|
msgid "Stop"
|
||||||
msgstr "Arrêter"
|
msgstr "Arrêter"
|
||||||
|
|
||||||
msgid "Mark as read"
|
msgid "Read"
|
||||||
msgstr "Marquer comme lu"
|
msgstr "Lu"
|
||||||
|
|
||||||
msgid "Mark as unread"
|
|
||||||
msgstr "Marquer comme non lu"
|
|
||||||
|
|
||||||
msgid "Open website"
|
msgid "Open website"
|
||||||
msgstr "Ouvrir le site web"
|
msgstr "Ouvrir le site web"
|
||||||
|
|
||||||
msgid "Could not fetch episodes"
|
|
||||||
msgstr "Impossible de récuprer les épisodes"
|
|
||||||
|
|
||||||
msgid "Could not change the status of the episode"
|
msgid "Could not change the status of the episode"
|
||||||
msgstr "Impossible de changer le status de l'épisode"
|
msgstr "Impossible de changer le status de l'épisode"
|
||||||
|
|
||||||
|
msgid "Could not fetch episodes"
|
||||||
|
msgstr "Impossible de récuprer les épisodes"
|
||||||
|
|
||||||
msgid "Export subscriptions"
|
msgid "Export subscriptions"
|
||||||
msgstr "Exporter les abonnements"
|
msgstr "Exporter les abonnements"
|
||||||
|
|
||||||
|
@ -128,12 +125,18 @@ msgstr "Donnez votre avis ❤️"
|
||||||
msgid "Playback speed"
|
msgid "Playback speed"
|
||||||
msgstr "Vitesse de lecture"
|
msgstr "Vitesse de lecture"
|
||||||
|
|
||||||
|
msgid "Favorite"
|
||||||
|
msgstr "Favori"
|
||||||
|
|
||||||
msgid "Are you sure you want to delete this subscription?"
|
msgid "Are you sure you want to delete this subscription?"
|
||||||
msgstr "Êtes-vous sûr de vouloir supprimer ce flux ?"
|
msgstr "Êtes-vous sûr de vouloir supprimer ce flux ?"
|
||||||
|
|
||||||
msgid "Error while removing the feed"
|
msgid "Error while removing the feed"
|
||||||
msgstr "Erreur lors de la suppression du flux"
|
msgstr "Erreur lors de la suppression du flux"
|
||||||
|
|
||||||
|
msgid "You can only have 10 favorites"
|
||||||
|
msgstr "Vous ne pouvez avoir que 10 favoris"
|
||||||
|
|
||||||
msgid "Add a podcast"
|
msgid "Add a podcast"
|
||||||
msgstr "Ajouter un podcast"
|
msgstr "Ajouter un podcast"
|
||||||
|
|
||||||
|
@ -151,3 +154,9 @@ msgstr "Une application requise est manquante"
|
||||||
|
|
||||||
msgid "Install GPodder Sync"
|
msgid "Install GPodder Sync"
|
||||||
msgstr "Installer GPodder Sync"
|
msgstr "Installer GPodder Sync"
|
||||||
|
|
||||||
|
msgid "Pin some subscriptions to see their latest updates"
|
||||||
|
msgstr "Ajoutez des abonnements en favoris pour obtenir les dernières nouvelles ici"
|
||||||
|
|
||||||
|
msgid "No favorites"
|
||||||
|
msgstr "Aucun favoris"
|
||||||
|
|
|
@ -8,7 +8,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Nextcloud 3.14159\n"
|
"Project-Id-Version: Nextcloud 3.14159\n"
|
||||||
"Report-Msgid-Bugs-To: translations\\@example.com\n"
|
"Report-Msgid-Bugs-To: translations\\@example.com\n"
|
||||||
"POT-Creation-Date: 2024-08-09 10:10+0000\n"
|
"POT-Creation-Date: 2024-09-02 09:08+0000\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
@ -48,8 +48,8 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /app/specialVueFakeDummyForL10nScript.js:1
|
#: /app/specialVueFakeDummyForL10nScript.js:1
|
||||||
#: /app/specialVueFakeDummyForL10nScript.js:27
|
#: /app/specialVueFakeDummyForL10nScript.js:24
|
||||||
#: /app/specialVueFakeDummyForL10nScript.js:28
|
#: /app/specialVueFakeDummyForL10nScript.js:25
|
||||||
msgid "Download"
|
msgid "Download"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -106,96 +106,109 @@ msgstr ""
|
||||||
#: /app/specialVueFakeDummyForL10nScript.js:19
|
#: /app/specialVueFakeDummyForL10nScript.js:19
|
||||||
#: /app/specialVueFakeDummyForL10nScript.js:20
|
#: /app/specialVueFakeDummyForL10nScript.js:20
|
||||||
#: /app/specialVueFakeDummyForL10nScript.js:21
|
#: /app/specialVueFakeDummyForL10nScript.js:21
|
||||||
msgid "Mark as read"
|
msgid "Read"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /app/specialVueFakeDummyForL10nScript.js:22
|
#: /app/specialVueFakeDummyForL10nScript.js:22
|
||||||
#: /app/specialVueFakeDummyForL10nScript.js:23
|
#: /app/specialVueFakeDummyForL10nScript.js:23
|
||||||
#: /app/specialVueFakeDummyForL10nScript.js:24
|
|
||||||
msgid "Mark as unread"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: /app/specialVueFakeDummyForL10nScript.js:25
|
|
||||||
#: /app/specialVueFakeDummyForL10nScript.js:26
|
|
||||||
msgid "Open website"
|
msgid "Open website"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /app/specialVueFakeDummyForL10nScript.js:29
|
#: /app/specialVueFakeDummyForL10nScript.js:26
|
||||||
msgid "Could not fetch episodes"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: /app/specialVueFakeDummyForL10nScript.js:30
|
|
||||||
msgid "Could not change the status of the episode"
|
msgid "Could not change the status of the episode"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /app/specialVueFakeDummyForL10nScript.js:31
|
#: /app/specialVueFakeDummyForL10nScript.js:27
|
||||||
|
#: /app/specialVueFakeDummyForL10nScript.js:28
|
||||||
|
msgid "Could not fetch episodes"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /app/specialVueFakeDummyForL10nScript.js:29
|
||||||
msgid "Export subscriptions"
|
msgid "Export subscriptions"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /app/specialVueFakeDummyForL10nScript.js:32
|
#: /app/specialVueFakeDummyForL10nScript.js:30
|
||||||
msgid "Filtering episodes"
|
msgid "Filtering episodes"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /app/specialVueFakeDummyForL10nScript.js:33
|
#: /app/specialVueFakeDummyForL10nScript.js:31
|
||||||
msgid "Show all"
|
msgid "Show all"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /app/specialVueFakeDummyForL10nScript.js:34
|
#: /app/specialVueFakeDummyForL10nScript.js:32
|
||||||
msgid "Listened"
|
msgid "Listened"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /app/specialVueFakeDummyForL10nScript.js:35
|
#: /app/specialVueFakeDummyForL10nScript.js:33
|
||||||
msgid "Listening"
|
msgid "Listening"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /app/specialVueFakeDummyForL10nScript.js:36
|
#: /app/specialVueFakeDummyForL10nScript.js:34
|
||||||
msgid "Unlistened"
|
msgid "Unlistened"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /app/specialVueFakeDummyForL10nScript.js:37
|
#: /app/specialVueFakeDummyForL10nScript.js:35
|
||||||
msgid "Import subscriptions"
|
msgid "Import subscriptions"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /app/specialVueFakeDummyForL10nScript.js:38
|
#: /app/specialVueFakeDummyForL10nScript.js:36
|
||||||
msgid "Import OPML file"
|
msgid "Import OPML file"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /app/specialVueFakeDummyForL10nScript.js:39
|
#: /app/specialVueFakeDummyForL10nScript.js:37
|
||||||
msgid "Rate RePod ❤️"
|
msgid "Rate RePod ❤️"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /app/specialVueFakeDummyForL10nScript.js:40
|
#: /app/specialVueFakeDummyForL10nScript.js:38
|
||||||
msgid "Playback speed"
|
msgid "Playback speed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: /app/specialVueFakeDummyForL10nScript.js:39
|
||||||
|
#: /app/specialVueFakeDummyForL10nScript.js:40
|
||||||
#: /app/specialVueFakeDummyForL10nScript.js:41
|
#: /app/specialVueFakeDummyForL10nScript.js:41
|
||||||
msgid "Are you sure you want to delete this subscription?"
|
msgid "Favorite"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /app/specialVueFakeDummyForL10nScript.js:42
|
#: /app/specialVueFakeDummyForL10nScript.js:42
|
||||||
msgid "Error while removing the feed"
|
msgid "Are you sure you want to delete this subscription?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /app/specialVueFakeDummyForL10nScript.js:43
|
#: /app/specialVueFakeDummyForL10nScript.js:43
|
||||||
msgid "Add a podcast"
|
msgid "Error while removing the feed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /app/specialVueFakeDummyForL10nScript.js:44
|
#: /app/specialVueFakeDummyForL10nScript.js:44
|
||||||
msgid "Could not fetch subscriptions"
|
msgid "You can only have 10 favorites"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /app/specialVueFakeDummyForL10nScript.js:45
|
#: /app/specialVueFakeDummyForL10nScript.js:45
|
||||||
msgid "Find a podcast"
|
msgid "Add a podcast"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /app/specialVueFakeDummyForL10nScript.js:46
|
#: /app/specialVueFakeDummyForL10nScript.js:46
|
||||||
msgid "Error loading feed"
|
msgid "Could not fetch subscriptions"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /app/specialVueFakeDummyForL10nScript.js:47
|
#: /app/specialVueFakeDummyForL10nScript.js:47
|
||||||
msgid "Missing required app"
|
msgid "Find a podcast"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /app/specialVueFakeDummyForL10nScript.js:48
|
#: /app/specialVueFakeDummyForL10nScript.js:48
|
||||||
|
msgid "Error loading feed"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /app/specialVueFakeDummyForL10nScript.js:49
|
||||||
|
msgid "Missing required app"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /app/specialVueFakeDummyForL10nScript.js:50
|
||||||
msgid "Install GPodder Sync"
|
msgid "Install GPodder Sync"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: /app/specialVueFakeDummyForL10nScript.js:51
|
||||||
|
msgid "Pin some subscriptions to see their latest updates"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /app/specialVueFakeDummyForL10nScript.js:52
|
||||||
|
msgid "No favorites"
|
||||||
|
msgstr ""
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { createAppConfig } from '@nextcloud/vite-config'
|
import { createAppConfig } from '@nextcloud/vite-config'
|
||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
|
import vueDevTools from 'vite-plugin-vue-devtools'
|
||||||
|
|
||||||
const config = defineConfig(({ mode }) => ({
|
const config = defineConfig(({ mode }) => ({
|
||||||
build: {
|
build: {
|
||||||
|
@ -10,8 +11,12 @@ const config = defineConfig(({ mode }) => ({
|
||||||
manualChunks: false,
|
manualChunks: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
sourcemap: mode === 'development',
|
sourcemap: mode !== 'production',
|
||||||
},
|
},
|
||||||
|
define: {
|
||||||
|
__VUE_PROD_DEVTOOLS__: mode !== 'production'
|
||||||
|
},
|
||||||
|
plugins: [vueDevTools()],
|
||||||
}))
|
}))
|
||||||
|
|
||||||
export default createAppConfig(
|
export default createAppConfig(
|
||||||
|
|
Loading…
Reference in New Issue