Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • petre/nextcloud-app-podcast
  • onny/nextcloud-app-podcast
2 results
Show changes
......@@ -22,26 +22,20 @@
import { ShowApi } from './../services/ShowApi'
const apiClient = new ShowApi()
const showApiClient = new ShowApi()
export default {
state: {
shows: [],
},
getters: {
subscribedShows: state => {
return state.shows
},
showById: state => (id) => {
return state.shows.find((show) => show.id === id)
},
showExists: state => (id) => {
return state.shows.some((show) => show.id === id)
},
},
mutations: {
addShow(state, show) {
state.shows.push(show)
state.shows.unshift(show)
},
removeShow(state, show) {
const existingIndex = state.shows.findIndex(_show => _show.id === show.id)
......@@ -55,17 +49,17 @@ export default {
},
actions: {
async loadShows({ commit }) {
const shows = await apiClient.loadShows()
commit('setShows', shows)
const shows = await showApiClient.queryShows()
commit('setShows', shows.data)
},
addShow({ commit }, show) {
apiClient.addShow(show)
showApiClient.addShow(show)
.then((show) => {
commit('addShow', show)
})
},
removeShow({ commit }, show) {
apiClient.removeShow(show)
showApiClient.removeShow(show)
.then((show) => {
commit('removeShow', show)
})
......
/**
* Helpfer function to update custom browser title.
*
* @param {string} title Title part to use.
*/
export function setBrowserTitle(title) {
document.title = title + ' - Podcast - Nextcloud'
}
......@@ -20,18 +20,19 @@
-
-->
<template>
<div>
<div style="padding-top: 10px;">
<BrowseEmpty v-show="loading" />
<div
v-show="!loading"
class="mainContent">
v-show="!loading">
<Header :title="t('podcast', 'Hot podcasts')">
<a href="#/browse/hot">Show all</a>
</Header>
<ItemSlider
title="Hot podcasts"
showallurl="#/browse/hot"
:podcasts="podcastsHot" />
<Header :title="t('podcast', 'New podcasts')">
<a href="#/browse/new">Show all</a>
</Header>
<ItemSlider
title="New podcasts"
showallurl="#/browse/new"
:podcasts="podcastsLatest" />
</div>
</div>
......@@ -40,15 +41,17 @@
<script>
import BrowseEmpty from './placeholder/Browse'
import ItemSlider from '../components/ItemSlider'
import { FyydApi } from './../services/FyydApi'
const fyydClient = new FyydApi()
import Header from '../components/Header'
import { setBrowserTitle } from '../utils/misc.js'
import { ShowApi } from './../services/ShowApi'
const showApiClient = new ShowApi()
export default {
name: 'Browse',
components: {
BrowseEmpty,
ItemSlider,
Header,
},
data: () => ({
podcastsHot: [],
......@@ -57,14 +60,15 @@ export default {
}),
mounted() {
this.queryPodcastLists()
setBrowserTitle(t('podcast', 'Browse'))
},
methods: {
async queryPodcastLists() {
const hotList = await fyydClient.queryList('hot', 10)
const hotList = await showApiClient.queryCategory('hot', 10)
this.podcastsHot = hotList.data
const latestList = await fyydClient.queryList('latest', 10)
const latestList = await showApiClient.queryCategory('latest', 10)
this.podcastsLatest = latestList.data
this.loading = false
......@@ -73,9 +77,3 @@ export default {
},
}
</script>
<style lang="scss">
.mainContent {
padding: 20px 30px;
}
</style>
......@@ -20,64 +20,73 @@
-
-->
<template>
<div class="mainContent">
<ItemGrid
:title="title"
:podcasts="podcasts" />
<div>
<Header :title="title" />
<LoadMore :page="page"
@load-more="queryPodcasts(page)">
<ItemGrid
:podcasts="podcasts" />
</LoadMore>
</div>
</template>
<script>
import ItemGrid from '../components/ItemGrid'
import Header from '../components/Header'
import LoadMore from '../components/LoadMore'
import { setBrowserTitle } from '../utils/misc.js'
import { ShowApi } from './../services/ShowApi'
import { FyydApi } from './../services/FyydApi'
const fyydClient = new FyydApi()
const showApiClient = new ShowApi()
export default {
name: 'BrowseAll',
components: {
ItemGrid,
Header,
LoadMore,
},
data: () => ({
podcasts: {},
podcasts: [],
category: null,
categoryId: null,
title: '',
page: 0,
}),
mounted() {
this.category = this.$route.params.category
this.categoryId = this.$route.params.categoryId
this.queryPodcasts()
},
methods: {
async queryPodcasts() {
async queryPodcasts(page = 0) {
let podcasts = null
if (this.category === 'hot') {
podcasts = await fyydClient.queryList('hot', 20)
this.title = 'Hot podcasts'
document.title = 'Hot podcasts - Podcast - Nextcloud'
this.podcasts = podcasts.data
podcasts = await showApiClient.queryCategory('hot', 20, page)
this.title = t('podcast', 'Hot podcasts')
this.podcasts = this.podcasts.concat(podcasts.data)
} else if (this.category === 'new') {
podcasts = await fyydClient.queryList('latest', 20)
this.title = 'New podcasts'
document.title = 'New podcasts - Podcast - Nextcloud'
this.podcasts = podcasts.data
podcasts = await showApiClient.queryCategory('latest', 20, page)
this.title = t('podcast', 'New podcasts')
this.podcasts = this.podcasts.concat(podcasts.data)
} else if (this.category === 'subscriptions') {
podcasts = await showApiClient.queryShows(page)
this.title = t('podcast', 'My subscriptions')
this.podcasts = this.podcasts.concat(podcasts.data)
} else {
podcasts = await fyydClient.queryList(this.categoryId, 20)
this.title = 'Podcasts in ' + podcasts.data.category.title
this.podcasts = podcasts.data.podcasts
podcasts = await showApiClient.queryCategory(this.categoryId, 20, page)
this.title = t('podcast', 'Podcasts in') + ' ' + podcasts.data.category.title
this.podcasts = this.podcasts.concat(podcasts.data.podcasts)
}
if (podcasts.meta.paging.next_page === null) {
this.page = null
} else {
this.page += 1
}
setBrowserTitle(this.title)
},
},
}
</script>
<style lang="scss">
.mainContent {
padding: 30px;
}
</style>
......@@ -32,17 +32,17 @@
:isshow="false">
<button
class="podcastButton button primary new-button"
:class="isPlaying(episode.id) ? 'icon-pause-white' : 'icon-play-white'"
:class="episodePlaying(episode.id) ? 'icon-pause-white' : 'icon-play-white'"
@click="doPlay">
{{ playButtonText }}
</button>
<div class="episodeDetails">
<span>
<b>Duration:</b>
<b>{{ t('podcast', 'Duration') }}:</b>
{{ episode.duration_string }}
</span>
<span>
<b>Publication date:</b>
<b>{{ t('podcast', 'Publication date') }}:</b>
<span class="inline" :title="readableDate(episode.pubdate)">
{{ readableTimeAgo(episode.pubdate) }}
</span>
......@@ -50,7 +50,7 @@
</div>
</MediaHeader>
<ContentCollapsable
title="Episode notes">
:title="t('podcast', 'Episode description')">
<!-- eslint-disable -->
<p
v-html="episodeDescriptionParsed"
......@@ -59,17 +59,21 @@
</ContentCollapsable>
<ContentCollapsable
v-show="episode.chapters"
title="Episode chapters">
:title="t('podcast', 'Episode chapters')">
<table class="chapterTable">
<tbody>
<tr
v-for="(chapter, idx) in episode.chapters"
:key="idx"
@click="seekEpisode(chapter.start)">
@click="startSeekEpisode(chapter.start)">
<td class="timeColumn">
{{ readableDuration(chapter.start) }}
</td>
<td class="titleColumn">
<PlayAnimation
v-show="episodeLoaded(episode.id) && chapterPlaying === idx"
:paused="isPaused(episode.id)"
style="margin-right: 8px;" />
{{ chapter.title }}
</td>
</tr>
......@@ -86,12 +90,15 @@
import EpisodeEmpty from './placeholder/Episode'
import ContentCollapsable from '../components/ContentCollapsable'
import MediaHeader from '../components/MediaHeader'
import { mapGetters, mapActions } from 'vuex'
import TimeAgo from 'javascript-time-ago'
import PlayAnimation from '../components/PlayAnimation'
import { setBrowserTitle } from '../utils/misc.js'
import { FyydApi } from './../services/FyydApi'
const fyydClient = new FyydApi()
import { EpisodeApi } from './../services/EpisodeApi'
import { ShowApi } from './../services/ShowApi'
const episodeApiClient = new EpisodeApi()
const showApiClient = new ShowApi()
const timeAgo = new TimeAgo('en-US')
......@@ -101,6 +108,7 @@ export default {
EpisodeEmpty,
ContentCollapsable,
MediaHeader,
PlayAnimation,
},
data: () => ({
episode: {},
......@@ -110,8 +118,14 @@ export default {
podcastName: null,
}),
computed: {
...mapGetters([
'episodePlaying',
'episodeLoaded',
'isPaused',
'getSeek',
]),
playButtonText() {
if (this.isPlaying(this.episode.id)) {
if (this.episodePlaying(this.episode.id)) {
return t('podcast', 'Pause episode')
} else if (this.isPaused(this.episode.id)) {
return t('podcast', 'Resume episode')
......@@ -124,26 +138,35 @@ export default {
return ''
}
const linkRegex = /((?:href|src)=")?(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#/%?=~_|!:,.;]*[-A-Z0-9+&@#/%=~_|])/ig
const timestampRegex = /(0)?(([0-9]?[0-9]):)?([0-5]?[0-9]):([0-5][0-9])/g
const timestampRegex = /(([0-9]?[0-9]):)?([0-5]?[0-9]):([0-5][0-9])/g
const episodeDescription = this.episode.description
let content = episodeDescription.replace(linkRegex, function(match, attr) {
if (typeof attr !== 'undefined') {
return match
}
return '<a target="_blank" href="' + match + '">' + match + '</a>'
})
content = content.replace(timestampRegex, function(match, attr) {
if (typeof attr !== 'undefined') {
return match
}
return '<a href="#' + match + '">' + match + '</a>'
})
return content
},
chapterPlaying() {
const vm = this
const arr = this.episode.chapters
for (const [index, chapter] of arr.entries()) {
if (parseInt(vm.getSeek) >= parseInt(chapter.start_ms / 1000)) {
if (vm.getSeek && arr.length === index + 1) {
return index
}
if (parseInt(vm.getSeek) < parseInt(arr[index + 1].start_ms / 1000)) {
return index
}
}
}
return false
},
},
mounted() {
this.episodeId = this.$route.params.episodeId
......@@ -152,15 +175,26 @@ export default {
this.queryPodcastName(this.podcastId)
const vm = this
this.$refs.episodeContent.addEventListener('click', function(event) {
event.preventDefault()
if (event.target.href) {
const timecode = event.target.href.split('#')[1]
vm.seekEpisode(timecode)
if (event.target.target !== '_blank') {
event.preventDefault()
if (event.target.href) {
const timecode = event.target.href.split('#')[1]
vm.startSeekEpisode(timecode)
if (!vm.episodePlaying(vm.episode.id)) {
vm.playEpisode(vm.episode)
}
}
}
})
},
methods: {
...mapActions([
'pauseEpisode',
'playEpisode',
'seekEpisode',
]),
hmsToSecondsOnly(str) {
const p = str.split(':')
let s = 0
......@@ -174,25 +208,21 @@ export default {
return s
},
seekEpisode(startTime) {
startSeekEpisode(startTime) {
if (!this.episodePlaying(this.episode.id)) {
this.playEpisode(this.episode)
}
const startTimeSec = this.hmsToSecondsOnly(startTime)
this.$store.dispatch('seekEpisode', startTimeSec)
},
isPlaying(episodeId) {
return this.$store.getters.isPlaying(episodeId)
},
isPaused(episodeId) {
return this.$store.getters.isPaused(episodeId)
this.seekEpisode(startTimeSec)
},
doPlay() {
if (this.isPlaying(this.episode.id)) {
this.$store.dispatch('pauseEpisode')
if (this.episodePlaying(this.episode.id)) {
this.pauseEpisode()
} else {
this.$store.dispatch('playEpisode', this.episode)
this.playEpisode(this.episode)
}
this.seekEpisode(this.episode.playtime)
},
readableDuration(timestamp) {
......@@ -212,18 +242,18 @@ export default {
},
async queryEpisode(episodeId) {
const episode = await fyydClient.queryEpisode(episodeId)
const episode = await episodeApiClient.queryEpisode(episodeId)
this.processEpisode(episode)
},
processEpisode(episode) {
this.episode = episode.data
document.title = episode.data.title + ' - Podcast - Nextcloud'
setBrowserTitle(episode.data.title)
this.loading = false
},
async queryPodcastName(podcastId) {
const podcast = await fyydClient.queryPodcast(podcastId)
const podcast = await showApiClient.queryShow(podcastId)
this.podcastName = podcast.data.title
},
......@@ -261,35 +291,35 @@ table.chapterTable {
tbody {
td {
padding: 15px 0px;
padding-left: 30px;
font-style: normal;
border-bottom: 1px solid var(--color-border);
cursor: pointer;
}
tr {
height: 30px;
background-color: var(--color-background-light);
transition: opacity 500ms ease 0s;
}
tr td * {
cursor: pointer;
}
td {
padding: 15px 0px;
padding-left: 30px;
font-style: normal;
border-bottom: 1px solid var(--color-border);
cursor: pointer;
td.timeColumn {
width: 120px;
color: #1976d2;
}
* {
cursor: pointer;
}
td.titleColumn {
padding-left: 0px;
}
&.timeColumn {
width: 120px;
color: #1976d2;
}
&.titleColumn {
padding-left: 0px;
}
}
tr:hover, tr:focus, tr.mouseOver td {
background-color: var(--color-background-hover);
&:hover, &:focus, &.mouseOver td {
background-color: var(--color-background-hover);
}
}
}
......
This diff is collapsed.
<!--
- @copyright Copyright (c) 2021 Jonas Heinrich
-
- @author Jonas Heinrich <onny@project-insanity.org>
-
- @license GNU AGPL version 3 or any later version
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-->
<template>
<div>
<Header :title="t('podcast', 'Currently listening')" />
<LoadMore :page="page"
@load-more="loadEpisodes(page)">
<Table
:episodes="getEpisodes"
:extended="true"
@doPlay="doPlay" />
</LoadMore>
</div>
</template>
<script>
import Table from '../components/Table'
import Header from '../components/Header'
import LoadMore from '../components/LoadMore'
import { mapGetters, mapActions } from 'vuex'
import { setBrowserTitle } from '../utils/misc.js'
export default {
name: 'Listening',
components: {
Table,
LoadMore,
Header,
},
data: () => ({
page: 0,
}),
computed: {
...mapGetters([
'episodePlaying',
'getEpisodes',
]),
},
mounted() {
setBrowserTitle(t('podcast', 'Currently listening'))
this.page = 0
this.clearEpisodes()
},
methods: {
...mapActions([
'pauseEpisode',
'playEpisode',
'queryEpisodes',
'clearEpisodes',
]),
async loadEpisodes(page) {
const response = await this.queryEpisodes(page, 'lastplayed')
if (response) {
this.page += 1
} else {
this.page = null
}
},
doPlay(episode) {
if (this.episodePlaying(episode.id)) {
this.pauseEpisode()
} else {
this.playEpisode(episode)
}
},
},
}
</script>
This diff is collapsed.
......@@ -21,7 +21,7 @@
-->
<template>
<div class="mainContent">
<div>
<div
v-for="mainIndex in 3"
:key="mainIndex"
......
This diff is collapsed.
This diff is collapsed.
{
"compilerOptions": {
"module": "ES6",
"moduleResolution": "node",
"target": "ES6",
"strictNullChecks": true,
"sourceMap": true,
"allowSyntheticDefaultImports": true,
"declaration": true,
},
"include": [
"./src/**/*"
]
}
This diff is collapsed.