<template> <div v-if="link.shown" v-show="link.canDisplay" ref="container" class="preview"> <div ref="content" :class="['toggle-content', 'toggle-type-' + link.type, {opened: isContentShown}]" > <template v-if="link.type === 'link'"> <a v-if="link.thumb" :href="link.link" class="toggle-thumbnail" target="_blank" rel="noopener" > <img :src="link.thumb" decoding="async" alt="" class="thumb" @error="onThumbnailError" @abort="onThumbnailError" @load="onPreviewReady" /> </a> <div class="toggle-text"> <div class="head"> <div class="overflowable"> <a :href="link.link" :title="link.head" target="_blank" rel="noopener" >{{ link.head }}</a > </div> <button v-if="showMoreButton" :aria-expanded="isContentShown" :aria-label="moreButtonLabel" class="more" @click="onMoreClick" > <span class="more-caret" /> </button> </div> <div class="body overflowable"> <a :href="link.link" :title="link.body" target="_blank" rel="noopener">{{ link.body }}</a> </div> </div> </template> <template v-else-if="link.type === 'image'"> <a :href="link.link" class="toggle-thumbnail" target="_blank" rel="noopener"> <img :src="link.thumb" decoding="async" alt="" @load="onPreviewReady" /> </a> </template> <template v-else-if="link.type === 'video'"> <video preload="metadata" controls @canplay="onPreviewReady"> <source :src="link.media" :type="link.mediaType" /> </video> </template> <template v-else-if="link.type === 'audio'"> <audio controls preload="metadata" @canplay="onPreviewReady"> <source :src="link.media" :type="link.mediaType" /> </audio> </template> <template v-else-if="link.type === 'error'"> <em v-if="link.error === 'image-too-big'"> This image is larger than {{ link.maxSize | friendlysize }} and cannot be previewed. <a :href="link.link" target="_blank" rel="noopener">Click here</a> to open it in a new window. </em> <template v-else-if="link.error === 'message'"> <div> <em> A preview could not be loaded. <a :href="link.link" target="_blank" rel="noopener">Click here</a> to open it in a new window. </em> <br /> <pre class="prefetch-error">{{ link.message }}</pre> </div> <button :aria-expanded="isContentShown" :aria-label="moreButtonLabel" class="more" @click="onMoreClick" > <span class="more-caret" /> </button> </template> </template> </div> </div> </template> <script> export default { name: "LinkPreview", props: { link: Object, keepScrollPosition: Function, }, data() { return { showMoreButton: false, isContentShown: false, }; }, computed: { moreButtonLabel() { return this.isContentShown ? "Less" : "More"; }, }, watch: { "link.type"() { this.updateShownState(); this.onPreviewUpdate(); }, }, created() { this.updateShownState(); }, mounted() { this.$root.$on("resize", this.handleResize); this.onPreviewUpdate(); }, 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; }, 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(); } }, 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(); }, onMoreClick() { this.isContentShown = !this.isContentShown; this.keepScrollPosition(); }, 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; }, }, }; </script>