pixelfed/resources/assets/components/partials/profile/ProfileFeed.vue

1193 lines
31 KiB
Vue
Raw Permalink Normal View History

2023-02-10 11:51:50 +00:00
<template>
<div class="profile-feed-component">
<div class="profile-feed-component-nav d-flex justify-content-center justify-content-md-between align-items-center mb-4">
<div class="d-none d-md-block border-bottom flex-grow-1 profile-nav-btns">
<div class="btn-group">
<button
class="btn btn-link"
:class="[ tabIndex === 1 ? 'active' : '' ]"
@click="toggleTab(1)"
>
Posts
</button>
<!-- <button
class="btn btn-link"
:class="[ tabIndex === 3 ? 'text-dark font-weight-bold' : 'text-lighter' ]"
@click="toggleTab(3)">
Albums
</button> -->
<button
v-if="isOwner"
class="btn btn-link"
:class="[ tabIndex === 'archives' ? 'active' : '' ]"
@click="toggleTab('archives')">
Archives
</button>
<button
v-if="isOwner"
class="btn btn-link"
:class="[ tabIndex === 'bookmarks' ? 'active' : '' ]"
@click="toggleTab('bookmarks')">
Bookmarks
</button>
<button
v-if="canViewCollections"
class="btn btn-link"
:class="[ tabIndex === 2 ? 'active' : '' ]"
@click="toggleTab(2)">
Collections
</button>
<button
v-if="isOwner"
class="btn btn-link"
:class="[ tabIndex === 3 ? 'active' : '' ]"
@click="toggleTab(3)">
Likes
</button>
</div>
</div>
<div v-if="tabIndex === 1" class="btn-group layout-sort-toggle">
<button
class="btn btn-sm"
:class="[ layoutIndex === 0 ? 'btn-dark' : 'btn-light' ]"
@click="toggleLayout(0, true)">
<i class="far fa-th fa-lg"></i>
</button>
<button
class="btn btn-sm"
:class="[ layoutIndex === 1 ? 'btn-dark' : 'btn-light' ]"
@click="toggleLayout(1, true)">
<i class="fas fa-th-large fa-lg"></i>
</button>
<button
class="btn btn-sm"
:class="[ layoutIndex === 2 ? 'btn-dark' : 'btn-light' ]"
@click="toggleLayout(2, true)">
<i class="far fa-bars fa-lg"></i>
</button>
</div>
</div>
<div v-if="tabIndex == 0" class="d-flex justify-content-center mt-5">
<b-spinner />
</div>
<div v-else-if="tabIndex == 1" class="px-0 mx-0">
<div v-if="layoutIndex === 0" class="row">
<div class="col-4 p-1" v-for="(s, index) in feed" :key="'tlob:'+index+s.id">
<a v-if="s.hasOwnProperty('pf_type') && s.pf_type == 'video'" class="card info-overlay card-md-border-0" :href="statusUrl(s)">
<div class="square">
<div v-if="s.sensitive" class="square-content">
<div class="info-overlay-text-label">
<h5 class="text-white m-auto font-weight-bold">
<span>
<span class="far fa-eye-slash fa-lg p-2 d-flex-inline"></span>
</span>
</h5>
</div>
<blur-hash-canvas
width="32"
height="32"
:hash="s.media_attachments[0].blurhash">
</blur-hash-canvas>
</div>
<div v-else class="square-content">
<blur-hash-image
width="32"
height="32"
:hash="s.media_attachments[0].blurhash"
:src="s.media_attachments[0].preview_url">
</blur-hash-image>
</div>
<div class="info-overlay-text">
<div class="text-white m-auto">
<p class="info-overlay-text-field font-weight-bold">
<span class="far fa-heart fa-lg p-2 d-flex-inline"></span>
<span class="d-flex-inline">{{formatCount(s.favourites_count)}}</span>
</p>
<p class="info-overlay-text-field font-weight-bold">
<span class="far fa-comment fa-lg p-2 d-flex-inline"></span>
<span class="d-flex-inline">{{formatCount(s.reply_count)}}</span>
</p>
<p class="mb-0 info-overlay-text-field font-weight-bold">
<span class="far fa-sync fa-lg p-2 d-flex-inline"></span>
<span class="d-flex-inline">{{formatCount(s.reblogs_count)}}</span>
</p>
</div>
</div>
</div>
<span class="badge badge-light video-overlay-badge">
<i class="far fa-video fa-2x"></i>
</span>
<span class="badge badge-light timestamp-overlay-badge">
{{ timeago(s.created_at) }}
</span>
</a>
<a v-else class="card info-overlay card-md-border-0" :href="statusUrl(s)">
<div class="square">
<div v-if="s.sensitive" class="square-content">
<div class="info-overlay-text-label">
<h5 class="text-white m-auto font-weight-bold">
<span>
<span class="far fa-eye-slash fa-lg p-2 d-flex-inline"></span>
</span>
</h5>
</div>
<blur-hash-canvas
width="32"
height="32"
:hash="s.media_attachments[0].blurhash">
</blur-hash-canvas>
</div>
<div v-else class="square-content">
<blur-hash-image
width="32"
height="32"
:hash="s.media_attachments[0].blurhash"
:src="s.media_attachments[0].url">
</blur-hash-image>
</div>
<div class="info-overlay-text">
<div class="text-white m-auto">
<p class="info-overlay-text-field font-weight-bold">
<span class="far fa-heart fa-lg p-2 d-flex-inline"></span>
<span class="d-flex-inline">{{formatCount(s.favourites_count)}}</span>
</p>
<p class="info-overlay-text-field font-weight-bold">
<span class="far fa-comment fa-lg p-2 d-flex-inline"></span>
<span class="d-flex-inline">{{formatCount(s.reply_count)}}</span>
</p>
<p class="mb-0 info-overlay-text-field font-weight-bold">
<span class="far fa-sync fa-lg p-2 d-flex-inline"></span>
<span class="d-flex-inline">{{formatCount(s.reblogs_count)}}</span>
</p>
</div>
</div>
</div>
<span class="badge badge-light timestamp-overlay-badge">
{{ timeago(s.created_at) }}
</span>
</a>
</div>
<intersect v-if="canLoadMore" @enter="enterIntersect">
<div class="col-4 ph-wrapper">
<div class="ph-item">
<div class="ph-picture big"></div>
</div>
</div>
</intersect>
</div>
<div v-else-if="layoutIndex === 1" class="row">
<masonry
:cols="{default: 3, 800: 2}"
:gutter="{default: '5px'}">
<div class="p-1" v-for="(s, index) in feed" :key="'tlog:'+index+s.id">
<a v-if="s.hasOwnProperty('pf_type') && s.pf_type == 'video'" class="card info-overlay card-md-border-0" :href="statusUrl(s)">
<div class="square">
<div class="square-content">
<blur-hash-image
width="32"
height="32"
class="rounded"
:hash="s.media_attachments[0].blurhash"
:src="s.media_attachments[0].preview_url">
</blur-hash-image>
</div>
</div>
<span class="badge badge-light video-overlay-badge">
<i class="far fa-video fa-2x"></i>
</span>
<span class="badge badge-light timestamp-overlay-badge">
{{ timeago(s.created_at) }}
</span>
</a>
<a v-else-if="s.sensitive" class="card info-overlay card-md-border-0" :href="statusUrl(s)">
<div class="square">
<div class="square-content">
<div class="info-overlay-text-label rounded">
<h5 class="text-white m-auto font-weight-bold">
<span>
<span class="far fa-eye-slash fa-lg p-2 d-flex-inline"></span>
</span>
</h5>
</div>
<blur-hash-canvas
width="32"
height="32"
class="rounded"
:hash="s.media_attachments[0].blurhash">
</blur-hash-canvas>
</div>
</div>
</a>
<a v-else class="card info-overlay card-md-border-0" :href="statusUrl(s)">
<img :src="previewUrl(s)" class="img-fluid w-100 rounded-lg" onerror="this.onerror=null;this.src='/storage/no-preview.png?v=0'">
<span class="badge badge-light timestamp-overlay-badge">
{{ timeago(s.created_at) }}
</span>
</a>
</div>
<intersect v-if="canLoadMore" @enter="enterIntersect">
<div class="p-1 ph-wrapper">
<div class="ph-item">
<div class="ph-picture big"></div>
</div>
</div>
</intersect>
</masonry>
</div>
<div v-else-if="layoutIndex === 2" class="row justify-content-center">
<div class="col-12 col-md-10">
<status-card
v-for="(s, index) in feed"
:key="'prs'+s.id+':'+index"
:profile="user"
:status="s"
v-on:like="likeStatus(index)"
v-on:unlike="unlikeStatus(index)"
v-on:share="shareStatus(index)"
v-on:unshare="unshareStatus(index)"
v-on:menu="openContextMenu(index)"
v-on:counter-change="counterChange(index, $event)"
v-on:likes-modal="openLikesModal(index)"
v-on:shares-modal="openSharesModal(index)"
v-on:comment-likes-modal="openCommentLikesModal"
2024-10-06 05:50:16 +00:00
v-on:bookmark="handleBookmark(index)"
2023-02-10 11:51:50 +00:00
v-on:handle-report="handleReport" />
</div>
<intersect v-if="canLoadMore" @enter="enterIntersect">
<div class="col-12 col-md-10">
<status-placeholder style="margin-bottom: 10rem;" />
</div>
</intersect>
</div>
<div v-if="feedLoaded && !feed.length">
<div class="row justify-content-center">
<div class="col-12 col-md-8 text-center">
<img src="/img/illustrations/dk-nature-man-monochrome.svg" class="img-fluid" style="opacity: 0.6;">
<p class="lead text-muted font-weight-bold">{{ $t('profile.emptyPosts') }}</p>
</div>
</div>
</div>
</div>
<div v-else-if="tabIndex === 'private'" class="row justify-content-center">
<div class="col-12 col-md-8 text-center">
<img src="/img/illustrations/dk-secure-feed.svg" class="img-fluid" style="opacity: 0.6;">
<p class="h3 text-dark font-weight-bold mt-3 py-3">This profile is private</p>
<div class="lead text-muted px-3">
Only approved followers can see <span class="font-weight-bold text-dark text-break">&commat;{{ profile.acct }}</span>'s <br />
posts. To request access, click <span class="font-weight-bold">Follow</span>.
</div>
</div>
</div>
<div v-else-if="tabIndex == 2" class="row justify-content-center">
<div class="col-12 col-md-8">
<div class="list-group">
<a
v-for="(collection, index) in collections"
class="list-group-item text-decoration-none text-dark"
:href="collection.url">
<div class="media">
<img :src="collection.thumb" width="65" height="65" style="object-fit: cover;" class="rounded-lg border mr-3" onerror="this.onerror=null;this.src='/storage/no-preview.png';">
<div class="media-body text-left">
<p class="lead mb-0">{{ collection.title ? collection.title : 'Untitled' }}</p>
<p class="small text-muted mb-1">{{ collection.description || 'No description available' }}</p>
<p class="small text-lighter mb-0 font-weight-bold">
<span>{{ collection.post_count }} posts</span>
<span>&middot;</span>
<span v-if="collection.visibility === 'public'" class="text-dark">Public</span>
<span v-else-if="collection.visibility === 'private'" class="text-dark"><i class="far fa-lock fa-sm"></i> Followers Only</span>
<span v-else-if="collection.visibility === 'draft'" class="primary"><i class="far fa-lock fa-sm"></i> Draft</span>
<span>&middot;</span>
<span v-if="collection.published_at">Created {{ timeago(collection.published_at) }} ago</span>
<span v-else class="text-warning">UNPUBLISHED</span>
</p>
</div>
</div>
</a>
</div>
</div>
<div v-if="collectionsLoaded && !collections.length" class="col-12 col-md-8 text-center">
<img src="/img/illustrations/dk-nature-man-monochrome.svg" class="img-fluid" style="opacity: 0.6;">
<p class="lead text-muted font-weight-bold">{{ $t('profile.emptyCollections') }}</p>
</div>
<div v-if="canLoadMoreCollections" class="col-12 col-md-8">
<intersect @enter="enterCollectionsIntersect">
<div class="d-flex justify-content-center mt-5">
<b-spinner small />
</div>
</intersect>
</div>
</div>
<div v-else-if="tabIndex == 3" class="px-0 mx-0">
<div class="row justify-content-center">
<div class="col-12 col-md-10">
<status-card
v-for="(s, index) in favourites"
:key="'prs'+s.id+':'+index"
:profile="user"
:status="s"
v-on:like="likeStatus(index)"
v-on:unlike="unlikeStatus(index)"
v-on:share="shareStatus(index)"
v-on:unshare="unshareStatus(index)"
v-on:counter-change="counterChange(index, $event)"
v-on:likes-modal="openLikesModal(index)"
v-on:comment-likes-modal="openCommentLikesModal"
v-on:handle-report="handleReport" />
</div>
<div v-if="canLoadMoreFavourites" class="col-12 col-md-10">
<intersect @enter="enterFavouritesIntersect">
<status-placeholder style="margin-bottom: 10rem;" />
</intersect>
</div>
</div>
<div v-if="!favourites || !favourites.length" class="row justify-content-center">
<div class="col-12 col-md-8 text-center">
<img src="/img/illustrations/dk-nature-man-monochrome.svg" class="img-fluid" style="opacity: 0.6;">
<p class="lead text-muted font-weight-bold">We can't seem to find any posts you have liked</p>
</div>
</div>
</div>
<div v-else-if="tabIndex == 'bookmarks'" class="px-0 mx-0">
<div class="row justify-content-center">
<div class="col-12 col-md-10">
<status-card
v-for="(s, index) in bookmarks"
:key="'prs'+s.id+':'+index"
:profile="user"
:new-reactions="true"
:status="s"
v-on:menu="openContextMenu(index)"
v-on:counter-change="counterChange(index, $event)"
v-on:likes-modal="openLikesModal(index)"
v-on:bookmark="handleBookmark(index)"
v-on:comment-likes-modal="openCommentLikesModal"
v-on:handle-report="handleReport" />
</div>
<div class="col-12 col-md-10">
<intersect v-if="canLoadMoreBookmarks" @enter="enterBookmarksIntersect">
<status-placeholder style="margin-bottom: 10rem;" />
</intersect>
</div>
</div>
<div v-if="!bookmarks || !bookmarks.length" class="row justify-content-center">
<div class="col-12 col-md-8 text-center">
<img src="/img/illustrations/dk-nature-man-monochrome.svg" class="img-fluid" style="opacity: 0.6;">
<p class="lead text-muted font-weight-bold">We can't seem to find any posts you have bookmarked</p>
</div>
</div>
</div>
<div v-else-if="tabIndex == 'archives'" class="px-0 mx-0">
<div class="row justify-content-center">
<div class="col-12 col-md-10">
<status-card
v-for="(s, index) in archives"
:key="'prarc'+s.id+':'+index"
:profile="user"
:new-reactions="true"
:reaction-bar="false"
:status="s"
v-on:menu="openContextMenu(index, 'archive')"
/>
</div>
<div v-if="canLoadMoreArchives" class="col-12 col-md-10">
<intersect @enter="enterArchivesIntersect">
<status-placeholder style="margin-bottom: 10rem;" />
</intersect>
</div>
</div>
<div v-if="!archives || !archives.length" class="row justify-content-center">
<div class="col-12 col-md-8 text-center">
<img src="/img/illustrations/dk-nature-man-monochrome.svg" class="img-fluid" style="opacity: 0.6;">
<p class="lead text-muted font-weight-bold">We can't seem to find any posts you have bookmarked</p>
</div>
</div>
</div>
<context-menu
v-if="showMenu"
ref="contextMenu"
:status="contextMenuPost"
:profile="user"
v-on:moderate="commitModeration"
v-on:delete="deletePost"
v-on:archived="handleArchived"
v-on:unarchived="handleUnarchived"
v-on:report-modal="handleReport"
/>
<likes-modal
v-if="showLikesModal"
ref="likesModal"
:status="likesModalPost"
:profile="user"
/>
<shares-modal
v-if="showSharesModal"
ref="sharesModal"
:status="sharesModalPost"
:profile="profile"
/>
<report-modal
ref="reportModal"
:key="reportedStatusId"
:status="reportedStatus"
/>
</div>
</template>
<script type="text/javascript">
import Intersect from 'vue-intersect'
import StatusCard from './../TimelineStatus.vue';
import StatusPlaceholder from './../StatusPlaceholder.vue';
import BlurHashCanvas from './../BlurhashCanvas.vue';
import ContextMenu from './../../partials/post/ContextMenu.vue';
import LikesModal from './../../partials/post/LikeModal.vue';
import SharesModal from './../../partials/post/ShareModal.vue';
import ReportModal from './../../partials/modal/ReportPost.vue';
import { parseLinkHeader } from '@web3-storage/parse-link-header';
export default {
props: {
profile: {
type: Object
},
relationship: {
type: Object
}
},
components: {
"intersect": Intersect,
"status-card": StatusCard,
"bh-canvas": BlurHashCanvas,
"status-placeholder": StatusPlaceholder,
"context-menu": ContextMenu,
"likes-modal": LikesModal,
"shares-modal": SharesModal,
"report-modal": ReportModal
},
data() {
return {
isLoaded: false,
user: {},
isOwner: false,
layoutIndex: 0,
tabIndex: 0,
ids: [],
feed: [],
feedLoaded: false,
collections: [],
collectionsLoaded: false,
canLoadMore: false,
max_id: 1,
isIntersecting: false,
postIndex: 0,
showMenu: false,
showLikesModal: false,
likesModalPost: {},
showReportModal: false,
reportedStatus: {},
reportedStatusId: 0,
favourites: [],
favouritesLoaded: false,
favouritesPage: 1,
canLoadMoreFavourites: false,
bookmarks: [],
bookmarksLoaded: false,
bookmarksPage: 1,
bookmarksCursor: undefined,
canLoadMoreBookmarks: false,
canLoadMoreCollections: false,
collectionsPage: 1,
isCollectionsIntersecting: false,
canViewCollections: false,
showSharesModal: false,
sharesModalPost: {},
archives: [],
archivesLoaded: false,
archivesPage: 1,
canLoadMoreArchives: false,
contextMenuPost: {}
}
},
mounted() {
this.init();
},
methods: {
init() {
this.user = window._sharedData.user;
if(this.$store.state.profileLayout != 'grid') {
let index = this.$store.state.profileLayout === 'masonry' ? 1 : 2;
this.toggleLayout(index);
}
if(this.user) {
this.isOwner = this.user.id == this.profile.id;
if(this.isOwner) {
this.canViewCollections = true;
}
}
if(this.profile.locked) {
this.privateProfileCheck();
} else {
if(this.profile.local) {
this.canViewCollections = true;
}
this.fetchFeed();
}
},
privateProfileCheck() {
if(this.relationship.following || this.isOwner) {
this.canViewCollections = true;
this.fetchFeed();
} else {
this.tabIndex = 'private';
this.isLoaded = true;
}
},
fetchFeed() {
axios.get('/api/pixelfed/v1/accounts/' + this.profile.id + '/statuses', {
params: {
limit: 9,
only_media: true,
min_id: 1
}
})
.then(res => {
this.tabIndex = 1;
let data = res.data.filter(status => status.media_attachments.length > 0);
let ids = data.map(status => status.id);
this.ids = ids;
this.max_id = Math.min(...ids);
data.forEach(s => {
this.feed.push(s);
});
setTimeout(() => {
this.canLoadMore = res.data.length > 1;
this.feedLoaded = true;
}, 500);
});
},
enterIntersect() {
if(this.isIntersecting) {
return;
}
this.isIntersecting = true;
axios.get('/api/pixelfed/v1/accounts/' + this.profile.id + '/statuses', {
params: {
limit: 9,
only_media: true,
max_id: this.max_id,
}
})
.then(res => {
if(!res.data || !res.data.length) {
this.canLoadMore = false;
}
let data = res.data
.filter(status => status.media_attachments.length > 0)
.filter(status => this.ids.indexOf(status.id) == -1)
if(!data || !data.length) {
this.canLoadMore = false;
this.isIntersecting = false;
return;
}
let filtered = data.forEach(status => {
if(status.id < this.max_id) {
this.max_id = status.id;
} else {
this.max_id--;
}
this.ids.push(status.id);
this.feed.push(status);
});
this.isIntersecting = false;
this.canLoadMore = res.data.length >= 1;
}).catch(err => {
this.canLoadMore = false;
});
},
toggleLayout(idx, blur = false) {
if(blur) {
event.currentTarget.blur();
}
this.layoutIndex = idx;
this.isIntersecting = false;
},
toggleTab(idx) {
event.currentTarget.blur();
switch(idx) {
case 1:
this.isIntersecting = false;
this.tabIndex = 1;
break;
case 2:
this.fetchCollections();
break;
case 3:
this.fetchFavourites();
break;
case 'bookmarks':
this.fetchBookmarks();
break;
case 'archives':
this.fetchArchives();
break;
}
},
fetchCollections() {
if(this.collectionsLoaded) {
this.tabIndex = 2;
}
axios.get('/api/local/profile/collections/' + this.profile.id)
.then(res => {
this.collections = res.data;
this.collectionsLoaded = true;
this.tabIndex = 2;
this.collectionsPage++;
this.canLoadMoreCollections = res.data.length === 9;
})
},
enterCollectionsIntersect() {
if(this.isCollectionsIntersecting) {
return;
}
this.isCollectionsIntersecting = true;
axios.get('/api/local/profile/collections/' + this.profile.id, {
params: {
limit: 9,
page: this.collectionsPage
}
})
.then(res => {
if(!res.data || !res.data.length) {
this.canLoadMoreCollections = false;
}
this.collectionsLoaded = true;
this.collections.push(...res.data);
this.collectionsPage++;
this.canLoadMoreCollections = res.data.length > 0;
this.isCollectionsIntersecting = false;
}).catch(err => {
this.canLoadMoreCollections = false;
this.isCollectionsIntersecting = false;
});
},
fetchFavourites() {
this.tabIndex = 0;
axios.get('/api/pixelfed/v1/favourites')
.then(res => {
this.tabIndex = 3;
this.favourites = res.data;
this.favouritesPage++;
this.favouritesLoaded = true;
if(res.data.length != 0) {
this.canLoadMoreFavourites = true;
}
})
},
enterFavouritesIntersect() {
if(this.isIntersecting) {
return;
}
this.isIntersecting = true;
axios.get('/api/pixelfed/v1/favourites', {
params: {
page: this.favouritesPage,
}
})
.then(res => {
this.favourites.push(...res.data);
this.favouritesPage++;
this.canLoadMoreFavourites = res.data.length != 0;
this.isIntersecting = false;
})
.catch(err => {
this.canLoadMoreFavourites = false;
})
},
fetchBookmarks() {
this.tabIndex = 0;
axios.get('/api/v1/bookmarks', {
params: {
'_pe': 1
}
})
.then(res => {
this.tabIndex = 'bookmarks';
this.bookmarks = res.data;
if(res.headers && res.headers.link) {
const links = parseLinkHeader(res.headers.link);
if(links.next) {
this.bookmarksPage = links.next.cursor;
this.canLoadMoreBookmarks = true;
} else {
this.canLoadMoreBookmarks = false;
}
}
this.bookmarksLoaded = true;
})
},
enterBookmarksIntersect() {
if(this.isIntersecting) {
return;
}
this.isIntersecting = true;
axios.get('/api/v1/bookmarks', {
params: {
'_pe': 1,
cursor: this.bookmarksPage,
}
})
.then(res => {
this.bookmarks.push(...res.data);
if(res.headers && res.headers.link) {
const links = parseLinkHeader(res.headers.link);
if(links.next) {
this.bookmarksPage = links.next.cursor;
this.canLoadMoreBookmarks = true;
} else {
this.canLoadMoreBookmarks = false;
}
}
this.isIntersecting = false;
})
.catch(err => {
this.canLoadMoreBookmarks = false;
})
},
fetchArchives() {
this.tabIndex = 0;
axios.get('/api/pixelfed/v2/statuses/archives')
.then(res => {
this.tabIndex = 'archives';
this.archives = res.data;
this.archivesPage++;
this.archivesLoaded = true;
if(res.data.length != 0) {
this.canLoadMoreArchives = true;
}
})
},
2024-10-06 05:50:16 +00:00
handleBookmark(index) {
let p = this.feed[index];
if(p.reblog) {
p = p.reblog;
}
axios.post('/i/bookmark', {
item: p.id
})
.then(res => {
if(this.feed[index].reblog) {
this.feed[index].reblog.bookmarked = !p.bookmarked;
} else {
this.feed[index].bookmarked = !p.bookmarked;
}
})
.catch(err => {
this.$bvToast.toast('Cannot bookmark post at this time.', {
title: 'Bookmark Error',
variant: 'danger',
autoHideDelay: 5000
});
});
},
2023-02-10 11:51:50 +00:00
formatCount(val) {
return App.util.format.count(val);
},
statusUrl(s) {
return '/i/web/post/' + s.id;
},
previewUrl(status) {
return status.sensitive ? '/storage/no-preview.png?v=' + new Date().getTime() : status.media_attachments[0].url;
},
timeago(ts) {
return App.util.format.timeAgo(ts);
},
likeStatus(index) {
let status = this.feed[index];
let state = status.favourited;
let count = status.favourites_count;
this.feed[index].favourites_count = count + 1;
this.feed[index].favourited = !status.favourited;
axios.post('/api/v1/statuses/' + status.id + '/favourite')
.catch(err => {
this.feed[index].favourites_count = count;
this.feed[index].favourited = false;
})
},
unlikeStatus(index) {
let status = this.feed[index];
let state = status.favourited;
let count = status.favourites_count;
this.feed[index].favourites_count = count - 1;
this.feed[index].favourited = !status.favourited;
axios.post('/api/v1/statuses/' + status.id + '/unfavourite')
.catch(err => {
this.feed[index].favourites_count = count;
this.feed[index].favourited = false;
})
},
openContextMenu(idx, type = 'feed') {
switch(type) {
case 'feed':
this.postIndex = idx;
this.contextMenuPost = this.feed[idx];
break;
case 'archive':
this.postIndex = idx;
this.contextMenuPost = this.archives[idx];
break;
}
this.showMenu = true;
this.$nextTick(() => {
this.$refs.contextMenu.open();
});
},
openLikesModal(idx) {
this.postIndex = idx;
this.likesModalPost = this.feed[this.postIndex];
this.showLikesModal = true;
this.$nextTick(() => {
this.$refs.likesModal.open();
});
},
openSharesModal(idx) {
this.postIndex = idx;
this.sharesModalPost = this.feed[this.postIndex];
this.showSharesModal = true;
this.$nextTick(() => {
this.$refs.sharesModal.open();
});
},
commitModeration(type) {
let idx = this.postIndex;
switch(type) {
case 'addcw':
this.feed[idx].sensitive = true;
break;
case 'remcw':
this.feed[idx].sensitive = false;
break;
case 'unlist':
this.feed.splice(idx, 1);
break;
case 'spammer':
let id = this.feed[idx].account.id;
this.feed = this.feed.filter(post => {
return post.account.id != id;
});
break;
}
},
counterChange(index, type) {
switch(type) {
case 'comment-increment':
this.feed[index].reply_count = this.feed[index].reply_count + 1;
break;
case 'comment-decrement':
this.feed[index].reply_count = this.feed[index].reply_count - 1;
break;
}
},
openCommentLikesModal(post) {
this.likesModalPost = post;
this.showLikesModal = true;
this.$nextTick(() => {
this.$refs.likesModal.open();
});
},
shareStatus(index) {
let status = this.feed[index];
let state = status.reblogged;
let count = status.reblogs_count;
this.feed[index].reblogs_count = count + 1;
this.feed[index].reblogged = !status.reblogged;
axios.post('/api/v1/statuses/' + status.id + '/reblog')
.catch(err => {
this.feed[index].reblogs_count = count;
this.feed[index].reblogged = false;
})
},
unshareStatus(index) {
let status = this.feed[index];
let state = status.reblogged;
let count = status.reblogs_count;
this.feed[index].reblogs_count = count - 1;
this.feed[index].reblogged = !status.reblogged;
axios.post('/api/v1/statuses/' + status.id + '/unreblog')
.catch(err => {
this.feed[index].reblogs_count = count;
this.feed[index].reblogged = false;
})
},
handleReport(post) {
this.reportedStatusId = post.id;
this.$nextTick(() => {
this.reportedStatus = post;
this.$refs.reportModal.open();
});
},
deletePost() {
this.feed.splice(this.postIndex, 1);
},
handleArchived(id) {
this.feed.splice(this.postIndex, 1);
},
handleUnarchived(id) {
this.feed = [];
this.fetchFeed();
},
enterArchivesIntersect() {
if(this.isIntersecting) {
return;
}
this.isIntersecting = true;
axios.get('/api/pixelfed/v2/statuses/archives', {
params: {
page: this.archivesPage
}
})
.then(res => {
this.archives.push(...res.data);
this.archivesPage++;
this.canLoadMoreArchives = res.data.length != 0;
this.isIntersecting = false;
})
.catch(err => {
this.canLoadMoreArchives = false;
})
},
handleBookmark(index) {
if(!window.confirm('Are you sure you want to unbookmark this post?')) {
return;
}
let p = this.bookmarks[index];
axios.post('/i/bookmark', {
item: p.id
})
.then(res => {
this.bookmarks = this.bookmarks.map(post => {
if(post.id == p.id) {
post.bookmarked = false;
delete post.bookmarked_at;
}
return post;
});
this.bookmarks.splice(index, 1);
})
.catch(err => {
this.$bvToast.toast('Cannot bookmark post at this time.', {
title: 'Bookmark Error',
variant: 'danger',
autoHideDelay: 5000
});
});
},
}
}
</script>
<style lang="scss">
.profile-feed-component {
margin-top: 0;
.ph-wrapper {
padding: 0.25rem;
.ph-item {
margin: 0;
padding: 0;
border: none;
background-color: transparent;
.ph-picture {
height: auto;
padding-bottom: 100%;
border-radius: 5px;
}
& > * {
margin-bottom: 0;
}
}
}
.info-overlay-text-field {
font-size: 13.5px;
margin-bottom: 2px;
@media (min-width: 768px) {
font-size: 20px;
margin-bottom: 15px;
}
}
.video-overlay-badge {
position: absolute;
top: 10px;
right: 10px;
opacity: 0.6;
color: var(--dark);
padding-bottom: 1px;
}
.timestamp-overlay-badge {
position: absolute;
bottom: 10px;
right: 10px;
opacity: 0.6;
}
.profile-nav-btns {
margin-right: 1rem;
.btn-group {
min-height: 45px;
}
.btn-link {
color: var(--text-lighter);
font-size: 14px;
border-radius: 0;
margin-right: 1rem;
font-weight: bold;
&:hover {
color: var(--text-muted);
text-decoration: none;
}
&.active {
color: var(--dark);
border-bottom: 1px solid var(--dark);
transition: border-bottom 250ms ease-in-out;
}
}
}
.layout-sort-toggle {
.btn {
border: none;
&.btn-light {
opacity: 0.4;
}
}
}
}
</style>