feat: video card

This commit is contained in:
Ricardo
2021-01-15 11:58:58 +00:00
committed by João Peixoto
parent 0221872e59
commit 17b7c193aa
13 changed files with 435 additions and 14 deletions

13
package-lock.json generated
View File

@@ -15185,6 +15185,11 @@
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=",
"dev": true
},
"resize-observer-polyfill": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
"integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg=="
},
"resolve": {
"version": "1.17.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz",
@@ -18362,6 +18367,14 @@
"integrity": "sha512-JNgiEJ5a8YPfk5y2lKyfOAGLmkpAVfhaUi+T4wGpSppRYZ3XSyawSDDketY5KV2CsAiBLAGEIO6jO+0l2hQubg==",
"dev": true
},
"vue-js-modal": {
"version": "2.0.0-rc.6",
"resolved": "https://registry.npmjs.org/vue-js-modal/-/vue-js-modal-2.0.0-rc.6.tgz",
"integrity": "sha512-bJOm7Yhrl0ur/QyXjoC3gMMmE7UxiVEcS2rl8v9iPXIe9QLvjiCSZElSOvvyps8LNuG1X0rPifZGxI/CWKCFaw==",
"requires": {
"resize-observer-polyfill": "^1.5.1"
}
},
"vue-loader": {
"version": "15.9.3",
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.9.3.tgz",

View File

@@ -10,7 +10,9 @@
@leave="leave"
@after-leave="afterLeave"
>
<slot></slot>
<main :key="withKey">
<slot></slot>
</main>
</transition>
</template>
@@ -34,6 +36,10 @@ export default {
type: Boolean,
default: false,
},
withKey: {
type: String,
default: null,
},
beforeEnter: Func,
enter: Func,
afterEnter: Func,

View File

@@ -1,17 +1,23 @@
<template>
<div>
<RegularCard v-if="!card.type" v-bind="card" class="card-post h-full" />
<component :is="computedCard" v-bind="card" class="card-post h-full" />
<component
:is="computedCard"
v-bind="card"
:open-video-modal="openVideoModal"
class="card-post h-full"
/>
</div>
</template>
<script>
import RegularCard from '@theme/components/blog/RegularCard'
import LinkCard from '@theme/components/blog/LinkCard'
import VideoCard from '@theme/components/blog/VideoCard'
export default {
name: 'BlogCard',
components: { RegularCard, LinkCard },
components: { RegularCard, LinkCard, VideoCard },
inheritAttrs: false,
props: {
@@ -19,6 +25,10 @@ export default {
type: Object,
required: true,
},
openVideoModal: {
type: Function,
default: () => {},
},
},
computed: {
computedCard() {
@@ -32,9 +42,11 @@ export default {
case 'News coverage':
case 'Release notes':
case 'Tutorial':
case 'Video':
return LinkCard
case 'Video':
return VideoCard
default:
return null
}

View File

@@ -4,11 +4,13 @@
v-for="social in socialLinks"
:key="social.text"
:network="social.network"
:url="url"
title=""
:url="url ? url : currentUrl"
:title="title ? title : ''"
description=""
quote=""
:quote="`${title} ${url ? url : currentUrl} via ${host}`"
hashtags=""
:twitter-user="social.twitterHandle"
class="mr-1 last:mr-0"
>
<SVGIcon
class="w-6 h-6 opacity-50 fill-current text-blueGreen hover:opacity-100 transition transition-opacity duration-300 ease-in-out"
@@ -24,12 +26,23 @@ import SVGIcon from '@theme/components/base/SVGIcon'
export default {
name: 'PostSocials',
components: { SVGIcon },
props: {
url: {
type: String,
default: '',
},
title: {
type: String,
default: '',
},
},
data: () => ({
socialLinks: [
{
text: 'Twitter',
network: 'twitter',
icon: 'twitter-icon',
twitterHandle: 'IPFS',
},
{
text: 'Facebook',
@@ -37,10 +50,12 @@ export default {
icon: 'facebook-icon',
},
],
url: '',
currentUrl: '',
host: '',
}),
mounted() {
this.url = window.location.href
this.currentUrl = window.location.href
this.host = window.location.host
},
}
</script>

View File

@@ -0,0 +1,145 @@
<template>
<div
class="group bg-gray-pale rounded flex flex-col transform hover:scale-105 duration-300 ease-in-out"
itemprop="mainEntityOfPage"
>
<article
itemprop="blogPost"
itemscope
itemtype="https://schema.org/BlogPosting"
>
<div class="cover embed-responsive overflow-visible embed-responsive-og">
<a
target="_blank"
:href="path"
class="embed-responsive-item p-2"
@click="handleVideoClick"
>
<div class="h-full w-full relative">
<LazyImage
class="h-full"
img-class="w-full h-full object-cover"
itemprop="image"
:alt="title"
:src="thumbnailPath"
/>
<div
class="absolute top-0 flex justify-center items-center w-full h-full bg-black bg-opacity-25"
>
<SVGIcon
name="play"
title="Play"
:class-list="['w-16', 'h-16', 'fill-current']"
/>
</div>
</div>
</a>
</div>
<div class="pt-1 pb-4 px-4 flex flex-grow flex-col">
<a
:href="path"
target="_blank"
class="text-left"
@click="handleVideoClick"
>
<h1 class="type-h5 font-bold text-primary hover:underline clamp-3">
{{ title }}
</h1>
</a>
<div>
<PostMeta
:category="frontmatter.type"
:author="frontmatter.author"
:date="frontmatter.date"
:tags="frontmatter.tags"
class="type-p4 text-primary"
/>
</div>
<footer class="flex-grow">
<p
v-if="frontmatter.description || frontmatter.description"
class="type-p1-serif text-primary clamp-5"
itemprop="description"
>
{{ frontmatter.description || frontmatter.description }}
</p>
</footer>
</div>
</article>
</div>
</template>
<script>
import SVGIcon from '@theme/components/base/SVGIcon.vue'
import PostMeta from '@theme/components/blog/PostMeta'
import LazyImage from '@theme/components/base/LazyImage'
export default {
name: 'VideoCard',
components: { SVGIcon, PostMeta, LazyImage },
inheritAttrs: false,
props: {
title: {
type: String,
required: true,
},
frontmatter: {
type: Object,
default: () => ({}),
validator: function (frontmatter) {
if (frontmatter.description && frontmatter.description.length > 200) {
return false
}
return true
},
},
path: {
type: String,
required: true,
},
openVideoModal: {
type: Function,
default: () => {},
},
},
computed: {
thumbnailPath() {
if (!this.path.includes('youtube')) {
return ''
}
const newPath = new URL(this.path)
const id =
newPath.searchParams.get('v') || newPath.searchParams.get('list')
return `http://img.youtube.com/vi/${id}/0.jpg`
},
},
methods: {
handleVideoClick(event) {
/* Only uses the anchor default behavior when it's a
new tab click - ctrl/cmd + click or "open in a
new tab" option */
if (
event.ctrlKey ||
event.shiftKey ||
event.metaKey ||
(event.button && event.button === 1)
) {
return
}
event.preventDefault()
this.$store.commit('appState/setVideoModalCard', {
frontmatter: this.frontmatter,
path: this.path,
title: this.title,
})
this.openVideoModal()
},
},
}
</script>

View File

@@ -0,0 +1,63 @@
<template>
<transition name="fade">
<div v-if="show" class="fixed top-0 right-0 bottom-0 left-0 z-50">
<div
class="fixed top-0 right-0 bottom-0 left-0 bg-black bg-opacity-25"
@click="closeModal()"
></div>
<div
role="dialog"
aria-modal="true"
class="modal-content absolute bg-white rounded w-11/12 md:w-4/5 max-w-screen-lg max-h-screen overflow-y-auto p-4 lg:p-8"
>
<VideoModalContent v-if="videoModalCard" :close-modal="closeModal" />
<button
type="button"
class="absolute top-0 right-0 mt-4 mr-4 text-blueGreen hover:underline font-bold text-xl"
@click="closeModal()"
>
X
</button>
</div>
</div>
</transition>
</template>
<script>
import { mapState } from 'vuex'
import VideoModalContent from '@theme/components/blog/VideoModalContent'
export default {
name: 'VideoModal',
components: {
VideoModalContent,
},
props: {},
data() {
return {
show: false,
}
},
computed: { ...mapState('appState', ['videoModalCard']) },
watch: {},
methods: {
closeModal() {
this.show = false
document.querySelector('body').classList.remove('overflow-hidden')
},
openModal() {
this.show = true
document.querySelector('body').classList.add('overflow-hidden')
},
},
}
</script>
<style lang="stylus" scoped>
.modal-content {
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
</style>

View File

@@ -0,0 +1,155 @@
<template>
<div class="group rounded flex flex-col" itemprop="mainEntityOfPage">
<article
itemprop="blogPost"
itemscope
itemtype="https://schema.org/BlogPosting"
>
<h1 class="type-h5 font-bold text-primary mr-4">
<UnstyledLink
:to="videoModalCard.path"
:item="{ target: '_blank' }"
class="clamp-3 hover:underline"
>
{{ videoModalCard.title }}
</UnstyledLink>
</h1>
<div class="cover embed-responsive embed-responsive-og my-4">
<iframe
class="h-full w-full"
type="text/html"
:src="resolvedPath"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen
frameborder="0"
></iframe>
</div>
<div>
<time
class="italic opacity-50"
pubdate
itemprop="datePublished"
:datetime="videoModalCard.frontmatter.date"
>
{{ resolvedDate }}
</time>
<div class="mt-3 flex flex-wrap" itemprop="keywords">
<button
v-if="videoModalCard.frontmatter.type"
class="px-2 py-1 bg-gray-pale text-blueGreen hover:underline rounded-lg text-sm mr-1"
@click="handleCatClick()"
>
{{ videoModalCard.frontmatter.type }}
</button>
<button
v-for="tag in resolvedTags"
:key="tag"
:tag="tag"
class="px-2 py-1 bg-gray-pale text-blueGreen hover:underline rounded-lg text-sm mr-1 last:mr-0"
@click="handleTagClick(tag)"
>
#{{ tag }}
</button>
</div>
</div>
<footer class="flex-grow">
<p
v-if="
videoModalCard.frontmatter.description ||
videoModalCard.frontmatter.description
"
class="type-p1-serif text-primary clamp-5"
itemprop="description"
>
{{
videoModalCard.frontmatter.description ||
videoModalCard.frontmatter.description
}}
</p>
</footer>
<div class="flex items-end mt-4">
<span class="text-sm opacity-50">Share this item:</span>
<PostSocials
class="flex ml-2"
:url="videoModalCard.path"
:title="videoModalCard.frontmatter.title"
/>
</div>
</article>
</div>
</template>
<script>
import { mapState } from 'vuex'
import dayjs from 'dayjs'
import UnstyledLink from '@theme/components/UnstyledLink'
import PostSocials from '@theme/components/blog/PostSocials.vue'
export default {
name: 'VideoModalContent',
components: { UnstyledLink, PostSocials },
props: {
closeModal: {
type: Function,
default: () => {},
},
},
computed: {
...mapState('appState', ['videoModalCard']),
resolvedPath() {
if (
!this.videoModalCard.path.includes('youtube') ||
this.videoModalCard.path.includes('embed')
) {
return this.videoModalCard.path
}
const newPath = new URL(this.videoModalCard.path)
const originalStartTime = newPath.searchParams.get('t')
const start =
originalStartTime &&
newPath.searchParams
.get('t')
.slice(0, newPath.searchParams.get('t').length - 1)
const id =
newPath.searchParams.get('v') || newPath.searchParams.get('list')
const isAList = newPath.pathname.includes('list')
return isAList
? `https://www.youtube.com/embed/videoseries?list=${id}`
: `https://www.youtube.com/embed/${id}?${start ? `start=${start}` : ''}`
},
resolvedDate() {
return dayjs(this.videoModalCard.date).format(
this.$themeLocaleConfig.dateFormat || 'YYYY-MM-DD'
)
},
resolvedTags() {
if (
!this.videoModalCard.frontmatter.tags ||
Array.isArray(this.videoModalCard.frontmatter.tags)
)
return this.videoModalCard.frontmatter.tags
return this.videoModalCard.frontmatter.tags
.replace(/, /g, ',')
.split(',')
.filter((tag) => tag)
},
},
methods: {
handleCatClick() {
this.$store.commit(
'appState/setActiveCategory',
this.videoModalCard.frontmatter.type
)
this.closeModal()
},
handleTagClick(tag) {
this.$store.commit('appState/setActiveTags', [tag])
this.closeModal()
},
},
}
</script>

View File

@@ -31,6 +31,7 @@
:key="page.key"
class="mb-4"
:card="page"
:open-video-modal="openVideoModal"
all
/>
</div>
@@ -55,6 +56,7 @@
v-observe-visibility="handleBottomVisibilityChange"
></div>
</div>
<VideoModal v-if="mountFinish" ref="videoModal" />
</Layout>
</template>
@@ -64,6 +66,7 @@ import { mapState } from 'vuex'
import Layout from '@theme/layouts/Layout.vue'
import Card from '@theme/components/blog/Card'
import VideoModal from '@theme/components/blog/VideoModal'
import SortAndFilter from '@theme/components/blog/SortAndFilter'
import Breadcrumbs from '@theme/components/Breadcrumbs'
import LanguageSelector from '@theme/components/base/LanguageSelector'
@@ -88,6 +91,7 @@ export default {
Breadcrumbs,
SortAndFilter,
LanguageSelector,
VideoModal,
},
data: function () {
return {
@@ -108,6 +112,7 @@ export default {
'activeTags',
'searchedText',
'activeAuthor',
'videoModalCard',
]),
tags() {
return getTags(this.publicPages)
@@ -236,6 +241,9 @@ export default {
this.current < this.delayValues.length - 1 ? this.current : -1
return this.delayValues[++this.current]
},
openVideoModal: function () {
this.$refs.videoModal.openModal()
},
},
}
</script>

View File

@@ -2,7 +2,7 @@
<div class="flex flex-col min-h-screen">
<Nav v-if="shouldDisplay('nav')" ref="nav" />
<MobileNav v-if="shouldDisplay('nav')" />
<Transition appear :after-leave="leaveScroll">
<Transition :with-key="$page.key" appear :after-leave="leaveScroll">
<component :is="layout" />
</Transition>
<Footer v-if="shouldDisplay('footer')" />

View File

@@ -1,5 +1,5 @@
<template>
<main>
<div>
<slot name="header"></slot>
<DynamicContent
v-if="$page.frontmatter.body"
@@ -7,7 +7,7 @@
/>
<slot></slot>
<slot name="footer"></slot>
</main>
</div>
</template>
<script>

View File

@@ -11,6 +11,7 @@ const appState = {
activeCategory: null,
activeAuthor: null,
latestWeeklyPost: null,
videoModalCard: null,
},
mutations: {
toggleMobileNav: (state, data) => {
@@ -43,6 +44,9 @@ const appState = {
Vue.set(state, 'activeCategory', null)
Vue.set(state, 'activeAuthor', null)
},
setVideoModalCard: (state, card) => {
Vue.set(state, 'videoModalCard', card)
},
},
}

View File

@@ -1 +1 @@
<svg data-v-5a8121a8="" xmlns="http://www.w3.org/2000/svg" width="24" viewBox="-2 -2 35 35" class="mr2"><path data-v-5a8121a8="" d="M22.675 0h-21.35c-.732 0-1.325.593-1.325 1.325v21.351c0 .731.593 1.324 1.325 1.324h11.495v-9.294h-3.128v-3.622h3.128v-2.671c0-3.1 1.893-4.788 4.659-4.788 1.325 0 2.463.099 2.795.143v3.24l-1.918.001c-1.504 0-1.795.715-1.795 1.763v2.313h3.587l-.467 3.622h-3.12v9.293h6.116c.73 0 1.323-.593 1.323-1.325v-21.35c0-.732-.593-1.325-1.325-1.325z"></path></svg>
<svg data-v-5a8121a8="" xmlns="http://www.w3.org/2000/svg" width="24" viewBox="-3 -3 35 35" class="mr2"><path data-v-5a8121a8="" d="M22.675 0h-21.35c-.732 0-1.325.593-1.325 1.325v21.351c0 .731.593 1.324 1.325 1.324h11.495v-9.294h-3.128v-3.622h3.128v-2.671c0-3.1 1.893-4.788 4.659-4.788 1.325 0 2.463.099 2.795.143v3.24l-1.918.001c-1.504 0-1.795.715-1.795 1.763v2.313h3.587l-.467 3.622h-3.12v9.293h6.116c.73 0 1.323-.593 1.323-1.325v-21.35c0-.732-.593-1.325-1.325-1.325z"></path></svg>

Before

Width:  |  Height:  |  Size: 484 B

After

Width:  |  Height:  |  Size: 484 B

View File

@@ -1 +1 @@
<svg fill="none" height="15" viewBox="0 0 12 15" width="12" xmlns="http://www.w3.org/2000/svg"><path d="m10.6432 6.652c.6267.39167.6267 1.30433 0 1.696l-9.1132 5.6958c-.666048.4162-1.52999966-.0626-1.52999962-.848l.00000049-11.39155c.00000004-.78544.86395213-1.264281 1.52999913-.848001z" fill="#fff"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 47.51 47.51"><circle fill="#231f20" opacity="0.8" cx="23.75" cy="23.75" r="23.75"/><polygon fill="#ffffff" points="37.16 23.75 16.05 11.57 16.05 35.94 37.16 23.75"/></svg>

Before

Width:  |  Height:  |  Size: 308 B

After

Width:  |  Height:  |  Size: 224 B