Merge pull request #2496 from pixelfed/staging

Staging
pull/2511/head
daniel 5 years ago committed by GitHub
commit d652de6f1b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -135,6 +135,10 @@
- Updated NotificationTransformer, add missing types. ([3a428366](https://github.com/pixelfed/pixelfed/commit/3a428366))
- Updated StatusService, fix json bug. ([1ea2db74](https://github.com/pixelfed/pixelfed/commit/1ea2db74))
- Updated NotificationTransformer, handle tagged deletes. ([881fa865](https://github.com/pixelfed/pixelfed/commit/881fa865))
- Updated horizon config, add new default values. ([90c8a721](https://github.com/pixelfed/pixelfed/commit/90c8a721))
- Updated ComposeModal, add maxlength attribute to alt text input. Fixes ([#2490](https://github.com/pixelfed/pixelfed/issues/2490)). ([526b5531](https://github.com/pixelfed/pixelfed/commit/526b5531))
- Updated PublicApiController, add state endpoint. ([9fc5a80c](https://github.com/pixelfed/pixelfed/commit/9fc5a80c))
- Updated PostComponent, add reply modal. ([a10d851f](https://github.com/pixelfed/pixelfed/commit/a10d851f))
## [v0.10.9 (2020-04-17)](https://github.com/pixelfed/pixelfed/compare/v0.10.8...v0.10.9)
### Added

@ -92,32 +92,47 @@ class PublicApiController extends Controller
$item = new Fractal\Resource\Item($status, new StatusStatelessTransformer());
$res = [
'status' => $this->fractal->createData($item)->toArray(),
'user' => [],
'likes' => [],
'shares' => [],
'reactions' => [
'liked' => false,
'shared' => false,
'bookmarked' => false,
],
];
return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
return $res;
});
return $res;
return response()->json($res);
}
$item = new Fractal\Resource\Item($status, new StatusTransformer());
$item = new Fractal\Resource\Item($status, new StatusStatelessTransformer());
$res = [
'status' => $this->fractal->createData($item)->toArray(),
'user' => $this->getUserData($request->user()),
'likes' => $this->getLikes($status),
'shares' => $this->getShares($status),
];
return response()->json($res);
}
public function statusState(Request $request, $username, int $postid)
{
$profile = Profile::whereUsername($username)->whereNull('status')->firstOrFail();
$status = Status::whereProfileId($profile->id)->findOrFail($postid);
$this->scopeCheck($profile, $status);
if(!Auth::check()) {
$res = [
'user' => [],
'likes' => [],
'shares' => [],
'reactions' => [
'liked' => false,
'shared' => false,
'bookmarked' => false,
],
];
return response()->json($res);
}
$res = [
'user' => $this->getUserData($request->user()),
'likes' => [],
'shares' => [],
'reactions' => [
'liked' => $status->liked(),
'shared' => $status->shared(),
'bookmarked' => $status->bookmarked(),
'liked' => (bool) $status->liked(),
'shared' => (bool) $status->shared(),
'bookmarked' => (bool) $status->bookmarked(),
],
];
return response()->json($res, 200, [], JSON_PRETTY_PRINT);
return response()->json($res);
}
public function statusComments(Request $request, $username, int $postId)

@ -97,7 +97,29 @@ return [
'trim' => [
'recent' => 60,
'pending' => 60,
'completed' => 60,
'recent_failed' => 10080,
'failed' => 10080,
'monitored' => 10080,
],
/*
|--------------------------------------------------------------------------
| Metrics
|--------------------------------------------------------------------------
|
| Here you can configure how many snapshots should be kept to display in
| the metrics graph. This will get used in combination with Horizon's
| `horizon:snapshot` schedule to define how long to retain metrics.
|
*/
'metrics' => [
'trim_snapshots' => [
'job' => 24,
'queue' => 24,
],
],
/*
@ -142,21 +164,25 @@ return [
'environments' => [
'production' => [
'supervisor-1' => [
'connection' => 'redis',
'queue' => ['high', 'default', 'feed'],
'balance' => 'auto',
'processes' => 20,
'tries' => 3,
'connection' => 'redis',
'queue' => ['high', 'default', 'feed'],
'balance' => 'auto',
'maxProcesses' => 20,
'memory' => 128,
'tries' => 3,
'nice' => 0,
],
],
'local' => [
'supervisor-1' => [
'connection' => 'redis',
'queue' => ['high', 'default', 'feed'],
'balance' => 'auto',
'processes' => 20,
'tries' => 3,
'connection' => 'redis',
'queue' => ['high', 'default', 'feed'],
'balance' => 'auto',
'maxProcesses' => 20,
'memory' => 128,
'tries' => 3,
'nice' => 0,
],
],
],

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -11,23 +11,23 @@
"/js/collectioncompose.js": "/js/collectioncompose.js?id=c6a07cb79dd7d6c7b8a0",
"/js/collections.js": "/js/collections.js?id=6f64a9032085ebac28b3",
"/js/components.js": "/js/components.js?id=88296701f1382d285031",
"/js/compose.js": "/js/compose.js?id=cd4e13a102450aa558cb",
"/js/compose.js": "/js/compose.js?id=1848b13369edc4e791ec",
"/js/compose-classic.js": "/js/compose-classic.js?id=283f19c895f4118a2a8b",
"/js/developers.js": "/js/developers.js?id=f75deca5ccf47d43eb07",
"/js/direct.js": "/js/direct.js?id=e1e4a830bfedc1870db1",
"/js/discover.js": "/js/discover.js?id=87071f23fc6d7118c66a",
"/js/hashtag.js": "/js/hashtag.js?id=4ebd78fde7fe65f5772c",
"/js/loops.js": "/js/loops.js?id=1dcb3790eb9ea4ea5848",
"/js/memoryprofile.js": "/js/memoryprofile.js?id=828e26d71b4330fedc1b",
"/js/memoryprofile.js": "/js/memoryprofile.js?id=75ea0503eca4f7ad3642",
"/js/mode-dot.js": "/js/mode-dot.js?id=dd9c87024fbaa8e75ac4",
"/js/profile.js": "/js/profile.js?id=559d809df9a10adc091a",
"/js/profile.js": "/js/profile.js?id=889a57bbb59e82d2d436",
"/js/profile-directory.js": "/js/profile-directory.js?id=855a548efdf56b8594bf",
"/js/quill.js": "/js/quill.js?id=866b31b9b9540305751d",
"/js/rempos.js": "/js/rempos.js?id=29dacb6e0ce2c936e5cd",
"/js/rempos.js": "/js/rempos.js?id=5d2841b753dc76ccd8ed",
"/js/rempro.js": "/js/rempro.js?id=6e7fc68a65bf82d5aec8",
"/js/search.js": "/js/search.js?id=5467143312ebae07695b",
"/js/status.js": "/js/status.js?id=70f5597c1069d7ead4bb",
"/js/status.js": "/js/status.js?id=c64d7542738b57eff9a7",
"/js/story-compose.js": "/js/story-compose.js?id=13f9606a3c4ce7acf17b",
"/js/theme-monokai.js": "/js/theme-monokai.js?id=8842103833ba4861bcfa",
"/js/timeline.js": "/js/timeline.js?id=44dae7cd501b4b9adce2"
"/js/timeline.js": "/js/timeline.js?id=b8504c8ae22a19804401"
}

@ -327,6 +327,7 @@
</div>
<p class="font-weight-bold text-center small text-muted pt-3 mb-0">When you tag someone, they are sent a notification.<br>For more information on tagging, <a href="#" class="text-primary" @click.prevent="showTagHelpCard()">click here</a>.</p>
</div>
<div v-if="page == 'tagPeopleHelp'" class="w-100 h-100 p-3">
<p class="mb-0 text-center py-3 px-2 lead">Tagging someone is like mentioning them, with the option to make it private between you.</p>
<p class="mb-3 py-3 px-2 font-weight-lighter">
@ -420,7 +421,7 @@
<div class="media">
<img :src="m.preview_url" class="mr-3" width="50px" height="50px">
<div class="media-body">
<textarea class="form-control" v-model="m.alt" placeholder="Add a media description here..."></textarea>
<textarea class="form-control" v-model="m.alt" placeholder="Add a media description here..." maxlength="140"></textarea>
<p class="help-text small text-right text-muted mb-0">{{m.alt ? m.alt.length : 0}}/140</p>
</div>
</div>
@ -468,7 +469,7 @@
<div class="media-body">
<div class="form-group">
<label class="font-weight-bold text-muted small">Media Description</label>
<textarea class="form-control" v-model="media[carouselCursor].alt" placeholder="Add a media description here..."></textarea>
<textarea class="form-control" v-model="media[carouselCursor].alt" placeholder="Add a media description here..." maxlength="140"></textarea>
<p class="help-text small text-muted mb-0 d-flex justify-content-between">
<span>Describe your photo for people with visual impairments.</span>
<span>{{media[carouselCursor].alt ? media[carouselCursor].alt.length : 0}}/140</span>

@ -35,24 +35,10 @@
</div>
<div v-if="user != false" class="float-right">
<div class="post-actions">
<div class="dropdown">
<button class="btn btn-link text-dark no-caret dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" title="Post options">
<span class="fas fa-ellipsis-v text-muted"></span>
<div>
<button class="btn btn-link text-dark no-caret" title="Post options" @click="ctxMenu()">
<span class="fas fa-ellipsis-v text-muted"></span>
</button>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuButton">
<a class="dropdown-item font-weight-bold" @click="copyPostUrl()">Copy Post Url</a>
<a class="dropdown-item font-weight-bold" @click="showEmbedPostModal()">Embed</a>
<div v-if="!owner()">
<a class="dropdown-item font-weight-bold" :href="reportUrl()">Report</a>
<a class="dropdown-item font-weight-bold" v-on:click="muteProfile()">Mute Profile</a>
<a class="dropdown-item font-weight-bold" v-on:click="blockProfile()">Block Profile</a>
</div>
<div v-if="ownerOrAdmin()">
<a class="dropdown-item font-weight-bold" href="#" v-on:click.prevent="toggleCommentVisibility">{{ showComments ? 'Disable' : 'Enable'}} Comments</a>
<a v-if="canEdit" class="dropdown-item font-weight-bold" :href="editUrl()">Edit</a>
<a class="dropdown-item font-weight-bold text-danger" v-on:click="deletePost(status)">Delete</a>
</div>
</div>
</div>
</div>
</div>
@ -108,30 +94,16 @@
</div>
<div class="float-right">
<div class="post-actions">
<div v-if="user != false" class="dropdown">
<button class="btn btn-link text-dark no-caret dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" title="Post options">
<span class="fas fa-ellipsis-v text-muted"></span>
</button>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuButton">
<a class="dropdown-item font-weight-bold" @click="copyPostUrl()">Copy Post Url</a>
<a class="dropdown-item font-weight-bold" @click="showEmbedPostModal()">Embed</a>
<span v-if="!owner()">
<a class="dropdown-item font-weight-bold" :href="reportUrl()">Report</a>
<a class="dropdown-item font-weight-bold" v-on:click="muteProfile">Mute Profile</a>
<a class="dropdown-item font-weight-bold" v-on:click="blockProfile">Block Profile</a>
</span>
<span v-if="ownerOrAdmin()">
<a class="dropdown-item font-weight-bold" href="#" v-on:click.prevent="toggleCommentVisibility">{{ showComments ? 'Disable' : 'Enable'}} Comments</a>
<a v-if="canEdit" class="dropdown-item font-weight-bold" :href="editUrl()">Edit</a>
<a class="dropdown-item font-weight-bold text-danger" v-on:click="deletePost">Delete</a>
</span>
</div>
<div v-if="user != false">
<button class="btn btn-link text-dark no-caret" title="Post options" @click="ctxMenu()">
<span class="fas fa-ellipsis-v text-muted"></span>
</button>
</div>
</div>
</div>
</div>
<div class="d-flex flex-md-column flex-column-reverse h-100" style="overflow-y: auto;">
<div class="card-body status-comments pb-5 pt-0">
<div class="card-body status-comments pt-0">
<div class="status-comment">
<div v-if="status.content.length" class="pt-3">
<div v-if="showCaption != true">
@ -227,7 +199,12 @@
</div>
</div>
<div class="card-body flex-grow-0 py-1">
<div v-if="reactionBarLoading" class="card-body flex-grow-0 py-4 text-center">
<div class="spinner-border" role="status">
<span class="sr-only">Loading...</span>
</div>
</div>
<div v-else class="card-body flex-grow-0 py-1">
<div v-if="loaded && user.hasOwnProperty('id')" class="reactions my-2 pb-1 d-flex justify-content-between">
<h3 v-bind:class="[reactions.liked ? 'fas fa-heart text-danger mr-3 m-0 cursor-pointer' : 'far fa-heart pr-3 m-0 like-btn cursor-pointer']" title="Like" v-on:click="likeStatus"></h3>
<h3 v-if="!status.comments_disabled" class="far fa-comment mr-3 m-0 cursor-pointer" title="Comment" v-on:click="replyFocus(status)"></h3>
@ -252,18 +229,13 @@
</div>
</div>
</div>
<!-- <div v-if="showComments && user.length !== 0" class="card-footer bg-white px-2 py-0">
<ul class="nav align-items-center emoji-reactions" style="overflow-x: scroll;flex-wrap: unset;">
<li class="nav-item" v-on:click="emojiReaction" v-for="e in emoji">{{e}}</li>
</ul>
</div> -->
<div v-if="showComments" class="card-footer bg-white sticky-md-bottom p-0">
<div v-if="user.length == 0" class="comment-form-guest p-3">
<a href="/login">Login</a> to like or comment.
</div>
<form v-else class="border-0 rounded-0 align-middle" method="post" action="/i/comment" :data-id="statusId" data-truncate="false">
<textarea class="form-control border-0 rounded-0" name="comment" placeholder="Add a comment…" autocomplete="off" autocorrect="off" style="height:56px;line-height: 18px;max-height:80px;resize: none; padding-right:4.2rem;" v-model="replyText"></textarea>
<input type="button" value="Post" class="d-inline-block btn btn-link font-weight-bold reply-btn text-decoration-none" v-on:click.prevent="postReply" :disabled="replyText.length == 0" />
<textarea class="form-control border-0 rounded-0" name="comment" placeholder="Add a comment…" autocomplete="off" autocorrect="off" style="height:56px;line-height: 18px;max-height:80px;resize: none; padding-right:4.2rem;" @click="replyFocus(status)"></textarea>
<input type="button" value="Post" class="d-inline-block btn btn-link font-weight-bold reply-btn text-decoration-none" disabled/>
</form>
</div>
</div>
@ -271,9 +243,6 @@
</div>
</div>
<div class="container" v-if="showProfileMorePosts">
<!-- <div class="py-4">
<hr>
</div> -->
<p class="text-lighter px-3 mt-5" style="font-weight: 600;font-size: 15px;">More posts from <a :href="'/'+statusUsername" class="text-dark">{{this.statusUsername}}</a></p>
<div class="profile-timeline mt-md-4">
<div class="row">
@ -474,6 +443,7 @@
</div>
</div>
</div>
<b-modal ref="likesModal"
id="l-modal"
hide-footer
@ -512,9 +482,9 @@
hide-footer
centered
title="Shares"
body-class="list-group-flush p-0">
body-class="list-group-flush py-3 px-0">
<div class="list-group">
<div class="list-group-item border-0" v-for="(user, index) in shares" :key="'modal_shares_'+index">
<div class="list-group-item border-0 py-1" v-for="(user, index) in shares" :key="'modal_shares_'+index">
<div class="media">
<a :href="user.url">
<img class="mr-3 rounded-circle box-shadow" :src="user.avatar" :alt="user.username + 's avatar'" width="30px">
@ -615,6 +585,88 @@
</div>
<p class="mb-0 text-center small text-muted font-weight-bold"><a href="/site/kb/tagging-people">Learn more</a> about Tagging People.</p>
</b-modal>
<b-modal ref="ctxModal"
id="ctx-modal"
hide-header
hide-footer
centered
rounded
size="sm"
body-class="list-group-flush p-0 rounded">
<div class="list-group text-center">
<div v-if="user && user.id != status.account.id && relationship && relationship.following" class="list-group-item rounded cursor-pointer font-weight-bold text-danger" @click="ctxMenuUnfollow()">Unfollow</div>
<div v-if="user && user.id != status.account.id && relationship && !relationship.following" class="list-group-item rounded cursor-pointer font-weight-bold text-primary" @click="ctxMenuFollow()">Follow</div>
<div v-if="status && status.local == true" class="list-group-item rounded cursor-pointer" @click="showEmbedPostModal()">Embed</div>
<div class="list-group-item rounded cursor-pointer" @click="ctxMenuCopyLink()">Copy Link</div>
<div v-if="status && user.id == status.account.id" class="list-group-item rounded cursor-pointer" @click="toggleCommentVisibility">{{ showComments ? 'Disable' : 'Enable'}} Comments</div>
<a v-if="status && user.id == status.account.id" class="list-group-item rounded cursor-pointer text-dark text-decoration-none" :href="editUrl()">Edit</a>
<div v-if="user && user.is_admin == true" class="list-group-item rounded cursor-pointer" @click="ctxModMenu()">ModTools</div>
<div v-if="status && user.id != status.account.id && !relationship.blocking && !user.is_admin" class="list-group-item rounded cursor-pointer font-weight-bold text-danger" @click="blockProfile()">Block</div>
<div v-if="status && user.id != status.account.id && relationship.blocking && !user.is_admin" class="list-group-item rounded cursor-pointer font-weight-bold text-danger" @click="unblockProfile()">Unblock</div>
<a v-if="user && user.id != status.account.id && !user.is_admin" class="list-group-item rounded cursor-pointer font-weight-bold text-danger text-decoration-none" :href="reportUrl()">Report</a>
<div v-if="status && (user.is_admin || user.id == status.account.id)" class="list-group-item rounded cursor-pointer font-weight-bold text-danger" @click="deletePost(ctxMenuStatus)">Delete</div>
<div class="list-group-item rounded cursor-pointer text-lighter" @click="closeCtxMenu()">Cancel</div>
</div>
</b-modal>
<b-modal ref="ctxModModal"
id="ctx-mod-modal"
hide-header
hide-footer
centered
rounded
size="sm"
body-class="list-group-flush p-0 rounded">
<div class="list-group text-center">
<div class="list-group-item rounded cursor-pointer" @click="toggleCommentVisibility">{{ showComments ? 'Disable' : 'Enable'}} Comments</div>
<div class="list-group-item rounded cursor-pointer" @click="moderatePost('unlist')">Unlist from Timelines</div>
<div v-if="status.sensitive" class="list-group-item rounded cursor-pointer" @click="moderatePost('remcw')">Remove Content Warning</div>
<div v-else class="list-group-item rounded cursor-pointer" @click="moderatePost('addcw')">Add Content Warning</div>
<div class="list-group-item rounded cursor-pointer text-lighter" @click="ctxModMenuClose()">Cancel</div>
</div>
</b-modal>
<b-modal ref="replyModal"
id="ctx-reply-modal"
hide-footer
centered
rounded
:title-html="replyingToUsername ? 'Reply to <span class=text-dark>' + replyingToUsername + '</span>' : ''"
title-tag="p"
title-class="font-weight-bold text-muted"
size="md"
body-class="p-2 rounded">
<div>
<textarea class="form-control" rows="4" style="border: none; font-size: 18px; resize: none; white-space: pre-wrap;outline: none;" placeholder="Reply here ..." v-model="replyText">
</textarea>
<div class="border-top border-bottom my-2">
<ul class="nav align-items-center emoji-reactions" style="overflow-x: scroll;flex-wrap: unset;">
<li class="nav-item" v-on:click="emojiReaction(status)" v-for="e in emoji">{{e}}</li>
</ul>
</div>
<div class="d-flex justify-content-between align-items-center">
<div>
<span class="pl-2 small text-muted font-weight-bold text-monospace">
<span :class="[replyText.length > config.uploader.max_caption_length ? 'text-danger':'text-dark']">{{replyText.length > config.uploader.max_caption_length ? config.uploader.max_caption_length - replyText.length : replyText.length}}</span>/{{config.uploader.max_caption_length}}
</span>
</div>
<div class="d-flex align-items-center">
<div class="custom-control custom-switch mr-3">
<input type="checkbox" class="custom-control-input" id="replyModalCWSwitch" v-model="replySensitive">
<label :class="[replySensitive ? 'custom-control-label font-weight-bold text-dark':'custom-control-label text-lighter']" for="replyModalCWSwitch">Mark as NSFW</label>
</div>
<!-- <select class="custom-select custom-select-sm my-0 mr-2">
<option value="public" selected="">Public</option>
<option value="unlisted">Unlisted</option>
<option value="followers">Followers Only</option>
</select> -->
<button class="btn btn-primary btn-sm py-2 px-4 lead text-uppercase font-weight-bold" v-on:click.prevent="postReply()" :disabled="replyText.length == 0">
{{replySending == true ? 'POSTING' : 'POST'}}
</button>
</div>
</div>
</div>
</b-modal>
</div>
</template>
@ -742,6 +794,7 @@ export default {
loaded: false,
loading: null,
replyingToId: this.statusId,
replyingToUsername: this.statusUsername,
replyToIndex: 0,
replySending: false,
emoji: window.App.util.emoji,
@ -753,9 +806,10 @@ export default {
ctxEmbedShowLikes: false,
ctxEmbedCompactMode: false,
layout: this.profileLayout,
canEdit: false,
showProfileMorePosts: false,
profileMorePosts: []
profileMorePosts: [],
replySending: false,
reactionBarLoading: true,
}
},
watch: {
@ -811,16 +865,6 @@ export default {
},
methods: {
showMuteBlock() {
let sid = this.status.account.id;
let uid = this.user.id;
if(sid == uid) {
$('.post-actions .menu-author').removeClass('d-none');
} else {
$('.post-actions .menu-user').removeClass('d-none');
}
},
reportUrl() {
return '/i/report?type=post&id=' + this.status.id;
},
@ -839,33 +883,20 @@ export default {
axios.get('/api/v2/profile/'+this.statusUsername+'/status/'+this.statusId)
.then(response => {
self.status = response.data.status;
self.user = response.data.user;
window._sharedData.curUser = self.user;
window.App.util.navatar();
self.media = self.status.media_attachments;
self.reactions = response.data.reactions;
self.likes = response.data.likes;
self.shares = response.data.shares;
self.likesPage = 2;
self.sharesPage = 2;
this.showMuteBlock();
self.showCaption = !response.data.status.sensitive;
if(self.status.comments_disabled == false) {
self.showComments = true;
this.fetchComments();
}
if(this.ownerOrAdmin()) {
let od = new Date(this.status.created_at).getTime() + (1 * 24 * 60 * 60 * 1000);
let now = new Date().getTime();
if(od > now) {
this.canEdit = true;
}
}
this.loaded = true;
setTimeout(function() {
self.fetchProfilePosts();
}, 3000);
setTimeout(function() {
self.fetchState();
document.querySelectorAll('.status-comment .comment-text a').forEach(function(i, e) {
if(i.href.startsWith(window.location.origin)) {
return;
@ -882,6 +913,20 @@ export default {
});
},
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);
@ -890,14 +935,31 @@ export default {
if(this.status.favourites_count == 0) {
return;
}
this.$refs.likesModal.show();
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;
}
this.$refs.sharesModal.show();
axios.get('/api/v2/shares/profile/'+this.statusUsername+'/status/'+this.statusId)
.then(res => {
this.shares = res.data.data;
this.$refs.sharesModal.show();
});
},
infiniteLikesHandler($state) {
@ -1010,31 +1072,35 @@ export default {
});
},
muteProfile() {
blockProfile() {
if($('body').hasClass('loggedIn') == false) {
return;
}
axios.post('/i/mute', {
axios.post('/i/block', {
type: 'user',
item: this.status.account.id
}).then(res => {
swal('Success', 'You have successfully muted ' + this.status.account.acct, 'success');
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');
});
},
blockProfile() {
unblockProfile() {
if($('body').hasClass('loggedIn') == false) {
return;
}
axios.post('/i/block', {
axios.post('/i/unblock', {
type: 'user',
item: this.status.account.id
}).then(res => {
swal('Success', 'You have successfully blocked ' + this.status.account.acct, 'success');
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');
});
@ -1082,6 +1148,7 @@ export default {
postReply() {
let self = this;
this.replySending = true;
if(this.replyText.length == 0 ||
this.replyText.trim() == '@'+this.status.account.acct) {
self.replyText = null;
@ -1106,7 +1173,7 @@ export default {
self.results.unshift(entity);
}
let elem = $('.status-comments')[0];
elem.scrollTop = elem.clientHeight;
elem.scrollTop = elem.clientHeight * 2;
} else {
if(self.replyToIndex >= 0) {
let el = self.results[self.replyToIndex];
@ -1114,6 +1181,8 @@ export default {
el.reply_count = el.reply_count + 1;
}
}
self.$refs.replyModal.hide();
self.replySending = false;
});
},
@ -1147,15 +1216,25 @@ export default {
},
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;
}
$('textarea[name="comment"]').focus();
this.$refs.replyModal.show();
},
fetchComments() {
@ -1289,7 +1368,9 @@ export default {
item: self.status.id,
disableComments: false
}).then(function(res) {
window.location.href = self.status.url;
self.status.comments_disabled = false;
self.$refs.ctxModal.hide();
window.location.reload();
}).catch(function(err) {
return;
});
@ -1299,8 +1380,9 @@ export default {
item: self.status.id,
disableComments: true
}).then(function(res) {
self.status.comments_disabled = false;
self.status.comments_disabled = true;
self.showComments = false;
self.$refs.ctxModal.hide();
}).catch(function(err) {
return;
});
@ -1374,6 +1456,7 @@ export default {
showEmbedPostModal() {
let mode = this.ctxEmbedCompactMode ? 'compact' : 'full';
this.ctxEmbedPayload = window.App.util.embed.post(this.status.url, this.ctxEmbedShowCaption, this.ctxEmbedShowLikes, mode);
this.$refs.ctxModal.hide();
this.$refs.embedModal.show();
},
@ -1461,10 +1544,166 @@ export default {
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);
});
},
},
}
</script>

@ -57,35 +57,8 @@
<!-- a class="list-group-item font-weight-bold text-decoration-none" :href="status.url">Share</a>
<a class="list-group-item font-weight-bold text-decoration-none" :href="status.url">Embed</a> -->
<a class="list-group-item text-dark text-decoration-none" href="#" @click.prevent="hidePost(status)">Hide</a>
<a v-if="activeSession == true && !statusOwner(status)" class="list-group-item text-dark text-decoration-none" :href="reportUrl(status)">Report</a>
<a v-if="activeSession == true && !statusOwner(status)" class="list-group-item text-dark text-decoration-none" @click.prevent="muteProfile(status)" href="#">Mute Profile</a>
<a v-if="activeSession == true && !statusOwner(status)" class="list-group-item text-dark text-decoration-none" @click.prevent="blockProfile(status)" href="#">Block Profile</a>
<span v-if="activeSession == true && statusOwner(status) == true || profile.is_admin == true">
<a class="list-group-item text-danger text-decoration-none" @click.prevent="deletePost">Delete</a>
</span>
<span v-if="activeSession == true && profile.is_admin == true">
<a class="list-group-item text-dark text-decoration-none" v-on:click="moderatePost(status, 'autocw')" href="#">
<p class="mb-0">Enforce CW</p>
<p class="mb-0 small text-muted">Adds a CW to every post <br> made by this account.</p>
</a>
<a class="list-group-item text-dark text-decoration-none" v-on:click="moderatePost(status, 'noautolink')" href="#">
<p class="mb-0">No Autolinking</p>
<p class="mb-0 small text-muted">Do not transform mentions, <br> hashtags or urls into HTML.</p>
</a>
<a class="list-group-item text-dark text-decoration-none" v-on:click="moderatePost(status, 'unlisted')" href="#">
<p class="mb-0">Unlisted Posts</p>
<p class="mb-0 small text-muted">Removes account from <br> public/network timelines.</p>
</a>
<a class="list-group-item text-dark text-decoration-none" v-on:click="moderatePost(status, 'disable')" href="#">
<p class="mb-0">Disable Account</p>
<p class="mb-0 small text-muted">Temporarily disable account <br> until next time user log in.</p>
</a>
<a class="list-group-item text-dark text-decoration-none" v-on:click="moderatePost(status, 'suspend')" href="#">
<p class="mb-0">Suspend Account</p>
<p class="mb-0 small text-muted">This prevents any new interactions, <br> without deleting existing data.</p>
</a>
</span>
<a v-if="activeSession == true && !statusOwner(status)" class="list-group-item text-danger font-weight-bold text-decoration-none" :href="reportUrl(status)">Report</a>
<div v-if="activeSession == true && statusOwner(status) == true || profile.is_admin == true" class="list-group-item text-danger font-weight-bold cursor-pointer" @click.prevent="deletePost">Delete</div>
</div>
</div>
</div>

@ -438,11 +438,11 @@
size="sm"
body-class="list-group-flush p-0 rounded">
<div class="list-group text-center">
<div v-if="ctxMenuStatus && ctxMenuStatus.account.id != profile.id" class="list-group-item rounded cursor-pointer font-weight-bold text-danger" @click="ctxMenuReportPost()">Report inappropriate</div>
<div v-if="ctxMenuStatus && ctxMenuStatus.account.id != profile.id" class="list-group-item rounded cursor-pointer font-weight-bold text-danger" @click="ctxMenuReportPost()">Report</div>
<div v-if="ctxMenuStatus && ctxMenuStatus.account.id != profile.id && ctxMenuRelationship && ctxMenuRelationship.following" class="list-group-item rounded cursor-pointer font-weight-bold text-danger" @click="ctxMenuUnfollow()">Unfollow</div>
<div v-if="ctxMenuStatus && ctxMenuStatus.account.id != profile.id && ctxMenuRelationship && !ctxMenuRelationship.following" class="list-group-item rounded cursor-pointer font-weight-bold text-primary" @click="ctxMenuFollow()">Follow</div>
<div class="list-group-item rounded cursor-pointer" @click="ctxMenuGoToPost()">Go to post</div>
<div v-if="ctxMenuStatus && ctxMenuStatus.local == true" class="list-group-item rounded cursor-pointer" @click="ctxMenuEmbed()">Embed</div>
<div v-if="ctxMenuStatus && ctxMenuStatus.local == true && !ctxMenuStatus.in_reply_to_id" class="list-group-item rounded cursor-pointer" @click="ctxMenuEmbed()">Embed</div>
<!-- <div class="list-group-item rounded cursor-pointer" @click="ctxMenuShare()">Share</div> -->
<div class="list-group-item rounded cursor-pointer" @click="ctxMenuCopyLink()">Copy Link</div>
<div v-if="profile && profile.is_admin == true" class="list-group-item rounded cursor-pointer" @click="ctxModMenuShow()">Moderation Tools</div>
@ -558,6 +558,10 @@
</span>
</div>
<div class="d-flex align-items-center">
<div class="custom-control custom-switch mr-3">
<input type="checkbox" class="custom-control-input" id="replyModalCWSwitch" v-model="replyNsfw">
<label :class="[replyNsfw ? 'custom-control-label font-weight-bold text-dark':'custom-control-label text-lighter']" for="replyModalCWSwitch">Mark as NSFW</label>
</div>
<!-- <select class="custom-select custom-select-sm my-0 mr-2">
<option value="public" selected="">Public</option>
<option value="unlisted">Unlisted</option>
@ -675,6 +679,7 @@
showReadMore: true,
replyStatus: {},
replyText: '',
replyNsfw: false,
emoji: window.App.util.emoji,
showHashtagPosts: false,
hashtagPosts: [],
@ -697,6 +702,7 @@
mpPoller: null
}
},
watch: {
ctxEmbedShowCaption: function (n,o) {
if(n == true) {
@ -721,6 +727,7 @@
this.ctxEmbedPayload = window.App.util.embed.post(this.ctxMenuStatus.url, this.ctxEmbedShowCaption, this.ctxEmbedShowLikes, mode);
}
},
beforeMount() {
this.fetchProfile();
this.fetchTimelineApi();
@ -1072,7 +1079,8 @@
}
axios.post('/i/comment', {
item: id,
comment: comment
comment: comment,
sensitive: this.replyNsfw
}).then(res => {
this.replyText = '';
this.replies.unshift(res.data.entity);
@ -1663,6 +1671,7 @@
}, 500);
},
},
beforeDestroy () {
clearInterval(this.mpInterval);
},

@ -120,6 +120,7 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
Route::get('discover', 'InternalApiController@discover');
Route::get('discover/posts', 'InternalApiController@discoverPosts')->middleware('auth:api');
Route::get('profile/{username}/status/{postid}', 'PublicApiController@status');
Route::get('profile/{username}/status/{postid}/state', 'PublicApiController@statusState');
Route::get('comments/{username}/status/{postId}', 'PublicApiController@statusComments');
Route::get('likes/profile/{username}/status/{id}', 'PublicApiController@statusLikes');
Route::get('shares/profile/{username}/status/{id}', 'PublicApiController@statusShares');

Loading…
Cancel
Save