From 1fa08644b43a802db0b6669af7dea7982cd32d23 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Fri, 18 Jun 2021 05:03:40 -0600 Subject: [PATCH 1/3] Update Remote Post + Profile hashtag to redirect to local urls --- resources/assets/js/components/RemotePost.vue | 2761 +++++++++-------- .../assets/js/components/RemoteProfile.vue | 6 + 2 files changed, 1388 insertions(+), 1379 deletions(-) diff --git a/resources/assets/js/components/RemotePost.vue b/resources/assets/js/components/RemotePost.vue index 35d019346..775871541 100644 --- a/resources/assets/js/components/RemotePost.vue +++ b/resources/assets/js/components/RemotePost.vue @@ -1,519 +1,519 @@ @@ -522,886 +522,889 @@ pixelfed.postComponent = {}; export default { - props: [ - 'status-id', - 'status-username', - 'status-template', - 'status-url', - 'status-profile-url', - 'status-avatar', - 'status-profile-id', - 'profile-layout' - ], - data() { - return { - config: window.App.config, - status: false, - media: {}, - user: false, - reactions: { - liked: false, - shared: false - }, - likes: [], - likesPage: 1, - shares: [], - sharesPage: 1, - replyText: '', - replyStatus: {}, - replySensitive: false, - relationship: {}, - results: [], - pagination: {}, - min_id: 0, - max_id: 0, - reply_to_profile_id: 0, - thread: false, - showComments: false, - warning: false, - loaded: false, - loading: null, - replyingToId: this.statusId, - replyingToUsername: this.statusUsername, - replyToIndex: 0, - replySending: false, - emoji: window.App.util.emoji, - showReadMore: true, - showCaption: true, - layout: this.profileLayout, - showProfileMorePosts: false, - profileMorePosts: [], - replySending: false, - reactionBarLoading: true, - profileUrl: null, - } - }, - - mounted() { - this.fetchRelationships(); - if(localStorage.getItem('pf_metro_ui.exp.rm') == 'false') { - this.showReadMore = false; - } else { - this.showReadMore = true; - } - }, - - updated() { - $('.carousel').carousel(); - $('[data-toggle="tooltip"]').tooltip(); - if(this.showReadMore == true) { - window.pixelfed.readmore(); - } - }, - - methods: { - reportUrl() { - return '/i/report?type=post&id=' + this.status.id; - }, - - editUrl() { - return this.status.url + '/edit'; - }, - - timestampFormat() { - let ts = new Date(this.status.created_at); - return ts.toDateString(); - }, - - fetchData() { - let self = this; - axios.get('/api/v2/profile/'+this.statusUsername+'/status/'+this.statusId) - .then(response => { - self.status = response.data.status; - self.media = self.status.media_attachments; - self.likesPage = 2; - self.sharesPage = 2; - self.showCaption = !response.data.status.sensitive; - if(self.status.comments_disabled == false) { - self.showComments = true; - this.fetchComments(); - } - self.profileUrl = '/i/web/profile/_/' + response.data.status.account.id; - this.loaded = true; - setTimeout(function() { - self.fetchProfilePosts(); - }, 3000); - setTimeout(function() { - self.fetchState(); - document.querySelectorAll('.status-comment .postCommentsContainer .comment-body a').forEach(function(i, e) { - i.href = App.util.format.rewriteLinks(i); - }); - }, 500); - }).catch(error => { - swal('Oops!', 'An error occured, please try refreshing the page.', 'error'); - }); - }, - - fetchState() { - let self = this; - axios.get('/api/v2/profile/'+this.statusUsername+'/status/'+this.statusId+'/state') - .then(res => { - self.user = res.data.user; - window._sharedData.curUser = self.user; - window.App.util.navatar(); - self.likes = res.data.likes; - self.shares = res.data.shares; - self.reactions = res.data.reactions; - self.reactionBarLoading = false; - }); - }, - - likesModal() { - if($('body').hasClass('loggedIn') == false) { - window.location.href = '/login?next=' + encodeURIComponent('/p/' + this.status.shortcode); - return; - } - if(this.status.favourites_count == 0) { - return; - } - if(this.likes.length) { - this.$refs.likesModal.show(); - return; - } - axios.get('/api/v2/likes/profile/'+this.statusUsername+'/status/'+this.statusId) - .then(res => { - this.likes = res.data.data; - this.$refs.likesModal.show(); - }); - }, - - sharesModal() { - if(this.status.reblogs_count == 0 || $('body').hasClass('loggedIn') == false) { - window.location.href = '/login?next=' + encodeURIComponent('/p/' + this.status.shortcode); - return; - } - if(this.shares.length) { - this.$refs.sharesModal.show(); - return; - } - axios.get('/api/v2/shares/profile/'+this.statusUsername+'/status/'+this.statusId) - .then(res => { - this.shares = res.data.data; - this.$refs.sharesModal.show(); - }); - }, - - infiniteLikesHandler($state) { - let api = '/api/v2/likes/profile/'+this.statusUsername+'/status/'+this.statusId; - axios.get(api, { - params: { - page: this.likesPage, - }, - }).then(({ data }) => { - if (data.data.length > 0) { - this.likes.push(...data.data); - this.likesPage++; - $state.loaded(); - } else { - $state.complete(); - } - }); - }, - - infiniteSharesHandler($state) { - axios.get('/api/v2/shares/profile/'+this.statusUsername+'/status/'+this.statusId, { - params: { - page: this.sharesPage, - }, - }).then(({ data }) => { - if (data.data.length > 0) { - this.shares.push(...data.data); - this.sharesPage++; - $state.loaded(); - } else { - $state.complete(); - } - }); - }, - - likeStatus(event) { - if($('body').hasClass('loggedIn') == false) { - window.location.href = '/login?next=' + encodeURIComponent(window.location.pathname); - return; - } - - axios.post('/i/like', { - item: this.status.id - }).then(res => { - this.status.favourites_count = res.data.count; - if(this.reactions.liked == true) { - this.reactions.liked = false; - let user = this.user.id; - this.likes = this.likes.filter(function(like) { - return like.id !== user; - }); - } else { - this.reactions.liked = true; - let user = this.user; - this.likes.unshift(user); - setTimeout(function() { - event.target.classList.add('animate__animated', 'animate__bounce'); - },100); - } - }).catch(err => { - console.error(err); - swal('Error', 'Something went wrong, please try again later.', 'error'); - }); - window.navigator.vibrate(200); - }, - - shareStatus() { - if($('body').hasClass('loggedIn') == false) { - window.location.href = '/login?next=' + encodeURIComponent(window.location.pathname); - return; - } - - axios.post('/i/share', { - item: this.status.id - }).then(res => { - this.status.reblogs_count = res.data.count; - if(this.reactions.shared == true) { - this.reactions.shared = false; - let user = this.user.id; - this.shares = this.shares.filter(function(reaction) { - return reaction.id !== user; - }); - } else { - this.reactions.shared = true; - let user = this.user; - this.shares.push(user); - } - }).catch(err => { - console.error(err); - swal('Error', 'Something went wrong, please try again later.', 'error'); - }); - }, - - bookmarkStatus() { - if($('body').hasClass('loggedIn') == false) { - window.location.href = '/login?next=' + encodeURIComponent(window.location.pathname); - return; - } - - axios.post('/i/bookmark', { - item: this.status.id - }).then(res => { - if(this.reactions.bookmarked == true) { - this.reactions.bookmarked = false; - } else { - this.reactions.bookmarked = true; - } - }).catch(err => { - swal('Error', 'Something went wrong, please try again later.', 'error'); - }); - }, - - blockProfile() { - if($('body').hasClass('loggedIn') == false) { - return; - } - - axios.post('/i/block', { - type: 'user', - item: this.status.account.id - }).then(res => { - this.$refs.ctxModal.hide(); - this.relationship.blocking = true; - swal('Success', 'You have successfully blocked ' + this.status.account.acct, 'success'); - }).catch(err => { - swal('Error', 'Something went wrong. Please try again later.', 'error'); - }); - }, - - unblockProfile() { - if($('body').hasClass('loggedIn') == false) { - return; - } - - axios.post('/i/unblock', { - type: 'user', - item: this.status.account.id - }).then(res => { - this.relationship.blocking = false; - this.$refs.ctxModal.hide(); - swal('Success', 'You have successfully unblocked ' + this.status.account.acct, 'success'); - }).catch(err => { - swal('Error', 'Something went wrong. Please try again later.', 'error'); - }); - }, - - deletePost(status) { - if(!this.ownerOrAdmin()) { - return; - } - var result = confirm('Are you sure you want to delete this post?'); - if (result) { - if($('body').hasClass('loggedIn') == false) { - return; - } - axios.post('/i/delete', { - type: 'status', - item: this.status.id - }).then(res => { - swal('Success', 'You have successfully deleted this post', 'success'); - setTimeout(function() { - window.location.href = '/'; - }, 3000); - }).catch(err => { - swal('Error', 'Something went wrong. Please try again later.', 'error'); - }); - } - }, - - owner() { - return this.user.id === this.status.account.id; - }, - - admin() { - return this.user.is_admin == true; - }, - - ownerOrAdmin() { - return this.owner() || this.admin(); - }, - - postReply() { - let self = this; - this.replySending = true; - if(this.replyText.length == 0 || - this.replyText.trim() == '@'+this.status.account.acct) { - self.replyText = null; - $('textarea[name="comment"]').blur(); - return; - } - let data = { - item: this.replyingToId, - comment: this.replyText, - sensitive: this.replySensitive - } - - this.replyText = ''; - - axios.post('/i/comment', data) - .then(function(res) { - let entity = res.data.entity; - if(entity.in_reply_to_id == self.status.id) { - if(self.layout == 'metro') { - self.results.push(entity); - } else { - self.results.unshift(entity); - } - let elem = $('.status-comments')[0]; - elem.scrollTop = elem.clientHeight * 2; - } else { - if(self.replyToIndex >= 0) { - let el = self.results[self.replyToIndex]; - el.replies.push(entity); - el.reply_count = el.reply_count + 1; - } - } - self.$refs.replyModal.hide(); - self.replySending = false; - }); - }, - - deleteComment(id, i) { - axios.post('/i/delete', { - type: 'comment', - item: id - }).then(res => { - this.results.splice(i, 1); - }).catch(err => { - swal('Something went wrong!', 'Please try again later', 'error'); - }); - }, - - deleteCommentReply(id, i, pi) { - axios.post('/i/delete', { - type: 'comment', - item: id - }).then(res => { - this.results[pi].replies.splice(i, 1); - --this.results[pi].reply_count; - }).catch(err => { - swal('Something went wrong!', 'Please try again later', 'error'); - }); - }, - - l(e) { - let len = e.length; - if(len < 10) { return e; } - return e.substr(0, 10)+'...'; - }, - - replyFocus(e, index, prependUsername = false) { - if($('body').hasClass('loggedIn') == false) { - this.redirect('/login?next=' + encodeURIComponent(window.location.pathname)); - return; - } - - if(this.status.comments_disabled) { - return; - } - - this.replyToIndex = index; - this.replyingToId = e.id; - this.replyingToUsername = e.account.username; - this.reply_to_profile_id = e.account.id; - let username = e.account.local ? '@' + e.account.username + ' ' - : '@' + e.account.acct + ' '; - if(prependUsername == true) { - this.replyText = username; - } - this.$refs.replyModal.show(); - }, - - fetchComments() { - let url = '/api/v2/comments/'+this.statusProfileId+'/status/'+this.statusId; - axios.get(url) - .then(response => { - let self = this; - this.results = this.layout == 'metro' ? - _.reverse(response.data.data) : - response.data.data; - this.pagination = response.data.meta.pagination; - if(this.results.length > 0) { - $('.load-more-link').removeClass('d-none'); - } - $('.postCommentsLoader').addClass('d-none'); - $('.postCommentsContainer').removeClass('d-none'); - setTimeout(function() { - document.querySelectorAll('.status-comment .postCommentsContainer .comment-body a').forEach(function(i, e) { - i.href = App.util.format.rewriteLinks(i); - }); - }, 500); - }).catch(error => { - if(!error.response) { - $('.postCommentsLoader .lds-ring') - .attr('style','width:100%') - .addClass('pt-4 font-weight-bold text-muted') - .text('An error occurred, cannot fetch comments. Please try again later.'); - } else { - switch(error.response.status) { - case 401: - $('.postCommentsLoader .lds-ring') - .attr('style','width:100%') - .addClass('pt-4 font-weight-bold text-muted') - .text('Please login to view.'); - break; - - default: - $('.postCommentsLoader .lds-ring') - .attr('style','width:100%') - .addClass('pt-4 font-weight-bold text-muted') - .text('An error occurred, cannot fetch comments. Please try again later.'); - break; - } - } - }); - }, - - loadMore(e) { - e.preventDefault(); - if(this.pagination.total_pages == 1 || this.pagination.current_page == this.pagination.total_pages) { - $('.load-more-link').addClass('d-none'); - return; - } - $('.load-more-link').addClass('d-none'); - $('.postCommentsLoader').removeClass('d-none'); - let next = this.pagination.links.next; - axios.get(next) - .then(response => { - let self = this; - let res = response.data.data; - $('.postCommentsLoader').addClass('d-none'); - for(let i=0; i < res.length; i++) { - this.results.unshift(res[i]); - } - this.pagination = response.data.meta.pagination; - $('.load-more-link').removeClass('d-none'); - }); - }, - - likeReply(status, $event) { - if($('body').hasClass('loggedIn') == false) { - swal('Login', 'Please login to perform this action.', 'info'); - return; - } - - axios.post('/i/like', { - item: status.id - }).then(res => { - status.favourites_count = res.data.count; - if(status.favourited == true) { - status.favourited = false; - } else { - status.favourited = true; - } - }).catch(err => { - swal('Error', 'Something went wrong, please try again later.', 'error'); - }); - }, - - truncate(str,lim) { - return _.truncate(str,{ - length: lim - }); - }, - - timeAgo(ts) { - return App.util.format.timeAgo(ts); - }, - - emojiReaction() { - let em = event.target.innerText; - if(this.replyText.length == 0) { - this.reply_to_profile_id = this.status.account.id; - this.replyText = em + ' '; - $('textarea[name="comment"]').focus(); - } else { - this.reply_to_profile_id = this.status.account.id; - this.replyText += em + ' '; - $('textarea[name="comment"]').focus(); - } - }, - - toggleCommentVisibility() { - if(this.ownerOrAdmin() == false) { - return; - } - - let state = this.status.comments_disabled; - let self = this; - - if(state == true) { - // re-enable comments - axios.post('/i/visibility', { - item: self.status.id, - disableComments: false - }).then(function(res) { - self.status.comments_disabled = false; - self.$refs.ctxModal.hide(); - window.location.reload(); - }).catch(function(err) { - return; - }); - } else { - // disable comments - axios.post('/i/visibility', { - item: self.status.id, - disableComments: true - }).then(function(res) { - self.status.comments_disabled = true; - self.showComments = false; - self.$refs.ctxModal.hide(); - }).catch(function(err) { - return; - }); - } - }, - - fetchRelationships() { - if(document.querySelectorAll('body')[0].classList.contains('loggedIn') == false) { - this.fetchData(); - return; - } else { - axios.get('/api/pixelfed/v1/accounts/relationships', { - params: { - 'id[]': this.statusProfileId - } - }).then(res => { - if(res.data[0] == null) { - this.fetchData(); - return; - } - this.relationship = res.data[0]; - if(res.data[0].blocking == true) { - this.loaded = true; - this.warning = true; - return; - } else { - this.fetchData(); - return; - } - }); - } - }, - - visibilityModal() { - switch(this.status.visibility) { - case 'public': - swal('Public Post', 'This post is visible to everyone.', 'info'); - break; - - case 'unlisted': - swal('Unlisted Post', 'This post is visible on profiles and with a direct links. It is not displayed on timelines.', 'info'); - break; - - case 'private': - swal('Private Post', 'This post is only visible to followers.', 'info'); - break; - } - }, - - toggleReplies(reply) { - if(reply.thread) { - reply.thread = false; - } else { - if(reply.replies.length > 0) { - reply.thread = true; - return; - } - let url = '/api/v2/comments/'+reply.account.id+'/status/'+reply.id; - axios.get(url) - .then(response => { - reply.replies = _.reverse(response.data.data); - reply.thread = true; - }); - } - }, - - redirect(url) { - window.location.href = url; - }, - - permalinkUrl(reply, showOrigin = false) { - let profile = reply.account; - if(profile.local == true) { - return reply.url; - } else { - return showOrigin ? - reply.url : - '/i/web/post/_/' + profile.id + '/' + reply.id; - } - }, - - fetchProfilePosts() { - if(!$('body').hasClass('loggedIn') && this.loaded) { - return; - } - let self = this; - let apiUrl = '/api/pixelfed/v1/accounts/' + this.statusProfileId + '/statuses'; - axios.get(apiUrl, { - params: { - only_media: true, - min_id: 1, - limit: 9 - } - }) - .then(res => { - let data = res.data.filter(function(status) { - return status.media_attachments.length > 0 && - status.id != self.statusId && - status.sensitive == false - }); - let ids = data.map(status => status.id); - if(data.length >= 3) { - self.showProfileMorePosts = true; - } - self.profileMorePosts = data.slice(0,6); - }) - }, - - previewUrl(status) { - return status.sensitive ? '/storage/no-preview.png?v=' + new Date().getTime() : status.media_attachments[0].preview_url; - }, - - previewBackground(status) { - let preview = this.previewUrl(status); - return 'background-image: url(' + preview + ');'; - }, - - getStatusUrl(status, showOrigin = false) { - - if(status.local == true || showOrigin == true) { - return status.url; - } - - return '/i/web/post/_/' + status.account.id + '/' + status.id; - }, - - showTaggedPeopleModal() { - if(!$('body').hasClass('loggedIn') && this.loaded) { - return; - } - this.$refs.taggedModal.show(); - }, - - untagMe() { - this.$refs.taggedModal.hide(); - let id = this.user.id; - axios.post('/api/local/compose/tag/untagme', { - status_id: this.statusId, - profile_id: id - }).then(res => { - this.status.taggedPeople = this.status.taggedPeople.filter(t => { - return t.id != id; - }); - swal('Untagged', 'You have been untagged from this post.', 'success'); - }).catch(err => { - swal('An Error Occurred', 'Please try again later.', 'error'); - }); - }, - - copyPostUrl() { - navigator.clipboard.writeText(this.statusUrl); - return; - }, - - moderatePost(action, $event) { - let status = this.status; - let username = status.account.username; - let msg = ''; - let self = this; - switch(action) { - case 'addcw': - msg = 'Are you sure you want to add a content warning to this post?'; - swal({ - title: 'Confirm', - text: msg, - icon: 'warning', - buttons: true, - dangerMode: true - }).then(res => { - if(res) { - axios.post('/api/v2/moderator/action', { - action: action, - item_id: status.id, - item_type: 'status' - }).then(res => { - swal('Success', 'Successfully added content warning', 'success'); - status.sensitive = true; - self.ctxModMenuClose(); - }).catch(err => { - swal( - 'Error', - 'Something went wrong, please try again later.', - 'error' - ); - self.ctxModMenuClose(); - }); - } - }); - break; - - case 'remcw': - msg = 'Are you sure you want to remove the content warning on this post?'; - swal({ - title: 'Confirm', - text: msg, - icon: 'warning', - buttons: true, - dangerMode: true - }).then(res => { - if(res) { - axios.post('/api/v2/moderator/action', { - action: action, - item_id: status.id, - item_type: 'status' - }).then(res => { - swal('Success', 'Successfully added content warning', 'success'); - status.sensitive = false; - self.ctxModMenuClose(); - }).catch(err => { - swal( - 'Error', - 'Something went wrong, please try again later.', - 'error' - ); - self.ctxModMenuClose(); - }); - } - }); - break; - - case 'unlist': - msg = 'Are you sure you want to unlist this post?'; - swal({ - title: 'Confirm', - text: msg, - icon: 'warning', - buttons: true, - dangerMode: true - }).then(res => { - if(res) { - axios.post('/api/v2/moderator/action', { - action: action, - item_id: status.id, - item_type: 'status' - }).then(res => { - // this.feed = this.feed.filter(f => { - // return f.id != status.id; - // }); - swal('Success', 'Successfully unlisted post', 'success'); - self.ctxModMenuClose(); - }).catch(err => { - self.ctxModMenuClose(); - swal( - 'Error', - 'Something went wrong, please try again later.', - 'error' - ); - }); - } - }); - break; - } - }, - - ctxMenu() { - this.$refs.ctxModal.show(); - return; - }, - - closeCtxMenu(truncate) { - this.$refs.ctxModal.hide(); - }, - - ctxModMenu() { - this.$refs.ctxModal.hide(); - this.$refs.ctxModModal.show(); - }, - - ctxModMenuClose() { - this.$refs.ctxModal.hide(); - this.$refs.ctxModModal.hide(); - }, - - ctxMenuCopyLink() { - let status = this.status; - navigator.clipboard.writeText(status.url); - this.closeCtxMenu(); - return; - }, - - ctxMenuFollow() { - let id = this.status.account.id; - axios.post('/i/follow', { - item: id - }).then(res => { - let username = this.status.account.acct; - this.relationship.following = true; - this.$refs.ctxModal.hide(); - setTimeout(function() { - swal('Follow successful!', 'You are now following ' + username, 'success'); - }, 500); - }); - }, - - ctxMenuUnfollow() { - let id = this.status.account.id; - axios.post('/i/follow', { - item: id - }).then(res => { - let username = this.status.account.acct; - this.relationship.following = false; - this.$refs.ctxModal.hide(); - setTimeout(function() { - swal('Unfollow successful!', 'You are no longer following ' + username, 'success'); - }, 500); - }); - }, - - }, + props: [ + 'status-id', + 'status-username', + 'status-template', + 'status-url', + 'status-profile-url', + 'status-avatar', + 'status-profile-id', + 'profile-layout' + ], + data() { + return { + config: window.App.config, + status: false, + media: {}, + user: false, + reactions: { + liked: false, + shared: false + }, + likes: [], + likesPage: 1, + shares: [], + sharesPage: 1, + replyText: '', + replyStatus: {}, + replySensitive: false, + relationship: {}, + results: [], + pagination: {}, + min_id: 0, + max_id: 0, + reply_to_profile_id: 0, + thread: false, + showComments: false, + warning: false, + loaded: false, + loading: null, + replyingToId: this.statusId, + replyingToUsername: this.statusUsername, + replyToIndex: 0, + replySending: false, + emoji: window.App.util.emoji, + showReadMore: true, + showCaption: true, + layout: this.profileLayout, + showProfileMorePosts: false, + profileMorePosts: [], + replySending: false, + reactionBarLoading: true, + profileUrl: null, + } + }, + + mounted() { + this.fetchRelationships(); + if(localStorage.getItem('pf_metro_ui.exp.rm') == 'false') { + this.showReadMore = false; + } else { + this.showReadMore = true; + } + }, + + updated() { + $('.carousel').carousel(); + $('[data-toggle="tooltip"]').tooltip(); + if(this.showReadMore == true) { + window.pixelfed.readmore(); + } + document.querySelectorAll('.hashtag').forEach(function(i, e) { + i.href = App.util.format.rewriteLinks(i); + }); + }, + + methods: { + reportUrl() { + return '/i/report?type=post&id=' + this.status.id; + }, + + editUrl() { + return this.status.url + '/edit'; + }, + + timestampFormat() { + let ts = new Date(this.status.created_at); + return ts.toDateString(); + }, + + fetchData() { + let self = this; + axios.get('/api/v2/profile/'+this.statusUsername+'/status/'+this.statusId) + .then(response => { + self.status = response.data.status; + self.media = self.status.media_attachments; + self.likesPage = 2; + self.sharesPage = 2; + self.showCaption = !response.data.status.sensitive; + if(self.status.comments_disabled == false) { + self.showComments = true; + this.fetchComments(); + } + self.profileUrl = '/i/web/profile/_/' + response.data.status.account.id; + this.loaded = true; + setTimeout(function() { + self.fetchProfilePosts(); + }, 3000); + setTimeout(function() { + self.fetchState(); + document.querySelectorAll('.status-comment .postCommentsContainer .comment-body a').forEach(function(i, e) { + i.href = App.util.format.rewriteLinks(i); + }); + }, 500); + }).catch(error => { + swal('Oops!', 'An error occured, please try refreshing the page.', 'error'); + }); + }, + + fetchState() { + let self = this; + axios.get('/api/v2/profile/'+this.statusUsername+'/status/'+this.statusId+'/state') + .then(res => { + self.user = res.data.user; + window._sharedData.curUser = self.user; + window.App.util.navatar(); + self.likes = res.data.likes; + self.shares = res.data.shares; + self.reactions = res.data.reactions; + self.reactionBarLoading = false; + }); + }, + + likesModal() { + if($('body').hasClass('loggedIn') == false) { + window.location.href = '/login?next=' + encodeURIComponent('/p/' + this.status.shortcode); + return; + } + if(this.status.favourites_count == 0) { + return; + } + if(this.likes.length) { + this.$refs.likesModal.show(); + return; + } + axios.get('/api/v2/likes/profile/'+this.statusUsername+'/status/'+this.statusId) + .then(res => { + this.likes = res.data.data; + this.$refs.likesModal.show(); + }); + }, + + sharesModal() { + if(this.status.reblogs_count == 0 || $('body').hasClass('loggedIn') == false) { + window.location.href = '/login?next=' + encodeURIComponent('/p/' + this.status.shortcode); + return; + } + if(this.shares.length) { + this.$refs.sharesModal.show(); + return; + } + axios.get('/api/v2/shares/profile/'+this.statusUsername+'/status/'+this.statusId) + .then(res => { + this.shares = res.data.data; + this.$refs.sharesModal.show(); + }); + }, + + infiniteLikesHandler($state) { + let api = '/api/v2/likes/profile/'+this.statusUsername+'/status/'+this.statusId; + axios.get(api, { + params: { + page: this.likesPage, + }, + }).then(({ data }) => { + if (data.data.length > 0) { + this.likes.push(...data.data); + this.likesPage++; + $state.loaded(); + } else { + $state.complete(); + } + }); + }, + + infiniteSharesHandler($state) { + axios.get('/api/v2/shares/profile/'+this.statusUsername+'/status/'+this.statusId, { + params: { + page: this.sharesPage, + }, + }).then(({ data }) => { + if (data.data.length > 0) { + this.shares.push(...data.data); + this.sharesPage++; + $state.loaded(); + } else { + $state.complete(); + } + }); + }, + + likeStatus(event) { + if($('body').hasClass('loggedIn') == false) { + window.location.href = '/login?next=' + encodeURIComponent(window.location.pathname); + return; + } + + axios.post('/i/like', { + item: this.status.id + }).then(res => { + this.status.favourites_count = res.data.count; + if(this.reactions.liked == true) { + this.reactions.liked = false; + let user = this.user.id; + this.likes = this.likes.filter(function(like) { + return like.id !== user; + }); + } else { + this.reactions.liked = true; + let user = this.user; + this.likes.unshift(user); + setTimeout(function() { + event.target.classList.add('animate__animated', 'animate__bounce'); + },100); + } + }).catch(err => { + console.error(err); + swal('Error', 'Something went wrong, please try again later.', 'error'); + }); + window.navigator.vibrate(200); + }, + + shareStatus() { + if($('body').hasClass('loggedIn') == false) { + window.location.href = '/login?next=' + encodeURIComponent(window.location.pathname); + return; + } + + axios.post('/i/share', { + item: this.status.id + }).then(res => { + this.status.reblogs_count = res.data.count; + if(this.reactions.shared == true) { + this.reactions.shared = false; + let user = this.user.id; + this.shares = this.shares.filter(function(reaction) { + return reaction.id !== user; + }); + } else { + this.reactions.shared = true; + let user = this.user; + this.shares.push(user); + } + }).catch(err => { + console.error(err); + swal('Error', 'Something went wrong, please try again later.', 'error'); + }); + }, + + bookmarkStatus() { + if($('body').hasClass('loggedIn') == false) { + window.location.href = '/login?next=' + encodeURIComponent(window.location.pathname); + return; + } + + axios.post('/i/bookmark', { + item: this.status.id + }).then(res => { + if(this.reactions.bookmarked == true) { + this.reactions.bookmarked = false; + } else { + this.reactions.bookmarked = true; + } + }).catch(err => { + swal('Error', 'Something went wrong, please try again later.', 'error'); + }); + }, + + blockProfile() { + if($('body').hasClass('loggedIn') == false) { + return; + } + + axios.post('/i/block', { + type: 'user', + item: this.status.account.id + }).then(res => { + this.$refs.ctxModal.hide(); + this.relationship.blocking = true; + swal('Success', 'You have successfully blocked ' + this.status.account.acct, 'success'); + }).catch(err => { + swal('Error', 'Something went wrong. Please try again later.', 'error'); + }); + }, + + unblockProfile() { + if($('body').hasClass('loggedIn') == false) { + return; + } + + axios.post('/i/unblock', { + type: 'user', + item: this.status.account.id + }).then(res => { + this.relationship.blocking = false; + this.$refs.ctxModal.hide(); + swal('Success', 'You have successfully unblocked ' + this.status.account.acct, 'success'); + }).catch(err => { + swal('Error', 'Something went wrong. Please try again later.', 'error'); + }); + }, + + deletePost(status) { + if(!this.ownerOrAdmin()) { + return; + } + var result = confirm('Are you sure you want to delete this post?'); + if (result) { + if($('body').hasClass('loggedIn') == false) { + return; + } + axios.post('/i/delete', { + type: 'status', + item: this.status.id + }).then(res => { + swal('Success', 'You have successfully deleted this post', 'success'); + setTimeout(function() { + window.location.href = '/'; + }, 3000); + }).catch(err => { + swal('Error', 'Something went wrong. Please try again later.', 'error'); + }); + } + }, + + owner() { + return this.user.id === this.status.account.id; + }, + + admin() { + return this.user.is_admin == true; + }, + + ownerOrAdmin() { + return this.owner() || this.admin(); + }, + + postReply() { + let self = this; + this.replySending = true; + if(this.replyText.length == 0 || + this.replyText.trim() == '@'+this.status.account.acct) { + self.replyText = null; + $('textarea[name="comment"]').blur(); + return; + } + let data = { + item: this.replyingToId, + comment: this.replyText, + sensitive: this.replySensitive + } + + this.replyText = ''; + + axios.post('/i/comment', data) + .then(function(res) { + let entity = res.data.entity; + if(entity.in_reply_to_id == self.status.id) { + if(self.layout == 'metro') { + self.results.push(entity); + } else { + self.results.unshift(entity); + } + let elem = $('.status-comments')[0]; + elem.scrollTop = elem.clientHeight * 2; + } else { + if(self.replyToIndex >= 0) { + let el = self.results[self.replyToIndex]; + el.replies.push(entity); + el.reply_count = el.reply_count + 1; + } + } + self.$refs.replyModal.hide(); + self.replySending = false; + }); + }, + + deleteComment(id, i) { + axios.post('/i/delete', { + type: 'comment', + item: id + }).then(res => { + this.results.splice(i, 1); + }).catch(err => { + swal('Something went wrong!', 'Please try again later', 'error'); + }); + }, + + deleteCommentReply(id, i, pi) { + axios.post('/i/delete', { + type: 'comment', + item: id + }).then(res => { + this.results[pi].replies.splice(i, 1); + --this.results[pi].reply_count; + }).catch(err => { + swal('Something went wrong!', 'Please try again later', 'error'); + }); + }, + + l(e) { + let len = e.length; + if(len < 10) { return e; } + return e.substr(0, 10)+'...'; + }, + + replyFocus(e, index, prependUsername = false) { + if($('body').hasClass('loggedIn') == false) { + this.redirect('/login?next=' + encodeURIComponent(window.location.pathname)); + return; + } + + if(this.status.comments_disabled) { + return; + } + + this.replyToIndex = index; + this.replyingToId = e.id; + this.replyingToUsername = e.account.username; + this.reply_to_profile_id = e.account.id; + let username = e.account.local ? '@' + e.account.username + ' ' + : '@' + e.account.acct + ' '; + if(prependUsername == true) { + this.replyText = username; + } + this.$refs.replyModal.show(); + }, + + fetchComments() { + let url = '/api/v2/comments/'+this.statusProfileId+'/status/'+this.statusId; + axios.get(url) + .then(response => { + let self = this; + this.results = this.layout == 'metro' ? + _.reverse(response.data.data) : + response.data.data; + this.pagination = response.data.meta.pagination; + if(this.results.length > 0) { + $('.load-more-link').removeClass('d-none'); + } + $('.postCommentsLoader').addClass('d-none'); + $('.postCommentsContainer').removeClass('d-none'); + setTimeout(function() { + document.querySelectorAll('.status-comment .postCommentsContainer .comment-body a').forEach(function(i, e) { + i.href = App.util.format.rewriteLinks(i); + }); + }, 500); + }).catch(error => { + if(!error.response) { + $('.postCommentsLoader .lds-ring') + .attr('style','width:100%') + .addClass('pt-4 font-weight-bold text-muted') + .text('An error occurred, cannot fetch comments. Please try again later.'); + } else { + switch(error.response.status) { + case 401: + $('.postCommentsLoader .lds-ring') + .attr('style','width:100%') + .addClass('pt-4 font-weight-bold text-muted') + .text('Please login to view.'); + break; + + default: + $('.postCommentsLoader .lds-ring') + .attr('style','width:100%') + .addClass('pt-4 font-weight-bold text-muted') + .text('An error occurred, cannot fetch comments. Please try again later.'); + break; + } + } + }); + }, + + loadMore(e) { + e.preventDefault(); + if(this.pagination.total_pages == 1 || this.pagination.current_page == this.pagination.total_pages) { + $('.load-more-link').addClass('d-none'); + return; + } + $('.load-more-link').addClass('d-none'); + $('.postCommentsLoader').removeClass('d-none'); + let next = this.pagination.links.next; + axios.get(next) + .then(response => { + let self = this; + let res = response.data.data; + $('.postCommentsLoader').addClass('d-none'); + for(let i=0; i < res.length; i++) { + this.results.unshift(res[i]); + } + this.pagination = response.data.meta.pagination; + $('.load-more-link').removeClass('d-none'); + }); + }, + + likeReply(status, $event) { + if($('body').hasClass('loggedIn') == false) { + swal('Login', 'Please login to perform this action.', 'info'); + return; + } + + axios.post('/i/like', { + item: status.id + }).then(res => { + status.favourites_count = res.data.count; + if(status.favourited == true) { + status.favourited = false; + } else { + status.favourited = true; + } + }).catch(err => { + swal('Error', 'Something went wrong, please try again later.', 'error'); + }); + }, + + truncate(str,lim) { + return _.truncate(str,{ + length: lim + }); + }, + + timeAgo(ts) { + return App.util.format.timeAgo(ts); + }, + + emojiReaction() { + let em = event.target.innerText; + if(this.replyText.length == 0) { + this.reply_to_profile_id = this.status.account.id; + this.replyText = em + ' '; + $('textarea[name="comment"]').focus(); + } else { + this.reply_to_profile_id = this.status.account.id; + this.replyText += em + ' '; + $('textarea[name="comment"]').focus(); + } + }, + + toggleCommentVisibility() { + if(this.ownerOrAdmin() == false) { + return; + } + + let state = this.status.comments_disabled; + let self = this; + + if(state == true) { + // re-enable comments + axios.post('/i/visibility', { + item: self.status.id, + disableComments: false + }).then(function(res) { + self.status.comments_disabled = false; + self.$refs.ctxModal.hide(); + window.location.reload(); + }).catch(function(err) { + return; + }); + } else { + // disable comments + axios.post('/i/visibility', { + item: self.status.id, + disableComments: true + }).then(function(res) { + self.status.comments_disabled = true; + self.showComments = false; + self.$refs.ctxModal.hide(); + }).catch(function(err) { + return; + }); + } + }, + + fetchRelationships() { + if(document.querySelectorAll('body')[0].classList.contains('loggedIn') == false) { + this.fetchData(); + return; + } else { + axios.get('/api/pixelfed/v1/accounts/relationships', { + params: { + 'id[]': this.statusProfileId + } + }).then(res => { + if(res.data[0] == null) { + this.fetchData(); + return; + } + this.relationship = res.data[0]; + if(res.data[0].blocking == true) { + this.loaded = true; + this.warning = true; + return; + } else { + this.fetchData(); + return; + } + }); + } + }, + + visibilityModal() { + switch(this.status.visibility) { + case 'public': + swal('Public Post', 'This post is visible to everyone.', 'info'); + break; + + case 'unlisted': + swal('Unlisted Post', 'This post is visible on profiles and with a direct links. It is not displayed on timelines.', 'info'); + break; + + case 'private': + swal('Private Post', 'This post is only visible to followers.', 'info'); + break; + } + }, + + toggleReplies(reply) { + if(reply.thread) { + reply.thread = false; + } else { + if(reply.replies.length > 0) { + reply.thread = true; + return; + } + let url = '/api/v2/comments/'+reply.account.id+'/status/'+reply.id; + axios.get(url) + .then(response => { + reply.replies = _.reverse(response.data.data); + reply.thread = true; + }); + } + }, + + redirect(url) { + window.location.href = url; + }, + + permalinkUrl(reply, showOrigin = false) { + let profile = reply.account; + if(profile.local == true) { + return reply.url; + } else { + return showOrigin ? + reply.url : + '/i/web/post/_/' + profile.id + '/' + reply.id; + } + }, + + fetchProfilePosts() { + if(!$('body').hasClass('loggedIn') && this.loaded) { + return; + } + let self = this; + let apiUrl = '/api/pixelfed/v1/accounts/' + this.statusProfileId + '/statuses'; + axios.get(apiUrl, { + params: { + only_media: true, + min_id: 1, + limit: 9 + } + }) + .then(res => { + let data = res.data.filter(function(status) { + return status.media_attachments.length > 0 && + status.id != self.statusId && + status.sensitive == false + }); + let ids = data.map(status => status.id); + if(data.length >= 3) { + self.showProfileMorePosts = true; + } + self.profileMorePosts = data.slice(0,6); + }) + }, + + previewUrl(status) { + return status.sensitive ? '/storage/no-preview.png?v=' + new Date().getTime() : status.media_attachments[0].preview_url; + }, + + previewBackground(status) { + let preview = this.previewUrl(status); + return 'background-image: url(' + preview + ');'; + }, + + getStatusUrl(status, showOrigin = false) { + + if(status.local == true || showOrigin == true) { + return status.url; + } + + return '/i/web/post/_/' + status.account.id + '/' + status.id; + }, + + showTaggedPeopleModal() { + if(!$('body').hasClass('loggedIn') && this.loaded) { + return; + } + this.$refs.taggedModal.show(); + }, + + untagMe() { + this.$refs.taggedModal.hide(); + let id = this.user.id; + axios.post('/api/local/compose/tag/untagme', { + status_id: this.statusId, + profile_id: id + }).then(res => { + this.status.taggedPeople = this.status.taggedPeople.filter(t => { + return t.id != id; + }); + swal('Untagged', 'You have been untagged from this post.', 'success'); + }).catch(err => { + swal('An Error Occurred', 'Please try again later.', 'error'); + }); + }, + + copyPostUrl() { + navigator.clipboard.writeText(this.statusUrl); + return; + }, + + moderatePost(action, $event) { + let status = this.status; + let username = status.account.username; + let msg = ''; + let self = this; + switch(action) { + case 'addcw': + msg = 'Are you sure you want to add a content warning to this post?'; + swal({ + title: 'Confirm', + text: msg, + icon: 'warning', + buttons: true, + dangerMode: true + }).then(res => { + if(res) { + axios.post('/api/v2/moderator/action', { + action: action, + item_id: status.id, + item_type: 'status' + }).then(res => { + swal('Success', 'Successfully added content warning', 'success'); + status.sensitive = true; + self.ctxModMenuClose(); + }).catch(err => { + swal( + 'Error', + 'Something went wrong, please try again later.', + 'error' + ); + self.ctxModMenuClose(); + }); + } + }); + break; + + case 'remcw': + msg = 'Are you sure you want to remove the content warning on this post?'; + swal({ + title: 'Confirm', + text: msg, + icon: 'warning', + buttons: true, + dangerMode: true + }).then(res => { + if(res) { + axios.post('/api/v2/moderator/action', { + action: action, + item_id: status.id, + item_type: 'status' + }).then(res => { + swal('Success', 'Successfully added content warning', 'success'); + status.sensitive = false; + self.ctxModMenuClose(); + }).catch(err => { + swal( + 'Error', + 'Something went wrong, please try again later.', + 'error' + ); + self.ctxModMenuClose(); + }); + } + }); + break; + + case 'unlist': + msg = 'Are you sure you want to unlist this post?'; + swal({ + title: 'Confirm', + text: msg, + icon: 'warning', + buttons: true, + dangerMode: true + }).then(res => { + if(res) { + axios.post('/api/v2/moderator/action', { + action: action, + item_id: status.id, + item_type: 'status' + }).then(res => { + // this.feed = this.feed.filter(f => { + // return f.id != status.id; + // }); + swal('Success', 'Successfully unlisted post', 'success'); + self.ctxModMenuClose(); + }).catch(err => { + self.ctxModMenuClose(); + swal( + 'Error', + 'Something went wrong, please try again later.', + 'error' + ); + }); + } + }); + break; + } + }, + + ctxMenu() { + this.$refs.ctxModal.show(); + return; + }, + + closeCtxMenu(truncate) { + this.$refs.ctxModal.hide(); + }, + + ctxModMenu() { + this.$refs.ctxModal.hide(); + this.$refs.ctxModModal.show(); + }, + + ctxModMenuClose() { + this.$refs.ctxModal.hide(); + this.$refs.ctxModModal.hide(); + }, + + ctxMenuCopyLink() { + let status = this.status; + navigator.clipboard.writeText(status.url); + this.closeCtxMenu(); + return; + }, + + ctxMenuFollow() { + let id = this.status.account.id; + axios.post('/i/follow', { + item: id + }).then(res => { + let username = this.status.account.acct; + this.relationship.following = true; + this.$refs.ctxModal.hide(); + setTimeout(function() { + swal('Follow successful!', 'You are now following ' + username, 'success'); + }, 500); + }); + }, + + ctxMenuUnfollow() { + let id = this.status.account.id; + axios.post('/i/follow', { + item: id + }).then(res => { + let username = this.status.account.acct; + this.relationship.following = false; + this.$refs.ctxModal.hide(); + setTimeout(function() { + swal('Unfollow successful!', 'You are no longer following ' + username, 'success'); + }, 500); + }); + }, + + }, } diff --git a/resources/assets/js/components/RemoteProfile.vue b/resources/assets/js/components/RemoteProfile.vue index ec52156d2..4483bc052 100644 --- a/resources/assets/js/components/RemoteProfile.vue +++ b/resources/assets/js/components/RemoteProfile.vue @@ -227,6 +227,12 @@ this.fetchProfile(); }, + updated() { + document.querySelectorAll('.hashtag').forEach(function(i, e) { + i.href = App.util.format.rewriteLinks(i); + }); + }, + methods: { fetchProfile() { From 2cc577a6be9857ab8e93acb95ce9c84bb6a9ec82 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Fri, 18 Jun 2021 05:06:24 -0600 Subject: [PATCH 2/3] Update compiled assets --- public/js/rempos.js | Bin 74872 -> 74974 bytes public/js/rempro.js | Bin 25549 -> 25672 bytes public/manifest.json | Bin 398 -> 409 bytes public/mix-manifest.json | Bin 2207 -> 2207 bytes 4 files changed, 0 insertions(+), 0 deletions(-) diff --git a/public/js/rempos.js b/public/js/rempos.js index e650fb732a9ccc545b3b503bc5c4ecbe2b70cf92..c3fce86a4a238b887d82920540574914fafe2949 100644 GIT binary patch delta 1172 zcmexyg5};xmJK$1ERJsZ{*wy?<@k#=l$;%lyp=R-b(}&%awjLK1n3$Vo0z7h7?{QC z#N_D}r6wkqWaj4;N2er~BQ&OIpQj%e%U{FwD4Kfm_?6#_YI80eA#K405;!Kc3tP~QHit}?yOH#p# zlk!VS@^h^e42__=i!#$QN~{!&V5UJ0H-qsfC#s1u0j;P|Gex+>QCV&BZ8aebPyA6c z!Vt4n*T)d6R@c@rw#^U|#pETQ2v`I?3t6C*RN(+{~|A!pWbjQKFNoX^UrWWl-0oBy0kVB|xU+MIPEj~f_ksBrSe zhhk_7C-dAXM;DrSM@kS`BhVgAtLcg2j3S#a+&RyJ&E-IYg^{&@*dXT_>=m87fs=Rg zg**I{xgY6b(+ac=k5<0P`>%0rUh?Q1Gcmr|yzJR5QC@^YKu);$S!Htk6!y&(f7n^L v5K>b+8TB||+^zc=FEYZD*Y+7qj0+f9vLc)^K&8(1b{0lqrtPlmjK;D6rdp;g delta 1252 zcmZ`(OK1~O6y+t6{%n$&v_%S`k5C~onV5u{)>N@}QLrdRaZ#}F{SAMX+MZ2)H3nLUk_o5V?d?sx&?d;xp=iGD8y^q=cWc&Tm z_V$cjo9TdEwlVSYH7eTUTqJ_R{R7d^&A_-6R1lveSeDcq0#A5ua_<)2AD1AcK}XjU zM~|Z>s=-&-eHbipb@rUYbE*c%V%!u;+{J`blZu?pCU~WyhHwfCBol+7-1J~o8F#s< z&B;EfsVmf+tm0CCF~}!WIXgj6S&)zkk;NDcN2(PRoX(IKj2IQG22tZo&@|!rEKci{ zl1L&*sn$^hq;Lu^;0aal2hw?GuPN-TuZQk9SrgvOL`>KP=A;RG%=k^%d*+x$AJ`@X zhat?MO8cR5QzEaVvBXs)g`?GolSm;r$!CoiA{K;fWgPS@>oqwLb|Bg?&6F(Z%fR=L zKDl5-L1Ni2#|r|M(lL0t>O{Y>-1(aH&^0zJu@=Ip5_er|CUwsrnJxf|DYDSzBy>aM3pti{MP}t^w{b@ z`giHqLGYjD^^tmJ<>c{`;BD1Qf&`05CA^p$krk0AUZ2nJsl3U0nTM`w$LYfrr#8A8 z2lo0)?bVvp=B&r`?Jn+gH4y3O)>I?3ven(bU+SUl7v1z+u~l<#FWL?YrPno^#ea0r zISWHyygN!qilKvay3=7gmJThqX~p7_)yy!*(7x|H>ulX?nwHFme!8?byLQ_?JpTcJ zV6(w1>;kX`nvFM6*sy|^puE+5xCQjsy&m!ESf9r?6Sy-roGD|4r$+#5vFdPsp$T-C R|6=(sO<=b0x(W2T{{vbtVl)5% diff --git a/public/js/rempro.js b/public/js/rempro.js index 1a31ec549cedaac3c637b2e976de9686bda858e6..74e5c80086a1f2e5ec70d4a4cb746cb51a3b1764 100644 GIT binary patch delta 284 zcmX?mobkj7#tAK~N`|?S?vquMGdT>+43bhz%nc?_OV--he1N6Cv>+w1BsIk0VxsTOU) zDO3WQ0iqFZ2f9EfQ?t4(u}C4$7N`pB?Lfb-%^X3m1bAz8qN9zBbqr!-V>PV-M(AT* delta 147 zcmX?cg7NHe#tAK(RT=lQY>sD(lVnM)%1GLLQ2v-EhlzQjL0X!*>E?dVJ*-NeWufI( zX{C9|C7Jno8YMcZI+>c)Wr;-!dA6AvN`<-M`jd|bb#3 diff --git a/public/manifest.json b/public/manifest.json index 71401db0435c3f377b12e1902ea595a376d57716..edcd1d4a5a5bf5b7b8f70fc2ee74149238ec39f8 100644 GIT binary patch delta 23 ecmeBUp2@&d diff --git a/public/mix-manifest.json b/public/mix-manifest.json index 25f92b75a49692794a95d1eb6fcbe97a87ca741d..8d8678b601837d2ee800aa9003a0d38553d0c4e5 100644 GIT binary patch delta 58 zcmbO)IA3tXXEu>EQzMHcOJfToV>8RNlw{K+<7DH>?Ce@1My4sr2BsF4MkZ!SW(H|y N<`&7Rn_bzX7ylE delta 58 zcmbO)IA3tXXEqUoq@-jsQ`59m(_|yVL<2)JBeT@W?Ce@176wL%Nrp)&<^~3-mMI1) N2IfY_n_bzX7y Date: Fri, 18 Jun 2021 05:06:39 -0600 Subject: [PATCH 3/3] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 96aff8dc0..e858d7dc9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ - Updated RemoteProfile component, implement pagination. ([02b04a4b](https://github.com/pixelfed/pixelfed/commit/02b04a4b)) - Updated AP Helpers, generate notification for remote replies. ([8edd8294](https://github.com/pixelfed/pixelfed/commit/8edd8294)) - Updated like api, store status_profile_id and is_comment. ([c8c6b983](https://github.com/pixelfed/pixelfed/commit/c8c6b983)) +- Updated Remote Post + Profile hashtag to redirect to local urls. ([1fa08644](https://github.com/pixelfed/pixelfed/commit/1fa08644)) - ([](https://github.com/pixelfed/pixelfed/commit/)) ## [v0.11.0 (2021-06-01)](https://github.com/pixelfed/pixelfed/compare/v0.10.10...v0.11.0)