thelounge/client/components/LinkPreview.vue

248 lines
5.1 KiB
Vue
Raw Normal View History

2018-07-10 13:57:11 +02:00
<template>
2018-07-10 16:44:50 +02:00
<div
v-if="link.shown"
v-show="link.canDisplay"
2018-07-12 10:26:12 +02:00
ref="container"
class="preview"
>
2018-07-12 10:26:12 +02:00
<div
ref="content"
:class="['toggle-content', 'toggle-type-' + link.type, { opened: isContentShown }]"
>
2018-07-10 13:57:11 +02:00
<template v-if="link.type === 'link'">
2018-07-10 14:04:25 +02:00
<a
v-if="link.thumb"
:href="link.link"
class="toggle-thumbnail"
target="_blank"
rel="noopener"
>
2018-07-10 14:04:25 +02:00
<img
:src="link.thumb"
decoding="async"
alt=""
2018-07-10 16:44:50 +02:00
class="thumb"
@error="onThumbnailError"
@abort="onThumbnailError"
@load="onPreviewReady"
>
2018-07-10 13:57:11 +02:00
</a>
<div class="toggle-text">
<div class="head">
<div class="overflowable">
2018-07-10 14:04:25 +02:00
<a
:href="link.link"
:title="link.head"
target="_blank"
rel="noopener"
>{{ link.head }}</a>
2018-07-10 13:57:11 +02:00
</div>
2018-07-10 14:04:25 +02:00
<button
2018-07-12 10:26:12 +02:00
v-if="showMoreButton"
:aria-expanded="isContentShown"
:aria-label="moreButtonLabel"
2018-07-10 14:04:25 +02:00
class="more"
@click="onMoreClick"
><span class="more-caret" /></button>
2018-07-10 13:57:11 +02:00
</div>
<div class="body overflowable">
2018-07-10 14:04:25 +02:00
<a
:href="link.link"
:title="link.body"
target="_blank"
rel="noopener"
>{{ link.body }}</a>
2018-07-10 13:57:11 +02:00
</div>
</div>
</template>
<template v-else-if="link.type === 'image'">
2018-07-10 14:04:25 +02:00
<a
:href="link.link"
class="toggle-thumbnail"
target="_blank"
rel="noopener"
>
2018-07-10 14:04:25 +02:00
<img
:src="link.thumb"
decoding="async"
2018-07-10 16:44:50 +02:00
alt=""
@load="onPreviewReady"
>
2018-07-10 13:57:11 +02:00
</a>
</template>
<template v-else-if="link.type === 'video'">
2018-07-10 14:04:25 +02:00
<video
preload="metadata"
2018-07-10 16:44:50 +02:00
controls
@canplay="onPreviewReady"
>
2018-07-10 14:04:25 +02:00
<source
:src="link.media"
:type="link.mediaType"
>
2018-07-10 13:57:11 +02:00
</video>
</template>
<template v-else-if="link.type === 'audio'">
2018-07-10 14:04:25 +02:00
<audio
controls
2018-07-10 16:44:50 +02:00
preload="metadata"
@canplay="onPreviewReady"
>
2018-07-10 14:04:25 +02:00
<source
:src="link.media"
:type="link.mediaType"
>
2018-07-10 13:57:11 +02:00
</audio>
</template>
<template v-else-if="link.type === 'error'">
<em v-if="link.error === 'image-too-big'">
2018-07-10 14:04:25 +02:00
This image is larger than {{ link.maxSize | friendlysize }} and cannot be
2018-07-10 13:57:11 +02:00
previewed.
2018-07-10 14:04:25 +02:00
<a
:href="link.link"
target="_blank"
rel="noopener"
>Click here</a>
2018-07-10 13:57:11 +02:00
to open it in a new window.
</em>
<template v-else-if="link.error === 'message'">
<div>
<em>
A preview could not be loaded.
2018-07-10 14:04:25 +02:00
<a
:href="link.link"
target="_blank"
rel="noopener"
>Click here</a>
2018-07-10 13:57:11 +02:00
to open it in a new window.
</em>
<br>
2018-07-10 14:04:25 +02:00
<pre class="prefetch-error">{{ link.message }}</pre>
2018-07-10 13:57:11 +02:00
</div>
2018-07-10 14:04:25 +02:00
<button
2018-07-12 10:26:12 +02:00
:aria-expanded="isContentShown"
:aria-label="moreButtonLabel"
2018-07-10 14:04:25 +02:00
class="more"
@click="onMoreClick"
><span class="more-caret" /></button>
2018-07-10 13:57:11 +02:00
</template>
</template>
</div>
</div>
</template>
<script>
export default {
name: "LinkPreview",
props: {
link: Object,
keepScrollPosition: Function,
2018-07-10 13:57:11 +02:00
},
2018-07-12 10:26:12 +02:00
data() {
return {
showMoreButton: false,
isContentShown: false,
};
},
computed: {
moreButtonLabel() {
return this.isContentShown ? "Less" : "More";
},
},
watch: {
"link.type"() {
this.updateShownState();
this.onPreviewUpdate();
},
},
created() {
this.updateShownState();
},
2018-07-10 16:44:50 +02:00
mounted() {
this.$root.$on("resize", this.handleResize);
this.onPreviewUpdate();
2018-07-10 16:44:50 +02:00
},
beforeDestroy() {
this.$root.$off("resize", this.handleResize);
},
destroyed() {
// Let this preview go through load/canplay events again,
// Otherwise the browser can cause a resize on video elements
this.link.canDisplay = false;
},
2018-07-10 16:44:50 +02:00
methods: {
onPreviewUpdate() {
// Don't display previews while they are loading on the server
if (this.link.type === "loading") {
return;
}
// Error don't have any media to render
if (this.link.type === "error") {
this.onPreviewReady();
}
// If link doesn't have a thumbnail, render it
if (this.link.type === "link" && !this.link.thumb) {
this.onPreviewReady();
}
},
2018-07-10 16:44:50 +02:00
onPreviewReady() {
this.$set(this.link, "canDisplay", true);
this.keepScrollPosition();
if (this.link.type !== "link") {
return;
}
this.handleResize();
},
onThumbnailError() {
// If thumbnail fails to load, hide it and show the preview without it
this.link.thumb = "";
this.onPreviewReady();
2018-07-10 16:44:50 +02:00
},
2018-07-12 10:26:12 +02:00
onMoreClick() {
this.isContentShown = !this.isContentShown;
2018-07-25 13:04:10 +02:00
this.keepScrollPosition();
2018-07-12 10:26:12 +02:00
},
handleResize() {
this.$nextTick(() => {
if (!this.$refs.content) {
return;
}
this.showMoreButton = this.$refs.content.offsetWidth >= this.$refs.container.offsetWidth;
});
},
updateShownState() {
let defaultState = true;
switch (this.link.type) {
case "error":
defaultState = this.link.error === "image-too-big" ? this.$root.settings.media : this.$root.settings.links;
break;
case "loading":
defaultState = false;
break;
case "link":
defaultState = this.$root.settings.links;
break;
default:
defaultState = this.$root.settings.media;
}
this.link.shown = this.link.shown && defaultState;
},
2018-07-10 16:44:50 +02:00
},
2018-07-10 13:57:11 +02:00
};
</script>