From b9c7f0262ca9094012cf60cf5ae7a77e8b75ab52 Mon Sep 17 00:00:00 2001
From: Jonas Heinrich <onny@project-insanity.org>
Date: Sat, 19 Dec 2020 13:56:23 +0100
Subject: [PATCH] modularize show and episode elements

---
 src/components/ContentCollapsable.vue |  84 +++++++++
 src/components/MediaHeader.vue        | 212 +++++++++++++++++++++++
 src/views/Episode.vue                 | 236 ++++++--------------------
 src/views/Show.vue                    | 151 +---------------
 4 files changed, 353 insertions(+), 330 deletions(-)
 create mode 100644 src/components/ContentCollapsable.vue
 create mode 100644 src/components/MediaHeader.vue

diff --git a/src/components/ContentCollapsable.vue b/src/components/ContentCollapsable.vue
new file mode 100644
index 0000000..8c45a45
--- /dev/null
+++ b/src/components/ContentCollapsable.vue
@@ -0,0 +1,84 @@
+<!--
+  - @copyright Copyright (c) 2020 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 class="episodeContent">
+		<div class="episodeContentHeader">
+			<h2>{{ title }}</h2>
+			<div
+				v-show="showContent"
+				class="menuToggle"
+				@click="showContent = !showContent">
+				{{ t('podcast', 'Hide') }}
+			</div>
+			<div
+				v-show="!showContent"
+				class="menuToggle"
+				@click="showContent = !showContent">
+				{{ t('podcast', 'Show') }}
+			</div>
+		</div>
+		<div v-show="showContent">
+			<slot />
+		</div>
+	</div>
+</template>
+
+<script>
+export default {
+	name: 'ContentCollapsable',
+	props: {
+		title: {
+			type: String,
+			default: '',
+		},
+	},
+	data: () => ({
+		showContent: true,
+	}),
+}
+</script>
+
+<style lang="scss">
+.episodeContent {
+
+	.episodeContentHeader {
+		border-bottom: 1px solid #ddd;
+		border-top: 1px solid #ddd;
+		padding: 10px 30px;
+		z-index: 60;
+		position: sticky;
+		top: 50px;
+		background: white;
+		display: flex;
+		align-items: center;
+		margin-bottom: -1px;
+	}
+	h2 {
+		flex-grow: 1;
+		margin-bottom: 0px;
+	}
+	.menuToggle {
+		color: #1976d2;
+		cursor: pointer;
+	}
+}
+</style>
diff --git a/src/components/MediaHeader.vue b/src/components/MediaHeader.vue
new file mode 100644
index 0000000..cd2795a
--- /dev/null
+++ b/src/components/MediaHeader.vue
@@ -0,0 +1,212 @@
+<!--
+  - @copyright Copyright (c) 2020 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
+		class="podcastHeaderBg"
+		:style="{ backgroundImage: `url(${imgurl})` }">
+		<div class="podcastHeader">
+			<div
+				class="podcastImage"
+				:style="{ backgroundImage: `url(${imgurl})` }" />
+			<div class="podcastDescription">
+				<h1>{{ title }}</h1>
+				<div class="podcastAuthor">
+					by <a :href="htmlURL" target="_blank">{{ author }}</a>
+				</div>
+				<div class="podcastControls">
+					<button class="icon-add-white podcastButton button primary new-button">
+						{{ t('podcast', 'Subscribe') }}
+					</button>
+					<ul class="podcastCategory">
+						<a v-for="(category, idx) in categories"
+							:key="idx"
+							:href="`#/browse/category/${categories[idx]}`">
+							<li>
+								{{ getCategoryName(categories[idx]) }}
+							</li>
+						</a>
+					</ul>
+				</div>
+				<vue-show-more-text
+					:text="description"
+					additional-container-css="padding: 0px;" />
+				<slot />
+			</div>
+		</div>
+	</div>
+</template>
+
+<script>
+import vueShowMoreText from 'vue-show-more-text'
+import { getCategoryName } from '../services/podcastApi'
+
+export default {
+	name: 'MediaHeader',
+	components: {
+		vueShowMoreText,
+	},
+	props: {
+		imgurl: {
+			type: String,
+			default: '',
+		},
+		title: {
+			type: String,
+			default: '',
+		},
+		author: {
+			type: String,
+			default: '',
+		},
+		htmlURL: {
+			type: String,
+			default: '',
+		},
+		categories: {
+			type: Array,
+			default() {
+				return []
+			},
+		},
+		description: {
+			type: String,
+			default: '',
+		},
+	},
+	methods: {
+		getCategoryName(categoryid) {
+			return getCategoryName(categoryid)
+		},
+	},
+}
+</script>
+
+<style lang="scss">
+
+.podcastHeaderBg {
+	background-size: cover;
+	background-position: center center;
+	background-attachment: fixed;
+}
+
+.podcastHeader {
+	width: 100%;
+	min-height: 300px;
+	display: flex;
+	color: white;
+	justify-content: center;
+	padding: 40px 20px;
+	/* gap: 30px; */
+	background-color: rgba(0, 0, 0, .7);
+	backdrop-filter: blur(8px);
+	text-shadow: 1px 1px 2px rgba(0,0,0,.5);
+
+	.button {
+		text-shadow: 0px 0px 0px;
+	}
+
+	.podcastImage {
+		width: 200px;
+		height: 200px;
+		flex-shrink: 0;
+		background-size: cover;
+		background-position: center;
+		box-shadow: 0 4px 60px rgba(0,0,0,.5);
+		margin-right: 25px;
+		border-radius: 5px;
+	}
+
+	.podcastDescription {
+		max-width: 500px;
+		width: 100%;
+		color: #ddd;
+
+		h1 {
+			font-size: 30px;
+			font-weight: bold;
+			line-height: 1.2em;
+			color: white;
+		}
+
+		.podcastAuthor {
+			padding-bottom: 5px;
+
+			a {
+				color: white;
+			}
+		}
+
+		.podcastControls {
+			display: flex;
+			align-items: center;
+			margin-bottom: 5px;
+
+			.button {
+				margin-right: 10px;
+			}
+		}
+
+		ul.podcastCategory li {
+			display: inline;
+			border: 1px solid var(--color-text-maxcontrast);
+			border-radius: var(--border-radius);
+			margin-right: 5px;
+			cursor: pointer;
+			color: var(--color-text-maxcontrast);
+			padding: 3px 6px;
+		}
+
+		ul.podcastCategory li:hover {
+			border: 1px solid white;
+			color: white;
+		}
+	}
+
+}
+
+.podcastButton {
+	padding-left: 35px;
+	padding-right: 15px;
+	background-position: 10px center;
+}
+
+@media only screen and (max-width: 500px) {
+	.podcastHeader {
+		flex-direction: column;
+		align-items: center;
+
+		.podcastImage {
+			margin-right: 0px;
+			margin-bottom: 25px;
+		}
+
+		.podcastDescription {
+			text-align: center;
+
+			.podcastControls {
+				justify-content: center;
+			}
+		}
+	}
+}
+
+</style>
diff --git a/src/views/Episode.vue b/src/views/Episode.vue
index 7f490da..e9abbc3 100644
--- a/src/views/Episode.vue
+++ b/src/views/Episode.vue
@@ -24,69 +24,49 @@
 		<ShowEmpty v-show="pageLoading" />
 		<transition name="fade">
 			<div v-show="!pageLoading">
-				<div
-					class="podcastHeaderBg"
-					:style="{ backgroundImage: `url(${episode.imgURL})` }">
-					<div class="podcastHeader">
-						<div
-							class="podcastImage"
-							:style="{ backgroundImage: `url(${episode.imgURL})` }" />
-						<div class="podcastDescription">
-							<h1>{{ episode.title }}</h1>
-							<div class="podcastAuthor">
-								{{ t('podcast', 'by') }} <a :href="episode.htmlURL" target="_blank">{{ episode.author }}</a>
-							</div>
-							<button class="icon-play-white podcastButton button primary new-button">
-								{{ t('podcast', 'Play episode') }}
-							</button>
-							<br><br>
-							<span>
-								<b>Duration:</b>
-								{{ episode.duration_string }}
-							</span>
-							<br>
-							<span>
-								<b>Publication date:</b>
-								{{ readableDate(episode.pubdate) }}
-							</span>
-						</div>
-					</div>
-				</div>
-				<div class="episodeContent">
-					<h2>{{ t('podcast', 'Episode notes') }}</h2>
-					<div
-						v-show="showNotes"
-						class="menuToggle"
-						@click="showNotes = !showNotes">
-						{{ t('podcast', 'Hide notes') }}
-					</div>
-					<div
-						v-show="!showNotes"
-						class="menuToggle"
-						@click="showNotes = !showNotes">
-						{{ t('podcast', 'Show notes') }}
-					</div>
+				<MediaHeader
+					:imgurl="episode.imgURL"
+					:title="episode.title"
+					:author="episode.author"
+					:html-url="episode.htmlURL">
+					<button class="icon-play-white podcastButton button primary new-button">
+						{{ t('podcast', 'Play episode') }}
+					</button>
+					<br><br>
+					<span>
+						<b>Duration:</b>
+						{{ episode.duration_string }}
+					</span>
+					<br>
+					<span>
+						<b>Publication date:</b>
+						{{ readableDate(episode.pubdate) }}
+					</span>
+				</MediaHeader>
+				<ContentCollapsable
+					title="Episode notes">
 					<!-- eslint-disable -->
-					<p
-						v-show="showNotes"
-						v-html="episode.description" />
-					<!-- eslint-enable -->
-				</div>
-				<div v-show="episode.chapters" class="episodeContent">
-					<h2>{{ t('podcast', 'Episode chapters') }}</h2>
+			    <p v-html="episode.description" />
+			    <!-- eslint-enable -->
+				</ContentCollapsable>
+				<ContentCollapsable
+					v-show="episode.chapters"
+					title="Episode chapters">
 					<table class="chapterTable">
 						<tbody>
 							<tr
 								v-for="(chapter, idx) in episode.chapters"
 								:key="idx">
 								<td class="timeColumn">
-									{{ chapter.start }}
+									{{ readableTime(chapter.start) }}
+								</td>
+								<td class="titleColumn">
+									{{ chapter.title }}
 								</td>
-								<td>{{ chapter.title }}</td>
 							</tr>
 						</tbody>
 					</table>
-				</div>
+				</ContentCollapsable>
 			</div>
 		</transition>
 	</div>
@@ -97,6 +77,8 @@
 import { showError } from '@nextcloud/dialogs'
 import axios from '@nextcloud/axios'
 import ShowEmpty from './ShowEmpty'
+import ContentCollapsable from '../components/ContentCollapsable'
+import MediaHeader from '../components/MediaHeader'
 
 import TimeAgo from 'javascript-time-ago'
 
@@ -106,11 +88,12 @@ export default {
 	name: 'Episode',
 	components: {
 		ShowEmpty,
+		ContentCollapsable,
+		MediaHeader,
 	},
 	data: () => ({
 		episode: {},
 		pageLoading: false,
-		showNotes: true,
 	}),
 	mounted() {
 		this.pageLoading = true
@@ -119,6 +102,9 @@ export default {
 	},
 	methods: {
 
+		readableTime(timestamp) {
+			return timestamp.split('.')[0]
+		},
 		readableDate(datetime) {
 			return timeAgo.format(Date.parse(datetime), 'twitter-minute-now')
 		},
@@ -154,137 +140,11 @@ export default {
 
 <style lang="scss">
 
-.podcastHeaderBg {
-	background-size: cover;
-	background-position: center center;
-	background-attachment: fixed;
-}
-
-.podcastHeader {
-	width: 100%;
-	min-height: 300px;
-	display: flex;
-	color: white;
-	justify-content: center;
-	padding: 40px 20px;
-	/* gap: 30px; */
-	background-color: rgba(0, 0, 0, .7);
-	backdrop-filter: blur(8px);
-	text-shadow: 1px 1px 2px rgba(0,0,0,.5);
-
-	.button {
-		text-shadow: 0px 0px 0px;
-	}
-
-	.podcastImage {
-		width: 200px;
-		height: 200px;
-		flex-shrink: 0;
-		background-size: cover;
-		background-position: center;
-		box-shadow: 0 4px 60px rgba(0,0,0,.5);
-		margin-right: 25px;
-		border-radius: 5px;
-	}
-
-	.podcastDescription {
-		max-width: 500px;
-		width: 100%;
-		color: #ddd;
-
-		h1 {
-			font-size: 30px;
-			font-weight: bold;
-			line-height: 1.2em;
-			color: white;
-		}
-
-		.podcastAuthor {
-			padding-bottom: 5px;
-
-			a {
-				color: white;
-			}
-		}
-
-		.podcastControls {
-			display: flex;
-			align-items: center;
-			margin-bottom: 5px;
-
-			.button {
-				margin-right: 10px;
-			}
-		}
-
-		ul.podcastCategory li {
-			display: inline;
-			border: 1px solid var(--color-text-maxcontrast);
-			border-radius: var(--border-radius);
-			margin-right: 5px;
-			cursor: pointer;
-			color: var(--color-text-maxcontrast);
-			padding: 3px 6px;
-		}
-
-		ul.podcastCategory li:hover {
-			border: 1px solid white;
-			color: white;
-		}
-	}
-
-}
-
-.empty-content {
-	margin-top: 10px !important;
-
-	.empty-content__icon {
-		margin-bottom: 0px !important;
-	}
-}
-
-@media only screen and (max-width: 500px) {
-	.podcastHeader {
-		flex-direction: column;
-		align-items: center;
-
-		.podcastImage {
-			margin-right: 0px;
-			margin-bottom: 25px;
-		}
-
-		.podcastDescription {
-			text-align: center;
-
-			.podcastControls {
-				justify-content: center;
-			}
-		}
-	}
-}
-
 .episodeContent {
-	h2 {
-		border-bottom: 1px solid #ddd;
-		border-top: 1px solid #ddd;
-		padding: 10px 30px;
-		z-index: 60;
-		position: sticky;
-		top: 50px;
-		background: white;
-	}
 	p {
 		padding: 10px 30px;
-		max-width: 700px;
-	}
-	.menuToggle {
-		float: right;
-		color: #1976d2;
-		cursor: pointer;
-		position: relative;
-		right: 20px;
-		top: -49px;
-		z-index: 60;
+		max-width: 550px;
+		margin-top: 1px;
 	}
 }
 
@@ -293,11 +153,13 @@ table.chapterTable {
 	min-width: 250px;
 	table-layout:fixed;
 	position: relative;
+	margin-top: 1px;
 
 	tbody {
 
 		td {
-			padding: 10px 30px;
+			padding: 15px 0px;
+			padding-left: 30px;
 			font-style: normal;
 			border-bottom: 1px solid var(--color-border);
 			cursor: pointer;
@@ -323,13 +185,11 @@ table.chapterTable {
 			color: #1976d2;
 		}
 
-	}
-}
+		td.titleColumn {
+			padding-left: 0px;
+		}
 
-.podcastButton {
-	padding-left: 35px;
-	padding-right: 15px;
-	background-position: 10px center;
+	}
 }
 
 </style>
diff --git a/src/views/Show.vue b/src/views/Show.vue
index 63a6540..3e365a7 100644
--- a/src/views/Show.vue
+++ b/src/views/Show.vue
@@ -24,38 +24,13 @@
 		<ShowEmpty v-show="pageLoading" />
 		<transition name="fade">
 			<div v-show="!pageLoading">
-				<div
-					class="podcastHeaderBg"
-					:style="{ backgroundImage: `url(${podcast.smallImageURL})` }">
-					<div class="podcastHeader">
-						<div
-							class="podcastImage"
-							:style="{ backgroundImage: `url(${podcast.smallImageURL})` }" />
-						<div class="podcastDescription">
-							<h1>{{ podcast.title }}</h1>
-							<div class="podcastAuthor">
-								by <a :href="podcast.htmlURL" target="_blank">{{ podcast.author }}</a>
-							</div>
-							<div class="podcastControls">
-								<button class="icon-add-white podcastButton button primary new-button">
-									{{ t('podcast', 'Subscribe') }}
-								</button>
-								<ul class="podcastCategory">
-									<a v-for="(category, idx) in podcast.categories"
-										:key="idx"
-										:href="`#/browse/category/${podcast.categories[idx]}`">
-										<li>
-											{{ getCategoryName(podcast.categories[idx]) }}
-										</li>
-									</a>
-								</ul>
-							</div>
-							<vue-show-more-text
-								:text="podcast.description"
-								additional-container-css="padding: 0px;" />
-						</div>
-					</div>
-				</div>
+				<MediaHeader
+					:imgurl="podcast.smallImageURL"
+					:title="podcast.title"
+					:author="podcast.author"
+					:html-url="podcast.htmlURL"
+					:categories="podcast.categories"
+					:description="podcast.description" />
 				<Table
 					v-resize="onResize"
 					:episodes="podcast.episodes"
@@ -70,15 +45,13 @@
 
 <script>
 import Table from '../components/Table'
+import MediaHeader from '../components/MediaHeader'
 
 import { showError } from '@nextcloud/dialogs'
 import EmptyContent from '@nextcloud/vue/dist/Components/EmptyContent'
 import axios from '@nextcloud/axios'
 
-import vueShowMoreText from 'vue-show-more-text'
-
 import { audioPlayer, doPlay } from '../services/player'
-import { getCategoryName } from '../services/podcastApi'
 
 import ShowEmpty from './ShowEmpty'
 
@@ -87,7 +60,7 @@ export default {
 	components: {
 		EmptyContent,
 		Table,
-		vueShowMoreText,
+		MediaHeader,
 		ShowEmpty,
 	},
 	data: () => ({
@@ -125,10 +98,6 @@ export default {
 	},
 	methods: {
 
-		getCategoryName(categoryid) {
-			return getCategoryName(categoryid)
-		},
-
 		onResize({ width, height }) {
 			const contentHeight = document.getElementById('app-content-vue').scrollHeight
 			const tableHeight = height
@@ -144,7 +113,6 @@ export default {
 
 		preFill() {
 			const route = this.$route
-			console.log('lol')
 			console.log(route)
 			// this.queryPodcast(route.name)
 		},
@@ -205,87 +173,6 @@ export default {
 
 <style lang="scss">
 
-.podcastHeaderBg {
-	background-size: cover;
-	background-position: center center;
-	background-attachment: fixed;
-}
-
-.podcastHeader {
-	width: 100%;
-	min-height: 300px;
-	display: flex;
-	color: white;
-	justify-content: center;
-	padding: 40px 20px;
-	/* gap: 30px; */
-	background-color: rgba(0, 0, 0, .7);
-	backdrop-filter: blur(8px);
-	text-shadow: 1px 1px 2px rgba(0,0,0,.5);
-
-	.button {
-		text-shadow: 0px 0px 0px;
-	}
-
-	.podcastImage {
-		width: 200px;
-		height: 200px;
-		flex-shrink: 0;
-		background-size: cover;
-		background-position: center;
-		box-shadow: 0 4px 60px rgba(0,0,0,.5);
-		margin-right: 25px;
-		border-radius: 5px;
-	}
-
-	.podcastDescription {
-		max-width: 500px;
-		width: 100%;
-		color: #ddd;
-
-		h1 {
-			font-size: 30px;
-			font-weight: bold;
-			line-height: 1.2em;
-			color: white;
-		}
-
-		.podcastAuthor {
-			padding-bottom: 5px;
-
-			a {
-				color: white;
-			}
-		}
-
-		.podcastControls {
-			display: flex;
-			align-items: center;
-			margin-bottom: 5px;
-
-			.button {
-				margin-right: 10px;
-			}
-		}
-
-		ul.podcastCategory li {
-			display: inline;
-			border: 1px solid var(--color-text-maxcontrast);
-			border-radius: var(--border-radius);
-			margin-right: 5px;
-			cursor: pointer;
-			color: var(--color-text-maxcontrast);
-			padding: 3px 6px;
-		}
-
-		ul.podcastCategory li:hover {
-			border: 1px solid white;
-			color: white;
-		}
-	}
-
-}
-
 .empty-content {
 	margin-top: 10px !important;
 
@@ -294,24 +181,4 @@ export default {
 	}
 }
 
-@media only screen and (max-width: 500px) {
-	.podcastHeader {
-		flex-direction: column;
-		align-items: center;
-
-		.podcastImage {
-			margin-right: 0px;
-			margin-bottom: 25px;
-		}
-
-		.podcastDescription {
-			text-align: center;
-
-			.podcastControls {
-				justify-content: center;
-			}
-		}
-	}
-}
-
 </style>
-- 
GitLab