Merge pull request 'typescript #149' (#152) from typescript into main
All checks were successful
repod / xml (push) Successful in 12s
repod / php (push) Successful in 38s
repod / nodejs (push) Successful in 1m0s
repod / release (push) Has been skipped

Reviewed-on: #152
This commit is contained in:
Michel Roux 2024-09-14 15:26:16 +00:00
commit 29c29cdfdd
57 changed files with 927 additions and 595 deletions

View File

@ -1,9 +1,11 @@
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

@ -15,7 +15,7 @@
"psalm": "psalm --threads=1 --no-cache --show-info=true"
},
"require-dev": {
"nextcloud/ocp": "^29.0.6",
"nextcloud/ocp": "^30.0.0",
"roave/security-advisories": "dev-latest",
"nextcloud/coding-standard": "^1.2.3",
"vimeo/psalm": "^5.26.1"

26
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "d1d678bc8d001322808264320c120173",
"content-hash": "f21708cb7d3f7f4033053536e295e633",
"packages": [],
"packages-dev": [
{
@ -733,16 +733,16 @@
},
{
"name": "nextcloud/ocp",
"version": "v29.0.7",
"version": "v30.0.0",
"source": {
"type": "git",
"url": "https://github.com/nextcloud-deps/ocp.git",
"reference": "b130f11ce24351a6a91115aa6f386271f7aeee9d"
"reference": "a26b4e1f75983f359bd835c2529ce37b5599d58f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nextcloud-deps/ocp/zipball/b130f11ce24351a6a91115aa6f386271f7aeee9d",
"reference": "b130f11ce24351a6a91115aa6f386271f7aeee9d",
"url": "https://api.github.com/repos/nextcloud-deps/ocp/zipball/a26b4e1f75983f359bd835c2529ce37b5599d58f",
"reference": "a26b4e1f75983f359bd835c2529ce37b5599d58f",
"shasum": ""
},
"require": {
@ -755,7 +755,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-stable29": "29.0.0-dev"
"dev-stable30": "30.0.0-dev"
}
},
"notification-url": "https://packagist.org/downloads/",
@ -771,9 +771,9 @@
"description": "Composer package containing Nextcloud's public API (classes, interfaces)",
"support": {
"issues": "https://github.com/nextcloud-deps/ocp/issues",
"source": "https://github.com/nextcloud-deps/ocp/tree/v29.0.7"
"source": "https://github.com/nextcloud-deps/ocp/tree/v30.0.0"
},
"time": "2024-09-05T00:40:09+00:00"
"time": "2024-09-13T00:40:45+00:00"
},
{
"name": "nikic/php-parser",
@ -1312,12 +1312,12 @@
"source": {
"type": "git",
"url": "https://github.com/Roave/SecurityAdvisories.git",
"reference": "ed0688c3e18bf76d2a17fb243b99acb52c2e29ef"
"reference": "fb263701a24214c3176ef23bfa98a7cbc59aa659"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/ed0688c3e18bf76d2a17fb243b99acb52c2e29ef",
"reference": "ed0688c3e18bf76d2a17fb243b99acb52c2e29ef",
"url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/fb263701a24214c3176ef23bfa98a7cbc59aa659",
"reference": "fb263701a24214c3176ef23bfa98a7cbc59aa659",
"shasum": ""
},
"conflict": {
@ -1769,7 +1769,7 @@
"phpmyfaq/phpmyfaq": "<3.2.5|==3.2.5",
"phpoffice/common": "<0.2.9",
"phpoffice/phpexcel": "<1.8",
"phpoffice/phpspreadsheet": "<1.29.1|>=2,<2.2.1",
"phpoffice/phpspreadsheet": "<1.29.1|>=2,<2.1.1|>=2.2,<2.2.1",
"phpseclib/phpseclib": "<2.0.47|>=3,<3.0.36",
"phpservermon/phpservermon": "<3.6",
"phpsysinfo/phpsysinfo": "<3.4.3",
@ -2126,7 +2126,7 @@
"type": "tidelift"
}
],
"time": "2024-09-10T18:06:22+00:00"
"time": "2024-09-13T14:04:35+00:00"
},
{
"name": "sebastian/diff",

View File

@ -26,7 +26,7 @@ class PageController extends Controller
* @NoCSRFRequired
*/
public function index(): TemplateResponse {
Util::addScript(Application::APP_ID, 'main');
Util::addScript(Application::APP_ID, Application::APP_ID.'-main');
$csp = new ContentSecurityPolicy();
$csp->addAllowedImageDomain('*');

639
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,9 @@
{
"name": "repod",
"license": "AGPL-3.0-or-later",
"type": "module",
"scripts": {
"build": "vite build --mode production",
"build": "vue-tsc && vite build --mode production",
"dev": "vite build --mode development",
"dev:watch": "vite build --mode development --watch",
"watch": "npm run dev:watch",
@ -11,9 +12,6 @@
"stylelint": "stylelint src/**/*.vue src/**/*.scss src/**/*.css",
"stylelint:fix": "stylelint src/**/*.vue src/**/*.scss src/**/*.css --fix"
},
"browserslist": [
"extends @nextcloud/browserslist-config"
],
"prettier": "@nextcloud/prettier-config",
"dependencies": {
"@nextcloud/axios": "^2.5.0",
@ -26,19 +24,29 @@
"linkify-html": "^4.1.3",
"pinia": "^2.2.2",
"toastify-js": "^1.12.0",
"vite": "^5.4.3",
"vite-plugin-vue-devtools": "^7.4.4",
"vue": "^3.5.4",
"vite": "^5.4.5",
"vite-plugin-vue-devtools": "^7.4.5",
"vue": "^3.5.5",
"vue-material-design-icons": "^5.3.0",
"vue-router": "^4.4.4"
"vue-router": "^4.4.5"
},
"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.0.0",
"@vue/tsconfig": "^0.5.1",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-pinia": "^0.4.1",
"eslint-plugin-prettier": "^5.2.1"
}
"eslint-plugin-prettier": "^5.2.1",
"ts-node": "^10.9.2",
"typescript": "5.5",
"vue-eslint-parser": "^9.4.3",
"vue-tsc": "^2.1.6"
},
"browserslist": [
"extends @nextcloud/browserslist-config"
]
}

View File

@ -7,7 +7,7 @@
</NcContent>
</template>
<script>
<script lang="ts">
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.js'
import { usePlayer } from './store/player.ts'
export default {
name: 'App',

View File

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

View File

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

View File

@ -16,7 +16,7 @@
</NcEmptyContent>
</template>
<script>
<script lang="ts">
import { NcEmptyContent } from '@nextcloud/vue'
export default {

View File

@ -2,7 +2,7 @@
<NcLoadingIcon class="loading" />
</template>
<script>
<script lang="ts">
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,13 +29,15 @@
</div>
</template>
<script>
<script lang="ts">
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.js'
import { humanFileSize } from '../../utils/size.js'
import { filenameFromUrl } from '../../utils/url.ts'
import { humanFileSize } from '../../utils/size.ts'
import { t } from '@nextcloud/l10n'
export default {
name: 'Modal',
@ -48,13 +50,14 @@ export default {
},
props: {
episode: {
type: Object,
type: Object as () => EpisodeInterface,
required: true,
},
},
methods: {
filenameFromUrl,
humanFileSize,
t,
},
}
</script>

View File

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

View File

@ -10,10 +10,11 @@
</NcAppNavigationList>
</template>
<script>
<script lang="ts">
import { NcAppNavigationList, NcAppNavigationNewItem } from '@nextcloud/vue'
import PlusIcon from 'vue-material-design-icons/Plus.vue'
import { toFeedUrl } from '../../utils/url.js'
import { t } from '@nextcloud/l10n'
import { toFeedUrl } from '../../utils/url.ts'
export default {
name: 'AddRss',
@ -23,6 +24,7 @@ export default {
PlusIcon,
},
methods: {
t,
toFeedUrl,
},
}

View File

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

View File

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

View File

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

View File

@ -2,7 +2,11 @@
<NcListItem
:active="isCurrentEpisode(episode)"
class="episode"
:details="!oneLine ? formatLocaleDate(new Date(episode.pubDate?.date)) : ''"
:details="
!oneLine && episode.pubDate
? formatLocaleDate(new Date(episode.pubDate?.date))
: ''
"
:force-display-actions="true"
:name="episode.name"
:one-line="oneLine"
@ -78,7 +82,7 @@
</template>
<template #indicator>
<NcProgressBar
v-if="isListening(episode) && !oneLine"
v-if="episode.action && isListening(episode) && !oneLine"
class="progress"
:value="(episode.action.position * 100) / episode.action.total" />
</template>
@ -88,7 +92,7 @@
</NcListItem>
</template>
<script>
<script lang="ts">
import {
NcActionButton,
NcActionLink,
@ -102,10 +106,11 @@ import {
durationToSeconds,
formatEpisodeTimestamp,
formatLocaleDate,
} from '../../utils/time.js'
import { hasEnded, isListening } from '../../utils/status.js'
} from '../../utils/time.ts'
import { hasEnded, isListening } from '../../utils/status.ts'
import { mapActions, mapState } from 'pinia'
import DownloadIcon from 'vue-material-design-icons/Download.vue'
import type { EpisodeInterface } from '../../utils/types.ts'
import Modal from '../Atoms/Modal.vue'
import OpenInNewIcon from 'vue-material-design-icons/OpenInNew.vue'
import PlayIcon from 'vue-material-design-icons/Play.vue'
@ -113,10 +118,11 @@ import PlaylistPlayIcon from 'vue-material-design-icons/PlaylistPlay.vue'
import PlaylistRemoveIcon from 'vue-material-design-icons/PlaylistRemove.vue'
import StopIcon from 'vue-material-design-icons/Stop.vue'
import axios from '@nextcloud/axios'
import { filenameFromUrl } from '../../utils/url.js'
import { filenameFromUrl } from '../../utils/url.ts'
import { generateUrl } from '@nextcloud/router'
import { showError } from '../../utils/toast.js'
import { usePlayer } from '../../store/player.js'
import { showError } from '../../utils/toast.ts'
import { t } from '@nextcloud/l10n'
import { usePlayer } from '../../store/player.ts'
export default {
name: 'Episode',
@ -138,7 +144,7 @@ export default {
},
props: {
episode: {
type: Object,
type: Object as () => EpisodeInterface,
required: true,
},
oneLine: {
@ -152,21 +158,22 @@ export default {
},
data: () => ({
loading: false,
modalEpisode: null,
modalEpisode: null as EpisodeInterface | null,
}),
computed: {
...mapState(usePlayer, { playerEpisode: 'episode' }),
},
methods: {
...mapActions(usePlayer, ['load']),
filenameFromUrl,
formatLocaleDate,
hasEnded,
isListening,
filenameFromUrl,
isCurrentEpisode(episode) {
t,
isCurrentEpisode(episode: EpisodeInterface) {
return this.playerEpisode?.url === episode.url
},
async markAs(episode, read) {
async markAs(episode: EpisodeInterface, read: boolean) {
try {
this.loading = true
episode.action = {
@ -176,8 +183,8 @@ export default {
action: 'play',
timestamp: formatEpisodeTimestamp(new Date()),
started: episode.action?.started || 0,
position: read ? durationToSeconds(episode.duration) : 0,
total: durationToSeconds(episode.duration),
position: read ? durationToSeconds(episode.duration || '') : 0,
total: durationToSeconds(episode.duration || ''),
}
await axios.post(
generateUrl('/apps/gpoddersync/episode_action/create'),

View File

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

View File

@ -1,15 +1,15 @@
<template>
<NcGuestContent class="guest">
<Loading v-if="!currentFavoriteData" />
<Loading v-if="!feed.data" />
<NcAvatar
v-if="currentFavoriteData"
v-if="feed.data"
class="avatar"
:display-name="currentFavoriteData.author || currentFavoriteData.title"
:display-name="feed.data.author || feed.data.title"
:is-no-user="true"
:size="222"
:url="currentFavoriteData.imageUrl" />
<div class="list">
<h2 class="title">{{ currentFavoriteData.title }}</h2>
:url="feed.data.imageUrl" />
<div v-if="feed.data" class="list">
<h2 class="title">{{ feed.data.title }}</h2>
<Loading v-if="loading" />
<ul v-if="!loading">
<Episode
@ -17,22 +17,22 @@
:key="episode.guid"
:episode="episode"
:one-line="true"
:url="url" />
:url="feed.metrics.url" />
</ul>
</div>
</NcGuestContent>
</template>
<script>
<script lang="ts">
import type { EpisodeInterface, SubscriptionInterface } from '../../utils/types.ts'
import { NcAvatar, NcGuestContent } from '@nextcloud/vue'
import Episode from './Episode.vue'
import Loading from '../Atoms/Loading.vue'
import axios from '@nextcloud/axios'
import { generateUrl } from '@nextcloud/router'
import { hasEnded } from '../../utils/status.js'
import { mapState } from 'pinia'
import { showError } from '../../utils/toast.js'
import { useSubscriptions } from '../../store/subscriptions.js'
import { hasEnded } from '../../utils/status.ts'
import { showError } from '../../utils/toast.ts'
import { t } from '@nextcloud/l10n'
export default {
name: 'Favorite',
@ -43,32 +43,28 @@ export default {
NcGuestContent,
},
props: {
url: {
type: String,
feed: {
type: Object as () => SubscriptionInterface,
required: true,
},
},
data: () => ({
episodes: [],
episodes: [] as EpisodeInterface[],
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(
const episodes = await axios.get<EpisodeInterface[]>(
generateUrl('/apps/repod/episodes/list?url={url}', {
url: this.url,
url: this.feed.metrics.url,
}),
)
this.episodes = [...episodes.data]
.sort(
(a, b) => new Date(b.pubDate?.date) - new Date(a.pubDate?.date),
(a, b) =>
new Date(b.pubDate?.date || '').getTime() -
new Date(a.pubDate?.date || '').getTime(),
)
.filter((episode) => !this.hasEnded(episode))
.slice(0, 4)

View File

@ -13,7 +13,7 @@
</div>
</template>
<script>
<script lang="ts">
import Controls from './Controls.vue'
import Infos from './Infos.vue'
import Loading from '../Atoms/Loading.vue'
@ -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.js'
import { usePlayer } from '../../store/player.ts'
export default {
name: 'Bar',

View File

@ -5,11 +5,11 @@
</div>
</template>
<script>
<script lang="ts">
import { mapActions, mapState } from 'pinia'
import PauseIcon from 'vue-material-design-icons/Pause.vue'
import PlayIcon from 'vue-material-design-icons/Play.vue'
import { usePlayer } from '../../store/player.js'
import { usePlayer } from '../../store/player.ts'
export default {
name: 'Controls',

View File

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

View File

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

View File

@ -1,15 +1,15 @@
<template>
<div class="root">
<div v-if="currentTime && duration" class="root">
<span>{{ formatTimer(new Date(currentTime * 1000)) }}</span>
<span>/</span>
<span>{{ formatTimer(new Date(duration * 1000)) }}</span>
</div>
</template>
<script>
import { formatTimer } from '../../utils/time.js'
<script lang="ts">
import { formatTimer } from '../../utils/time.ts'
import { mapState } from 'pinia'
import { usePlayer } from '../../store/player.js'
import { usePlayer } from '../../store/player.ts'
export default {
name: 'Timer',

View File

@ -26,17 +26,20 @@
step="0.1"
type="range"
:value="volume"
@change="(event) => setVolume(event.target.value)" />
@change="
(event) =>
setVolume(parseInt((event.target as HTMLInputElement).value))
" />
</div>
</template>
<script>
<script lang="ts">
import { mapActions, mapState } from 'pinia'
import VolumeHighIcon from 'vue-material-design-icons/VolumeHigh.vue'
import VolumeLowIcon from 'vue-material-design-icons/VolumeLow.vue'
import VolumeMediumIcon from 'vue-material-design-icons/VolumeMedium.vue'
import VolumeMuteIcon from 'vue-material-design-icons/VolumeMute.vue'
import { usePlayer } from '../../store/player.js'
import { usePlayer } from '../../store/player.ts'
export default {
name: 'Volume',

View File

@ -8,10 +8,11 @@
</NcAppNavigationItem>
</template>
<script>
<script lang="ts">
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',
@ -21,6 +22,7 @@ export default {
},
methods: {
generateUrl,
t,
},
}
</script>

View File

@ -39,12 +39,13 @@
</NcAppNavigationItem>
</template>
<script>
<script lang="ts">
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 { useSettings } from '../../store/settings.js'
import { t } from '@nextcloud/l10n'
import { useSettings } from '../../store/settings.ts'
export default {
name: 'Filters',
@ -66,6 +67,7 @@ export default {
},
methods: {
...mapActions(useSettings, ['setFilters']),
t,
},
}
</script>

View File

@ -29,12 +29,13 @@
</NcAppNavigationItem>
</template>
<script>
<script lang="ts">
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',
@ -50,11 +51,13 @@ export default {
}),
methods: {
generateUrl,
async importOpml(event) {
t,
async importOpml(event: Event) {
try {
const formData = new FormData(event.target)
this.importLoading = true
await axios.post(event.target.action, formData)
const target = event.target as HTMLFormElement
const formData = new FormData(target)
this.loading = true
await axios.post(target.action, formData)
} catch (e) {
console.error(e)
} finally {

View File

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

View File

@ -8,7 +8,7 @@
</NcAppNavigationSettings>
</template>
<script>
<script lang="ts">
import Export from './Export.vue'
import Filters from './Filters.vue'
import Import from './Import.vue'

View File

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

View File

@ -1,18 +1,18 @@
<template>
<NcAppNavigationItem
:loading="loading"
:name="feed ? feed.title : url"
:name="feed?.data?.title || url"
:to="toFeedUrl(url)">
<template #actions>
<NcActionButton
:aria-label="t('repod', 'Favorite')"
:model-value="isFavorite"
:model-value="feed?.isFavorite"
:name="t('repod', 'Favorite')"
:title="t('repod', 'Favorite')"
@update:modelValue="switchFavorite($event)">
<template #icon>
<StarPlusIcon v-if="!isFavorite" :size="20" />
<StarRemoveIcon v-if="isFavorite" :size="20" />
<StarPlusIcon v-if="!feed?.isFavorite" :size="20" />
<StarRemoveIcon v-if="feed?.isFavorite" :size="20" />
</template>
</NcActionButton>
<NcActionButton
@ -27,29 +27,30 @@
</template>
<template #icon>
<NcAvatar
v-if="feed"
:display-name="feed.author || feed.title"
:display-name="feed?.data?.author || feed?.data?.title"
:is-no-user="true"
:url="feed.imageUrl" />
<StarIcon v-if="feed && isFavorite" class="star" :size="20" />
:url="feed?.data?.imageUrl" />
<StarIcon v-if="feed?.isFavorite" class="star" :size="20" />
<AlertIcon v-if="failed" />
</template>
</NcAppNavigationItem>
</template>
<script>
<script lang="ts">
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.js'
import { toFeedUrl } from '../../utils/url.js'
import { useSubscriptions } from '../../store/subscriptions.js'
import { showError } from '../../utils/toast.ts'
import { t } from '@nextcloud/l10n'
import { toFeedUrl } from '../../utils/url.ts'
import { useSubscriptions } from '../../store/subscriptions.ts'
export default {
name: 'Subscription',
@ -72,26 +73,25 @@ export default {
data: () => ({
failed: false,
loading<