Compare commits

..

No commits in common. "main" and "3.1.0" have entirely different histories.
main ... 3.1.0

92 changed files with 2705 additions and 2889 deletions

View File

@ -9,6 +9,5 @@
/node_modules/
/js/
/css/
/build/

View File

@ -1,11 +1,9 @@
module.exports = {
extends: [
'@nextcloud',
'@vue/eslint-config-typescript/recommended',
'plugin:pinia/recommended',
'plugin:prettier/recommended',
],
parser: 'vue-eslint-parser',
rules: {
'jsdoc/require-jsdoc': 'off',
'vue/first-attribute-linebreak': 'off',

View File

@ -14,7 +14,7 @@ jobs:
php:
runs-on: ubuntu-latest
container: nextcloud:30
container: nextcloud:29
steps:
- run: apt-get update
- run: apt-get install -y git nodejs
@ -30,9 +30,15 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: skjnldsv/read-package-engines-version-actions@v3
id: versions
with:
fallbackNode: '^20'
fallbackNpm: '^10'
- uses: actions/setup-node@v4
with:
node-version: "^20"
node-version: ${{ steps.versions.outputs.nodeVersion }}
- run: npm i -g npm@${{ steps.versions.outputs.npmVersion }}
- run: npm ci
- run: npm run lint
- run: npm run stylelint
@ -41,16 +47,22 @@ jobs:
release:
if: gitea.ref_type == 'tag'
runs-on: ubuntu-latest
container: nextcloud:30
container: nextcloud:29
steps:
- run: apt-get update
- run: apt-get install -y git nodejs
- uses: actions/checkout@v4
- run: curl -sSLo /usr/local/bin/composer https://getcomposer.org/download/latest-stable/composer.phar
- run: chmod +x /usr/local/bin/composer
- uses: skjnldsv/read-package-engines-version-actions@v3
id: versions
with:
fallbackNode: '^20'
fallbackNpm: '^10'
- uses: actions/setup-node@v4
with:
node-version: "^20"
node-version: ${{ steps.versions.outputs.nodeVersion }}
- run: npm i -g npm@${{ steps.versions.outputs.npmVersion }}
- run: make dist
- uses: akkuman/gitea-release-action@v1
with:

1
.gitignore vendored
View File

@ -9,6 +9,5 @@
/node_modules/
/js/
/css/
/build/

View File

@ -9,6 +9,5 @@ tests/.phpunit.cache
node_modules/
js/
css/
build/

View File

@ -15,6 +15,7 @@ class MyConfig extends Config
$rules['curly_braces_position']['classes_opening_brace'] = 'next_line_unless_newline_at_signature_end';
$rules['phpdoc_separation'] = false;
$rules['phpdoc_to_comment'] = false;
$rules['single_line_comment_style'] = false;
return $rules;
}
}

View File

@ -1,65 +1,4 @@
## 3.4.0 - Good Night - 2024-11-09
### Added
- ♿ Improve accessibility by adding titles when missing
- ✨ Playback speed and volume setting doesn't stick
[#185](https://git.crystalyx.net/Xefir/repod/issues/185) reported by @SteveDinn
- ✨ Skip back or forward
[#159](https://git.crystalyx.net/Xefir/repod/issues/159) reported by @moonlike8812
- ✨ Sleep timer
[#119](https://git.crystalyx.net/Xefir/repod/issues/119) reported by @Markusphi and @OiledAmoeba
### Changed
- 💄 Add padding around favorites on mobile
### Fixed
- 🔒 Increase security when Nextcloud is in debug mode
## 3.3.2 - What a Nightmare - 2024-10-24
### Fixed
- 🚑 Revert [#178](https://git.crystalyx.net/Xefir/repod/issues/178) not working on big subscriptions lists
[#182](https://git.crystalyx.net/Xefir/repod/issues/182) reported by @SteveDinn
## 3.3.1 - Breaking the Loop - 2024-10-24
### Changed
- ⚡ Speed up the loading time of subscriptions
[#178](https://git.crystalyx.net/Xefir/repod/issues/178) reported by @MikeAndrews
### Fixed
- 🐛 Prevent Firefox for going nuts when having Plasma Integration addon installed
[#164](https://git.crystalyx.net/Xefir/repod/issues/164) reported by @cichy1173, @Share1440 and @mark.collins
## 3.3.0 - Into The Jet Lag - 2024-10-18
### Changed
- 🧑‍💻 CSS isn't mixed in the main JS file anymore
### Fixed
- 🐛 App won't load on Firefox 115
[#158](https://git.crystalyx.net/Xefir/repod/issues/158) reported by @Jaunty and @mark.collins
- 🔇 Volume slider didn't work properly
### Deprecated
- 💣 Require Nextcloud 29 or more
## 3.2.0 - Typing fast - 2024-09-15
### Added
- 📝 Add Cardo to list of compatible clients
[#176](https://github.com/thrillfall/nextcloud-gpodder/pull/176) reported by @n0vella
### Changed
- 🧑‍💻 Switch entiere project to TypeScript
### Fixed
- 💄 Missing icon on home when aren't any favorites
- 💄 Tweaks spacing in several spaces on Home and banners
- 💩 Leverage the available space between the episode title and the play button (but hacky way for now)
[#59](https://git.crystalyx.net/Xefir/repod/issues/59#issuecomment-6246) reported by @W_LL_M
## 3.1.0 - Above the stars - 2024-09-02
## 3.1.0 - 2024-09-02
### Added
- ⭐ You can now add favorites subscriptions !
@ -74,7 +13,7 @@ It will show's up on the homepage instead of the recommendations witch appear on
[#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 - What a vue - 2024-08-17
## 3.0.0 - 2024-08-17
### Added
- 🌐 Add german translation
@ -92,7 +31,7 @@ Thanks to @OiledAmoeba [#120](https://git.crystalyx.net/Xefir/repod/issues/120)
### Removed
- 🗑️ Temporary replacing @nextcloud/dialogs to toastyjs
## 2.3.3 - The Cake is a Lie - 2024-06-14
## 2.3.3 - 2024-06-14
### Changed
- ⬆️ Update @nextcloud/dialogs to 5.3.2
@ -101,18 +40,18 @@ Thanks to @OiledAmoeba [#120](https://git.crystalyx.net/Xefir/repod/issues/120)
- 🐛 App crashed when no cache system available
[#107](https://git.crystalyx.net/Xefir/repod/issues/107) reported by @skvaller and @PhilTraere
## 2.3.2 - Young Youth - 2024-05-31
## 2.3.2 - 2024-05-31
### Fixed
- 🐛 New subscribe button on search not disapearing if subscribed
- ♿ Missing accessibility label on this button as well
## 2.3.1 - Powerwash the Universe - 2024-05-29
## 2.3.1 - 2024-05-29
### Changed
- ⚡ Reduce app size by not shipping sourcemap
## 2.3.0 - Star Align - 2024-05-29
## 2.3.0 - 2024-05-29
### Added
- Ability to subscribe to podcast from search list
@ -129,12 +68,12 @@ Thanks to @OiledAmoeba [#120](https://git.crystalyx.net/Xefir/repod/issues/120)
- ⚰️ Drop support for PHP 8.0
- 🌐 Removed babel
## 2.2.1 - Shami was here - 2024-05-18
## 2.2.1 - 2024-05-18
### Removed
- ♻️ Rollback: Hide unreadable episodes because of insecure sources
## 2.2.0 - Moving in and out - 2024-05-18
## 2.2.0 - 2024-05-18
### Added
- 🚨 Linting the code with ESLint
@ -146,7 +85,7 @@ Thanks to @OiledAmoeba [#120](https://git.crystalyx.net/Xefir/repod/issues/120)
### Fixed
- 🔓 Hide unreadable episodes because of insecure sources
## 2.1.0 - Pocket Gundams - 2024-03-16
## 2.1.0 - 2024-03-16
### Added
- 🔍 Add CTA for rating the app on the store
@ -160,7 +99,7 @@ Thanks to @OiledAmoeba [#120](https://git.crystalyx.net/Xefir/repod/issues/120)
- 🔒 App wasn't working for non admin users
[#76](https://git.crystalyx.net/Xefir/repod/issues/76) reported by @devasservice
## 2.0.0 - Taking Actions - 2024-03-05
## 2.0.0 - 2024-03-05
### Added
- 🍪 Saving filters preference
@ -177,13 +116,13 @@ Thanks to @OiledAmoeba [#120](https://git.crystalyx.net/Xefir/repod/issues/120)
### Fixed
- ❤️‍🔥 Better handling ended episodes
## 1.5.9 - Just According to Keikaku - 2024-02-21
## 1.5.9 - 2024-02-21
### Changed
- 🧑‍💻 Change some endpoints to match gPodder.net "specifications"
- ⬆️ Update @nextcloud/vue to 8.7.0
## 1.5.8 - Goblet of Eonothem - 2024-02-11
## 1.5.8 - 2024-02-11
### Fixed
- Fyyd API sometime send empty feeds, ignoring them
@ -201,19 +140,19 @@ Thanks to @OiledAmoeba [#120](https://git.crystalyx.net/Xefir/repod/issues/120)
### Changed
- Update @nextcloud/vue to v8.6.2
## 1.5.5 - Hide and seek - 2024-02-04
## 1.5.5 - 2024-02-04
### Fixed
- Can't open podcast details if cache missing or misconfigured
[#58](https://git.crystalyx.net/Xefir/repod/issues/58) reported by @raxventus
## 1.5.4 - In search of the truth - 2024-02-03
## 1.5.4 - 2024-02-03
### Fixed
- Nextcloud search engine didn't work on Nextcloud 26 and 27
[#57](https://git.crystalyx.net/Xefir/repod/issues/57) reported by @JonOfUs
## 1.5.3 - The date where it all ends - 2024-02-01
## 1.5.3 - 2024-02-01
### Changed
- Update @nextcloud/vue to v8.6.1
@ -221,7 +160,7 @@ Thanks to @OiledAmoeba [#120](https://git.crystalyx.net/Xefir/repod/issues/120)
### Fixed
- Fix episode listing crashing if an invalid publication date is present in the RSS
## 1.5.2 - A little to the top - 2024-02-01
## 1.5.2 - 2024-02-01
### Changed
- Update @nextcloud/router to v3.0.0
@ -229,7 +168,7 @@ Thanks to @OiledAmoeba [#120](https://git.crystalyx.net/Xefir/repod/issues/120)
### Fixed
- Fix player alignment off by a couple of pixels
## 1.5.1 - Play on the PlayHead - 2024-01-30
## 1.5.1 - 2024-01-30
### Changed
- Update @nextcloud/vue to v8.6.0
@ -239,7 +178,7 @@ Thanks to @OiledAmoeba [#120](https://git.crystalyx.net/Xefir/repod/issues/120)
### Fixed
- Force the placement of the filter settings to the top
## 1.5.0 - Featuring the filtering - 2024-01-30
## 1.5.0 - 2024-01-30
### Added
- Filtering options for each podcast section

View File

@ -1,4 +1,4 @@
FROM nextcloud:30
FROM nextcloud:29
ENV NEXTCLOUD_UPDATE=1
ENV NEXTCLOUD_ADMIN_USER=repod

View File

@ -140,8 +140,8 @@ appstore:
--exclude="$(app_name)/protractor\.*" \
--exclude="$(app_name)/.*" \
--exclude="$(app_name)/js/.*" \
--exclude="$(app_name)/tsconfig.json" \
--exclude="$(app_name)/stylelint.config.cjs" \
--exclude="$(app_name)/webpack.js" \
--exclude="$(app_name)/stylelint.config.js" \
--exclude="$(app_name)/README.md" \
--exclude="$(app_name)/package-lock.json" \
--exclude="$(app_name)/LICENSE" \
@ -153,7 +153,7 @@ appstore:
--exclude="$(app_name)/Dockerfile" \
--exclude="$(app_name)/psalm.xml" \
--exclude="$(app_name)/renovate.json" \
--exclude="$(app_name)/vite.config.ts" \
--exclude="$(app_name)/vite.config.mjs" \
$(app_name)
# Start a nextcloud server on Docker to kickstart developement

View File

@ -39,9 +39,6 @@ You need to have [GPodderSync](https://apps.nextcloud.com/apps/gpoddersync) inst
### Homepage
![homepage](./screens/index.png)
### Discover
![homepage](./screens/discover.png)
### Search
![search](./screens/search.png)
@ -58,16 +55,11 @@ You need to have [GPodderSync](https://apps.nextcloud.com/apps/gpoddersync) inst
| [AntennaPod](https://antennapod.org) | Initial purpose for this project, as a synchronization endpoint for this client.<br> Support is available [as of version 2.5.1](https://github.com/AntennaPod/AntennaPod/pull/5243/). |
| [KDE Kasts](https://apps.kde.org/de/kasts/) | Supported since version 21.12 |
| [Podcast Merlin](https://github.com/yoyoooooooooo/Podcast-Merlin--Nextcloud-Gpodder-Client-For-Windows) | Full sync support podcast client for Windows |
| [Cardo](https://cardo-podcast.github.io/#/cardo) | Podcast client with sync support, for Windows, Mac and Linux |
## Installation
Either from the official Nextcloud [app store](https://apps.nextcloud.com/apps/repod) or by downloading the [latest release](https://git.crystalyx.net/Xefir/repod/releases/latest) and extracting it into your Nextcloud `apps/` directory.
## Known issues
- Conflict with Plasma Integration Firefox addon ([#164](https://git.crystalyx.net/Xefir/repod/issues/164))
## Credits
- [GPodder Sync](https://github.com/thrillfall/nextcloud-gpodder) for the database API

View File

@ -7,14 +7,11 @@
<description><![CDATA[## Features
- 🔍 Browse and subscribe huge collection of podcasts
- 🔊 Listen to episodes directly in Nextcloud
- 🌐 Sync your activity with [AntennaPod](https://antennapod.org/) and [other apps](https://git.crystalyx.net/Xefir/repod#clients-supporting-sync-of-gpoddersync)
- 📱 Mobile friendly interface
- 📡 Import and export your subscriptions
- ➡️ Full features comparison [here](https://git.crystalyx.net/Xefir/repod#comparaison-with-similar-apps-for-nextcloud)
- 🌐 Sync your activity with [AntennaPod](https://antennapod.org/)
## Requirements
You need to have [GPodderSync](https://apps.nextcloud.com/apps/gpoddersync) installed to use this app!]]></description>
<version>3.4.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>
@ -23,21 +20,17 @@ You need to have [GPodderSync](https://apps.nextcloud.com/apps/gpoddersync) inst
<website>https://git.crystalyx.net/Xefir/repod</website>
<bugs>https://git.crystalyx.net/Xefir/repod/issues</bugs>
<screenshot>https://git.crystalyx.net/Xefir/repod/raw/branch/main/screens/index.png</screenshot>
<screenshot>https://git.crystalyx.net/Xefir/repod/raw/branch/main/screens/discover.png</screenshot>
<screenshot>https://git.crystalyx.net/Xefir/repod/raw/branch/main/screens/search.png</screenshot>
<screenshot>https://git.crystalyx.net/Xefir/repod/raw/branch/main/screens/episodes.png</screenshot>
<screenshot>https://git.crystalyx.net/Xefir/repod/raw/branch/main/screens/modal.png</screenshot>
<dependencies>
<php min-version="8.1"/>
<nextcloud min-version="29" max-version="30"/>
<nextcloud min-version="27" max-version="30"/>
</dependencies>
<navigations>
<navigation>
<id>repod</id>
<name>Podcast</name>
<route>repod.page.index</route>
<icon>app.svg</icon>
<type>link</type>
</navigation>
</navigations>
</info>

27
appinfo/routes.php Normal file
View File

@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
/**
* Create your routes in here. The name is the lowercase name of the controller
* without the controller part, the stuff after the hash is the method.
* e.g. page#index -> OCA\RePod\Controller\PageController->index().
*
* The controller class has to be registered in the application.php file since
* it's instantiated in there
*/
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'],
['name' => 'opml#import', 'url' => '/opml/import', 'verb' => 'POST'],
['name' => 'podcast#index', 'url' => '/podcast', 'verb' => 'GET'],
['name' => 'search#index', 'url' => '/search', 'verb' => 'GET'],
['name' => 'toplist#hot', 'url' => '/toplist/hot', 'verb' => 'GET'],
['name' => 'toplist#new', 'url' => '/toplist/new', 'verb' => 'GET'],
],
];

View File

@ -15,10 +15,10 @@
"psalm": "psalm --threads=1 --no-cache --show-info=true"
},
"require-dev": {
"nextcloud/ocp": "^30.0.2",
"nextcloud/ocp": "^29.0.5",
"roave/security-advisories": "dev-latest",
"nextcloud/coding-standard": "^1.3.2",
"vimeo/psalm": "^5.26.1"
"nextcloud/coding-standard": "^1.2.3",
"vimeo/psalm": "^5.25.0"
},
"config": {
"optimize-autoloader": true,

451
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@ OC.L10N.register(
"Podcast" : "Podcast",
"RePod" : "RePod",
"🔊 Browse, manage and listen to podcasts" : "🔊 Suchen, Verwalten und Anhören von Podcasts",
"## Features\n- 🔍 Browse and subscribe huge collection of podcasts\n- 🔊 Listen to episodes directly in Nextcloud\n- 🌐 Sync your activity with [AntennaPod](https://antennapod.org/) and [other apps](https://git.crystalyx.net/Xefir/repod#clients-supporting-sync-of-gpoddersync)\n- 📱 Mobile friendly interface\n- 📡 Import and export your subscriptions\n- ➡️ Full features comparison [here](https://git.crystalyx.net/Xefir/repod#comparaison-with-similar-apps-for-nextcloud)\n\n## Requirements\nYou need to have [GPodderSync](https://apps.nextcloud.com/apps/gpoddersync) installed to use this app!" : "## Funktionen\n- 🔍 Durchsuchen und abonnieren einer großen Sammlung von Podcasts\n- 🔊 Episoden direkt in Nextcloud anhören\n- 🌐 Synchronisiere deine Aktivität mit [AntennaPod](https://antennapod.org/)\n- 📱 Mobile-freundliche Schnittstelle\n- 📡 Importieren und Exportieren Ihrer Abonnements\n\n## Voraussetzungen\nDu musst [GPodderSync](https://apps.nextcloud.com/apps/gpoddersync) installiert haben, um diese App zu benutzen!",
"## Features\n- 🔍 Browse and subscribe huge collection of podcasts\n- 🔊 Listen to episodes directly in Nextcloud\n- 🌐 Sync your activity with [AntennaPod](https://antennapod.org/)\n\n## Requirements\nYou need to have [GPodderSync](https://apps.nextcloud.com/apps/gpoddersync) installed to use this app!" : "## Funktionen\n- 🔍 Durchsuchen und abonnieren einer großen Sammlung von Podcasts\n- 🔊 Episoden direkt in Nextcloud anhören\n- 🌐 Synchronisiere deine Aktivität mit [AntennaPod](https://antennapod.org/)\n\n## Voraussetzungen\nDu musst [GPodderSync](https://apps.nextcloud.com/apps/gpoddersync) installiert haben, um diese App zu benutzen!",
"Download" : "Herunterladen",
"Add a RSS link" : "Einen RSS-Link hinzufügen",
"Subscribe" : "Abonnieren",
@ -22,11 +22,6 @@ OC.L10N.register(
"Open website" : "Webseite aufrufen",
"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",
"Rewind 10 seconds" : "10 Sekunden zurückspulen",
"Pause" : "Pause",
"Fast forward 30 seconds" : "30 Sekunden vorspulen",
"Mute" : "Stumm",
"Unmute" : "Stummschalten",
"Export subscriptions" : "Abonnements exportieren",
"Filtering episodes" : "Folgen filtern",
"Show all" : "Zeige alles",
@ -36,10 +31,6 @@ OC.L10N.register(
"Import subscriptions" : "Importiere Abonnements",
"Import OPML file" : "Importiere OPML-Datei",
"Rate RePod ❤️" : "Bewerte RePod ❤️",
"Sleep timer" : "Einschlaftimer",
"Minutes" : "Minuten",
"_%n min_::_%n mins_" : ["%n min","%n mins"],
"_%n sec_::_%n secs_" : ["%s sec","%n secs"],
"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?",
@ -52,7 +43,6 @@ OC.L10N.register(
"Missing required app" : "Benötigte App fehlt",
"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",
"A browser extension conflict with RePod" : "Ein Browser-Erweiterungskonflikt mit RePod"
"No favorites" : "Keine Favoriten"
},
"");

View File

@ -3,7 +3,7 @@
"Podcast" : "Podcast",
"RePod" : "RePod",
"🔊 Browse, manage and listen to podcasts" : "🔊 Suchen, Verwalten und Anhören von Podcasts",
"## Features\n- 🔍 Browse and subscribe huge collection of podcasts\n- 🔊 Listen to episodes directly in Nextcloud\n- 🌐 Sync your activity with [AntennaPod](https://antennapod.org/) and [other apps](https://git.crystalyx.net/Xefir/repod#clients-supporting-sync-of-gpoddersync)\n- 📱 Mobile friendly interface\n- 📡 Import and export your subscriptions\n- ➡️ Full features comparison [here](https://git.crystalyx.net/Xefir/repod#comparaison-with-similar-apps-for-nextcloud)\n\n## Requirements\nYou need to have [GPodderSync](https://apps.nextcloud.com/apps/gpoddersync) installed to use this app!" : "## Funktionen\n- 🔍 Durchsuchen und abonnieren einer großen Sammlung von Podcasts\n- 🔊 Episoden direkt in Nextcloud anhören\n- 🌐 Synchronisiere deine Aktivität mit [AntennaPod](https://antennapod.org/)\n- 📱 Mobile-freundliche Schnittstelle\n- 📡 Importieren und Exportieren Ihrer Abonnements\n\n## Voraussetzungen\nDu musst [GPodderSync](https://apps.nextcloud.com/apps/gpoddersync) installiert haben, um diese App zu benutzen!",
"## Features\n- 🔍 Browse and subscribe huge collection of podcasts\n- 🔊 Listen to episodes directly in Nextcloud\n- 🌐 Sync your activity with [AntennaPod](https://antennapod.org/)\n\n## Requirements\nYou need to have [GPodderSync](https://apps.nextcloud.com/apps/gpoddersync) installed to use this app!" : "## Funktionen\n- 🔍 Durchsuchen und abonnieren einer großen Sammlung von Podcasts\n- 🔊 Episoden direkt in Nextcloud anhören\n- 🌐 Synchronisiere deine Aktivität mit [AntennaPod](https://antennapod.org/)\n\n## Voraussetzungen\nDu musst [GPodderSync](https://apps.nextcloud.com/apps/gpoddersync) installiert haben, um diese App zu benutzen!",
"Download" : "Herunterladen",
"Add a RSS link" : "Einen RSS-Link hinzufügen",
"Subscribe" : "Abonnieren",
@ -20,11 +20,6 @@
"Open website" : "Webseite aufrufen",
"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",
"Rewind 10 seconds" : "10 Sekunden zurückspulen",
"Pause" : "Pause",
"Fast forward 30 seconds" : "30 Sekunden vorspulen",
"Mute" : "Stumm",
"Unmute" : "Stummschalten",
"Export subscriptions" : "Abonnements exportieren",
"Filtering episodes" : "Folgen filtern",
"Show all" : "Zeige alles",
@ -34,10 +29,6 @@
"Import subscriptions" : "Importiere Abonnements",
"Import OPML file" : "Importiere OPML-Datei",
"Rate RePod ❤️" : "Bewerte RePod ❤️",
"Sleep timer" : "Einschlaftimer",
"Minutes" : "Minuten",
"_%n min_::_%n mins_" : ["%n min","%n mins"],
"_%n sec_::_%n secs_" : ["%s sec","%n secs"],
"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?",
@ -50,7 +41,6 @@
"Missing required app" : "Benötigte App fehlt",
"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",
"A browser extension conflict with RePod" : "Ein Browser-Erweiterungskonflikt mit RePod"
"No favorites" : "Keine Favoriten"
},"pluralForm" :""
}

View File

@ -5,7 +5,7 @@ OC.L10N.register(
"Podcast" : "Podcast",
"RePod" : "RePod",
"🔊 Browse, manage and listen to podcasts" : "🔊 Parcourir, gérer et écouter vos podcasts",
"## Features\n- 🔍 Browse and subscribe huge collection of podcasts\n- 🔊 Listen to episodes directly in Nextcloud\n- 🌐 Sync your activity with [AntennaPod](https://antennapod.org/) and [other apps](https://git.crystalyx.net/Xefir/repod#clients-supporting-sync-of-gpoddersync)\n- 📱 Mobile friendly interface\n- 📡 Import and export your subscriptions\n- ➡️ Full features comparison [here](https://git.crystalyx.net/Xefir/repod#comparaison-with-similar-apps-for-nextcloud)\n\n## Requirements\nYou need to have [GPodderSync](https://apps.nextcloud.com/apps/gpoddersync) installed to use this app!" : "## Fonctionnalités\n- 🔍 Parcourir et s'abonner à une grande collections de podcasts\n- 🔊 Écouter vos épisodes directement sur Nextcloud\n- 🌐 Synchroniser son activité avec [AntennaPod](https://antennapod.org/) et d'autres [applications](https://git.crystalyx.net/Xefir/repod#clients-supporting-sync-of-gpoddersync)\n- 📱 Interface optimisée pour mobiles et ordinateurs\n- 📡 Import/export de ses abonnements\n- ➡️ Tableau récapitulatif complet des fonctionnalitées [ici](https://git.crystalyx.net/Xefir/repod#comparaison-with-similar-apps-for-nextcloud)\n\n## Pré-requis\nVous devez avoir [GPodderSync](https://apps.nextcloud.com/apps/gpoddersync) installé pour utiliser cette application !",
"## Features\n- 🔍 Browse and subscribe huge collection of podcasts\n- 🔊 Listen to episodes directly in Nextcloud\n- 🌐 Sync your activity with [AntennaPod](https://antennapod.org/)\n\n## Requirements\nYou need to have [GPodderSync](https://apps.nextcloud.com/apps/gpoddersync) installed to use this app!" : "## Fonctionnalités\n- 🔍 Parcourir et s'abonner à une grande collections de podcasts\n- 🔊 Écouter vos épisodes directement sur Nextcloud\n- 🌐 Synchroniser son activité avec [AntennaPod](https://antennapod.org/)\n\n## Pré-requis\nVous devez avoir [GPodderSync](https://apps.nextcloud.com/apps/gpoddersync) installé pour utiliser cette application !",
"Download" : "Télécharger",
"Add a RSS link" : "Ajouter un lien RSS",
"Subscribe" : "S'abonner",
@ -22,11 +22,6 @@ OC.L10N.register(
"Open website" : "Ouvrir le site web",
"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",
"Rewind 10 seconds" : "Retour rapide de 10 secondes",
"Pause" : "Pause",
"Fast forward 30 seconds" : "Avance rapide de 30 secondes",
"Mute" : "Silencer",
"Unmute" : "Paroler",
"Export subscriptions" : "Exporter les abonnements",
"Filtering episodes" : "Filtrage des épisodes",
"Show all" : "Montrer tout",
@ -36,10 +31,6 @@ OC.L10N.register(
"Import subscriptions" : "Importer les abonnements",
"Import OPML file" : "Importer un fichier OPML",
"Rate RePod ❤️" : "Donnez votre avis ❤️",
"Sleep timer" : "Minuteur",
"Minutes" : "Minutes",
"_%n min_::_%n mins_" : ["%n min","%n mins"],
"_%n sec_::_%n secs_" : ["%s sec","%n secs"],
"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 ?",
@ -52,7 +43,6 @@ OC.L10N.register(
"Missing required app" : "Une application requise est manquante",
"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",
"A browser extension conflict with RePod" : "Une extension de votre navigateur entre en conflit avec RePod"
"No favorites" : "Aucun favoris"
},
"");

View File

@ -3,7 +3,7 @@
"Podcast" : "Podcast",
"RePod" : "RePod",
"🔊 Browse, manage and listen to podcasts" : "🔊 Parcourir, gérer et écouter vos podcasts",
"## Features\n- 🔍 Browse and subscribe huge collection of podcasts\n- 🔊 Listen to episodes directly in Nextcloud\n- 🌐 Sync your activity with [AntennaPod](https://antennapod.org/) and [other apps](https://git.crystalyx.net/Xefir/repod#clients-supporting-sync-of-gpoddersync)\n- 📱 Mobile friendly interface\n- 📡 Import and export your subscriptions\n- ➡️ Full features comparison [here](https://git.crystalyx.net/Xefir/repod#comparaison-with-similar-apps-for-nextcloud)\n\n## Requirements\nYou need to have [GPodderSync](https://apps.nextcloud.com/apps/gpoddersync) installed to use this app!" : "## Fonctionnalités\n- 🔍 Parcourir et s'abonner à une grande collections de podcasts\n- 🔊 Écouter vos épisodes directement sur Nextcloud\n- 🌐 Synchroniser son activité avec [AntennaPod](https://antennapod.org/) et d'autres [applications](https://git.crystalyx.net/Xefir/repod#clients-supporting-sync-of-gpoddersync)\n- 📱 Interface optimisée pour mobiles et ordinateurs\n- 📡 Import/export de ses abonnements\n- ➡️ Tableau récapitulatif complet des fonctionnalitées [ici](https://git.crystalyx.net/Xefir/repod#comparaison-with-similar-apps-for-nextcloud)\n\n## Pré-requis\nVous devez avoir [GPodderSync](https://apps.nextcloud.com/apps/gpoddersync) installé pour utiliser cette application !",
"## Features\n- 🔍 Browse and subscribe huge collection of podcasts\n- 🔊 Listen to episodes directly in Nextcloud\n- 🌐 Sync your activity with [AntennaPod](https://antennapod.org/)\n\n## Requirements\nYou need to have [GPodderSync](https://apps.nextcloud.com/apps/gpoddersync) installed to use this app!" : "## Fonctionnalités\n- 🔍 Parcourir et s'abonner à une grande collections de podcasts\n- 🔊 Écouter vos épisodes directement sur Nextcloud\n- 🌐 Synchroniser son activité avec [AntennaPod](https://antennapod.org/)\n\n## Pré-requis\nVous devez avoir [GPodderSync](https://apps.nextcloud.com/apps/gpoddersync) installé pour utiliser cette application !",
"Download" : "Télécharger",
"Add a RSS link" : "Ajouter un lien RSS",
"Subscribe" : "S'abonner",
@ -20,11 +20,6 @@
"Open website" : "Ouvrir le site web",
"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",
"Rewind 10 seconds" : "Retour rapide de 10 secondes",
"Pause" : "Pause",
"Fast forward 30 seconds" : "Avance rapide de 30 secondes",
"Mute" : "Silencer",
"Unmute" : "Paroler",
"Export subscriptions" : "Exporter les abonnements",
"Filtering episodes" : "Filtrage des épisodes",
"Show all" : "Montrer tout",
@ -34,10 +29,6 @@
"Import subscriptions" : "Importer les abonnements",
"Import OPML file" : "Importer un fichier OPML",
"Rate RePod ❤️" : "Donnez votre avis ❤️",
"Sleep timer" : "Minuteur",
"Minutes" : "Minutes",
"_%n min_::_%n mins_" : ["%n min","%n mins"],
"_%n sec_::_%n secs_" : ["%s sec","%n secs"],
"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 ?",
@ -50,7 +41,6 @@
"Missing required app" : "Une application requise est manquante",
"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",
"A browser extension conflict with RePod" : "Une extension de votre navigateur entre en conflit avec RePod"
"No favorites" : "Aucun favoris"
},"pluralForm" :""
}

View File

@ -11,9 +11,6 @@ use OCA\RePod\Core\EpisodeAction\EpisodeActionReader;
use OCA\RePod\Service\UserService;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\JSONResponse;
use OCP\Http\Client\IClientService;
use OCP\IRequest;
@ -30,9 +27,10 @@ class EpisodesController extends Controller
parent::__construct(Application::APP_ID, $request);
}
#[NoAdminRequired]
#[NoCSRFRequired]
#[FrontpageRoute(verb: 'GET', url: '/episodes/list')]
/**
* @NoAdminRequired
* @NoCSRFRequired
*/
public function list(string $url): JSONResponse {
$client = $this->clientService->newClient();
$feed = $client->get($url);
@ -43,9 +41,10 @@ class EpisodesController extends Controller
return new JSONResponse($episodes, $feed->getStatusCode());
}
#[NoAdminRequired]
#[NoCSRFRequired]
#[FrontpageRoute(verb: 'GET', url: '/episodes/action')]
/**
* @NoAdminRequired
* @NoCSRFRequired
*/
public function action(string $url): JSONResponse {
$action = $this->episodeActionRepository->findByEpisodeUrl($url, $this->userService->getUserUID());

View File

@ -10,9 +10,6 @@ use OCA\GPodderSync\Core\SubscriptionChange\SubscriptionChangeSaver;
use OCA\RePod\AppInfo\Application;
use OCA\RePod\Service\UserService;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\DataDownloadResponse;
use OCP\AppFramework\Http\Response;
use OCP\IL10N;
@ -31,9 +28,10 @@ class OpmlController extends Controller
parent::__construct(Application::APP_ID, $request);
}
#[NoAdminRequired]
#[NoCSRFRequired]
#[FrontpageRoute(verb: 'GET', url: '/opml/export')]
/**
* @NoAdminRequired
* @NoCSRFRequired
*/
public function export(): DataDownloadResponse {
// https://github.com/AntennaPod/AntennaPod/blob/master/core/src/main/java/de/danoeh/antennapod/core/export/opml/OpmlWriter.java
$xml = new \SimpleXMLElement('<opml/>', namespaceOrPrefix: 'http://xmlpull.org/v1/doc/features.html#indent-output');
@ -84,9 +82,10 @@ class OpmlController extends Controller
return new DataDownloadResponse((string) $xml->asXML(), 'repod-'.$dateCreated->getTimestamp().'.opml', ' application/xml');
}
#[NoAdminRequired]
#[NoCSRFRequired]
#[FrontpageRoute(verb: 'POST', url: '/opml/import')]
/**
* @NoAdminRequired
* @NoCSRFRequired
*/
public function import(): Response {
$file = $this->request->getUploadedFile('import');

View File

@ -6,9 +6,6 @@ namespace OCA\RePod\Controller;
use OCA\RePod\AppInfo\Application;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\ContentSecurityPolicy;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\IConfig;
@ -24,33 +21,42 @@ class PageController extends Controller
parent::__construct(Application::APP_ID, $request);
}
#[NoAdminRequired]
#[NoCSRFRequired]
#[FrontpageRoute(verb: 'GET', url: '/')]
/**
* @NoAdminRequired
* @NoCSRFRequired
*/
public function index(): TemplateResponse {
Util::addScript(Application::APP_ID, Application::APP_ID.'-main');
Util::addStyle(Application::APP_ID, Application::APP_ID.'-main');
Util::addScript(Application::APP_ID, 'main');
$csp = new ContentSecurityPolicy();
$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]
#[FrontpageRoute(verb: 'GET', url: '/discover')]
/**
* @NoAdminRequired
* @NoCSRFRequired
*/
public function discover(): TemplateResponse {
return $this->index();
}
#[NoAdminRequired]
#[NoCSRFRequired]
#[FrontpageRoute(verb: 'GET', url: '/feed/{path}', requirements: ['path' => '.+'])]
/**
* @NoAdminRequired
* @NoCSRFRequired
*/
public function feed(): TemplateResponse {
return $this->index();
}

View File

@ -8,9 +8,6 @@ use OCA\GPodderSync\Core\PodcastData\PodcastData;
use OCA\GPodderSync\Core\PodcastData\PodcastDataReader;
use OCA\RePod\AppInfo\Application;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\JSONResponse;
use OCP\Http\Client\IClientService;
use OCP\ICacheFactory;
@ -27,9 +24,10 @@ class PodcastController extends Controller
parent::__construct(Application::APP_ID, $request);
}
#[NoAdminRequired]
#[NoCSRFRequired]
#[FrontpageRoute(verb: 'GET', url: '/podcast')]
/**
* @NoAdminRequired
* @NoCSRFRequired
*/
public function index(string $url): JSONResponse {
$podcast = null;

View File

@ -7,9 +7,6 @@ namespace OCA\RePod\Controller;
use OCA\RePod\AppInfo\Application;
use OCA\RePod\Service\MultiPodService;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\JSONResponse;
use OCP\IRequest;
@ -22,9 +19,10 @@ class SearchController extends Controller
parent::__construct(Application::APP_ID, $request);
}
#[NoAdminRequired]
#[NoCSRFRequired]
#[FrontpageRoute(verb: 'GET', url: '/search')]
/**
* @NoAdminRequired
* @NoCSRFRequired
*/
public function index(string $q): JSONResponse {
return new JSONResponse($this->multiPodService->search($q));
}

View File

@ -7,9 +7,6 @@ namespace OCA\RePod\Controller;
use OCA\RePod\AppInfo\Application;
use OCA\RePod\Service\FyydService;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\JSONResponse;
use OCP\IRequest;
@ -22,16 +19,18 @@ class ToplistController extends Controller
parent::__construct(Application::APP_ID, $request);
}
#[NoAdminRequired]
#[NoCSRFRequired]
#[FrontpageRoute(verb: 'GET', url: '/toplist/hot')]
/**
* @NoAdminRequired
* @NoCSRFRequired
*/
public function hot(): JSONResponse {
return new JSONResponse($this->fyydService->hot());
}
#[NoAdminRequired]
#[NoCSRFRequired]
#[FrontpageRoute(verb: 'GET', url: '/toplist/new')]
/**
* @NoAdminRequired
* @NoCSRFRequired
*/
public function new(): JSONResponse {
return new JSONResponse($this->fyydService->latest());
}

3413
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -2,50 +2,43 @@
"name": "repod",
"license": "AGPL-3.0-or-later",
"scripts": {
"build": "vue-tsc && vite build",
"dev": "vite --mode development build",
"watch": "vite --mode development build --watch",
"lint": "vue-tsc && eslint src",
"lint:fix": "vue-tsc && eslint src --fix",
"build": "vite build --mode production",
"dev": "vite build --mode development",
"dev:watch": "vite build --mode development --watch",
"watch": "npm run dev:watch",
"lint": "eslint src",
"lint:fix": "eslint src --fix",
"stylelint": "stylelint src/**/*.vue src/**/*.scss src/**/*.css",
"stylelint:fix": "stylelint src/**/*.vue src/**/*.scss src/**/*.css --fix"
},
"type": "module",
"browserslist": [
"extends @nextcloud/browserslist-config"
],
"prettier": "@nextcloud/prettier-config",
"dependencies": {
"@formatjs/intl-segmenter": "^11.7.3",
"@nextcloud/axios": "^2.5.1",
"@nextcloud/axios": "^2.5.0",
"@nextcloud/initial-state": "^2.2.0",
"@nextcloud/l10n": "^3.1.0",
"@nextcloud/router": "^3.0.1",
"@nextcloud/vite-config": "^2.2.2",
"@nextcloud/vue": "9.0.0-alpha.5",
"dompurify": "^3.1.7",
"dompurify": "^3.1.6",
"linkify-html": "^4.1.3",
"pinia": "^2.2.6",
"pinia": "^2.2.2",
"toastify-js": "^1.12.0",
"vite": "^5.4.10",
"vue": "^3.5.12",
"vue-material-design-icons": "^5.3.1",
"vue-router": "^4.4.5"
"vite": "^5.4.2",
"vue": "^3.4.38",
"vue-material-design-icons": "^5.3.0",
"vue-router": "^4.4.3"
},
"devDependencies": {
"@nextcloud/browserslist-config": "^3.0.1",
"@nextcloud/eslint-config": "^8.4.1",
"@nextcloud/prettier-config": "^1.1.0",
"@nextcloud/stylelint-config": "^3.0.1",
"@types/toastify-js": "^1.12.3",
"@vue/eslint-config-typescript": "^13",
"@vue/tsconfig": "^0.5.1",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-pinia": "^0.4.1",
"eslint-plugin-prettier": "^5.2.1",
"ts-node": "^10.9.2",
"typescript": "5.5",
"vue-eslint-parser": "^9.4.3",
"vue-tsc": "^2.1.10"
},
"prettier": "@nextcloud/prettier-config"
"vite-plugin-vue-devtools": "^7.3.9"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 857 KiB

After

Width:  |  Height:  |  Size: 282 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 429 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 817 KiB

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 961 KiB

After

Width:  |  Height:  |  Size: 214 KiB

View File

@ -7,7 +7,7 @@
</NcContent>
</template>
<script lang="ts">
<script>
import 'toastify-js/src/toastify.css'
import { mapActions, mapState } from 'pinia'
import Bar from './components/Player/Bar.vue'
@ -15,7 +15,7 @@ import GPodder from './views/GPodder.vue'
import { NcContent } from '@nextcloud/vue'
import Subscriptions from './components/Sidebar/Subscriptions.vue'
import { loadState } from '@nextcloud/initial-state'
import { usePlayer } from './store/player.ts'
import { usePlayer } from './store/player.js'
export default {
name: 'App',
@ -33,9 +33,15 @@ export default {
},
mounted() {
this.init()
setInterval(this.loop, 40000)
},
methods: {
...mapActions(usePlayer, ['init']),
...mapActions(usePlayer, ['init', 'time']),
loop() {
if (this.paused === false) {
this.time()
}
},
},
}
</script>

View File

@ -4,10 +4,10 @@
</NcAppContent>
</template>
<script lang="ts">
<script>
import { NcAppContent } from '@nextcloud/vue'
import { mapState } from 'pinia'
import { usePlayer } from '../../store/player.ts'
import { usePlayer } from '../../store/player.js'
export default {
name: 'AppContent',

View File

@ -1,5 +1,5 @@
<template>
<NcAppNavigation :class="{ episode }">
<NcAppNavigation :class="{ padding: episode }">
<slot />
<template #list>
<slot name="list" />
@ -10,10 +10,10 @@
</NcAppNavigation>
</template>
<script lang="ts">
<script>
import { NcAppNavigation } from '@nextcloud/vue'
import { mapState } from 'pinia'
import { usePlayer } from '../../store/player.ts'
import { usePlayer } from '../../store/player.js'
export default {
name: 'AppNavigation',
@ -27,7 +27,7 @@ export default {
</script>
<style scoped>
.episode {
.padding {
padding-bottom: 6rem;
}
</style>

View File

@ -1,22 +1,10 @@
<template>
<NcEmptyContent class="empty">
<slot />
<template #icon>
<slot name="icon" />
</template>
<template #name>
<slot name="name" />
</template>
<template #description>
<slot name="description" />
</template>
<template #action>
<slot name="action" />
</template>
</NcEmptyContent>
</template>
<script lang="ts">
<script>
import { NcEmptyContent } from '@nextcloud/vue'
export default {

View File

@ -2,7 +2,7 @@
<NcLoadingIcon class="loading" />
</template>
<script lang="ts">
<script>
import { NcLoadingIcon } from '@nextcloud/vue'
export default {

View File

@ -6,7 +6,7 @@
:size="256"
:url="episode.image" />
<h2>{{ episode.name }}</h2>
<SafeHtml :source="episode.description || ''" />
<SafeHtml :source="episode.description" />
<div class="flex">
<NcButton v-if="episode.link" :href="episode.link" target="_blank">
<template #icon>
@ -29,15 +29,13 @@
</div>
</template>
<script lang="ts">
<script>
import { NcAvatar, NcButton } from '@nextcloud/vue'
import DownloadIcon from 'vue-material-design-icons/Download.vue'
import type { EpisodeInterface } from '../../utils/types.ts'
import OpenInNewIcon from 'vue-material-design-icons/OpenInNew.vue'
import SafeHtml from './SafeHtml.vue'
import { filenameFromUrl } from '../../utils/url.ts'
import { humanFileSize } from '../../utils/size.ts'
import { t } from '@nextcloud/l10n'
import { filenameFromUrl } from '../../utils/url.js'
import { humanFileSize } from '../../utils/size.js'
export default {
name: 'Modal',
@ -50,14 +48,13 @@ export default {
},
props: {
episode: {
type: Object as () => EpisodeInterface,
type: Object,
required: true,
},
},
methods: {
filenameFromUrl,
humanFileSize,
t,
},
}
</script>

View File

@ -2,7 +2,7 @@
<div v-sanitize="source" class="html" />
</template>
<script lang="ts">
<script>
import dompurify from 'dompurify'
import linkifyHtml from 'linkify-html'

View File

@ -1,5 +1,5 @@
<template>
<NcAppNavigationList class="list">
<NcAppNavigationList>
<NcAppNavigationNewItem
:name="t('repod', 'Add a RSS link')"
@new-item="(url) => $router.push(toFeedUrl(url))">
@ -10,11 +10,10 @@
</NcAppNavigationList>
</template>
<script lang="ts">
<script>
import { NcAppNavigationList, NcAppNavigationNewItem } from '@nextcloud/vue'
import PlusIcon from 'vue-material-design-icons/Plus.vue'
import { t } from '@nextcloud/l10n'
import { toFeedUrl } from '../../utils/url.ts'
import { toFeedUrl } from '../../utils/url.js'
export default {
name: 'AddRss',
@ -24,14 +23,13 @@ export default {
PlusIcon,
},
methods: {
t,
toFeedUrl,
},
}
</script>
<style scoped>
.list {
ul {
margin-top: 2rem;
}
</style>

View File

@ -19,7 +19,7 @@
</template>
<template #actions>
<NcActionButton
v-if="!getSubByUrl(feed.link)"
v-if="!getSubscriptions.includes(feed.link)"
:aria-label="t('repod', 'Subscribe')"
:name="t('repod', 'Subscribe')"
:title="t('repod', 'Subscribe')"
@ -34,19 +34,18 @@
</div>
</template>
<script lang="ts">
<script>
import { NcActionButton, NcAvatar, NcListItem } from '@nextcloud/vue'
import { mapActions, mapState } from 'pinia'
import Loading from '../Atoms/Loading.vue'
import PlusIcon from 'vue-material-design-icons/Plus.vue'
import type { PodcastDataInterface } from '../../utils/types.ts'
import axios from '@nextcloud/axios'
import { formatLocaleDate } from '../../utils/time.ts'
import { debounce } from '../../utils/debounce.js'
import { formatLocaleDate } from '../../utils/time.js'
import { generateUrl } from '@nextcloud/router'
import { showError } from '../../utils/toast.ts'
import { t } from '@nextcloud/l10n'
import { toFeedUrl } from '../../utils/url.ts'
import { useSubscriptions } from '../../store/subscriptions.ts'
import { showError } from '../../utils/toast.js'
import { toFeedUrl } from '../../utils/url.js'
import { useSubscriptions } from '../../store/subscriptions.js'
export default {
name: 'Search',
@ -64,27 +63,22 @@ export default {
},
},
data: () => ({
feeds: [] as PodcastDataInterface[],
feeds: [],
loading: false,
timeout: null as NodeJS.Timeout | null,
}),
computed: {
...mapState(useSubscriptions, ['getSubByUrl']),
...mapState(useSubscriptions, ['getSubscriptions']),
},
watch: {
value() {
if (this.timeout) {
clearTimeout(this.timeout)
}
this.timeout = setTimeout(this.search, 200)
this.search()
},
},
methods: {
...mapActions(useSubscriptions, ['fetch']),
formatLocaleDate,
t,
toFeedUrl,
async addSubscription(url: string) {
async addSubscription(url) {
try {
await axios.post(
generateUrl('/apps/gpoddersync/subscription_change/create'),
@ -97,13 +91,14 @@ export default {
console.error(e)
showError(t('repod', 'Error while adding the feed'))
}
this.fetch()
},
async search() {
search: debounce(async function value() {
try {
this.loading = true
const currentSearch = this.value
const feeds = await axios.get<PodcastDataInterface[]>(
const feeds = await axios.get(
generateUrl('/apps/repod/search?q={value}', {
value: currentSearch,
}),
@ -121,7 +116,7 @@ export default {
this.loading = false
}
}
},
}, 200),
},
}
</script>

View File

@ -12,14 +12,12 @@
</div>
</template>
<script lang="ts">
<script>
import Loading from '../Atoms/Loading.vue'
import type { PodcastDataInterface } from '../../utils/types.ts'
import axios from '@nextcloud/axios'
import { generateUrl } from '@nextcloud/router'
import { showError } from '../../utils/toast.ts'
import { t } from '@nextcloud/l10n'
import { toFeedUrl } from '../../utils/url.ts'
import { showError } from '../../utils/toast.js'
import { toFeedUrl } from '../../utils/url.js'
export default {
name: 'Toplist',
@ -34,7 +32,7 @@ export default {
},
data: () => ({
loading: true,
tops: [] as PodcastDataInterface[],
tops: [],
}),
computed: {
title() {
@ -51,8 +49,8 @@ export default {
async mounted() {
try {
this.loading = true
const tops = await axios.get<PodcastDataInterface[]>(
generateUrl('/apps/repod/toplist/{type}', { type: this.type }),
const tops = await axios.get(
generateUrl(`/apps/repod/toplist/${this.type}`),
)
this.tops = tops.data
} catch (e) {

View File

@ -1,13 +1,13 @@
<template>
<div class="header">
<img class="background" :src="feed.imageUrl" />
<img class="background" :src="imageUrl" />
<div class="content">
<div>
<NcAvatar
:display-name="feed.author || feed.title"
:display-name="author || title"
:is-no-user="true"
:size="128"
:url="feed.imageUrl" />
:url="imageUrl" />
<a class="feed" :href="url" @click.prevent="copyFeed">
<RssIcon :size="20" />
<i>{{ t('repod', 'Copy feed') }}</i>
@ -15,15 +15,15 @@
</div>
<div class="inner">
<div class="infos">
<h2>{{ feed.title }}</h2>
<a :href="feed.link" target="_blank">
<i>{{ feed.author }}</i>
<h2>{{ title }}</h2>
<a :href="link" target="_blank">
<i>{{ author }}</i>
</a>
<br /><br />
<SafeHtml :source="feed.description || ''" />
<SafeHtml :source="description" />
</div>
<NcAppNavigationNew
v-if="!getSubByUrl(url)"
v-if="!getSubscriptions.includes(url)"
:text="t('repod', 'Subscribe')"
@click="addSubscription">
<template #icon>
@ -35,19 +35,17 @@
</div>
</template>
<script lang="ts">
<script>
import { NcAppNavigationNew, NcAvatar } from '@nextcloud/vue'
import { mapActions, mapState } from 'pinia'
import { showError, showSuccess } from '../../utils/toast.ts'
import { showError, showSuccess } from '../../utils/toast.js'
import PlusIcon from 'vue-material-design-icons/Plus.vue'
import type { PodcastDataInterface } from '../../utils/types.ts'
import RssIcon from 'vue-material-design-icons/Rss.vue'
import SafeHtml from '../Atoms/SafeHtml.vue'
import axios from '@nextcloud/axios'
import { decodeUrl } from '../../utils/url.ts'
import { decodeUrl } from '../../utils/url.js'
import { generateUrl } from '@nextcloud/router'
import { t } from '@nextcloud/l10n'
import { useSubscriptions } from '../../store/subscriptions.ts'
import { useSubscriptions } from '../../store/subscriptions.js'
export default {
name: 'Banner',
@ -59,20 +57,35 @@ export default {
SafeHtml,
},
props: {
feed: {
type: Object as () => PodcastDataInterface,
author: {
type: String,
required: true,
},
description: {
type: String,
required: true,
},
imageUrl: {
type: String,
required: true,
},
link: {
type: String,
required: true,
},
title: {
type: String,
required: true,
},
},
computed: {
...mapState(useSubscriptions, ['getSubByUrl']),
...mapState(useSubscriptions, ['getSubscriptions']),
url() {
return decodeUrl(this.$route.params.url as string)
return decodeUrl(this.$route.params.url)
},
},
methods: {
...mapActions(useSubscriptions, ['fetch']),
t,
async addSubscription() {
try {
await axios.post(
@ -86,6 +99,7 @@ export default {
console.error(e)
showError(t('repod', 'Error while adding the feed'))
}
this.fetch()
},
copyFeed() {
@ -129,13 +143,11 @@ export default {
}
.infos {
flex: 1;
overflow: auto;
}
.inner {
display: flex;
flex: 1;
}
@media only screen and (max-width: 768px) {

View File

@ -1,12 +1,7 @@
<template>
<NcListItem
:active="isCurrentEpisode(episode)"
class="episode"
:details="
!oneLine && episode.pubDate
? formatLocaleDate(new Date(episode.pubDate?.date))
: ''
"
:details="!oneLine ? formatLocaleDate(new Date(episode.pubDate?.date)) : ''"
:force-display-actions="true"
:name="episode.name"
:one-line="oneLine"
@ -82,17 +77,17 @@
</template>
<template #indicator>
<NcProgressBar
v-if="episode.action && isListening(episode) && !oneLine"
v-if="isListening(episode) && !oneLine"
class="progress"
:value="(episode.action.position * 100) / episode.action.total" />
</template>
<template #subname>
<template v-if="!oneLine" #subname>
{{ episode.duration }}
</template>
</NcListItem>
</template>
<script lang="ts">
<script>
import {
NcActionButton,
NcActionLink,
@ -106,11 +101,10 @@ import {
durationToSeconds,
formatEpisodeTimestamp,
formatLocaleDate,
} from '../../utils/time.ts'
import { hasEnded, isListening } from '../../utils/status.ts'
} 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 type { EpisodeInterface } from '../../utils/types.ts'
import Modal from '../Atoms/Modal.vue'
import OpenInNewIcon from 'vue-material-design-icons/OpenInNew.vue'
import PlayIcon from 'vue-material-design-icons/Play.vue'
@ -118,11 +112,10 @@ 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.ts'
import { filenameFromUrl } from '../../utils/url.js'
import { generateUrl } from '@nextcloud/router'
import { showError } from '../../utils/toast.ts'
import { t } from '@nextcloud/l10n'
import { usePlayer } from '../../store/player.ts'
import { showError } from '../../utils/toast.js'
import { usePlayer } from '../../store/player.js'
export default {
name: 'Episode',
@ -144,7 +137,7 @@ export default {
},
props: {
episode: {
type: Object as () => EpisodeInterface,
type: Object,
required: true,
},
oneLine: {
@ -158,22 +151,21 @@ export default {
},
data: () => ({
loading: false,
modalEpisode: null as EpisodeInterface | null,
modalEpisode: null,
}),
computed: {
...mapState(usePlayer, { playerEpisode: 'episode' }),
},
methods: {
...mapActions(usePlayer, ['load']),
filenameFromUrl,
formatLocaleDate,
hasEnded,
isListening,
t,
isCurrentEpisode(episode: EpisodeInterface) {
filenameFromUrl,
isCurrentEpisode(episode) {
return this.playerEpisode?.url === episode.url
},
async markAs(episode: EpisodeInterface, read: boolean) {
async markAs(episode, read) {
try {
this.loading = true
episode.action = {
@ -183,8 +175,8 @@ export default {
action: 'play',
timestamp: formatEpisodeTimestamp(new Date()),
started: episode.action?.started || 0,
position: read ? durationToSeconds(episode.duration || '') : 0,
total: durationToSeconds(episode.duration || ''),
position: read ? durationToSeconds(episode.duration) : 0,
total: durationToSeconds(episode.duration),
}
await axios.post(
generateUrl('/apps/gpoddersync/episode_action/create'),
@ -209,14 +201,3 @@ export default {
margin-top: 0.4rem;
}
</style>
<style>
.episode .list-item-content__name {
max-width: 100%;
}
.episode .list-item-content__subname {
flex-basis: auto;
flex-grow: 0;
}
</style>

View File

@ -11,19 +11,17 @@
</div>
</template>
<script lang="ts">
import { hasEnded, isListening } from '../../utils/status.ts'
<script>
import { hasEnded, isListening } from '../../utils/status.js'
import Episode from './Episode.vue'
import type { EpisodeInterface } from '../../utils/types.ts'
import Loading from '../Atoms/Loading.vue'
import axios from '@nextcloud/axios'
import { decodeUrl } from '../../utils/url.ts'
import { decodeUrl } from '../../utils/url.js'
import { generateUrl } from '@nextcloud/router'
import { mapState } from 'pinia'
import { showError } from '../../utils/toast.ts'
import { t } from '@nextcloud/l10n'
import { usePlayer } from '../../store/player.ts'
import { useSettings } from '../../store/settings.ts'
import { showError } from '../../utils/toast.js'
import { usePlayer } from '../../store/player.js'
import { useSettings } from '../../store/settings.js'
export default {
name: 'Episodes',
@ -32,7 +30,7 @@ export default {
Loading,
},
data: () => ({
episodes: [] as EpisodeInterface[],
episodes: [],
loading: true,
}),
computed: {
@ -43,24 +41,27 @@ export default {
if (!this.filters.listened && this.hasEnded(episode)) {
return false
}
if (!this.filters.listening && this.isListening(episode)) {
return false
}
if (!this.filters.unlistened && !this.isListening(episode)) {
return false
}
return true
})
},
url() {
return decodeUrl(this.$route.params.url as string)
return decodeUrl(this.$route.params.url)
},
},
watch: {
episode() {
if (this.episode) {
this.episodes = this.episodes.map((e) =>
e.url === this.episode?.url ? this.episode : e,
e.url === this.episode.url ? this.episode : e,
)
}
},
@ -68,15 +69,13 @@ export default {
async mounted() {
try {
this.loading = true
const episodes = await axios.get<EpisodeInterface[]>(
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 || '').getTime() -
new Date(a.pubDate?.date || '').getTime(),
(a, b) => new Date(b.pubDate?.date) - new Date(a.pubDate?.date),
)
} catch (e) {
console.error(e)

View File

@ -1,16 +1,15 @@
<template>
<NcGuestContent class="guest">
<Loading v-if="!feed.data" />
<Loading v-if="!currentFavoriteData" />
<NcAvatar
v-if="feed.data"
v-if="currentFavoriteData"
class="avatar"
:display-name="feed.data.author || feed.data.title"
:display-name="currentFavoriteData.author || currentFavoriteData.title"
:is-no-user="true"
:size="222"
:title="feed.data.author"
:url="feed.data.imageUrl" />
<div v-if="feed.data" class="list">
<h2 class="title">{{ feed.data.title }}</h2>
:url="currentFavoriteData.imageUrl" />
<div class="list">
<h2 class="title">{{ currentFavoriteData.title }}</h2>
<Loading v-if="loading" />
<ul v-if="!loading">
<Episode
@ -18,25 +17,25 @@
:key="episode.guid"
:episode="episode"
:one-line="true"
:url="feed.metrics.url" />
:url="url" />
</ul>
</div>
</NcGuestContent>
</template>
<script lang="ts">
import type { EpisodeInterface, SubscriptionInterface } from '../../utils/types.ts'
<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.ts'
import { showError } from '../../utils/toast.ts'
import { t } from '@nextcloud/l10n'
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: 'Favorite',
name: 'Favorites',
components: {
Episode,
Loading,
@ -44,28 +43,32 @@ export default {
NcGuestContent,
},
props: {
feed: {
type: Object as () => SubscriptionInterface,
url: {
type: String,
required: true,
},
},
data: () => ({
episodes: [] as EpisodeInterface[],
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<EpisodeInterface[]>(
const episodes = await axios.get(
generateUrl('/apps/repod/episodes/list?url={url}', {
url: this.feed.metrics.url,
url: this.url,
}),
)
this.episodes = [...episodes.data]
.sort(
(a, b) =>
new Date(b.pubDate?.date || '').getTime() -
new Date(a.pubDate?.date || '').getTime(),
(a, b) => new Date(b.pubDate?.date) - new Date(a.pubDate?.date),
)
.filter((episode) => !this.hasEnded(episode))
.slice(0, 4)
@ -87,7 +90,6 @@ export default {
display: flex;
flex-wrap: wrap;
gap: 1rem;
margin: 20px !important;
}
.list {

View File

@ -13,7 +13,7 @@
</div>
</template>
<script lang="ts">
<script>
import Controls from './Controls.vue'
import Infos from './Infos.vue'
import Loading from '../Atoms/Loading.vue'
@ -21,7 +21,7 @@ import ProgressBar from './ProgressBar.vue'
import Timer from './Timer.vue'
import Volume from './Volume.vue'
import { mapState } from 'pinia'
import { usePlayer } from '../../store/player.ts'
import { usePlayer } from '../../store/player.js'
export default {
name: 'Bar',

View File

@ -1,53 +1,27 @@
<template>
<div class="controls">
<Rewind10Icon
class="pointer rewind"
:size="20"
:title="t('repod', 'Rewind 10 seconds')"
@click="seek((currentTime ?? 0) - 10)" />
<PauseIcon
v-if="!paused"
class="pointer"
:size="50"
:title="t('repod', 'Pause')"
@click="pause" />
<PlayIcon
v-if="paused"
class="pointer"
:size="50"
:title="t('repod', 'Play')"
@click="play" />
<FastForward30Icon
class="pointer forward"
:size="20"
:title="t('repod', 'Fast forward 30 seconds')"
@click="seek((currentTime ?? 0) + 30)" />
<PauseIcon v-if="!paused" class="pointer" :size="50" @click="pause" />
<PlayIcon v-if="paused" class="pointer" :size="50" @click="play" />
</div>
</template>
<script lang="ts">
<script>
import { mapActions, mapState } from 'pinia'
import FastForward30Icon from 'vue-material-design-icons/FastForward30.vue'
import PauseIcon from 'vue-material-design-icons/Pause.vue'
import PlayIcon from 'vue-material-design-icons/Play.vue'
import Rewind10Icon from 'vue-material-design-icons/Rewind10.vue'
import { t } from '@nextcloud/l10n'
import { usePlayer } from '../../store/player.ts'
import { usePlayer } from '../../store/player.js'
export default {
name: 'Controls',
components: {
FastForward30Icon,
PauseIcon,
PlayIcon,
Rewind10Icon,
},
computed: {
...mapState(usePlayer, ['currentTime', 'paused']),
...mapState(usePlayer, ['paused']),
},
methods: {
...mapActions(usePlayer, ['play', 'pause', 'seek']),
t,
...mapActions(usePlayer, ['play', 'pause']),
},
}
</script>
@ -60,11 +34,4 @@ export default {
.pointer {
cursor: pointer;
}
@media only screen and (max-width: 768px) {
.forward,
.rewind {
display: none;
}
}
</style>

View File

@ -1,5 +1,5 @@
<template>
<div v-if="episode && podcastUrl" class="root">
<div class="root">
<strong class="pointer" @click="modal = true">
{{ episode.name }}
</strong>
@ -12,12 +12,12 @@
</div>
</template>
<script lang="ts">
<script>
import Modal from '../Atoms/Modal.vue'
import { NcModal } from '@nextcloud/vue'
import { mapState } from 'pinia'
import { toFeedUrl } from '../../utils/url.ts'
import { usePlayer } from '../../store/player.ts'
import { toFeedUrl } from '../../utils/url.js'
import { usePlayer } from '../../store/player.js'
export default {
name: 'Infos',

View File

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

View File

@ -1,15 +1,15 @@
<template>
<div v-if="currentTime && duration" class="root">
<div>
<span>{{ formatTimer(new Date(currentTime * 1000)) }}</span>
<span>/</span>
<span>{{ formatTimer(new Date(duration * 1000)) }}</span>
</div>
</template>
<script lang="ts">
import { formatTimer } from '../../utils/time.ts'
<script>
import { formatTimer } from '../../utils/time.js'
import { mapState } from 'pinia'
import { usePlayer } from '../../store/player.ts'
import { usePlayer } from '../../store/player.js'
export default {
name: 'Timer',
@ -23,7 +23,7 @@ export default {
</script>
<style scoped>
.root {
div {
align-items: center;
display: flex;
gap: 5px;

View File

@ -4,25 +4,21 @@
v-if="volume > 0.7"
class="pointer"
:size="30"
:title="t('repod', 'Mute')"
@click="mute" />
<VolumeLowIcon
v-if="volume > 0 && volume <= 0.3"
class="pointer"
:size="30"
:title="t('repod', 'Mute')"
@click="mute" />
<VolumeMediumIcon
v-if="volume > 0.3 && volume <= 0.7"
class="pointer"
:size="30"
:title="t('repod', 'Mute')"
@click="mute" />
<VolumeMuteIcon
v-if="volume === 0"
class="pointer"
:size="30"
:title="t('repod', 'Unmute')"
@click="setVolume(volumeMuted)" />
<input
max="1"
@ -30,21 +26,17 @@
step="0.1"
type="range"
:value="volume"
@change="
(event) =>
setVolume(parseFloat((event.target as HTMLInputElement).value))
" />
@change="(event) => setVolume(event.target.value)" />
</div>
</template>
<script lang="ts">
<script>
import { mapActions, mapState } from 'pinia'
import VolumeHighIcon from 'vue-material-design-icons/VolumeHigh.vue'
import VolumeLowIcon from 'vue-material-design-icons/VolumeLow.vue'
import VolumeMediumIcon from 'vue-material-design-icons/VolumeMedium.vue'
import VolumeMuteIcon from 'vue-material-design-icons/VolumeMute.vue'
import { t } from '@nextcloud/l10n'
import { usePlayer } from '../../store/player.ts'
import { usePlayer } from '../../store/player.js'
export default {
name: 'Volume',
@ -66,7 +58,6 @@ export default {
this.volumeMuted = this.volume
this.setVolume(0)
},
t,
},
}
</script>

View File

@ -8,11 +8,10 @@
</NcAppNavigationItem>
</template>
<script lang="ts">
<script>
import ExportIcon from 'vue-material-design-icons/Export.vue'
import { NcAppNavigationItem } from '@nextcloud/vue'
import { generateUrl } from '@nextcloud/router'
import { t } from '@nextcloud/l10n'
export default {
name: 'Export',
@ -22,7 +21,6 @@ export default {
},
methods: {
generateUrl,
t,
},
}
</script>

View File

@ -1,5 +1,6 @@
<template>
<NcAppNavigationItem
:allow-collapse="true"
menu-placement="top"
:name="t('repod', 'Filtering episodes')">
<template #actions>
@ -38,13 +39,12 @@
</NcAppNavigationItem>
</template>
<script lang="ts">
<script>
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 { t } from '@nextcloud/l10n'
import { useSettings } from '../../store/settings.ts'
import { useSettings } from '../../store/settings.js'
export default {
name: 'Filters',
@ -66,7 +66,6 @@ export default {
},
methods: {
...mapActions(useSettings, ['setFilters']),
t,
},
}
</script>

View File

@ -29,13 +29,12 @@
</NcAppNavigationItem>
</template>
<script lang="ts">
<script>
import { NcAppNavigationItem, NcModal } from '@nextcloud/vue'
import ImportIcon from 'vue-material-design-icons/Import.vue'
import Loading from '../Atoms/Loading.vue'
import axios from '@nextcloud/axios'
import { generateUrl } from '@nextcloud/router'
import { t } from '@nextcloud/l10n'
export default {
name: 'Import',
@ -51,13 +50,11 @@ export default {
}),
methods: {
generateUrl,
t,
async importOpml(event: Event) {
async importOpml(event) {
try {
const target = event.target as HTMLFormElement
const formData = new FormData(target)
this.loading = true
await axios.post(target.action, formData)
const formData = new FormData(event.target)
this.importLoading = true
await axios.post(event.target.action, formData)
} catch (e) {
console.error(e)
} finally {

View File

@ -8,10 +8,9 @@
</NcAppNavigationItem>
</template>
<script lang="ts">
<script>
import { NcAppNavigationItem } from '@nextcloud/vue'
import StarIcon from 'vue-material-design-icons/Star.vue'
import { t } from '@nextcloud/l10n'
export default {
name: 'Rate',
@ -19,8 +18,5 @@ export default {
NcAppNavigationItem,
StarIcon,
},
methods: {
t,
},
}
</script>

View File

@ -1,7 +1,6 @@
<template>
<NcAppNavigationSettings>
<Filters />
<Sleep />
<Speed />
<Import />
<Export />
@ -9,13 +8,12 @@
</NcAppNavigationSettings>
</template>
<script lang="ts">
<script>
import Export from './Export.vue'
import Filters from './Filters.vue'
import Import from './Import.vue'
import { NcAppNavigationSettings } from '@nextcloud/vue'
import Rate from './Rate.vue'
import Sleep from './Sleep.vue'
import Speed from './Speed.vue'
export default {
@ -26,7 +24,6 @@ export default {
Import,
NcAppNavigationSettings,
Rate,
Sleep,
Speed,
},
}

View File

@ -1,103 +0,0 @@
<template>
<NcAppNavigationItem menu-placement="top" :name="t('repod', 'Sleep timer')">
<template #actions>
<NcActionInput
v-if="!sleep"
v-model="input"
:label="t('repod', 'Minutes')"
:label-outside="false"
type="number"
@submit="setTimer">
<template #icon>
<ClockVue :size="20" />
</template>
</NcActionInput>
</template>
<template #extra>
<div v-if="sleep" class="extra">
{{ label }}
<BellCancel class="pointer" :size="20" @click="stopTimer" />
</div>
</template>
<template #icon>
<BellSleepIcon v-if="sleep" :size="20" />
<BellSleepOutlineIcon v-if="!sleep" :size="20" />
</template>
</NcAppNavigationItem>
</template>
<script lang="ts">
import { NcActionInput, NcAppNavigationItem } from '@nextcloud/vue'
import { n, t } from '@nextcloud/l10n'
import BellCancel from 'vue-material-design-icons/BellCancel.vue'
import BellSleepIcon from 'vue-material-design-icons/BellSleep.vue'
import BellSleepOutlineIcon from 'vue-material-design-icons/BellSleepOutline.vue'
import ClockVue from 'vue-material-design-icons/Clock.vue'
import { mapActions } from 'pinia'
import { usePlayer } from '../../store/player.ts'
export default {
name: 'Sleep',
components: {
BellCancel,
BellSleepIcon,
BellSleepOutlineIcon,
ClockVue,
NcActionInput,
NcAppNavigationItem,
},
data: () => ({
input: 10,
sleep: null as NodeJS.Timeout | null,
timer: 0,
}),
computed: {
label() {
if (this.timer > 60) {
return this.n(
'repod',
'%n min',
'%n mins',
Math.round(this.timer / 60),
)
} else {
return this.n('repod', '%n sec', '%n secs', this.timer)
}
},
},
methods: {
...mapActions(usePlayer, ['stop']),
n,
t,
setTimer() {
this.timer = this.input * 60
this.sleep = setInterval(() => {
if (this.timer > 0) {
this.timer--
} else if (this.sleep) {
this.stopTimer()
this.stop()
}
}, 1000)
},
stopTimer() {
if (this.sleep) {
clearTimeout(this.sleep)
this.sleep = null
}
},
},
}
</script>
<style scoped>
.extra {
align-items: center;
display: flex;
gap: 0.5rem;
}
.pointer {
cursor: pointer;
}
</style>

View File

@ -15,7 +15,7 @@
</NcAppNavigationItem>
</template>
<script lang="ts">
<script>
import { NcAppNavigationItem, NcCounterBubble } from '@nextcloud/vue'
import { mapActions, mapState } from 'pinia'
import MinusIcon from 'vue-material-design-icons/Minus.vue'
@ -23,8 +23,7 @@ import PlusIcon from 'vue-material-design-icons/Plus.vue'
import SpeedometerIcon from 'vue-material-design-icons/Speedometer.vue'
import SpeedometerMediumIcon from 'vue-material-design-icons/SpeedometerMedium.vue'
import SpeedometerSlowIcon from 'vue-material-design-icons/SpeedometerSlow.vue'
import { t } from '@nextcloud/l10n'
import { usePlayer } from '../../store/player.ts'
import { usePlayer } from '../../store/player.js'
export default {
name: 'Speed',
@ -42,9 +41,8 @@ export default {
},
methods: {
...mapActions(usePlayer, ['setRate']),
t,
changeRate(diff: number) {
const newRate = parseFloat((this.rate + diff).toPrecision(2))
changeRate(diff) {
const newRate = (this.rate + diff).toPrecision(2)
this.setRate(newRate > 0 ? newRate : this.rate)
},
},

View File

@ -1,18 +1,18 @@
<template>
<NcAppNavigationItem
:loading="loading"
:name="feed?.data?.title || url"
:name="feed ? feed.title : url"
:to="toFeedUrl(url)">
<template #actions>
<NcActionButton
:aria-label="t('repod', 'Favorite')"
:model-value="feed?.isFavorite"
:model-value="isFavorite"
:name="t('repod', 'Favorite')"
:title="t('repod', 'Favorite')"
@update:modelValue="switchFavorite($event)">
<template #icon>
<StarPlusIcon v-if="!feed?.isFavorite" :size="20" />
<StarRemoveIcon v-if="feed?.isFavorite" :size="20" />
<StarPlusIcon v-if="!isFavorite" :size="20" />
<StarRemoveIcon v-if="isFavorite" :size="20" />
</template>
</NcActionButton>
<NcActionButton
@ -27,31 +27,29 @@
</template>
<template #icon>
<NcAvatar
:display-name="feed?.data?.author || feed?.data?.title"
v-if="feed"
:display-name="feed.author || feed.title"
:is-no-user="true"
:title="feed?.data?.author"
:url="feed?.data?.imageUrl" />
<StarIcon v-if="feed?.isFavorite" class="star" :size="20" />
:url="feed.imageUrl" />
<StarIcon v-if="feed && isFavorite" class="star" :size="20" />
<AlertIcon v-if="failed" />
</template>
</NcAppNavigationItem>
</template>
<script lang="ts">
<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 type { PersonalSettingsPodcastDataInterface } from '../../utils/types.ts'
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 { showError } from '../../utils/toast.ts'
import { t } from '@nextcloud/l10n'
import { toFeedUrl } from '../../utils/url.ts'
import { useSubscriptions } from '../../store/subscriptions.ts'
import { showError } from '../../utils/toast.js'
import { toFeedUrl } from '../../utils/url.js'
import { useSubscriptions } from '../../store/subscriptions.js'
export default {
name: 'Subscription',
@ -74,17 +72,17 @@ export default {
data: () => ({
failed: false,
loading: true,
feed: null,
}),
computed: {
...mapState(useSubscriptions, ['getSubByUrl', 'subs']),
feed() {
return this.getSubByUrl(this.url)
...mapState(useSubscriptions, ['getFavorites']),
isFavorite() {
return this.getFavorites.map((fav) => fav.url).includes(this.url)
},
},
async mounted() {
try {
const podcastData =
await axios.get<PersonalSettingsPodcastDataInterface>(
const podcastData = await axios.get(
generateUrl(
'/apps/gpoddersync/personal_settings/podcast_data?url={url}',
{
@ -92,7 +90,8 @@ export default {
},
),
)
this.addMetadatas(this.url, podcastData.data.data)
this.feed = podcastData.data.data
this.editFavoriteData(this.url, podcastData.data.data)
} catch (e) {
this.failed = true
console.error(e)
@ -101,8 +100,12 @@ export default {
}
},
methods: {
...mapActions(useSubscriptions, ['fetch', 'addMetadatas', 'setFavorite']),
t,
...mapActions(useSubscriptions, [
'fetch',
'addFavorite',
'editFavoriteData',
'removeFavorite',
]),
toFeedUrl,
async deleteSubscription() {
if (
@ -120,20 +123,23 @@ export default {
console.error(e)
showError(t('repod', 'Error while removing the feed'))
} finally {
this.setFavorite(this.url, false)
this.removeFavorite(this.url)
this.loading = false
this.fetch()
}
}
},
switchFavorite(value: boolean) {
switchFavorite(value) {
if (value) {
if (this.subs.filter((sub) => sub.isFavorite).length >= 10) {
if (this.getFavorites.length >= 10) {
showError(t('repod', 'You can only have 10 favorites'))
return
}
this.addFavorite(this.url)
} else {
this.removeFavorite(this.url)
}
this.setFavorite(this.url, value)
},
},
}

View File

@ -12,13 +12,16 @@
<Loading v-if="loading" />
<NcAppNavigationList v-if="!loading">
<Subscription
v-for="sub of subs.filter((sub) => sub.isFavorite)"
:key="sub.metrics.url"
:url="sub.metrics.url" />
v-for="url of getFavorites.map((fav) => fav.url)"
:key="url"
:url="url" />
<Subscription
v-for="sub of subs.filter((sub) => !sub.isFavorite)"
:key="sub.metrics.url"
:url="sub.metrics.url" />
v-for="url of getSubscriptions.filter(
(sub) =>
!getFavorites.map((fav) => fav.url).includes(sub),
)"
:key="url"
:url="url" />
</NcAppNavigationList>
</NcAppContentList>
</template>
@ -28,7 +31,7 @@
</AppNavigation>
</template>
<script lang="ts">
<script>
import {
NcAppContentList,
NcAppNavigationList,
@ -40,9 +43,8 @@ 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.ts'
import { t } from '@nextcloud/l10n'
import { useSubscriptions } from '../../store/subscriptions.ts'
import { showError } from '../../utils/toast.js'
import { useSubscriptions } from '../../store/subscriptions.js'
export default {
name: 'Subscriptions',
@ -60,7 +62,7 @@ export default {
loading: true,
}),
computed: {
...mapState(useSubscriptions, ['subs']),
...mapState(useSubscriptions, ['getSubscriptions', 'getFavorites']),
},
async mounted() {
try {
@ -74,7 +76,6 @@ export default {
},
methods: {
...mapActions(useSubscriptions, ['fetch']),
t,
},
}
</script>

View File

@ -1,12 +1,13 @@
import '@formatjs/intl-segmenter/polyfill'
import { n, t } from '@nextcloud/l10n'
import App from './App.vue'
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import router from './router.ts'
import router from './router.js'
const Vue = createApp(App)
const pinia = createPinia()
Vue.mixin({ methods: { t, n } })
Vue.use(pinia)
Vue.use(router)
Vue.mount('#content')

View File

@ -1,32 +1,24 @@
import type { EpisodeActionInterface, EpisodeInterface } from '../utils/types.ts'
import { getCookie, setCookie } from '../utils/cookies.ts'
import axios from '@nextcloud/axios'
import { defineStore } from 'pinia'
import { formatEpisodeTimestamp } from '../utils/time.ts'
import { formatEpisodeTimestamp } from '../utils/time.js'
import { generateUrl } from '@nextcloud/router'
import { showError } from '../utils/toast.ts'
import { t } from '@nextcloud/l10n'
const audio = new Audio()
export const usePlayer = defineStore('player', {
state: () => ({
currentTime: null as number | null,
duration: null as number | null,
episode: null as EpisodeInterface | null,
currentTime: null,
duration: null,
episode: null,
loaded: false,
paused: true,
playCount: 0,
podcastUrl: null as string | null,
paused: null,
podcastUrl: null,
volume: 1,
rate: 1,
started: 0,
}),
actions: {
init() {
audio.playbackRate = parseFloat(getCookie('repod.rate') || '1')
audio.volume = parseFloat(getCookie('repod.volume') || '1')
audio.ondurationchange = () => (this.duration = audio.duration)
audio.onended = () => this.stop()
audio.onloadeddata = () => (this.loaded = true)
@ -36,28 +28,17 @@ export const usePlayer = defineStore('player', {
audio.onseeked = () => (this.currentTime = audio.currentTime)
audio.ontimeupdate = () => (this.currentTime = audio.currentTime)
audio.onvolumechange = () => (this.volume = audio.volume)
setInterval(this.act, 40000)
setInterval(this.conflict, 1000)
},
act() {
if (this.paused === false) {
this.time()
}
},
conflict() {
this.playCount = 0
},
async load(episode: EpisodeInterface | null, podcastUrl?: string) {
async load(episode, podcastUrl) {
this.episode = episode
this.podcastUrl = podcastUrl || null
this.podcastUrl = podcastUrl
if (this.episode?.url) {
if (this.episode) {
audio.src = this.episode.url
audio.load()
try {
const action = await axios.get<EpisodeActionInterface>(
const action = await axios.get(
generateUrl('/apps/repod/episodes/action?url={url}', {
url: this.episode.url,
}),
@ -87,17 +68,11 @@ export const usePlayer = defineStore('player', {
this.time()
},
play() {
this.playCount++
if (this.playCount > 10) {
showError(t('repod', 'A browser extension conflict with RePod'))
} else {
audio.play()
this.paused = false
this.started = audio.currentTime
}
},
seek(currentTime: number) {
seek(currentTime) {
audio.currentTime = currentTime
this.time()
},
@ -106,10 +81,6 @@ export const usePlayer = defineStore('player', {
this.episode = null
},
time() {
if (!this.podcastUrl || !this.episode?.url) {
return
}
this.episode.action = {
podcast: this.podcastUrl,
episode: this.episode.url,
@ -120,18 +91,15 @@ export const usePlayer = defineStore('player', {
position: Math.round(audio.currentTime),
total: Math.round(audio.duration),
}
axios.post(generateUrl('/apps/gpoddersync/episode_action/create'), [
this.episode.action,
])
},
setVolume(volume: number) {
setVolume(volume) {
audio.volume = volume
setCookie('repod.volume', volume.toString(), 365)
},
setRate(rate: number) {
setRate(rate) {
audio.playbackRate = rate
setCookie('repod.rate', rate.toString(), 365)
},
},
})

View File

@ -1,16 +1,10 @@
import { getCookie, setCookie } from '../utils/cookies.ts'
import type { FiltersInterface } from '../utils/types.ts'
import { getCookie, setCookie } from '../utils/cookies.js'
import { defineStore } from 'pinia'
export const useSettings = defineStore('settings', {
state: () => {
try {
const filters = JSON.parse(getCookie('repod.filters') || '{}') || {}
if (!filters.length) {
throw new Error('Empty cookie')
}
const filters = JSON.parse(getCookie('repod.filters'))
return {
filters: {
listened: filters.listened,
@ -29,7 +23,7 @@ export const useSettings = defineStore('settings', {
}
},
actions: {
setFilters(filters: Partial<FiltersInterface>) {
setFilters(filters) {
this.filters = { ...this.filters, ...filters }
setCookie('repod.filters', JSON.stringify(this.filters), 365)
},

View File

@ -0,0 +1,58 @@
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: () => ({
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(
generateUrl('/apps/gpoddersync/personal_settings/metrics'),
)
const subs = [...metrics.data.subscriptions].sort(
(a, b) => b.listenedSeconds - a.listenedSeconds,
)
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,
)
},
},
})

View File

@ -1,59 +0,0 @@
import type {
PersonalSettingsMetricsInterface,
PodcastDataInterface,
SubscriptionInterface,
} from '../utils/types.ts'
import { getCookie, setCookie } from '../utils/cookies.ts'
import axios from '@nextcloud/axios'
import { defineStore } from 'pinia'
import { generateUrl } from '@nextcloud/router'
export const useSubscriptions = defineStore('subscriptions', {
state: () => ({
subs: [] as SubscriptionInterface[],
}),
getters: {
getSubByUrl: (state) => (url: string) =>
state.subs.find((sub) => sub.metrics.url === url),
},
actions: {
async fetch() {
let favorites: string[] = []
try {
favorites = JSON.parse(getCookie('repod.favorites') || '[]') || []
} catch {}
const metrics = await axios.get<PersonalSettingsMetricsInterface>(
generateUrl('/apps/gpoddersync/personal_settings/metrics'),
)
this.subs = [...metrics.data.subscriptions]
.sort((a, b) => b.listenedSeconds - a.listenedSeconds)
.map((sub) => ({
metrics: sub,
isFavorite: favorites.includes(sub.url),
data: this.getSubByUrl(sub.url)?.data,
}))
},
addMetadatas(link: string, data: PodcastDataInterface) {
this.subs = this.subs.map((sub) =>
sub.metrics.url === link ? { ...sub, data } : sub,
)
},
setFavorite(link: string, isFavorite: boolean) {
this.subs = this.subs.map((sub) =>
sub.metrics.url === link ? { ...sub, isFavorite } : sub,
)
setCookie(
'repod.favorites',
JSON.stringify(
this.subs
.filter((sub) => sub.isFavorite)
.map((sub) => sub.metrics.url),
),
365,
)
},
},
})

View File

@ -4,7 +4,7 @@
* @param {string} name Nom du cookie à récupérer
* @return {string|null}
*/
export const getCookie = (name: string): string | null => {
export const getCookie = (name) => {
const cookies = document.cookie.split('; ')
const value = cookies.find((c) => c.startsWith(name + '='))?.split('=')[1]
if (value === undefined) {
@ -19,7 +19,7 @@ export const getCookie = (name: string): string | null => {
* @param {string} value Value du cookie
* @param {number} days Durée de vie du cookie (en jours)
*/
export const setCookie = (name: string, value: string, days: number) => {
export const setCookie = (name, value, days) => {
const date = new Date()
date.setDate(date.getDate() + days)
document.cookie = `${name}=${encodeURIComponent(value)}; expires=${date.toUTCString()}; SameSite=Strict;`

12
src/utils/debounce.js Normal file
View File

@ -0,0 +1,12 @@
// https://stackoverflow.com/a/53486112
export const debounce = (fn, delay) => {
let timeoutID = null
return function () {
clearTimeout(timeoutID)
const args = arguments
const that = this
timeoutID = setTimeout(function () {
fn.apply(that, args)
}, delay)
}
}

View File

@ -1,8 +1,8 @@
// https://stackoverflow.com/a/20732091
export const humanFileSize = (size: number) => {
export const humanFileSize = (size) => {
const i = size === 0 ? 0 : Math.floor(Math.log(size) / Math.log(1024))
return (
(size / Math.pow(1024, i)).toFixed(2) +
(size / Math.pow(1024, i)).toFixed(2) * 1 +
' ' +
['B', 'kB', 'MB', 'GB', 'TB'][i]
)

View File

@ -1,6 +1,4 @@
import type { EpisodeInterface } from './types'
export const hasEnded = (episode: EpisodeInterface) =>
export const hasEnded = (episode) =>
episode.action &&
episode.action.action &&
(episode.action.action.toLowerCase() === 'delete' ||
@ -8,7 +6,7 @@ export const hasEnded = (episode: EpisodeInterface) =>
episode.action.total > 0 &&
episode.action.position >= episode.action.total))
export const isListening = (episode: EpisodeInterface) =>
export const isListening = (episode) =>
episode.action &&
episode.action.action &&
episode.action.action.toLowerCase() === 'play' &&

View File

@ -3,9 +3,9 @@
* @param {Date} date The date
* @return {string}
*/
export const formatTimer = (date: Date): string => {
const minutes = date.getUTCMinutes().toString().padStart(2, '0')
const seconds = date.getUTCSeconds().toString().padStart(2, '0')
export const formatTimer = (date) => {
const minutes = date.getUTCMinutes().toString().padStart(2, 0)
const seconds = date.getUTCSeconds().toString().padStart(2, 0)
let timer = `${minutes}:${seconds}`
if (date.getUTCHours()) {
@ -20,7 +20,7 @@ export const formatTimer = (date: Date): string => {
* @param {Date} date The date
* @return {string}
*/
export const formatEpisodeTimestamp = (date: Date): string => {
export const formatEpisodeTimestamp = (date) => {
const year = date.getFullYear()
const month = (date.getMonth() + 1).toString().padStart(2, '0')
const day = date.getDate().toString().padStart(2, '0')
@ -36,7 +36,7 @@ export const formatEpisodeTimestamp = (date: Date): string => {
* @param {Date} date The date
* @return {string}
*/
export const formatLocaleDate = (date: Date): string =>
export const formatLocaleDate = (date) =>
date.toLocaleDateString(undefined, { dateStyle: 'medium' })
/**
@ -44,7 +44,7 @@ export const formatLocaleDate = (date: Date): string =>
* @param {string} duration The duration feed's entry
* @return {number}
*/
export const durationToSeconds = (duration: string): number => {
export const durationToSeconds = (duration) => {
const splitDuration = duration.split(':').reverse()
let seconds = parseInt(splitDuration[0])
seconds += splitDuration.length > 1 ? parseInt(splitDuration[1]) * 60 : 0

12
src/utils/toast.js Normal file
View File

@ -0,0 +1,12 @@
import toastify from 'toastify-js'
export const showMessage = (text, backgroundColor) =>
toastify({
text,
backgroundColor,
}).showToast()
export const showError = (text) => showMessage(text, 'var(--color-error)')
export const showWarning = (text) => showMessage(text, 'var(--color-warning)')
export const showInfo = (text) => showMessage(text, 'var(--color-primary)')
export const showSuccess = (text) => showMessage(text, 'var(--color-success)')

View File

@ -1,14 +0,0 @@
import toastify from 'toastify-js'
export const showMessage = (text: string, backgroundColor: string) =>
toastify({
text,
backgroundColor,
}).showToast()
export const showError = (text: string) => showMessage(text, 'var(--color-error)')
export const showWarning = (text: string) =>
showMessage(text, 'var(--color-warning)')
export const showInfo = (text: string) => showMessage(text, 'var(--color-primary)')
export const showSuccess = (text: string) =>
showMessage(text, 'var(--color-success)')

View File

@ -1,73 +0,0 @@
export interface EpisodeActionInterface {
podcast: string
episode: string
action: string
timestamp: string
started: number
position: number
total: number
guid?: string
id?: number
}
export interface EpisodeInterface {
title: string
url: string
name: string
link?: string
image?: string
description?: string
fetchedAtUnix: number
guid: string
type?: string
size?: number
pubDate?: {
date: string
timezone_type: number
timezone: string
}
duration?: string
action?: EpisodeActionInterface
}
export interface FiltersInterface {
listened: boolean
listening: boolean
unlistened: boolean
}
export interface PodcastDataInterface {
title: string
author?: string
link: string
description?: string
imageUrl?: string
fetchedAtUnix: number
imageBlob?: string | null
}
export interface PodcastMetricsInterface {
url: string
listenedSeconds: number
actionCounts: {
delete: number
download: number
flattr: number
new: number
play: number
}
}
export interface SubscriptionInterface {
data?: PodcastDataInterface
isFavorite?: boolean
metrics: PodcastMetricsInterface
}
export interface PersonalSettingsMetricsInterface {
subscriptions: PodcastMetricsInterface[]
}
export interface PersonalSettingsPodcastDataInterface {
data: PodcastDataInterface
}

7
src/utils/url.js Normal file
View File

@ -0,0 +1,7 @@
export const encodeUrl = (url) => encodeURIComponent(btoa(url))
export const decodeUrl = (url) => atob(decodeURIComponent(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 +0,0 @@
export const encodeUrl = (url: string) => encodeURIComponent(btoa(url))
export const decodeUrl = (url: string) => atob(decodeURIComponent(url))
export const toFeedUrl = (url: string) => `/feed/${encodeUrl(url)}`
export const filenameFromUrl = (url: string) =>
new URL(url).pathname.split('/').pop()

View File

@ -12,14 +12,13 @@
</AppContent>
</template>
<script lang="ts">
<script>
import AddRss from '../components/Discover/AddRss.vue'
import AppContent from '../components/Atoms/AppContent.vue'
import Magnify from 'vue-material-design-icons/Magnify.vue'
import { NcTextField } from '@nextcloud/vue'
import Search from '../components/Discover/Search.vue'
import Toplist from '../components/Discover/Toplist.vue'
import { t } from '@nextcloud/l10n'
export default {
name: 'Discover',
@ -34,9 +33,6 @@ export default {
data: () => ({
search: '',
}),
methods: {
t,
},
}
</script>

View File

@ -1,28 +1,35 @@
<template>
<AppContent>
<Loading v-if="loading" />
<EmptyContent v-if="failed" :name="t('repod', 'Error loading feed')">
<EmptyContent
v-if="failed"
class="error"
:name="t('repod', 'Error loading feed')">
<template #icon>
<Alert />
</template>
</EmptyContent>
<Banner v-if="feed" :feed="feed" />
<Banner
v-if="feed"
:author="feed.author"
:description="feed.description"
:image-url="feed.imageUrl"
:link="feed.link"
:title="feed.title" />
<Episodes v-if="feed" />
</AppContent>
</template>
<script lang="ts">
<script>
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 type { PodcastDataInterface } from '../utils/types.ts'
import axios from '@nextcloud/axios'
import { decodeUrl } from '../utils/url.ts'
import { decodeUrl } from '../utils/url.js'
import { generateUrl } from '@nextcloud/router'
import { t } from '@nextcloud/l10n'
export default {
name: 'Feed',
@ -37,17 +44,16 @@ export default {
data: () => ({
failed: false,
loading: true,
feed: null as PodcastDataInterface | null,
feed: null,
}),
computed: {
url() {
return decodeUrl(this.$route.params.url as string)
return decodeUrl(this.$route.params.url)
},
},
async mounted() {
try {
this.loading = true
const podcastData = await axios.get<PodcastDataInterface>(
const podcastData = await axios.get(
generateUrl('/apps/repod/podcast?url={url}', { url: this.url }),
)
this.feed = podcastData.data
@ -58,8 +64,11 @@ export default {
this.loading = false
}
},
methods: {
t,
},
}
</script>
<style scoped>
.error {
margin: 2rem;
}
</style>

View File

@ -1,6 +1,6 @@
<template>
<AppContent>
<EmptyContent :name="t('repod', 'Missing required app')">
<EmptyContent class="empty" :name="t('repod', 'Missing required app')">
<template #action>
<NcButton :href="gPodderSyncUrl">
{{ t('repod', 'Install GPodder Sync') }}
@ -13,13 +13,12 @@
</AppContent>
</template>
<script lang="ts">
<script>
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 { t } from '@nextcloud/l10n'
export default {
name: 'GPodder',
@ -34,8 +33,5 @@ export default {
return generateUrl('/settings/apps/installed/gpoddersync')
},
},
methods: {
t,
},
}
</script>

View File

@ -1,55 +1,42 @@
<template>
<AppContent>
<EmptyContent
v-if="!favorites.length"
class="padding"
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 />
<StarOffIcon :size="20" />
</template>
</EmptyContent>
<ul v-if="favorites.length">
<li v-for="favorite in favorites" :key="favorite.metrics.url">
<Favorite :feed="favorite" />
<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 lang="ts">
<script>
import AppContent from '../components/Atoms/AppContent.vue'
import EmptyContent from '../components/Atoms/EmptyContent.vue'
import Favorite from '../components/Feed/Favorite.vue'
import Favorites from '../components/Feed/Favorites.vue'
import StarOffIcon from 'vue-material-design-icons/StarOff.vue'
import { mapState } from 'pinia'
import { t } from '@nextcloud/l10n'
import { useSubscriptions } from '../store/subscriptions.ts'
import { useSubscriptions } from '../store/subscriptions.js'
export default {
name: 'Home',
components: {
AppContent,
EmptyContent,
Favorite,
Favorites,
StarOffIcon,
},
computed: {
...mapState(useSubscriptions, ['subs']),
favorites() {
return this.subs.filter((sub) => sub.isFavorite)
},
},
methods: {
t,
...mapState(useSubscriptions, ['getFavorites']),
},
}
</script>
<style scoped>
.padding {
padding: 15px 51px;
}
</style>

View File

@ -6,9 +6,7 @@ namespace OCA\GPodderSync\Core\SubscriptionChange;
class SubscriptionChangeRequestParser
{
public function __construct(
private SubscriptionChangesReader $subscriptionChangeReader
) {}
public function __construct(private SubscriptionChangesReader $subscriptionChangeReader) {}
/**
* @return SubscriptionChange[]

View File

@ -8,9 +8,7 @@ use OCA\GPodderSync\Core\EpisodeAction\EpisodeAction;
class EpisodeActionRepository
{
public function __construct(
private EpisodeActionMapper $episodeActionMapper
) {}
public function __construct(private EpisodeActionMapper $episodeActionMapper) {}
/**
* @return EpisodeAction[]

View File

@ -8,9 +8,7 @@ use OCP\DB\Exception;
class EpisodeActionWriter
{
public function __construct(
private EpisodeActionMapper $episodeActionMapper
) {}
public function __construct(private EpisodeActionMapper $episodeActionMapper) {}
/**
* @return EpisodeActionEntity

View File

@ -6,9 +6,7 @@ namespace OCA\GPodderSync\Db\SubscriptionChange;
class SubscriptionChangeRepository
{
public function __construct(
private SubscriptionChangeMapper $subscriptionChangeMapper
) {}
public function __construct(private SubscriptionChangeMapper $subscriptionChangeMapper) {}
/**
* @return SubscriptionChangeEntity[]

View File

@ -6,9 +6,7 @@ namespace OCA\GPodderSync\Db\SubscriptionChange;
class SubscriptionChangeWriter
{
public function __construct(
private SubscriptionChangeMapper $subscriptionChangeMapper
) {}
public function __construct(private SubscriptionChangeMapper $subscriptionChangeMapper) {}
public function purge(): void {}

View File

@ -32,13 +32,7 @@ msgid ""
"## Features\n"
"- 🔍 Browse and subscribe huge collection of podcasts\n"
"- 🔊 Listen to episodes directly in Nextcloud\n"
"- 🌐 Sync your activity with [AntennaPod](https://antennapod.org/) and [other "
"apps](https://git.crystalyx.net/Xefir/repod#clients-supporting-sync-of-"
"gpoddersync)\n"
"- 📱 Mobile friendly interface\n"
"- 📡 Import and export your subscriptions\n"
"- ➡️ Full features comparison [here](https://git.crystalyx.net/Xefir/"
"repod#comparaison-with-similar-apps-for-nextcloud)\n"
"- 🌐 Sync your activity with [AntennaPod](https://antennapod.org/)\n"
"\n"
"## Requirements\n"
"You need to have [GPodderSync](https://apps.nextcloud.com/apps/gpoddersync) "
@ -48,8 +42,6 @@ msgstr ""
"- 🔍 Durchsuchen und abonnieren einer großen Sammlung von Podcasts\n"
"- 🔊 Episoden direkt in Nextcloud anhören\n"
"- 🌐 Synchronisiere deine Aktivität mit [AntennaPod](https://antennapod.org/)\n"
"- 📱 Mobile-freundliche Schnittstelle\n"
"- 📡 Importieren und Exportieren Ihrer Abonnements\n"
"\n"
"## Voraussetzungen\n"
"Du musst [GPodderSync](https://apps.nextcloud.com/apps/gpoddersync) "
@ -103,21 +95,6 @@ msgstr "Kann den Status der Folge nicht ändern"
msgid "Could not fetch episodes"
msgstr "Folgen können nicht abgerufen werden"
msgid "Rewind 10 seconds"
msgstr "10 Sekunden zurückspulen"
msgid "Pause"
msgstr "Pause"
msgid "Fast forward 30 seconds"
msgstr "30 Sekunden vorspulen"
msgid "Mute"
msgstr "Stumm"
msgid "Unmute"
msgstr "Stummschalten"
msgid "Export subscriptions"
msgstr "Abonnements exportieren"
@ -145,22 +122,6 @@ msgstr "Importiere OPML-Datei"
msgid "Rate RePod ❤️"
msgstr "Bewerte RePod ❤️"
msgid "Sleep timer"
msgstr "Einschlaftimer"
msgid "Minutes"
msgstr "Minuten"
msgid "%n min"
msgid_plural "%n mins"
msgstr[0] "%n min"
msgstr[1] "%n mins"
msgid "%n sec"
msgid_plural "%n secs"
msgstr[0] "%s sec"
msgstr[1] "%n secs"
msgid "Playback speed"
msgstr "Wiedergabegeschwindigkeit"
@ -199,6 +160,3 @@ msgstr "Pinne einige Abonnements, um ihre neuesten Updates zu sehen"
msgid "No favorites"
msgstr "Keine Favoriten"
msgid "A browser extension conflict with RePod"
msgstr "Ein Browser-Erweiterungskonflikt mit RePod"

View File

@ -32,13 +32,7 @@ msgid ""
"## Features\n"
"- 🔍 Browse and subscribe huge collection of podcasts\n"
"- 🔊 Listen to episodes directly in Nextcloud\n"
"- 🌐 Sync your activity with [AntennaPod](https://antennapod.org/) and [other "
"apps](https://git.crystalyx.net/Xefir/repod#clients-supporting-sync-of-"
"gpoddersync)\n"
"- 📱 Mobile friendly interface\n"
"- 📡 Import and export your subscriptions\n"
"- ➡️ Full features comparison [here](https://git.crystalyx.net/Xefir/"
"repod#comparaison-with-similar-apps-for-nextcloud)\n"
"- 🌐 Sync your activity with [AntennaPod](https://antennapod.org/)\n"
"\n"
"## Requirements\n"
"You need to have [GPodderSync](https://apps.nextcloud.com/apps/gpoddersync) "
@ -47,13 +41,7 @@ msgstr ""
"## Fonctionnalités\n"
"- 🔍 Parcourir et s'abonner à une grande collections de podcasts\n"
"- 🔊 Écouter vos épisodes directement sur Nextcloud\n"
"- 🌐 Synchroniser son activité avec [AntennaPod](https://antennapod.org/) et d'autres "
"[applications](https://git.crystalyx.net/Xefir/repod#clients-supporting-sync-of-"
"gpoddersync)\n"
"- 📱 Interface optimisée pour mobiles et ordinateurs\n"
"- 📡 Import/export de ses abonnements\n"
"- ➡️ Tableau récapitulatif complet des fonctionnalitées [ici](https://git.crystalyx."
"net/Xefir/repod#comparaison-with-similar-apps-for-nextcloud)\n"
"- 🌐 Synchroniser son activité avec [AntennaPod](https://antennapod.org/)\n"
"\n"
"## Pré-requis\n"
"Vous devez avoir [GPodderSync](https://apps.nextcloud.com/apps/gpoddersync) "
@ -107,21 +95,6 @@ msgstr "Impossible de changer le status de l'épisode"
msgid "Could not fetch episodes"
msgstr "Impossible de récuprer les épisodes"
msgid "Rewind 10 seconds"
msgstr "Retour rapide de 10 secondes"
msgid "Pause"
msgstr "Pause"
msgid "Fast forward 30 seconds"
msgstr "Avance rapide de 30 secondes"
msgid "Mute"
msgstr "Silencer"
msgid "Unmute"
msgstr "Paroler"
msgid "Export subscriptions"
msgstr "Exporter les abonnements"
@ -149,22 +122,6 @@ msgstr "Importer un fichier OPML"
msgid "Rate RePod ❤️"
msgstr "Donnez votre avis ❤️"
msgid "Sleep timer"
msgstr "Minuteur"
msgid "Minutes"
msgstr "Minutes"
msgid "%n min"
msgid_plural "%n mins"
msgstr[0] "%n min"
msgstr[1] "%n mins"
msgid "%n sec"
msgid_plural "%n secs"
msgstr[0] "%s sec"
msgstr[1] "%n secs"
msgid "Playback speed"
msgstr "Vitesse de lecture"
@ -203,6 +160,3 @@ msgstr "Ajoutez des abonnements en favoris pour obtenir les dernières nouvelles
msgid "No favorites"
msgstr "Aucun favoris"
msgid "A browser extension conflict with RePod"
msgstr "Une extension de votre navigateur entre en conflit avec RePod"

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-11-09 18:34+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"
@ -16,9 +16,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
#: /app/lib/Controller/OpmlController.php:46
#: /app/lib/Controller/OpmlController.php:44
msgid "RePod Subscriptions"
msgstr ""
@ -41,13 +40,7 @@ msgid ""
"## Features\n"
"- 🔍 Browse and subscribe huge collection of podcasts\n"
"- 🔊 Listen to episodes directly in Nextcloud\n"
"- 🌐 Sync your activity with [AntennaPod](https://antennapod.org/) and [other "
"apps](https://git.crystalyx.net/Xefir/repod#clients-supporting-sync-of-"
"gpoddersync)\n"
"- 📱 Mobile friendly interface\n"
"- 📡 Import and export your subscriptions\n"
"- ➡️ Full features comparison [here](https://git.crystalyx.net/Xefir/"
"repod#comparaison-with-similar-apps-for-nextcloud)\n"
"- 🌐 Sync your activity with [AntennaPod](https://antennapod.org/)\n"
"\n"
"## Requirements\n"
"You need to have [GPodderSync](https://apps.nextcloud.com/apps/gpoddersync) "
@ -102,7 +95,6 @@ msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:15
#: /app/specialVueFakeDummyForL10nScript.js:16
#: /app/specialVueFakeDummyForL10nScript.js:31
msgid "Play"
msgstr ""
@ -132,137 +124,91 @@ msgid "Could not fetch episodes"
msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:29
msgid "Rewind 10 seconds"
msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:30
msgid "Pause"
msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:32
msgid "Fast forward 30 seconds"
msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:33
#: /app/specialVueFakeDummyForL10nScript.js:34
#: /app/specialVueFakeDummyForL10nScript.js:35
msgid "Mute"
msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:36
msgid "Unmute"
msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:37
msgid "Export subscriptions"
msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:38
#: /app/specialVueFakeDummyForL10nScript.js:30
msgid "Filtering episodes"
msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:39
#: /app/specialVueFakeDummyForL10nScript.js:31
msgid "Show all"
msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:40
#: /app/specialVueFakeDummyForL10nScript.js:32
msgid "Listened"
msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:41
#: /app/specialVueFakeDummyForL10nScript.js:33
msgid "Listening"
msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:42
#: /app/specialVueFakeDummyForL10nScript.js:34
msgid "Unlistened"
msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:43
#: /app/specialVueFakeDummyForL10nScript.js:35
msgid "Import subscriptions"
msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:44
#: /app/specialVueFakeDummyForL10nScript.js:36
msgid "Import OPML file"
msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:45
#: /app/specialVueFakeDummyForL10nScript.js:37
msgid "Rate RePod ❤️"
msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:46
msgid "Sleep timer"
msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:47
msgid "Minutes"
msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:48
msgid "%n min"
msgid_plural "%n mins"
msgstr[0] ""
msgstr[1] ""
#: /app/specialVueFakeDummyForL10nScript.js:49
msgid "%n sec"
msgid_plural "%n secs"
msgstr[0] ""
msgstr[1] ""
#: /app/specialVueFakeDummyForL10nScript.js:50
#: /app/specialVueFakeDummyForL10nScript.js:38
msgid "Playback speed"
msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:51
#: /app/specialVueFakeDummyForL10nScript.js:52
#: /app/specialVueFakeDummyForL10nScript.js:53
#: /app/specialVueFakeDummyForL10nScript.js:39
#: /app/specialVueFakeDummyForL10nScript.js:40
#: /app/specialVueFakeDummyForL10nScript.js:41
msgid "Favorite"
msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:54
#: /app/specialVueFakeDummyForL10nScript.js:42
msgid "Are you sure you want to delete this subscription?"
msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:55
#: /app/specialVueFakeDummyForL10nScript.js:43
msgid "Error while removing the feed"
msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:56
#: /app/specialVueFakeDummyForL10nScript.js:44
msgid "You can only have 10 favorites"
msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:57
#: /app/specialVueFakeDummyForL10nScript.js:45
msgid "Add a podcast"
msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:58
#: /app/specialVueFakeDummyForL10nScript.js:46
msgid "Could not fetch subscriptions"
msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:59
#: /app/specialVueFakeDummyForL10nScript.js:47
msgid "Find a podcast"
msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:60
#: /app/specialVueFakeDummyForL10nScript.js:48
msgid "Error loading feed"
msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:61
#: /app/specialVueFakeDummyForL10nScript.js:49
msgid "Missing required app"
msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:62
#: /app/specialVueFakeDummyForL10nScript.js:50
msgid "Install GPodder Sync"
msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:63
#: /app/specialVueFakeDummyForL10nScript.js:51
msgid "Pin some subscriptions to see their latest updates"
msgstr ""
#: /app/specialVueFakeDummyForL10nScript.js:64
#: /app/specialVueFakeDummyForL10nScript.js:52
msgid "No favorites"
msgstr ""
#: /app/src/store/player.ts:93
msgid "A browser extension conflict with RePod"
msgstr ""

View File

@ -1,15 +0,0 @@
{
"extends": "@vue/tsconfig",
"include": ["./src/**/*.ts", "./src/**/*.vue", "**/*.ts"],
"compilerOptions": {
"allowImportingTsExtensions": true,
"allowSyntheticDefaultImports": true,
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Bundler",
"noImplicitAny": false,
"rootDir": ".",
"strict": true,
"noEmit": true,
}
}

27
vite.config.mjs Normal file
View File

@ -0,0 +1,27 @@
import { createAppConfig } from '@nextcloud/vite-config'
import { defineConfig } from 'vite'
import vueDevTools from 'vite-plugin-vue-devtools'
const config = defineConfig(({ mode }) => ({
build: {
rollupOptions: {
output: {
entryFileNames: 'js/[name].js',
format: 'iife',
manualChunks: false,
},
},
sourcemap: mode !== 'production',
},
define: {
__VUE_PROD_DEVTOOLS__: mode !== 'production'
},
plugins: [vueDevTools()],
}))
export default createAppConfig(
{
main: 'src/main.js',
},
{ config },
)

View File

@ -1,20 +0,0 @@
import { join, resolve } from 'path'
import { createAppConfig } from '@nextcloud/vite-config'
import { defineConfig } from 'vite'
const config = defineConfig({
build: {
sourcemap: false,
},
})
export default createAppConfig(
{
main: resolve(join('src', 'main.ts')),
},
{
config,
createEmptyCSSEntryPoints: true,
thirdPartyLicense: false,
},
)