Compare commits

..

24 Commits

Author SHA1 Message Date
094b7812cd Merge pull request 'new homepage based on favorites (fixes #130 #59)' (#131) from favorites into main
All checks were successful
repod / xml (push) Successful in 11s
repod / php (push) Successful in 59s
repod / nodejs (push) Successful in 1m10s
repod / release (push) Successful in 1m37s
Reviewed-on: #131
2024-09-02 09:27:56 +00:00
491ad89242 docs: 📝 update changelog
All checks were successful
repod / xml (push) Successful in 12s
repod / php (push) Successful in 1m3s
repod / nodejs (push) Successful in 54s
repod / release (push) Has been skipped
2024-09-02 11:27:18 +02:00
01e2dabb65 chore: 🔖 update version 2024-09-02 11:27:10 +02:00
a86ea6ab3f chore: 🌐 update langs
All checks were successful
repod / xml (push) Successful in 14s
repod / php (push) Successful in 54s
repod / nodejs (push) Successful in 59s
repod / release (push) Has been skipped
2024-09-02 11:12:58 +02:00
7b7ceef503 perf: write getters for accessible filtered cookies values
All checks were successful
repod / xml (push) Successful in 1m36s
repod / php (push) Successful in 59s
repod / nodejs (push) Successful in 1m34s
repod / release (push) Has been skipped
2024-09-02 10:51:48 +02:00
437c7868dd fix: 🐛 fix wrong state placement on action (fix #136) 2024-09-02 10:51:21 +02:00
7c151d8f58 style: 💄 tweak home for mobile
All checks were successful
repod / xml (push) Successful in 21s
repod / php (push) Successful in 1m6s
repod / nodejs (push) Successful in 1m9s
repod / release (push) Has been skipped
2024-08-27 17:39:53 +02:00
b7025a7aa1 fix: 💄 fix padding and routing
All checks were successful
repod / xml (push) Successful in 17s
repod / php (push) Successful in 1m3s
repod / nodejs (push) Successful in 1m6s
repod / release (push) Has been skipped
2024-08-27 16:41:58 +02:00
42035d6e18 refactor: 💥 big rewrite to extract episode to his own component
All checks were successful
repod / xml (push) Successful in 35s
repod / php (push) Successful in 1m6s
repod / nodejs (push) Successful in 1m10s
repod / release (push) Has been skipped
2024-08-27 16:11:12 +02:00
5ed33d1cf6 refactor: 🚚 rework how routing works
All checks were successful
repod / xml (push) Successful in 27s
repod / php (push) Successful in 1m3s
repod / nodejs (push) Successful in 1m11s
repod / release (push) Has been skipped
2024-08-27 09:42:52 +02:00
4e4730efd5 style: 💄 use NcGuestContent to separate favorites
All checks were successful
repod / xml (push) Successful in 19s
repod / php (push) Successful in 1m5s
repod / nodejs (push) Successful in 1m6s
repod / release (push) Has been skipped
2024-08-26 17:17:14 +02:00
9005b519f3 refactor: 💄 refacto NcEmptyContent
All checks were successful
repod / xml (push) Successful in 26s
repod / php (push) Successful in 1m3s
repod / nodejs (push) Successful in 1m11s
repod / release (push) Has been skipped
2024-08-26 16:36:13 +02:00
b0132287f0 style: 💄 improve style and detection of gpodder sync 2024-08-26 16:23:58 +02:00
062da25264 Merge branch 'main' into favorites
All checks were successful
repod / xml (push) Successful in 28s
repod / php (push) Successful in 1m8s
repod / nodejs (push) Successful in 1m21s
repod / release (push) Has been skipped
2024-08-26 13:14:56 +00:00
60aedf3be5 fix: ⬆️ update lock and fix severall typos
All checks were successful
repod / xml (push) Successful in 21s
repod / php (push) Successful in 1m7s
repod / nodejs (push) Successful in 1m16s
repod / release (push) Has been skipped
2024-08-25 09:53:52 +00:00
4bafb3306b Merge branch 'main' into favorites 2024-08-25 09:42:58 +00:00
eb1196c841 feat: 🚧 wip on showing favorites
Some checks failed
repod / xml (push) Successful in 31s
repod / php (push) Failing after 1m8s
repod / nodejs (push) Failing after 1m20s
repod / release (push) Has been skipped
2024-08-22 17:35:05 +02:00
dd275a1f03 feat: 🧑‍💻 add vue devtools in dev 2024-08-22 17:34:27 +02:00
f205d3243f refactor: ♻️ rewrite data to arrow function 2024-08-22 13:30:52 +02:00
46b30f1ebb feat: ⚗️ implement waiting on home
All checks were successful
repod / xml (push) Successful in 30s
repod / php (push) Successful in 1m4s
repod / nodejs (push) Successful in 1m14s
repod / release (push) Has been skipped
2024-08-21 13:11:09 +00:00
9be107edc9 chore: ⬆️ update lock
All checks were successful
repod / xml (push) Successful in 15s
repod / php (push) Successful in 58s
repod / nodejs (push) Successful in 1m11s
repod / release (push) Has been skipped
2024-08-21 09:33:30 +00:00
4e6eee96bf Merge branch 'main' into favorites
All checks were successful
repod / xml (push) Successful in 30s
repod / php (push) Successful in 1m12s
repod / nodejs (push) Successful in 1m24s
repod / release (push) Has been skipped
2024-08-21 09:25:09 +00:00
2824431330 refactor: 🌐 rework read and add missing translations
All checks were successful
repod / xml (push) Successful in 16s
repod / php (push) Successful in 1m0s
repod / nodejs (push) Successful in 58s
repod / release (push) Has been skipped
2024-08-17 18:19:45 +02:00
a30678bfd2 feat: add favorites (missing homepage)
All checks were successful
repod / xml (push) Successful in 27s
repod / php (push) Successful in 1m1s
repod / nodejs (push) Successful in 1m12s
repod / release (push) Has been skipped
2024-08-17 17:56:12 +02:00
44 changed files with 2010 additions and 764 deletions

View File

@ -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
### Added

View File

@ -11,7 +11,7 @@
## Requirements
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>
<author mail="xefir@crystalyx.net" homepage="https://crystalyx.net">Michel Roux</author>
<namespace>RePod</namespace>

View File

@ -13,6 +13,8 @@ declare(strict_types=1);
return [
'routes' => [
['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#list', 'url' => '/episodes/list', 'verb' => 'GET'],
['name' => 'opml#export', 'url' => '/opml/export', 'verb' => 'GET'],

View File

@ -15,9 +15,9 @@
"psalm": "psalm --threads=1 --no-cache --show-info=true"
},
"require-dev": {
"nextcloud/ocp": "^29.0.4",
"nextcloud/ocp": "^29.0.5",
"roave/security-advisories": "dev-latest",
"nextcloud/coding-standard": "^1.2.1",
"nextcloud/coding-standard": "^1.2.3",
"vimeo/psalm": "^5.25.0"
},
"config": {

30
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "855f98aa313f86222776e061139c42ec",
"content-hash": "a59841dc91b50fc36ec116bab55543b0",
"packages": [],
"packages-dev": [
{
@ -169,26 +169,26 @@
},
{
"name": "composer/pcre",
"version": "3.2.0",
"version": "3.3.0",
"source": {
"type": "git",
"url": "https://github.com/composer/pcre.git",
"reference": "ea4ab6f9580a4fd221e0418f2c357cdd39102a90"
"reference": "1637e067347a0c40bbb1e3cd786b20dcab556a81"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/pcre/zipball/ea4ab6f9580a4fd221e0418f2c357cdd39102a90",
"reference": "ea4ab6f9580a4fd221e0418f2c357cdd39102a90",
"url": "https://api.github.com/repos/composer/pcre/zipball/1637e067347a0c40bbb1e3cd786b20dcab556a81",
"reference": "1637e067347a0c40bbb1e3cd786b20dcab556a81",
"shasum": ""
},
"require": {
"php": "^7.4 || ^8.0"
},
"conflict": {
"phpstan/phpstan": "<1.11.8"
"phpstan/phpstan": "<1.11.10"
},
"require-dev": {
"phpstan/phpstan": "^1.11.8",
"phpstan/phpstan": "^1.11.10",
"phpstan/phpstan-strict-rules": "^1.1",
"phpunit/phpunit": "^8 || ^9"
},
@ -228,7 +228,7 @@
],
"support": {
"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": [
{
@ -244,7 +244,7 @@
"type": "tidelift"
}
],
"time": "2024-07-25T09:36:02+00:00"
"time": "2024-08-19T19:43:53+00:00"
},
{
"name": "composer/semver",
@ -1312,12 +1312,12 @@
"source": {
"type": "git",
"url": "https://github.com/Roave/SecurityAdvisories.git",
"reference": "251a4f1fefcc6e6cc90d50514fee6b6e3745cb3e"
"reference": "f8de2a81061775002d96aea80b12f2ab3c5eeb8d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/251a4f1fefcc6e6cc90d50514fee6b6e3745cb3e",
"reference": "251a4f1fefcc6e6cc90d50514fee6b6e3745cb3e",
"url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/f8de2a81061775002d96aea80b12f2ab3c5eeb8d",
"reference": "f8de2a81061775002d96aea80b12f2ab3c5eeb8d",
"shasum": ""
},
"conflict": {
@ -1355,7 +1355,7 @@
"athlon1600/php-proxy-app": "<=3",
"austintoddj/canvas": "<=3.4.2",
"auth0/wordpress": "<=4.6",
"automad/automad": "<=2.0.0.0-alpha5",
"automad/automad": "<2.0.0.0-alpha5",
"automattic/jetpack": "<9.8",
"awesome-support/awesome-support": "<=6.0.7",
"aws/aws-sdk-php": "<3.288.1",
@ -1528,7 +1528,7 @@
"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",
"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",
"fuel/core": "<1.8.1",
"funadmin/funadmin": "<=3.2|>=3.3.2,<=3.3.3",
@ -2121,7 +2121,7 @@
"type": "tidelift"
}
],
"time": "2024-08-14T19:05:08+00:00"
"time": "2024-08-23T19:04:38+00:00"
},
{
"name": "sebastian/diff",

View File

@ -18,11 +18,10 @@ OC.L10N.register(
"Link copied to the clipboard" : "Der Link des Feeds wurde in die Zwischenablage kopiert",
"Play" : "Abspielen",
"Stop" : "Stopp",
"Mark as read" : "Als gelesen markieren",
"Mark as unread" : "Als ungelesen markieren",
"Read" : "Gelesen",
"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 fetch episodes" : "Folgen können nicht abgerufen werden",
"Export subscriptions" : "Abonnements exportieren",
"Filtering episodes" : "Folgen filtern",
"Show all" : "Zeige alles",
@ -33,13 +32,17 @@ OC.L10N.register(
"Import OPML file" : "Importiere OPML-Datei",
"Rate RePod ❤️" : "Bewerte RePod ❤️",
"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?",
"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",
"Could not fetch subscriptions" : "Abonnements können nicht abgerufen werden",
"Find a podcast" : "Finde einen Podcast",
"Error loading feed" : "Fehler beim Laden des Feeds",
"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"
},
"");

View File

@ -16,11 +16,10 @@
"Link copied to the clipboard" : "Der Link des Feeds wurde in die Zwischenablage kopiert",
"Play" : "Abspielen",
"Stop" : "Stopp",
"Mark as read" : "Als gelesen markieren",
"Mark as unread" : "Als ungelesen markieren",
"Read" : "Gelesen",
"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 fetch episodes" : "Folgen können nicht abgerufen werden",
"Export subscriptions" : "Abonnements exportieren",
"Filtering episodes" : "Folgen filtern",
"Show all" : "Zeige alles",
@ -31,13 +30,17 @@
"Import OPML file" : "Importiere OPML-Datei",
"Rate RePod ❤️" : "Bewerte RePod ❤️",
"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?",
"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",
"Could not fetch subscriptions" : "Abonnements können nicht abgerufen werden",
"Find a podcast" : "Finde einen Podcast",
"Error loading feed" : "Fehler beim Laden des Feeds",
"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" :""
}

View File

@ -18,11 +18,10 @@ OC.L10N.register(
"Link copied to the clipboard" : "Lien vers le flux copié dans le presse-papiers",
"Play" : "Lecture",
"Stop" : "Arrêter",
"Mark as read" : "Marquer comme lu",
"Mark as unread" : "Marquer comme non lu",
"Read" : "Lu",
"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 fetch episodes" : "Impossible de récuprer les épisodes",
"Export subscriptions" : "Exporter les abonnements",
"Filtering episodes" : "Filtrage des épisodes",
"Show all" : "Montrer tout",
@ -33,13 +32,17 @@ OC.L10N.register(
"Import OPML file" : "Importer un fichier OPML",
"Rate RePod ❤️" : "Donnez votre avis ❤️",
"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 ?",
"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",
"Could not fetch subscriptions" : "Impossible de récupérer les flux",
"Find a podcast" : "Chercher un podcast",
"Error loading feed" : "Erreur lors du chargement du flux",
"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"
},
"");

View File

@ -16,11 +16,10 @@
"Link copied to the clipboard" : "Lien vers le flux copié dans le presse-papiers",
"Play" : "Lecture",
"Stop" : "Arrêter",
"Mark as read" : "Marquer comme lu",
"Mark as unread" : "Marquer comme non lu",
"Read" : "Lu",
"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 fetch episodes" : "Impossible de récuprer les épisodes",
"Export subscriptions" : "Exporter les abonnements",
"Filtering episodes" : "Filtrage des épisodes",
"Show all" : "Montrer tout",
@ -31,13 +30,17 @@
"Import OPML file" : "Importer un fichier OPML",
"Rate RePod ❤️" : "Donnez votre avis ❤️",
"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 ?",
"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",
"Could not fetch subscriptions" : "Impossible de récupérer les flux",
"Find a podcast" : "Chercher un podcast",
"Error loading feed" : "Erreur lors du chargement du flux",
"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" :""
}

View File

@ -32,6 +32,10 @@ class Application extends App implements IBootstrap
/** @var IInitialState $initialState */
$initialState = $appContainer->get(IInitialState::class);
if (null === $appManager->getAppInfo(self::GPODDERSYNC_ID)) {
$appManager->disableApp(self::GPODDERSYNC_ID);
}
$gpoddersync = $appManager->isEnabledForUser(self::GPODDERSYNC_ID);
if (!$gpoddersync) {
try {

View File

@ -8,10 +8,19 @@ use OCA\RePod\AppInfo\Application;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\ContentSecurityPolicy;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\IConfig;
use OCP\IRequest;
use OCP\Util;
class PageController extends Controller
{
public function __construct(
IRequest $request,
private IConfig $config
) {
parent::__construct(Application::APP_ID, $request);
}
/**
* @NoAdminRequired
* @NoCSRFRequired
@ -23,9 +32,32 @@ class PageController extends Controller
$csp->addAllowedImageDomain('*');
$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->setContentSecurityPolicy($csp);
return $response;
}
/**
* @NoAdminRequired
* @NoCSRFRequired
*/
public function discover(): TemplateResponse {
return $this->index();
}
/**
* @NoAdminRequired
* @NoCSRFRequired
*/
public function feed(): TemplateResponse {
return $this->index();
}
}

1494
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -26,7 +26,7 @@
"linkify-html": "^4.1.3",
"pinia": "^2.2.2",
"toastify-js": "^1.12.0",
"vite": "^5.4.1",
"vite": "^5.4.2",
"vue": "^3.4.38",
"vue-material-design-icons": "^5.3.0",
"vue-router": "^4.4.3"
@ -37,7 +37,8 @@
"@nextcloud/prettier-config": "^1.1.0",
"@nextcloud/stylelint-config": "^3.0.1",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-pinia": "^0.4.0",
"eslint-plugin-prettier": "^5.2.1"
"eslint-plugin-pinia": "^0.4.1",
"eslint-plugin-prettier": "^5.2.1",
"vite-plugin-vue-devtools": "^7.3.9"
}
}

View File

@ -1,5 +1,5 @@
<template>
<NcAppContent :class="{ padding: episode }">
<NcAppContent :class="{ episode }">
<slot />
</NcAppContent>
</template>
@ -21,7 +21,7 @@ export default {
</script>
<style scoped>
.padding {
.episode {
padding-bottom: 6rem;
}
</style>

View File

@ -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>

View File

@ -1,25 +1,29 @@
<template>
<div class="flex">
<NcAvatar :display-name="name" :is-no-user="true" :size="256" :url="image" />
<h2>{{ name }}</h2>
<SafeHtml :source="description" />
<NcAvatar
:display-name="episode.name"
:is-no-user="true"
:size="256"
:url="episode.image" />
<h2>{{ episode.name }}</h2>
<SafeHtml :source="episode.description" />
<div class="flex">
<NcButton v-if="link" :href="link" target="_blank">
<NcButton v-if="episode.link" :href="episode.link" target="_blank">
<template #icon>
<OpenInNewIcon :size="20" />
</template>
{{ title }}
{{ episode.title }}
</NcButton>
<NcButton
v-if="url"
:download="filenameFromUrl(url)"
:href="url"
v-if="episode.url"
:download="filenameFromUrl(episode.url)"
:href="episode.url"
target="_blank">
<template #icon>
<DownloadIcon :size="20" />
</template>
{{ t('repod', 'Download') }}
{{ size ? `(${humanFileSize(size)})` : '' }}
{{ episode.size ? `(${humanFileSize(episode.size)})` : '' }}
</NcButton>
</div>
</div>
@ -43,32 +47,8 @@ export default {
SafeHtml,
},
props: {
description: {
type: String,
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,
episode: {
type: Object,
required: true,
},
},

View File

@ -2,7 +2,7 @@
<NcAppNavigationList>
<NcAppNavigationNewItem
:name="t('repod', 'Add a RSS link')"
@new-item="addSubscription">
@new-item="(url) => $router.push(toFeedUrl(url))">
<template #icon>
<PlusIcon :size="20" />
</template>
@ -13,7 +13,7 @@
<script>
import { NcAppNavigationList, NcAppNavigationNewItem } from '@nextcloud/vue'
import PlusIcon from 'vue-material-design-icons/Plus.vue'
import { encodeUrl } from '../../utils/url.js'
import { toFeedUrl } from '../../utils/url.js'
export default {
name: 'AddRss',
@ -23,9 +23,7 @@ export default {
PlusIcon,
},
methods: {
addSubscription(feedUrl) {
this.$router.push(encodeUrl(feedUrl))
},
toFeedUrl,
},
}
</script>

View File

@ -7,7 +7,7 @@
:key="feed.link"
:details="formatLocaleDate(new Date(feed.fetchedAtUnix * 1000))"
:name="feed.title"
:to="toUrl(feed.link)">
:to="toFeedUrl(feed.link)">
<template #icon>
<NcAvatar
:display-name="feed.author"
@ -19,7 +19,7 @@
</template>
<template #actions>
<NcActionButton
v-if="!subscriptions.includes(feed.link)"
v-if="!getSubscriptions.includes(feed.link)"
:aria-label="t('repod', 'Subscribe')"
:name="t('repod', 'Subscribe')"
:title="t('repod', 'Subscribe')"
@ -44,7 +44,7 @@ import { debounce } from '../../utils/debounce.js'
import { formatLocaleDate } from '../../utils/time.js'
import { generateUrl } from '@nextcloud/router'
import { showError } from '../../utils/toast.js'
import { toUrl } from '../../utils/url.js'
import { toFeedUrl } from '../../utils/url.js'
import { useSubscriptions } from '../../store/subscriptions.js'
export default {
@ -62,14 +62,12 @@ export default {
required: true,
},
},
data() {
return {
data: () => ({
feeds: [],
loading: false,
}
},
}),
computed: {
...mapState(useSubscriptions, ['subscriptions']),
...mapState(useSubscriptions, ['getSubscriptions']),
},
watch: {
value() {
@ -79,7 +77,7 @@ export default {
methods: {
...mapActions(useSubscriptions, ['fetch']),
formatLocaleDate,
toUrl,
toFeedUrl,
async addSubscription(url) {
try {
await axios.post(

View File

@ -4,7 +4,7 @@
<Loading v-if="loading" />
<ul v-if="!loading">
<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" />
</router-link>
</li>
@ -17,7 +17,7 @@ import Loading from '../Atoms/Loading.vue'
import axios from '@nextcloud/axios'
import { generateUrl } from '@nextcloud/router'
import { showError } from '../../utils/toast.js'
import { toUrl } from '../../utils/url.js'
import { toFeedUrl } from '../../utils/url.js'
export default {
name: 'Toplist',
@ -30,12 +30,10 @@ export default {
required: true,
},
},
data() {
return {
data: () => ({
loading: true,
tops: [],
}
},
}),
computed: {
title() {
switch (this.type) {
@ -63,7 +61,7 @@ export default {
}
},
methods: {
toUrl,
toFeedUrl,
},
}
</script>

View File

@ -23,7 +23,7 @@
<SafeHtml :source="description" />
</div>
<NcAppNavigationNew
v-if="!subscriptions.includes(url)"
v-if="!getSubscriptions.includes(url)"
:text="t('repod', 'Subscribe')"
@click="addSubscription">
<template #icon>
@ -79,7 +79,7 @@ export default {
},
},
computed: {
...mapState(useSubscriptions, ['subscriptions']),
...mapState(useSubscriptions, ['getSubscriptions']),
url() {
return decodeUrl(this.$route.params.url)
},

View File

@ -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>

View File

@ -2,144 +2,23 @@
<div>
<Loading v-if="loading" />
<ul v-if="!loading">
<NcListItem
<Episode
v-for="episode in filteredEpisodes"
:key="episode.guid"
:active="isCurrentEpisode(episode)"
:details="formatLocaleDate(new Date(episode.pubDate?.date))"
: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>
:episode="episode"
:url="url" />
</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>
</template>
<script>
import {
NcActionButton,
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 { hasEnded, isListening } from '../../utils/status.js'
import Episode from './Episode.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 { decodeUrl } from '../../utils/url.js'
import { generateUrl } from '@nextcloud/router'
import { mapState } from 'pinia'
import { showError } from '../../utils/toast.js'
import { usePlayer } from '../../store/player.js'
import { useSettings } from '../../store/settings.js'
@ -147,30 +26,13 @@ import { useSettings } from '../../store/settings.js'
export default {
name: 'Episodes',
components: {
DownloadIcon,
Episode,
Loading,
Modal,
NcActionButton,
NcActionLink,
NcActions,
NcAvatar,
NcListItem,
NcModal,
NcProgressBar,
OpenInNewIcon,
PlayIcon,
PlaylistPlayIcon,
PlaylistRemoveIcon,
StopIcon,
},
data() {
return {
data: () => ({
episodes: [],
loading: true,
loadingAction: false,
modalEpisode: null,
}
},
}),
computed: {
...mapState(usePlayer, ['episode']),
...mapState(useSettings, ['filters']),
@ -223,63 +85,8 @@ export default {
}
},
methods: {
...mapActions(usePlayer, ['load']),
filenameFromUrl,
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
}
},
hasEnded,
isListening,
},
}
</script>
<style scoped>
.progress {
margin-top: 0.4rem;
}
</style>

View File

@ -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>

View File

@ -78,7 +78,7 @@ export default {
@media only screen and (max-width: 768px) {
.infos {
flex: 2;
flex: 1;
}
.timer,

View File

@ -3,18 +3,11 @@
<strong class="pointer" @click="modal = true">
{{ episode.name }}
</strong>
<router-link :to="hash">
<router-link :to="toFeedUrl(podcastUrl)">
<i>{{ episode.title }}</i>
</router-link>
<NcModal v-if="modal" @close="modal = false">
<Modal
:description="episode.description"
:image="episode.image"
:link="episode.link"
:name="episode.name"
:size="episode.size"
:title="episode.title"
:url="episode.url" />
<Modal :episode="episode" />
</NcModal>
</div>
</template>
@ -23,7 +16,7 @@
import Modal from '../Atoms/Modal.vue'
import { NcModal } from '@nextcloud/vue'
import { mapState } from 'pinia'
import { toUrl } from '../../utils/url.js'
import { toFeedUrl } from '../../utils/url.js'
import { usePlayer } from '../../store/player.js'
export default {
@ -32,16 +25,14 @@ export default {
Modal,
NcModal,
},
data() {
return {
data: () => ({
modal: false,
}
},
}),
computed: {
...mapState(usePlayer, ['episode', 'podcastUrl']),
hash() {
return toUrl(this.podcastUrl)
},
methods: {
toFeedUrl,
},
}
</script>

View File

@ -46,11 +46,9 @@ export default {
VolumeMediumIcon,
VolumeMuteIcon,
},
data() {
return {
data: () => ({
volumeMuted: 0,
}
},
}),
computed: {
...mapState(usePlayer, ['volume']),
},

View File

@ -44,7 +44,6 @@ import { NcActionCheckbox, NcAppNavigationItem } from '@nextcloud/vue'
import { mapActions, mapState } from 'pinia'
import FilterIcon from 'vue-material-design-icons/Filter.vue'
import FilterSettingsIcon from 'vue-material-design-icons/FilterSettings.vue'
import { getCookie } from '../../utils/cookies.js'
import { useSettings } from '../../store/settings.js'
export default {
@ -65,12 +64,6 @@ export default {
)
},
},
mounted() {
try {
const filters = getCookie('repod.filters')
this.filters = JSON.parse(filters)
} catch {}
},
methods: {
...mapActions(useSettings, ['setFilters']),
},

View File

@ -44,12 +44,10 @@ export default {
NcAppNavigationItem,
NcModal,
},
data() {
return {
data: () => ({
loading: false,
modal: false,
}
},
}),
methods: {
generateUrl,
async importOpml(event) {

View File

@ -2,8 +2,19 @@
<NcAppNavigationItem
:loading="loading"
:name="feed ? feed.title : url"
:to="hash">
:to="toFeedUrl(url)">
<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
:aria-label="t(`core`, 'Delete')"
:name="t(`core`, 'Delete')"
@ -20,6 +31,7 @@
:display-name="feed.author || feed.title"
:is-no-user="true"
:url="feed.imageUrl" />
<StarIcon v-if="feed && isFavorite" class="star" :size="20" />
<AlertIcon v-if="failed" />
</template>
</NcAppNavigationItem>
@ -27,23 +39,29 @@
<script>
import { NcActionButton, NcAppNavigationItem, NcAvatar } from '@nextcloud/vue'
import { mapActions, mapState } from 'pinia'
import AlertIcon from 'vue-material-design-icons/Alert.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 { generateUrl } from '@nextcloud/router'
import { mapActions } from 'pinia'
import { showError } from '../../utils/toast.js'
import { toUrl } from '../../utils/url.js'
import { toFeedUrl } from '../../utils/url.js'
import { useSubscriptions } from '../../store/subscriptions.js'
export default {
name: 'Item',
name: 'Subscription',
components: {
AlertIcon,
DeleteIcon,
NcActionButton,
NcAppNavigationItem,
NcAvatar,
StarIcon,
StarPlusIcon,
StarRemoveIcon,
},
props: {
url: {
@ -51,16 +69,15 @@ export default {
required: true,
},
},
data() {
return {
data: () => ({
failed: false,
loading: true,
feed: null,
}
},
}),
computed: {
hash() {
return toUrl(this.url)
...mapState(useSubscriptions, ['getFavorites']),
isFavorite() {
return this.getFavorites.map((fav) => fav.url).includes(this.url)
},
},
async mounted() {
@ -74,6 +91,7 @@ export default {
),
)
this.feed = podcastData.data.data
this.editFavoriteData(this.url, podcastData.data.data)
} catch (e) {
this.failed = true
console.error(e)
@ -82,7 +100,13 @@ export default {
}
},
methods: {
...mapActions(useSubscriptions, ['fetch']),
...mapActions(useSubscriptions, [
'fetch',
'addFavorite',
'editFavoriteData',
'removeFavorite',
]),
toFeedUrl,
async deleteSubscription() {
if (
confirm(
@ -99,11 +123,33 @@ export default {
console.error(e)
showError(t('repod', 'Error while removing the feed'))
} finally {
this.removeFavorite(this.url)
this.loading = false
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>
<style scoped>
.star {
bottom: 2px;
color: yellow;
left: 22px;
position: absolute;
}
</style>

View File

@ -2,7 +2,7 @@
<AppNavigation>
<template #list>
<NcAppContentList>
<router-link to="/">
<router-link to="/discover">
<NcAppNavigationNew :text="t('repod', 'Add a podcast')">
<template #icon>
<PlusIcon :size="20" />
@ -11,10 +11,17 @@
</router-link>
<Loading v-if="loading" />
<NcAppNavigationList v-if="!loading">
<Item
v-for="subscriptionUrl of subscriptions"
:key="subscriptionUrl"
:url="subscriptionUrl" />
<Subscription
v-for="url of getFavorites.map((fav) => fav.url)"
:key="url"
:url="url" />
<Subscription
v-for="url of getSubscriptions.filter(
(sub) =>
!getFavorites.map((fav) => fav.url).includes(sub),
)"
:key="url"
:url="url" />
</NcAppNavigationList>
</NcAppContentList>
</template>
@ -32,10 +39,10 @@ import {
} from '@nextcloud/vue'
import { mapActions, mapState } from 'pinia'
import AppNavigation from '../Atoms/AppNavigation.vue'
import Item from './Item.vue'
import Loading from '../Atoms/Loading.vue'
import PlusIcon from 'vue-material-design-icons/Plus.vue'
import Settings from '../Settings/Settings.vue'
import Subscription from './Subscription.vue'
import { showError } from '../../utils/toast.js'
import { useSubscriptions } from '../../store/subscriptions.js'
@ -43,21 +50,19 @@ export default {
name: 'Subscriptions',
components: {
AppNavigation,
Item,
Loading,
NcAppContentList,
NcAppNavigationList,
NcAppNavigationNew,
PlusIcon,
Settings,
Subscription,
},
data() {
return {
data: () => ({
loading: true,
}
},
}),
computed: {
...mapState(useSubscriptions, ['subscriptions']),
...mapState(useSubscriptions, ['getSubscriptions', 'getFavorites']),
},
async mounted() {
try {

View File

@ -1,17 +1,22 @@
import { createRouter, createWebHashHistory } from 'vue-router'
import { createRouter, createWebHistory } from 'vue-router'
import Discover from './views/Discover.vue'
import Feed from './views/Feed.vue'
import Home from './views/Home.vue'
import { generateUrl } from '@nextcloud/router'
const router = createRouter({
history: createWebHashHistory(generateUrl('apps/repod')),
history: createWebHistory(generateUrl('apps/repod')),
routes: [
{
path: '/',
component: Home,
},
{
path: '/discover',
component: Discover,
},
{
path: '/:url',
path: '/feed/:url',
component: Feed,
},
],

View File

@ -44,12 +44,11 @@ export const usePlayer = defineStore('player', {
}),
)
this.episode.action = action
this.episode.action = action.data
} catch {}
if (
this.episode.action &&
this.episode.action.position &&
this.episode.action.position < this.episode.action.total
) {
audio.currentTime = this.episode.action.position

View File

@ -1,14 +1,27 @@
import { getCookie, setCookie } from '../utils/cookies.js'
import { defineStore } from 'pinia'
import { setCookie } from '../utils/cookies.js'
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: {
listened: true,
listening: true,
unlistened: true,
},
}),
}
}
},
actions: {
setFilters(filters) {
this.filters = { ...this.filters, ...filters }

View File

@ -1,11 +1,23 @@
import { getCookie, setCookie } from '../utils/cookies.js'
import axios from '@nextcloud/axios'
import { defineStore } from 'pinia'
import { generateUrl } from '@nextcloud/router'
export const useSubscriptions = defineStore('subscriptions', {
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: {
async fetch() {
const metrics = await axios.get(
@ -14,7 +26,33 @@ export const useSubscriptions = defineStore('subscriptions', {
const subs = [...metrics.data.subscriptions].sort(
(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,
)
},
},
})

14
src/utils/status.js Normal file
View File

@ -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)

View File

@ -1,6 +1,6 @@
export const encodeUrl = (url) => encodeURIComponent(btoa(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) => {
const url = new URL(str)
return url.pathname.split('/').pop()

View File

@ -1,5 +1,5 @@
<template>
<AppContent class="main">
<AppContent class="padding">
<NcTextField v-model="search" :label="t('repod', 'Find a podcast')">
<template #icon>
<Magnify :size="20" />
@ -30,16 +30,14 @@ export default {
Search,
Toplist,
},
data() {
return {
data: () => ({
search: '',
}
},
}),
}
</script>
<style scoped>
.main {
.padding {
padding: 15px 51px;
}
</style>

View File

@ -1,14 +1,14 @@
<template>
<AppContent>
<Loading v-if="loading" />
<NcEmptyContent
<EmptyContent
v-if="failed"
class="error"
:name="t('repod', 'Error loading feed')">
<template #icon>
<Alert />
</template>
</NcEmptyContent>
</EmptyContent>
<Banner
v-if="feed"
:author="feed.author"
@ -24,9 +24,9 @@
import Alert from 'vue-material-design-icons/Alert.vue'
import AppContent from '../components/Atoms/AppContent.vue'
import Banner from '../components/Feed/Banner.vue'
import EmptyContent from '../components/Atoms/EmptyContent.vue'
import Episodes from '../components/Feed/Episodes.vue'
import Loading from '../components/Atoms/Loading.vue'
import { NcEmptyContent } from '@nextcloud/vue'
import axios from '@nextcloud/axios'
import { decodeUrl } from '../utils/url.js'
import { generateUrl } from '@nextcloud/router'
@ -37,17 +37,15 @@ export default {
Alert,
AppContent,
Banner,
EmptyContent,
Episodes,
Loading,
NcEmptyContent,
},
data() {
return {
data: () => ({
failed: false,
loading: true,
feed: null,
}
},
}),
computed: {
url() {
return decodeUrl(this.$route.params.url)

View File

@ -1,6 +1,6 @@
<template>
<NcAppContent class="content">
<NcEmptyContent :name="t('repod', 'Missing required app')">
<AppContent>
<EmptyContent class="empty" :name="t('repod', 'Missing required app')">
<template #action>
<NcButton :href="gPodderSyncUrl">
{{ t('repod', 'Install GPodder Sync') }}
@ -9,22 +9,24 @@
<template #icon>
<Alert />
</template>
</NcEmptyContent>
</NcAppContent>
</EmptyContent>
</AppContent>
</template>
<script>
import { NcAppContent, NcButton, NcEmptyContent } from '@nextcloud/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'
export default {
name: 'GPodder',
components: {
Alert,
NcAppContent,
AppContent,
EmptyContent,
NcButton,
NcEmptyContent,
},
computed: {
gPodderSyncUrl() {

42
src/views/Home.vue Normal file
View File

@ -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>

View File

@ -83,21 +83,18 @@ msgstr "Abspielen"
msgid "Stop"
msgstr "Stopp"
msgid "Mark as read"
msgstr "Als gelesen markieren"
msgid "Mark as unread"
msgstr "Als ungelesen markieren"
msgid "Read"
msgstr "Gelesen"
msgid "Open website"
msgstr "Webseite aufrufen"
msgid "Could not fetch episodes"
msgstr "Folgen können nicht abgerufen werden"
msgid "Could not change the status of the episode"
msgstr "Kann den Status der Folge nicht ändern"
msgid "Could not fetch episodes"
msgstr "Folgen können nicht abgerufen werden"
msgid "Export subscriptions"
msgstr "Abonnements exportieren"
@ -128,12 +125,18 @@ msgstr "Bewerte RePod ❤️"
msgid "Playback speed"
msgstr "Wiedergabegeschwindigkeit"
msgid "Favorite"
msgstr "Favorit"
msgid "Are you sure you want to delete this subscription?"
msgstr "Bist Du sicher, dass Du das Abonnement löschen möchtest?"
msgid "Error while removing the feed"
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"
msgstr "Einen Podcast hinzufügen"
@ -151,3 +154,9 @@ msgstr "Benötigte App fehlt"
msgid "Install 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"

View File

@ -83,21 +83,18 @@ msgstr "Lecture"
msgid "Stop"
msgstr "Arrêter"
msgid "Mark as read"
msgstr "Marquer comme lu"
msgid "Mark as unread"
msgstr "Marquer comme non lu"
msgid "Read"
msgstr "Lu"
msgid "Open website"
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"
msgstr "Impossible de changer le status de l'épisode"
msgid "Could not fetch episodes"
msgstr "Impossible de récuprer les épisodes"
msgid "Export subscriptions"
msgstr "Exporter les abonnements"
@ -128,12 +125,18 @@ msgstr "Donnez votre avis ❤️"
msgid "Playback speed"
msgstr "Vitesse de lecture"
msgid "Favorite"
msgstr "Favori"
msgid "Are you sure you want to delete this subscription?"
msgstr "Êtes-vous sûr de vouloir supprimer ce flux ?"
msgid "Error while removing the feed"
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"
msgstr "Ajouter un podcast"
@ -151,3 +154,9 @@ msgstr "Une application requise est manquante"
msgid "Install 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"

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Nextcloud 3.14159\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"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -48,8 +48,8 @@ msgid ""
msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:1
#: /app/specialVueFakeDummyForL10nScript.js:27
#: /app/specialVueFakeDummyForL10nScript.js:28
#: /app/specialVueFakeDummyForL10nScript.js:24
#: /app/specialVueFakeDummyForL10nScript.js:25
msgid "Download"
msgstr ""
@ -106,96 +106,109 @@ msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:19
#: /app/specialVueFakeDummyForL10nScript.js:20
#: /app/specialVueFakeDummyForL10nScript.js:21
msgid "Mark as read"
msgid "Read"
msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:22
#: /app/specialVueFakeDummyForL10nScript.js:23
#: /app/specialVueFakeDummyForL10nScript.js:24
msgid "Mark as unread"
msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:25
#: /app/specialVueFakeDummyForL10nScript.js:26
msgid "Open website"
msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:29
msgid "Could not fetch episodes"
msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:30
#: /app/specialVueFakeDummyForL10nScript.js:26
msgid "Could not change the status of the episode"
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"
msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:32
#: /app/specialVueFakeDummyForL10nScript.js:30
msgid "Filtering episodes"
msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:33
#: /app/specialVueFakeDummyForL10nScript.js:31
msgid "Show all"
msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:34
#: /app/specialVueFakeDummyForL10nScript.js:32
msgid "Listened"
msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:35
#: /app/specialVueFakeDummyForL10nScript.js:33
msgid "Listening"
msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:36
#: /app/specialVueFakeDummyForL10nScript.js:34
msgid "Unlistened"
msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:37
#: /app/specialVueFakeDummyForL10nScript.js:35
msgid "Import subscriptions"
msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:38
#: /app/specialVueFakeDummyForL10nScript.js:36
msgid "Import OPML file"
msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:39
#: /app/specialVueFakeDummyForL10nScript.js:37
msgid "Rate RePod ❤️"
msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:40
#: /app/specialVueFakeDummyForL10nScript.js:38
msgid "Playback speed"
msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:39
#: /app/specialVueFakeDummyForL10nScript.js:40
#: /app/specialVueFakeDummyForL10nScript.js:41
msgid "Are you sure you want to delete this subscription?"
msgid "Favorite"
msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:42
msgid "Error while removing the feed"
msgid "Are you sure you want to delete this subscription?"
msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:43
msgid "Add a podcast"
msgid "Error while removing the feed"
msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:44
msgid "Could not fetch subscriptions"
msgid "You can only have 10 favorites"
msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:45
msgid "Find a podcast"
msgid "Add a podcast"
msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:46
msgid "Error loading feed"
msgid "Could not fetch subscriptions"
msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:47
msgid "Missing required app"
msgid "Find a podcast"
msgstr ""
#: /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"
msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:51
msgid "Pin some subscriptions to see their latest updates"
msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:52
msgid "No favorites"
msgstr ""

View File

@ -1,5 +1,6 @@
import { createAppConfig } from '@nextcloud/vite-config'
import { defineConfig } from 'vite'
import vueDevTools from 'vite-plugin-vue-devtools'
const config = defineConfig(({ mode }) => ({
build: {
@ -10,8 +11,12 @@ const config = defineConfig(({ mode }) => ({
manualChunks: false,
},
},
sourcemap: mode === 'development',
sourcemap: mode !== 'production',
},
define: {
__VUE_PROD_DEVTOOLS__: mode !== 'production'
},
plugins: [vueDevTools()],
}))
export default createAppConfig(