Browse Source

Update PostContent, TimelineStatus and PhotoPresenter components to support filters

Daniel Supernault 2 months ago
parent
commit
7d6f7c48ef

+ 155 - 89
resources/assets/components/partials/TimelineStatus.vue

@@ -10,46 +10,77 @@
                 @follow="follow"
                 @unfollow="unfollow" />
 
-            <post-content
-                :profile="profile"
-                :status="shadowStatus" />
+            <template v-if="!isFiltered || (isFiltered && filterType === 'blur')">
+                <post-content
+                    :profile="profile"
+                    :status="shadowStatus"
+                    :is-filtered="isFiltered"
+                    :filters="filters"
+                />
 
-            <post-reactions
-            	v-if="reactionBar"
-                :status="shadowStatus"
-                :profile="profile"
-                :admin="admin"
-                v-on:like="like"
-                v-on:unlike="unlike"
-                v-on:share="shareStatus"
-                v-on:unshare="unshareStatus"
-                v-on:likes-modal="showLikes"
-                v-on:shares-modal="showShares"
-                v-on:toggle-comments="showComments"
-                v-on:bookmark="handleBookmark"
-                v-on:mod-tools="openModTools" />
-
-            <div v-if="showCommentDrawer" class="card-footer rounded-bottom border-0" style="background: rgba(0,0,0,0.02);z-index: 3;">
-                <comment-drawer
+                <post-reactions
+                	v-if="reactionBar"
                     :status="shadowStatus"
                     :profile="profile"
-                    v-on:handle-report="handleReport"
-                    v-on:counter-change="counterChange"
-                    v-on:show-likes="showCommentLikes"
-                    v-on:follow="follow"
-                    v-on:unfollow="unfollow" />
-            </div>
+                    :admin="admin"
+                    @like="like"
+                    @unlike="unlike"
+                    @share="shareStatus"
+                    @unshare="unshareStatus"
+                    @likes-modal="showLikes"
+                    @shares-modal="showShares"
+                    @toggle-comments="showComments"
+                    @bookmark="handleBookmark"
+                    @mod-tools="openModTools" />
+
+                <div v-if="showCommentDrawer" class="card-footer rounded-bottom border-0" style="background: rgba(0,0,0,0.02);z-index: 3;">
+                    <comment-drawer
+                        :status="shadowStatus"
+                        :profile="profile"
+                        @handle-report="handleReport"
+                        @counter-change="counterChange"
+                        @show-likes="showCommentLikes"
+                        @follow="follow"
+                        @unfollow="unfollow" />
+                </div>
+            </template>
+
+            <template v-else>
+                <div class="card shadow-none mx-3 border-0">
+                  <div class="card-body bg-warning-light p-3">
+                    <div class="d-flex justify-content-center align-items-center">
+                      <i class="fas fa-exclamation-triangle text-warning mr-2" aria-hidden="true"></i>
+                      <h5 class="card-title mb-0">Filtered</h5>
+                    </div>
+                    <p class="card-text mt-2 text-center">
+                      Hidden because it contains the following filtered keywords:
+                      <span v-for="term in filteredTerms" class="badge badge-warning mr-1">{{ term }}</span>
+                    </p>
+                    <button class="btn btn-primary rounded-pill btn-block" @click="showHiddenStatus()">
+                      <i class="fas fa-eye mr-1"></i> Show
+                    </button>
+                  </div>
+                </div>
+
+            </template>
         </div>
     </div>
 </template>
 
 <script type="text/javascript">
-    import CommentDrawer from './post/CommentDrawer.vue';
-    import PostHeader from './post/PostHeader.vue';
-    import PostContent from './post/PostContent.vue';
-    import PostReactions from './post/PostReactions.vue';
+    import CommentDrawer from "./post/CommentDrawer.vue";
+    import PostHeader from "./post/PostHeader.vue";
+    import PostContent from "./post/PostContent.vue";
+    import PostReactions from "./post/PostReactions.vue";
 
     export default {
+
+        components: {
+            "comment-drawer": CommentDrawer,
+            "post-content": PostContent,
+            "post-header": PostHeader,
+            "post-reactions": PostReactions
+        },
         props: {
             status: {
                 type: Object
@@ -60,8 +91,8 @@
             },
 
             reactionBar: {
-            	type: Boolean,
-            	default: true
+                type: Boolean,
+                default: true
             },
 
             useDropdownMenu: {
@@ -70,13 +101,6 @@
             }
         },
 
-        components: {
-            "comment-drawer": CommentDrawer,
-            "post-content": PostContent,
-            "post-header": PostHeader,
-            "post-reactions": PostReactions
-        },
-
         data() {
             return {
                 key: 1,
@@ -87,23 +111,12 @@
                 isBookmarking: false,
                 owner: false,
                 admin: false,
-                license: false
-            }
-        },
-
-        mounted() {
-            this.license = this.shadowStatus.media_attachments && this.shadowStatus.media_attachments.length ?
-                this.shadowStatus
-                .media_attachments
-                .filter(m => m.hasOwnProperty('license') && m.license && m.license.hasOwnProperty('id'))
-                .map(m => m.license)[0] : false;
-            this.admin = window._sharedData.user.is_admin;
-            this.owner = this.shadowStatus.account.id == window._sharedData.user.id;
-            if(this.shadowStatus.reply_count && this.autoloadComments && this.shadowStatus.comments_disabled === false) {
-                setTimeout(() => {
-                    this.showCommentDrawer = true;
-                }, 1000);
-            }
+                license: false,
+                isFiltered: false,
+                filterType: undefined,
+                filters: [],
+                filteredTerms: []
+            };
         },
 
         computed: {
@@ -128,7 +141,7 @@
             newReactions: {
                 get() {
                     return this.$store.state.newReactions;
-                },
+                }
             },
 
             isReblog: {
@@ -150,35 +163,25 @@
             }
         },
 
-        watch: {
-            status: {
-                deep: true,
-                immediate: true,
-                handler: function(o, n) {
-                    this.isBookmarking = false;
-                }
-            },
-        },
-
         methods: {
             openMenu() {
-                this.$emit('menu');
+                this.$emit("menu");
             },
 
             like() {
-                this.$emit('like');
+                this.$emit("like");
             },
 
             unlike() {
-                this.$emit('unlike');
+                this.$emit("unlike");
             },
 
             showLikes() {
-                this.$emit('likes-modal');
+                this.$emit("likes-modal");
             },
 
             showShares() {
-                this.$emit('shares-modal');
+                this.$emit("shares-modal");
             },
 
             showComments() {
@@ -195,47 +198,47 @@
                     navigator.share({
                         url: this.status.url
                     })
-                    .then(() => console.log('Share was successful.'))
-                    .catch((error) => console.log('Sharing failed', error));
+                        .then(() => console.log("Share was successful."))
+                        .catch((error) => console.log("Sharing failed", error));
                 } else {
-                    swal('Not supported', 'Your current device does not support native sharing.', 'error');
+                    swal("Not supported", "Your current device does not support native sharing.", "error");
                 }
             },
 
             counterChange(type) {
-                this.$emit('counter-change', type);
+                this.$emit("counter-change", type);
             },
 
             showCommentLikes(post) {
-                this.$emit('comment-likes-modal', post);
+                this.$emit("comment-likes-modal", post);
             },
 
             shareStatus() {
-                this.$emit('share');
+                this.$emit("share");
             },
 
             unshareStatus() {
-                this.$emit('unshare');
+                this.$emit("unshare");
             },
 
             handleReport(post) {
-                this.$emit('handle-report', post);
+                this.$emit("handle-report", post);
             },
 
             follow() {
-                this.$emit('follow');
+                this.$emit("follow");
             },
 
             unfollow() {
-                this.$emit('unfollow');
+                this.$emit("unfollow");
             },
 
             handleReblog() {
                 this.isReblogging = true;
-                if(this.status.reblogged) {
-                    this.$emit('unshare');
+                if (this.status.reblogged) {
+                    this.$emit("unshare");
                 } else {
-                    this.$emit('share');
+                    this.$emit("share");
                 }
 
                 setTimeout(() => {
@@ -246,7 +249,7 @@
             handleBookmark() {
                 event.currentTarget.blur();
                 this.isBookmarking = true;
-                this.$emit('bookmark');
+                this.$emit("bookmark");
 
                 setTimeout(() => {
                     this.isBookmarking = false;
@@ -254,7 +257,7 @@
             },
 
             getStatusAvatar() {
-                if(window._sharedData.user.id == this.status.account.id) {
+                if (window._sharedData.user.id == this.status.account.id) {
                     return window._sharedData.user.avatar;
                 }
 
@@ -262,10 +265,73 @@
             },
 
             openModTools() {
-                this.$emit('mod-tools');
+                this.$emit("mod-tools");
+            },
+
+            applyStatusFilters() {
+                const filterTypes = this.status.filtered.map(f => f.filter.filter_action);
+
+                if (filterTypes.includes("warn")) {
+                    this.applyWarnStatusFilter();
+                    return;
+                }
+                if (filterTypes.includes("blur")) {
+                    this.applyBlurStatusFilter();
+                    return;
+                }
+            },
+
+            applyWarnStatusFilter() {
+                this.isFiltered = true;
+                this.filterType = "warn";
+                this.filters = this.status.filtered;
+                this.filteredTerms = this.status.filtered.map(f => f.keyword_matches).flat(1);
+            },
+
+            applyBlurStatusFilter() {
+                this.isFiltered = true;
+                this.filterType = "blur";
+                this.filters = this.status.filtered;
+                this.filteredTerms = this.status.filtered.map(f => f.keyword_matches).flat(1);
+            },
+
+            showHiddenStatus() {
+                this.isFiltered = false;
+                this.filterType = null;
+                this.filters = [];
+                this.filteredTerms = [];
+            }
+        },
+
+        mounted() {
+            this.license = this.shadowStatus.media_attachments && this.shadowStatus.media_attachments.length ?
+                this.shadowStatus
+                    .media_attachments
+                    .filter(m => m.hasOwnProperty("license") && m.license && m.license.hasOwnProperty("id"))
+                    .map(m => m.license)[0] : false;
+            this.admin = window._sharedData.user.is_admin;
+            this.owner = this.shadowStatus.account.id == window._sharedData.user.id;
+            if (this.shadowStatus.reply_count && this.autoloadComments && this.shadowStatus.comments_disabled === false) {
+                setTimeout(() => {
+                    this.showCommentDrawer = true;
+                }, 1000);
+            }
+
+            if (this.status.filtered && this.status.filtered.length) {
+                this.applyStatusFilters();
+            }
+        },
+
+        watch: {
+            status: {
+                deep: true,
+                immediate: true,
+                handler: function(o, n) {
+                    this.isBookmarking = false;
+                }
             }
         }
-    }
+    };
 </script>
 
 <style lang="scss">

+ 250 - 181
resources/assets/components/partials/post/PostContent.vue

@@ -1,128 +1,129 @@
 <template>
-	<div class="timeline-status-component-content">
-		<div v-if="status.pf_type === 'poll'" class="postPresenterContainer" style="background: #000;">
-		</div>
-
-		<div v-else-if="!fixedHeight" class="postPresenterContainer" style="background: #000;">
-			<div v-if="status.pf_type === 'photo'" class="w-100">
-				<photo-presenter
-					:status="status"
-					v-on:lightbox="toggleLightbox"
-					v-on:togglecw="status.sensitive = false"/>
-			</div>
-
-			<div v-else-if="status.pf_type === 'video'" class="w-100">
-                <video-player :status="status" :fixedHeight="fixedHeight" v-on:togglecw="status.sensitive = false" />
-			</div>
-
-			<div v-else-if="status.pf_type === 'photo:album'" class="w-100">
-				<photo-album-presenter :status="status" v-on:lightbox="toggleLightbox" v-on:togglecw="status.sensitive = false"></photo-album-presenter>
-			</div>
-
-			<div v-else-if="status.pf_type === 'video:album'" class="w-100">
-				<video-album-presenter :status="status" v-on:togglecw="status.sensitive = false"></video-album-presenter>
-			</div>
-
-			<div v-else-if="status.pf_type === 'photo:video:album'" class="w-100">
-				<mixed-album-presenter :status="status" v-on:lightbox="toggleLightbox" v-on:togglecw="status.sensitive = false"></mixed-album-presenter>
-			</div>
-		</div>
-
-		<div v-else class="card-body p-0">
-			<div v-if="status.pf_type === 'photo'" :class="{ fixedHeight: fixedHeight }">
-				<div v-if="status.sensitive == true" class="content-label-wrapper">
-					<div class="text-light content-label">
-						<p class="text-center">
-							<i class="far fa-eye-slash fa-2x"></i>
-						</p>
-						<p class="h4 font-weight-bold text-center">
-							{{ $t('common.sensitiveContent') }}
-						</p>
-						<p class="text-center py-2 content-label-text">
-							{{ status.spoiler_text ? status.spoiler_text : $t('common.sensitiveContentWarning') }}
-						</p>
-						<p class="mb-0">
-							<button class="btn btn-outline-light btn-block btn-sm font-weight-bold" @click="toggleContentWarning()">See Post</button>
-						</p>
-					</div>
-
-					<blur-hash-image
-						width="32"
-						height="32"
-						:punch="1"
-						class="blurhash-wrapper"
-						:hash="status.media_attachments[0].blurhash"
-						/>
-				</div>
-				<div
-					v-else
-					@click.prevent="toggleLightbox"
-					class="content-label-wrapper"
-					style="position: relative;width:100%;height: 400px;overflow: hidden;z-index:1"
-					>
-
-					<img
+    <div class="timeline-status-component-content">
+        <div v-if="status.pf_type === 'poll'" class="postPresenterContainer" style="background: #000;">
+        </div>
+
+        <div v-else-if="!fixedHeight" class="postPresenterContainer" style="background: #000;">
+            <div v-if="status.pf_type === 'photo'" class="w-100">
+                <photo-presenter
+                    :status="status"
+                    :is-filtered="isFiltered"
+                    @lightbox="toggleLightbox"
+                    @togglecw="toggleContentWarning" />
+            </div>
+
+            <div v-else-if="status.pf_type === 'video'" class="w-100">
+                <video-player
+                    :status="statusRender"
+                    :fixed-height="fixedHeight"
+                    @togglecw="toggleContentWarning" />
+            </div>
+
+            <div v-else-if="status.pf_type === 'photo:album'" class="w-100">
+                <photo-album-presenter
+                    :status="status"
+                    @lightbox="toggleLightbox"
+                    @togglecw="toggleContentWarning" />
+            </div>
+
+            <div v-else-if="status.pf_type === 'video:album'" class="w-100">
+                <video-album-presenter
+                    :status="status"
+                    @togglecw="toggleContentWarning" />
+            </div>
+
+            <div v-else-if="status.pf_type === 'photo:video:album'" class="w-100">
+                <mixed-album-presenter
+                    :status="status"
+                    @lightbox="toggleLightbox"
+                    @togglecw="toggleContentWarning" />
+            </div>
+        </div>
+
+        <div v-else class="card-body p-0">
+            <div v-if="status.pf_type === 'photo'" :class="{ fixedHeight: fixedHeight }">
+                <div v-if="statusRender.sensitive == true" class="content-label-wrapper">
+                    <div class="text-light content-label">
+                        <p class="text-center">
+                            <i class="far fa-eye-slash fa-2x"></i>
+                        </p>
+                        <p class="h4 font-weight-bold text-center">
+                            {{ isFiltered ? 'Filtered Content' : $t('common.sensitiveContent') }}
+                        </p>
+                        <p class="text-center py-2 content-label-text">
+                            {{ status.spoiler_text ? status.spoiler_text : $t('common.sensitiveContentWarning') }}
+                        </p>
+                        <p class="mb-0">
+                            <button class="btn btn-outline-light btn-block btn-sm font-weight-bold" @click="toggleContentWarning()">See Post</button>
+                        </p>
+                    </div>
+
+                    <blur-hash-image
+                        width="32"
+                        height="32"
+                        :punch="1"
+                        class="blurhash-wrapper"
+                        :hash="status.media_attachments[0].blurhash"
+                        />
+                </div>
+                <div
+                    v-else
+                    class="content-label-wrapper"
+                    @click.prevent="toggleLightbox"
+                    >
+
+                    <img
+                        :src="status.media_attachments[0].url"
+                        class="content-label-wrapper-img" />
+
+                    <blur-hash-image
+                        :key="key"
+                        width="32"
+                        height="32"
+                        :punch="1"
+                        :hash="status.media_attachments[0].blurhash"
                         :src="status.media_attachments[0].url"
-                        style="position: absolute;width: 105%;height: 410px;object-fit: cover;z-index: 1;top:0;left:0;filter: brightness(0.35) blur(6px);margin:-5px;">
-
-					<!-- <blur-hash-canvas
-						v-if="status.media_attachments[0].blurhash && status.media_attachments[0].blurhash != 'U4Rfzst8?bt7ogayj[j[~pfQ9Goe%Mj[WBay'"
-						:key="key"
-						width="32"
-						height="32"
-						:punch="1"
-						:hash="status.media_attachments[0].blurhash"
-						style="position: absolute;width: 105%;height: 410px;object-fit: cover;z-index: 1;top:0;left:0;filter: brightness(0.35);"
-						/> -->
-
-					<blur-hash-image
-						:key="key"
-						width="32"
-						height="32"
-						:punch="1"
-						:hash="status.media_attachments[0].blurhash"
-						:src="status.media_attachments[0].url"
-						class="blurhash-wrapper"
+                        class="blurhash-wrapper"
                         :alt="status.media_attachments[0].description"
                         :title="status.media_attachments[0].description"
-						style="width: 100%;position: absolute;z-index:9;top:0:left:0"
-						/>
-
-					<p v-if="!status.sensitive && sensitive"
-						@click="status.sensitive = true"
-						style="
-						margin-top: 0;
-						padding: 10px;
-						color: #000;
-						font-size: 10px;
-						text-align: right;
-						position: absolute;
-						top: 0;
-						right: 0;
-						border-radius: 11px;
-						cursor: pointer;
-						background: rgba(255, 255, 255,.5);
-					">
-						<i class="fas fa-eye-slash fa-lg"></i>
-					</p>
-				</div>
-			</div>
+                        style="width: 100%;position: absolute;z-index:9;top:0:left:0"
+                        />
+
+                    <p
+                        v-if="!status.sensitive && sensitive"
+                        class="sensitive-curtain"
+                        @click="status.sensitive = true">
+                        <i class="fas fa-eye-slash fa-lg"></i>
+                    </p>
+                </div>
+            </div>
 
             <video-player
                 v-else-if="status.pf_type === 'video'"
                 :status="status"
-                :fixedHeight="fixedHeight"
+                :fixed-height="fixedHeight"
             />
 
-			<div v-else-if="status.pf_type === 'photo:album'" class="card-img-top shadow" style="border-radius: 15px;">
-				<photo-album-presenter :status="status" v-on:lightbox="toggleLightbox" v-on:togglecw="toggleContentWarning()" style="border-radius:15px !important;object-fit: contain;background-color: #000;overflow: hidden;" :class="{ fixedHeight: fixedHeight }"/>
-			</div>
+            <div v-else-if="status.pf_type === 'photo:album'" class="card-img-top shadow" style="border-radius: 15px;">
+                <photo-album-presenter
+                    :status="status"
+                    class="photo-presenter"
+                    :class="{ fixedHeight: fixedHeight }"
+                    @lightbox="toggleLightbox"
+                    @togglecw="toggleContentWarning()" />
+            </div>
+
+            <div v-else-if="status.pf_type === 'photo:video:album'" class="card-img-top shadow" style="border-radius: 15px;">
+                <mixed-album-presenter
+                    :status="status"
+                    class="mixed-presenter"
+                    :class="{ fixedHeight: fixedHeight }"
+                    @lightbox="toggleLightbox"
+                    @togglecw="status.sensitive = false" />
 
-			<div v-else-if="status.pf_type === 'photo:video:album'" class="card-img-top shadow" style="border-radius: 15px;">
-				<mixed-album-presenter :status="status" v-on:lightbox="toggleLightbox" v-on:togglecw="status.sensitive = false" style="border-radius:15px !important;object-fit: contain;background-color: #000;overflow: hidden;align-items:center" :class="{ fixedHeight: fixedHeight }"></mixed-album-presenter>
-			</div>
+            </div>
 
-			<div v-else-if="status.pf_type === 'text'">
+            <div v-else-if="status.pf_type === 'text'">
                 <div v-if="status.sensitive" class="border m-3 p-5 rounded-lg">
                     <p class="text-center">
                         <i class="far fa-eye-slash fa-2x"></i>
@@ -135,85 +136,153 @@
                 </div>
             </div>
 
-			<div v-else class="bg-light rounded-lg d-flex align-items-center justify-content-center" style="height: 400px;">
-				<div>
-					<p class="text-center">
-						<i class="fas fa-exclamation-triangle fa-4x"></i>
-					</p>
-
-					<p class="lead text-center mb-0">
-						Cannot display post
-					</p>
-
-					<p class="small text-center mb-0">
-						<!-- <a class="font-weight-bold primary" href="#">Report issue</a> -->
-						{{status.pf_type}}:{{status.id}}
-					</p>
-				</div>
-			</div>
-		</div>
-
-		<div
-			v-if="status.content && !status.sensitive"
-			class="card-body status-text"
-			:class="[ status.pf_type === 'text' ? 'py-0' : 'pb-0']">
-			<p>
-				<read-more :status="status" :cursor-limit="300"/>
-			</p>
-			<!-- <p v-html="status.content_text || status.content">
-			</p> -->
-		</div>
-	</div>
+            <div v-else class="bg-light rounded-lg d-flex align-items-center justify-content-center" style="height: 400px;">
+                <div>
+                    <p class="text-center">
+                        <i class="fas fa-exclamation-triangle fa-4x"></i>
+                    </p>
+
+                    <p class="lead text-center mb-0">
+                        Cannot display post
+                    </p>
+
+                    <p class="small text-center mb-0">
+                        {{ status.pf_type }}:{{ status.id }}
+                    </p>
+                </div>
+            </div>
+        </div>
+
+        <div
+            v-if="status.content && !status.sensitive"
+            class="card-body status-text"
+            :class="[ status.pf_type === 'text' ? 'py-0' : 'pb-0']">
+            <p>
+                <read-more :status="status" :cursor-limit="300" />
+            </p>
+        </div>
+    </div>
 </template>
 
 <script type="text/javascript">
-	import BigPicture from 'bigpicture';
-	import ReadMore from './ReadMore.vue';
-    import VideoPlayer from '@/presenter/VideoPlayer.vue';
+    import BigPicture from "bigpicture";
+    import ReadMore from "./ReadMore.vue";
+    import VideoPlayer from "@/presenter/VideoPlayer.vue";
 
-	export default {
-		props: ['status'],
+    export default {
 
-		components: {
-			"read-more": ReadMore,
+        components: {
+            "read-more": ReadMore,
             "video-player": VideoPlayer
-		},
-
-		data() {
-			return {
-				key: 1,
-				sensitive: false,
-			};
-		},
-
-		computed: {
-			fixedHeight: {
-				get() {
-					return this.$store.state.fixedHeight == true;
-				}
-			}
-		},
-
-		methods: {
-			toggleLightbox(e) {
-				BigPicture({
-					el: e.target
-				})
-			},
-
-			toggleContentWarning() {
-				this.key++;
-				this.sensitive = true;
-				this.status.sensitive = !this.status.sensitive;
-			},
+        },
+        props: {
+
+            status: {
+                type: Object
+            },
+            isFiltered: {
+                type: Boolean
+            },
+            filters: {
+                type: Array
+            }
+        },
+
+        data() {
+            return {
+                key: 1,
+                sensitive: false
+            };
+        },
+
+        computed: {
+            statusRender: {
+                get() {
+                    if (this.isFiltered) {
+                        this.status.spoiler_text = "Filtered because it contains the following keywords: " + this.status.filtered.map(f => f.keyword_matches).flat(1).join(", ");
+                        this.status.sensitive = true;
+                    }
+                    return this.status;
+                }
+            },
+            fixedHeight: {
+                get() {
+                    return this.$store.state.fixedHeight == true;
+                }
+            }
+        },
+
+        methods: {
+            toggleLightbox(e) {
+                BigPicture({
+                    el: e.target
+                });
+            },
+
+            toggleContentWarning() {
+                this.key++;
+                this.sensitive = true;
+                this.status.sensitive = !this.status.sensitive;
+            },
 
             getPoster(status) {
                 let url = status.media_attachments[0].preview_url;
-                if(url.endsWith('no-preview.jpg') || url.endsWith('no-preview.png')) {
+
+                if (url.endsWith("no-preview.jpg") || url.endsWith("no-preview.png")) {
                     return;
                 }
                 return url;
             }
-		}
-	}
+        }
+    };
 </script>
+
+<style scoped>
+    .sensitive-curtain {
+        margin-top: 0;
+        padding: 10px;
+        color: #000;
+        font-size: 10px;
+        text-align: right;
+        position: absolute;
+        top: 0;
+        right: 0;
+        border-radius: 11px;
+        cursor: pointer;
+        background: rgba(255, 255, 255,.5);
+    }
+
+    .content-label-wrapper {
+        position: relative;
+        width:100%;
+        height: 400px;
+        overflow: hidden;
+        z-index:1
+    }
+
+    .content-label-wrapper-img {
+        position: absolute;
+        width: 105%;height: 410px;
+        object-fit: cover;
+        z-index: 1;
+        top:0;
+        left:0;
+        filter: brightness(0.35) blur(6px);
+        margin:-5px;
+    }
+
+    .photo-presenter {
+        border-radius:15px !important;
+        object-fit: contain;
+        background-color: #000;
+        overflow: hidden;
+    }
+
+    .mixed-presenter {
+        border-radius:15px !important;
+        object-fit: contain;
+        background-color: #000;
+        overflow: hidden;
+        align-items:center;
+    }
+</style>

+ 150 - 143
resources/assets/components/presenter/PhotoPresenter.vue

@@ -1,160 +1,167 @@
 <template>
-	<div v-if="status.sensitive == true" class="content-label-wrapper">
-		<div class="text-light content-label">
-			<p class="text-center">
-				<i class="far fa-eye-slash fa-2x"></i>
-			</p>
-			<p class="h4 font-weight-bold text-center">
-				Sensitive Content
-			</p>
-			<p class="text-center py-2 content-label-text">
-				{{ status.spoiler_text ? status.spoiler_text : 'This post may contain sensitive content.'}}
-			</p>
-			<p class="mb-0">
-				<button @click="toggleContentWarning()" class="btn btn-outline-light btn-block btn-sm font-weight-bold">See Post</button>
-			</p>
-		</div>
-		<blur-hash-image
-			width="32"
-			height="32"
-			:punch="1"
-			:hash="status.media_attachments[0].blurhash"
-			:alt="altText(status)"/>
-	</div>
-	<div v-else>
-		<div :title="status.media_attachments[0].description" style="position: relative;">
-			<img class="card-img-top"
-				:src="status.media_attachments[0].url"
-				loading="lazy"
-				:alt="altText(status)"
-				:width="width()"
-				:height="height()"
-				onerror="this.onerror=null;this.src='/storage/no-preview.png'"
-				@click.prevent="toggleLightbox">
+    <div v-if="status.sensitive == true" class="content-label-wrapper">
+        <div class="text-light content-label">
+            <p class="text-center">
+                <i class="far fa-eye-slash fa-2x"></i>
+            </p>
+            <p class="h4 font-weight-bold text-center">
+                {{ isFiltered ? 'Filtered Content' : 'Sensitive Content' }}
+            </p>
+            <p class="text-center py-2 content-label-text">
+                {{ status.spoiler_text ? status.spoiler_text : 'This post may contain sensitive content.' }}
+            </p>
+            <p class="mb-0">
+                <button class="btn btn-outline-light btn-block btn-sm font-weight-bold" @click="toggleContentWarning()">See Post</button>
+            </p>
+        </div>
+        <blur-hash-image
+            width="32"
+            height="32"
+            :punch="1"
+            :hash="status.media_attachments[0].blurhash"
+            :alt="altText(status)"
+        />
+    </div>
+    <div v-else>
+        <div
+            :title="status.media_attachments[0].description"
+            style="position: relative;">
 
-				<!-- <blur-hash-image
-					class="card-img-top"
-					width="32"
-					height="32"
-					:punch="1"
-					:hash="status.media_attachments[0].blurhash"
-					:src="status.media_attachments[0].url"
-					:alt="altText(status)"
-					@click.prevent="toggleLightbox"/> -->
+            <img
+                class="card-img-top"
+                :src="status.media_attachments[0].url"
+                loading="lazy"
+                :alt="altText(status)"
+                :width="width()"
+                :height="height()"
+                onerror="this.onerror=null;this.src='/storage/no-preview.png'"
+                @click.prevent="toggleLightbox"
+            />
 
-				<p v-if="!status.sensitive && sensitive"
-					@click="status.sensitive = true"
-					style="
-					margin-top: 0;
-					padding: 10px;
-					color: #fff;
-					font-size: 10px;
-					text-align: right;
-					position: absolute;
-					top: 0;
-					right: 0;
-					border-top-left-radius: 5px;
-					cursor: pointer;
-					background: linear-gradient(0deg, rgba(0,0,0,0.5), rgba(0,0,0,0.5));
-				">
-					<i class="fas fa-eye-slash fa-lg"></i>
-				</p>
+            <p
+                v-if="!status.sensitive && sensitive"
+                class="sensitive-curtain"
+                @click="status.sensitive = true">
+                <i class="fas fa-eye-slash fa-lg"></i>
+            </p>
 
-				<p
-					v-if="status.media_attachments[0].license"
-					style="
-					margin-bottom: 0;
-					padding: 0 5px;
-					color: #fff;
-					font-size: 10px;
-					text-align: right;
-					position: absolute;
-					bottom: 0;
-					right: 0;
-					border-top-left-radius: 5px;
-					background: linear-gradient(0deg, rgba(0,0,0,0.5), rgba(0,0,0,0.5));
-				"><a :href="status.url" class="font-weight-bold text-light">Photo</a> by <a :href="status.account.url" class="font-weight-bold text-light">&commat;{{status.account.username}}</a> licensed under <a :href="status.media_attachments[0].license.url" class="font-weight-bold text-light">{{status.media_attachments[0].license.title}}</a></p>
-		</div>
-	</div>
+            <p
+                v-if="status.media_attachments[0].license"
+                class="photo-license">
+                <a :href="status.url" class="font-weight-bold text-light">Photo</a> by <a :href="status.account.url" class="font-weight-bold text-light">&commat;{{ status.account.username }}</a> licensed under <a :href="status.media_attachments[0].license.url" class="font-weight-bold text-light">{{ status.media_attachments[0].license.title }}</a>
+            </p>
+        </div>
+    </div>
 </template>
 
-<style type="text/css" scoped>
-  .card-img-top {
-    border-top-left-radius: 0 !important;
-    border-top-right-radius: 0 !important;
-  }
-  .content-label-wrapper {
-  	position: relative;
-  }
-  .content-label {
-  	margin: 0;
-  	position: absolute;
-  	top:50%;
-  	left:50%;
-  	transform: translate(-50%, -50%);
-  	display: flex;
-  	flex-direction: column;
-  	align-items: center;
-  	justify-content: center;
-  	width: 100%;
-  	height: 100%;
-  	z-index: 2;
-  	background: rgba(0, 0, 0, 0.2)
-  }
-</style>
-
 <script type="text/javascript">
-	import BigPicture from 'bigpicture';
+    import BigPicture from "bigpicture";
 
-	export default {
-		props: ['status'],
+    export default {
+        props: {
+            status: {
+                type: Object
+            },
+            isFiltered: {
+                type: Boolean,
+                default: false
+            }
+        },
 
-		data() {
-			return {
-				sensitive: this.status.sensitive
-			}
-		},
+        data() {
+            return {
+                sensitive: this.status.sensitive
+            };
+        },
 
-		mounted() {
-		},
+        methods: {
+            altText(status) {
+              let desc = status.media_attachments[0].description;
 
-		methods: {
-			altText(status) {
-				let desc = status.media_attachments[0].description;
-				if(desc) {
-					return desc;
-				}
+              if (desc) {
+                return desc;
+            }
 
-				return 'Photo was not tagged with any alt text.';
-			},
+            return "Photo was not tagged with any alt text.";
+            },
 
-			toggleContentWarning(status) {
-				this.$emit('togglecw');
-			},
+            toggleContentWarning(status) {
+                this.$emit("togglecw");
+            },
 
-			toggleLightbox(e) {
-				BigPicture({
-					el: e.target
-				})
-			},
+            toggleLightbox(e) {
+                BigPicture({
+                    el: e.target
+                });
+            },
 
-			width() {
-				if( !this.status.media_attachments[0].meta ||
-					!this.status.media_attachments[0].meta.original ||
-					!this.status.media_attachments[0].meta.original.width ) {
-					return;
-				}
-				return this.status.media_attachments[0].meta.original.width;
-			},
+            width() {
+                if (!this.status.media_attachments[0].meta ||
+                    !this.status.media_attachments[0].meta.original ||
+                    !this.status.media_attachments[0].meta.original.width) {
+                    return;
+                }
+                return this.status.media_attachments[0].meta.original.width;
+            },
 
-			height() {
-				if( !this.status.media_attachments[0].meta ||
-					!this.status.media_attachments[0].meta.original ||
-					!this.status.media_attachments[0].meta.original.height ) {
-					return;
-				}
-				return this.status.media_attachments[0].meta.original.height;
-			}
-		}
-	}
+            height() {
+                if (!this.status.media_attachments[0].meta ||
+                    !this.status.media_attachments[0].meta.original ||
+                    !this.status.media_attachments[0].meta.original.height) {
+                    return;
+                }
+                return this.status.media_attachments[0].meta.original.height;
+            }
+        }
+    };
 </script>
+
+<style type="text/css" scoped>
+.card-img-top {
+    border-top-left-radius: 0 !important;
+    border-top-right-radius: 0 !important;
+}
+.content-label-wrapper {
+    position: relative;
+}
+.content-label {
+    margin: 0;
+    position: absolute;
+    top:50%;
+    left:50%;
+    transform: translate(-50%, -50%);
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    width: 100%;
+    height: 100%;
+    z-index: 2;
+    background: rgba(0, 0, 0, 0.2)
+}
+.sensitive-curtain {
+    margin-top: 0;
+    padding: 10px;
+    color: #fff;
+    font-size: 10px;
+    text-align: right;
+    position: absolute;
+    top: 0;
+    right: 0;
+    border-top-left-radius: 5px;
+    cursor: pointer;
+    background: linear-gradient(0deg, rgba(0,0,0,0.5), rgba(0,0,0,0.5));
+}
+.photo-license {
+    margin-bottom: 0;
+    padding: 0 5px;
+    color: #fff;
+    font-size: 10px;
+    text-align: right;
+    position: absolute;
+    bottom: 0;
+    right: 0;
+    border-top-left-radius: 5px;
+    background: linear-gradient(0deg, rgba(0,0,0,0.5), rgba(0,0,0,0.5));
+}
+</style>