Merge pull request #4563 from pixelfed/staging

Reblogs in feeds
This commit is contained in:
daniel 2023-07-30 04:11:47 -06:00 committed by GitHub
commit e559187411
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 747 additions and 541 deletions

View file

@ -6,6 +6,7 @@
- Import from Instagram ([#4466](https://github.com/pixelfed/pixelfed/pull/4466)) ([cf3078c5](https://github.com/pixelfed/pixelfed/commit/cf3078c5))
- Sign-in with Mastodon ([#4545](https://github.com/pixelfed/pixelfed/pull/4545)) ([45b9404e](https://github.com/pixelfed/pixelfed/commit/45b9404e))
- Health check endpoint at /api/service/health-check ([ff58f970](https://github.com/pixelfed/pixelfed/commit/ff58f970))
- Reblogs in home feed ([#4563](https://github.com/pixelfed/pixelfed/pull/4563)) ([b86d47bf](https://github.com/pixelfed/pixelfed/commit/b86d47bf))
### Updates
- Update Notifications.vue component, fix filtering logic to prevent endless spinner ([3df9b53f](https://github.com/pixelfed/pixelfed/commit/3df9b53f))
@ -42,6 +43,9 @@
- Update FanoutDeletePipeline, fix AP object ([0d802c31](https://github.com/pixelfed/pixelfed/commit/0d802c31))
- Update Remote Auth feature, fix custom domain bug and enforce banned domains ([acabf603](https://github.com/pixelfed/pixelfed/commit/acabf603))
- Update StatusService, reduce cache ttl from 7 days to 6 hours ([59b64378](https://github.com/pixelfed/pixelfed/commit/59b64378))
- Update ProfileController, allow albums in atom feed. Closes #4561. Fixes #4526 ([1c105a6c](https://github.com/pixelfed/pixelfed/commit/1c105a6c))
- Update admin users view, fix website value. Closes #4557 ([c469d475](https://github.com/pixelfed/pixelfed/commit/c469d475))
- Update StatusStatelessTransformer, allow unlisted reblogs ([1c13b518](https://github.com/pixelfed/pixelfed/commit/1c13b518))
- ([](https://github.com/pixelfed/pixelfed/commit/))
## [v0.11.8 (2023-05-29)](https://github.com/pixelfed/pixelfed/compare/v0.11.7...v0.11.8)

View file

@ -17,6 +17,7 @@ use App\Report;
use App\Profile;
use App\StatusArchived;
use App\User;
use App\UserSetting;
use App\Services\AccountService;
use App\Services\StatusService;
use App\Services\ProfileStatusService;
@ -845,4 +846,41 @@ class ApiV1Dot1Controller extends Controller
return StatusService::get($status->id, false);
}
public function getWebSettings(Request $request)
{
abort_if(!$request->user(), 403);
$uid = $request->user()->id;
$settings = UserSetting::firstOrCreate([
'user_id' => $uid
]);
if(!$settings->other) {
return [];
}
return $settings->other;
}
public function setWebSettings(Request $request)
{
abort_if(!$request->user(), 403);
$this->validate($request, [
'field' => 'required|in:enable_reblogs,hide_reblog_banner',
'value' => 'required'
]);
$field = $request->input('field');
$value = $request->input('value');
$settings = UserSetting::firstOrCreate([
'user_id' => $request->user()->id
]);
if(!$settings->other) {
$other = [];
} else {
$other = $settings->other;
}
$other[$field] = $value;
$settings->other = $other;
$settings->save();
return [200];
}
}

View file

@ -230,30 +230,44 @@ class SettingsController extends Controller
public function timelineSettings(Request $request)
{
$pid = $request->user()->profile_id;
$top = Redis::zscore('pf:tl:top', $pid) != false;
$replies = Redis::zscore('pf:tl:replies', $pid) != false;
return view('settings.timeline', compact('top', 'replies'));
$uid = $request->user()->id;
$pid = $request->user()->profile_id;
$top = Redis::zscore('pf:tl:top', $pid) != false;
$replies = Redis::zscore('pf:tl:replies', $pid) != false;
$userSettings = UserSetting::firstOrCreate([
'user_id' => $uid
]);
if(!$userSettings || !$userSettings->other) {
$userSettings = [
'enable_reblogs' => false,
];
} else {
$userSettings = $userSettings->other;
}
return view('settings.timeline', compact('top', 'replies', 'userSettings'));
}
public function updateTimelineSettings(Request $request)
{
$pid = $request->user()->profile_id;
$top = $request->has('top') && $request->input('top') === 'on';
$replies = $request->has('replies') && $request->input('replies') === 'on';
if($top) {
Redis::zadd('pf:tl:top', $pid, $pid);
} else {
Redis::zrem('pf:tl:top', $pid);
}
if($replies) {
Redis::zadd('pf:tl:replies', $pid, $pid);
} else {
Redis::zrem('pf:tl:replies', $pid);
}
return redirect(route('settings'))->with('status', 'Timeline settings successfully updated!');
$pid = $request->user()->profile_id;
$uid = $request->user()->id;
$this->validate($request, [
'enable_reblogs' => 'sometimes'
]);
Redis::zrem('pf:tl:top', $pid);
Redis::zrem('pf:tl:replies', $pid);
$userSettings = UserSetting::firstOrCreate([
'user_id' => $uid
]);
if($userSettings->other) {
$other = $userSettings->other;
$other['enable_reblogs'] = $request->has('enable_reblogs');
} else {
$other['enable_reblogs'] = $request->has('enable_reblogs');
}
$userSettings->other = $other;
$userSettings->save();
return redirect(route('settings'))->with('status', 'Timeline settings successfully updated!');
}
public function mediaSettings(Request $request)

View file

@ -33,7 +33,7 @@ class StatusStatelessTransformer extends Fractal\TransformerAbstract
'url' => $status->url(),
'in_reply_to_id' => $status->in_reply_to_id ? (string) $status->in_reply_to_id : null,
'in_reply_to_account_id' => $status->in_reply_to_profile_id ? (string) $status->in_reply_to_profile_id : null,
'reblog' => $status->reblog_of_id ? StatusService::get($status->reblog_of_id) : null,
'reblog' => $status->reblog_of_id ? StatusService::get($status->reblog_of_id, false) : null,
'content' => $status->rendered ?? $status->caption,
'content_text' => $status->caption,
'created_at' => str_replace('+00:00', 'Z', $status->created_at->format(DATE_RFC3339_EXTENDED)),

BIN
public/css/spa.css vendored

Binary file not shown.

BIN
public/js/daci.chunk.06d17098233d10d2.js vendored Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
public/js/home.chunk.3be19ae25a42cbd5.js vendored Normal file

Binary file not shown.

BIN
public/js/manifest.js vendored

Binary file not shown.

Binary file not shown.

BIN
public/js/post.chunk.ce42d6040d1683fd.js vendored Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -1,348 +1,382 @@
<template>
<div class="card-header border-0" style="border-top-left-radius: 15px;border-top-right-radius: 15px;">
<div class="media align-items-center">
<a :href="status.account.url" @click.prevent="goToProfile()" style="margin-right: 10px;">
<img :src="getStatusAvatar()" style="border-radius:15px;" width="44" height="44" onerror="this.onerror=null;this.src='/storage/avatars/default.png?v=0';">
</a>
<div>
<div v-if="isReblog" class="card-header bg-light border-0" style="border-top-left-radius: 15px;border-top-right-radius: 15px;">
<div class="media align-items-center" style="height:10px;">
<a :href="reblogAccount.url" class="mx-2" @click.prevent="goToProfileById(reblogAccount.id)">
<img :src="reblogAccount.avatar" style="border-radius:10px;" width="24" height="24" onerror="this.onerror=null;this.src='/storage/avatars/default.png?v=0';">
</a>
<div style="font-size:12px;font-weight:bold">
<i class="far fa-retweet text-warning mr-1"></i> Reblogged by <a :href="reblogAccount.url" class="text-dark" @click.prevent="goToProfileById(reblogAccount.id)">&commat;{{ reblogAccount.acct }}</a>
</div>
</div>
</div>
<div class="card-header border-0" style="border-top-left-radius: 15px;border-top-right-radius: 15px;">
<div class="media align-items-center">
<a :href="status.account.url" @click.prevent="goToProfile()" style="margin-right: 10px;">
<img :src="getStatusAvatar()" style="border-radius:15px;" width="44" height="44" onerror="this.onerror=null;this.src='/storage/avatars/default.png?v=0';">
</a>
<div class="media-body">
<p class="font-weight-bold username">
<a :href="status.account.url" class="text-dark" :id="'apop_'+status.id" @click.prevent="goToProfile">
{{ status.account.acct }}
</a>
<b-popover :target="'apop_'+status.id" triggers="hover" placement="bottom" custom-class="shadow border-0 rounded-px">
<profile-hover-card
:profile="status.account"
v-on:follow="follow"
v-on:unfollow="unfollow" />
</b-popover>
</p>
<p class="text-lighter mb-0" style="font-size: 13px;">
<span v-if="status.account.is_admin" class="d-none d-md-inline-block">
<span class="badge badge-light text-danger user-select-none" title="Admin account">ADMIN</span>
<span class="mx-1 text-lighter">·</span>
</span>
<a class="timestamp text-lighter" :href="status.url" @click.prevent="goToPost()" :title="status.created_at">
{{ timeago(status.created_at) }}
</a>
<div class="media-body">
<p class="font-weight-bold username">
<a :href="status.account.url" class="text-dark" :id="'apop_'+status.id" @click.prevent="goToProfile">
{{ status.account.acct }}
</a>
<b-popover :target="'apop_'+status.id" triggers="hover" placement="bottom" custom-class="shadow border-0 rounded-px">
<profile-hover-card
:profile="status.account"
v-on:follow="follow"
v-on:unfollow="unfollow" />
</b-popover>
</p>
<p class="text-lighter mb-0" style="font-size: 13px;">
<span v-if="status.account.is_admin" class="d-none d-md-inline-block">
<span class="badge badge-light text-danger user-select-none" title="Admin account">ADMIN</span>
<span class="mx-1 text-lighter">·</span>
</span>
<a class="timestamp text-lighter" :href="status.url" @click.prevent="goToPost()" :title="status.created_at">
{{ timeago(status.created_at) }}
</a>
<span v-if="config.ab.pue && status.hasOwnProperty('edited_at') && status.edited_at">
<span class="mx-1 text-lighter">·</span>
<a class="text-lighter" href="#" @click.prevent="openEditModal">Edited</a>
</span>
<span v-if="config.ab.pue && status.hasOwnProperty('edited_at') && status.edited_at">
<span class="mx-1 text-lighter">·</span>
<a class="text-lighter" href="#" @click.prevent="openEditModal">Edited</a>
</span>
<span class="mx-1 text-lighter">·</span>
<span class="visibility text-lighter" :title="scopeTitle(status.visibility)"><i :class="scopeIcon(status.visibility)"></i></span>
<span class="mx-1 text-lighter">·</span>
<span class="visibility text-lighter" :title="scopeTitle(status.visibility)"><i :class="scopeIcon(status.visibility)"></i></span>
<span v-if="status.place && status.place.hasOwnProperty('name')" class="d-none d-md-inline-block">
<span class="mx-1 text-lighter">·</span>
<span class="location text-lighter"><i class="far fa-map-marker-alt"></i> {{ status.place.name }}, {{ status.place.country }}</span>
</span>
</p>
</div>
<span v-if="status.place && status.place.hasOwnProperty('name')" class="d-none d-md-inline-block">
<span class="mx-1 text-lighter">·</span>
<span class="location text-lighter"><i class="far fa-map-marker-alt"></i> {{ status.place.name }}, {{ status.place.country }}</span>
</span>
</p>
</div>
<button v-if="!useDropdownMenu" class="btn btn-link text-lighter" @click="openMenu">
<i class="far fa-ellipsis-v fa-lg"></i>
</button>
<button v-if="!useDropdownMenu" class="btn btn-link text-lighter" @click="openMenu">
<i class="far fa-ellipsis-v fa-lg"></i>
</button>
<b-dropdown
v-else
no-caret
right
variant="link"
toggle-class="text-lighter"
html="<i class='far fa-ellipsis-v fa-lg px-3'></i>"
>
<b-dropdown-item>
<p class="mb-0 font-weight-bold">{{ $t('menu.viewPost') }}</p>
</b-dropdown-item>
<b-dropdown-item>
<p class="mb-0 font-weight-bold">{{ $t('common.copyLink') }}</p>
</b-dropdown-item>
<b-dropdown-item v-if="status.local">
<p class="mb-0 font-weight-bold">{{ $t('menu.embed') }}</p>
</b-dropdown-item>
<b-dropdown-divider v-if="!owner"></b-dropdown-divider>
<b-dropdown-item v-if="!owner">
<p class="mb-0 font-weight-bold">{{ $t('menu.report') }}</p>
<p class="small text-muted mb-0">Report content that violate our rules</p>
</b-dropdown-item>
<b-dropdown-item v-if="!owner && status.hasOwnProperty('relationship')">
<p class="mb-0 font-weight-bold">{{ status.relationship.muting ? 'Unmute' : 'Mute' }}</p>
<p class="small text-muted mb-0">Hide posts from this account in your feeds</p>
</b-dropdown-item>
<b-dropdown-item v-if="!owner && status.hasOwnProperty('relationship')">
<p class="mb-0 font-weight-bold text-danger">{{ status.relationship.blocking ? 'Unblock' : 'Block' }}</p>
<p class="small text-muted mb-0">Restrict all content from this account</p>
</b-dropdown-item>
<b-dropdown-divider v-if="owner || admin"></b-dropdown-divider>
<b-dropdown-item v-if="owner || admin">
<p class="mb-0 font-weight-bold text-danger">
{{ $t('common.delete') }}
</p>
</b-dropdown-item>
</b-dropdown>
</div>
<b-dropdown
v-else
no-caret
right
variant="link"
toggle-class="text-lighter"
html="<i class='far fa-ellipsis-v fa-lg px-3'></i>"
>
<b-dropdown-item>
<p class="mb-0 font-weight-bold">{{ $t('menu.viewPost') }}</p>
</b-dropdown-item>
<b-dropdown-item>
<p class="mb-0 font-weight-bold">{{ $t('common.copyLink') }}</p>
</b-dropdown-item>
<b-dropdown-item v-if="status.local">
<p class="mb-0 font-weight-bold">{{ $t('menu.embed') }}</p>
</b-dropdown-item>
<b-dropdown-divider v-if="!owner"></b-dropdown-divider>
<b-dropdown-item v-if="!owner">
<p class="mb-0 font-weight-bold">{{ $t('menu.report') }}</p>
<p class="small text-muted mb-0">Report content that violate our rules</p>
</b-dropdown-item>
<b-dropdown-item v-if="!owner && status.hasOwnProperty('relationship')">
<p class="mb-0 font-weight-bold">{{ status.relationship.muting ? 'Unmute' : 'Mute' }}</p>
<p class="small text-muted mb-0">Hide posts from this account in your feeds</p>
</b-dropdown-item>
<b-dropdown-item v-if="!owner && status.hasOwnProperty('relationship')">
<p class="mb-0 font-weight-bold text-danger">{{ status.relationship.blocking ? 'Unblock' : 'Block' }}</p>
<p class="small text-muted mb-0">Restrict all content from this account</p>
</b-dropdown-item>
<b-dropdown-divider v-if="owner || admin"></b-dropdown-divider>
<b-dropdown-item v-if="owner || admin">
<p class="mb-0 font-weight-bold text-danger">
{{ $t('common.delete') }}
</p>
</b-dropdown-item>
</b-dropdown>
</div>
<edit-history-modal ref="editModal" :status="status" />
</div>
<edit-history-modal ref="editModal" :status="status" />
</div>
</div>
</template>
<script type="text/javascript">
import ProfileHoverCard from './../profile/ProfileHoverCard.vue';
import EditHistoryModal from './EditHistoryModal.vue';
import ProfileHoverCard from './../profile/ProfileHoverCard.vue';
import EditHistoryModal from './EditHistoryModal.vue';
export default {
props: {
status: {
type: Object
},
export default {
props: {
status: {
type: Object
},
profile: {
type: Object
},
profile: {
type: Object
},
useDropdownMenu: {
type: Boolean,
default: false
}
},
useDropdownMenu: {
type: Boolean,
default: false
},
components: {
"profile-hover-card": ProfileHoverCard,
"edit-history-modal": EditHistoryModal
},
isReblog: {
type: Boolean,
default: false
},
data() {
return {
config: window.App.config,
menuLoading: true,
owner: false,
admin: false,
license: false
}
},
reblogAccount: {
type: Object
}
},
methods: {
timeago(ts) {
let short = App.util.format.timeAgo(ts);
if(
short.endsWith('s') ||
short.endsWith('m') ||
short.endsWith('h')
) {
return short;
}
const intl = new Intl.DateTimeFormat(undefined, {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: 'numeric',
minute: 'numeric'
});
return intl.format(new Date(ts));
},
components: {
"profile-hover-card": ProfileHoverCard,
"edit-history-modal": EditHistoryModal
},
openMenu() {
this.$emit('menu');
},
data() {
return {
config: window.App.config,
menuLoading: true,
owner: false,
admin: false,
license: false
}
},
scopeIcon(scope) {
switch(scope) {
case 'public':
return 'far fa-globe';
break;
methods: {
timeago(ts) {
let short = App.util.format.timeAgo(ts);
if(
short.endsWith('s') ||
short.endsWith('m') ||
short.endsWith('h')
) {
return short;
}
const intl = new Intl.DateTimeFormat(undefined, {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: 'numeric',
minute: 'numeric'
});
return intl.format(new Date(ts));
},
case 'unlisted':
return 'far fa-lock-open';
break;
openMenu() {
this.$emit('menu');
},
case 'private':
return 'far fa-lock';
break;
scopeIcon(scope) {
switch(scope) {
case 'public':
return 'far fa-globe';
break;
default:
return 'far fa-globe';
break;
}
},
case 'unlisted':
return 'far fa-lock-open';
break;
scopeTitle(scope) {
switch(scope) {
case 'public':
return 'Visible to everyone';
break;
case 'private':
return 'far fa-lock';
break;
case 'unlisted':
return 'Hidden from public feeds';
break;
default:
return 'far fa-globe';
break;
}
},
case 'private':
return 'Only visible to followers';
break;
scopeTitle(scope) {
switch(scope) {
case 'public':
return 'Visible to everyone';
break;
default:
return '';
break;
}
},
case 'unlisted':
return 'Hidden from public feeds';
break;
goToPost() {
if(location.pathname.split('/').pop() == this.status.id) {
location.href = this.status.local ? this.status.url + '?fs=1' : this.status.url;
return;
}
case 'private':
return 'Only visible to followers';
break;
this.$router.push({
name: 'post',
path: `/i/web/post/${this.status.id}`,
params: {
id: this.status.id,
cachedStatus: this.status,
cachedProfile: this.profile
}
})
},
default:
return '';
break;
}
},
goToProfile() {
this.$nextTick(() => {
this.$router.push({
name: 'profile',
path: `/i/web/profile/${this.status.account.id}`,
params: {
id: this.status.account.id,
cachedProfile: this.status.account,
cachedUser: this.profile
}
});
});
},
goToPost() {
if(location.pathname.split('/').pop() == this.status.id) {
location.href = this.status.local ? this.status.url + '?fs=1' : this.status.url;
return;
}
toggleContentWarning() {
this.key++;
this.sensitive = true;
this.status.sensitive = !this.status.sensitive;
},
this.$router.push({
name: 'post',
path: `/i/web/post/${this.status.id}`,
params: {
id: this.status.id,
cachedStatus: this.status,
cachedProfile: this.profile
}
})
},
like() {
event.currentTarget.blur();
if(this.status.favourited) {
this.$emit('unlike');
} else {
this.$emit('like');
}
},
goToProfileById(id) {
this.$nextTick(() => {
this.$router.push({
name: 'profile',
path: `/i/web/profile/${id}`,
params: {
id: id,
cachedUser: this.profile
}
});
});
},
toggleMenu(bvEvent) {
setTimeout(() => {
this.menuLoading = false;
}, 500);
},
goToProfile() {
this.$nextTick(() => {
this.$router.push({
name: 'profile',
path: `/i/web/profile/${this.status.account.id}`,
params: {
id: this.status.account.id,
cachedProfile: this.status.account,
cachedUser: this.profile
}
});
});
},
closeMenu(bvEvent) {
setTimeout(() => {
bvEvent.target.parentNode.firstElementChild.blur();
}, 100);
},
toggleContentWarning() {
this.key++;
this.sensitive = true;
this.status.sensitive = !this.status.sensitive;
},
showLikes() {
event.currentTarget.blur();
this.$emit('likes-modal');
},
like() {
event.currentTarget.blur();
if(this.status.favourited) {
this.$emit('unlike');
} else {
this.$emit('like');
}
},
showShares() {
event.currentTarget.blur();
this.$emit('shares-modal');
},
toggleMenu(bvEvent) {
setTimeout(() => {
this.menuLoading = false;
}, 500);
},
showComments() {
event.currentTarget.blur();
this.showCommentDrawer = !this.showCommentDrawer;
},
closeMenu(bvEvent) {
setTimeout(() => {
bvEvent.target.parentNode.firstElementChild.blur();
}, 100);
},
copyLink() {
event.currentTarget.blur();
App.util.clipboard(this.status.url);
},
showLikes() {
event.currentTarget.blur();
this.$emit('likes-modal');
},
shareToOther() {
if (navigator.canShare) {
navigator.share({
url: this.status.url
})
.then(() => console.log('Share was successful.'))
.catch((error) => console.log('Sharing failed', error));
} else {
swal('Not supported', 'Your current device does not support native sharing.', 'error');
}
},
showShares() {
event.currentTarget.blur();
this.$emit('shares-modal');
},
counterChange(type) {
this.$emit('counter-change', type);
},
showComments() {
event.currentTarget.blur();
this.showCommentDrawer = !this.showCommentDrawer;
},
showCommentLikes(post) {
this.$emit('comment-likes-modal', post);
},
copyLink() {
event.currentTarget.blur();
App.util.clipboard(this.status.url);
},
shareStatus() {
this.$emit('share');
},
shareToOther() {
if (navigator.canShare) {
navigator.share({
url: this.status.url
})
.then(() => console.log('Share was successful.'))
.catch((error) => console.log('Sharing failed', error));
} else {
swal('Not supported', 'Your current device does not support native sharing.', 'error');
}
},
unshareStatus() {
this.$emit('unshare');
},
counterChange(type) {
this.$emit('counter-change', type);
},
handleReport(post) {
this.$emit('handle-report', post);
},
showCommentLikes(post) {
this.$emit('comment-likes-modal', post);
},
follow() {
this.$emit('follow');
},
shareStatus() {
this.$emit('share');
},
unfollow() {
this.$emit('unfollow');
},
unshareStatus() {
this.$emit('unshare');
},
handleReblog() {
this.isReblogging = true;
if(this.status.reblogged) {
this.$emit('unshare');
} else {
this.$emit('share');
}
handleReport(post) {
this.$emit('handle-report', post);
},
setTimeout(() => {
this.isReblogging = false;
}, 5000);
},
follow() {
this.$emit('follow');
},
handleBookmark() {
event.currentTarget.blur();
this.isBookmarking = true;
this.$emit('bookmark');
unfollow() {
this.$emit('unfollow');
},
setTimeout(() => {
this.isBookmarking = false;
}, 5000);
},
handleReblog() {
this.isReblogging = true;
if(this.status.reblogged) {
this.$emit('unshare');
} else {
this.$emit('share');
}
getStatusAvatar() {
if(window._sharedData.user.id == this.status.account.id) {
return window._sharedData.user.avatar;
}
setTimeout(() => {
this.isReblogging = false;
}, 5000);
},
return this.status.account.avatar;
},
handleBookmark() {
event.currentTarget.blur();
this.isBookmarking = true;
this.$emit('bookmark');
openModTools() {
this.$emit('mod-tools');
},
setTimeout(() => {
this.isBookmarking = false;
}, 5000);
},
openEditModal() {
this.$refs.editModal.open();
}
}
}
getStatusAvatar() {
if(window._sharedData.user.id == this.status.account.id) {
return window._sharedData.user.avatar;
}
return this.status.account.avatar;
},
openModTools() {
this.$emit('mod-tools');
},
openEditModal() {
this.$refs.editModal.open();
}
}
}
</script>

View file

@ -8,6 +8,30 @@
</div>
<div v-else>
<transition name="fade">
<div v-if="showReblogBanner && getScope() === 'home'" class="card bg-g-amin card-body shadow-sm mb-3" style="border-radius: 15px;">
<div class="d-flex justify-content-around align-items-center">
<div class="flex-grow-1 ft-std">
<h2 class="font-weight-bold text-white mb-0">Introducing Reblogs in feeds</h2>
<hr />
<p class="lead text-white mb-0">
See reblogs from accounts you follow in your home feed!
</p>
<p class="text-white small mb-1" style="opacity:0.6">
You can disable reblogs in feeds on the Timeline Settings page.
</p>
<hr />
<div class="d-flex">
<button class="btn btn-light rounded-pill font-weight-bold btn-block mr-2" @click.prevent="enableReblogs()">
<template v-if="!enablingReblogs">Show reblogs in home feed</template>
<b-spinner small v-else />
</button>
<button class="btn btn-outline-light rounded-pill font-weight-bold px-5" @click.prevent="hideReblogs()">Hide</button>
</div>
</div>
</div>
</div>
</transition>
<status
v-for="(status, index) in feed"
:key="'pf_feed:' + status.id + ':idx:' + index + ':fui:' + forceUpdateIdx"
@ -140,6 +164,7 @@
data() {
return {
settings: [],
isLoaded: false,
feed: [],
ids: [],
@ -159,7 +184,9 @@
reportedStatusId: 0,
showSharesModal: false,
sharesModalPost: {},
forceUpdateIdx: 0
forceUpdateIdx: 0,
showReblogBanner: false,
enablingReblogs: false
}
},
@ -174,7 +201,7 @@
return;
};
}
this.fetchTimeline();
this.fetchSettings();
},
methods: {
@ -194,13 +221,48 @@
}
},
fetchTimeline(scrollToTop = false) {
let url = `/api/pixelfed/v1/timelines/${this.getScope()}`;
axios.get(url, {
params: {
max_id: this.max_id,
limit: 6
fetchSettings() {
axios.get('/api/pixelfed/v1/web/settings')
.then(res => {
this.settings = res.data;
if(!res.data) {
this.showReblogBanner = true;
} else {
if(res.data.hasOwnProperty('hide_reblog_banner')) {
} else if(res.data.hasOwnProperty('enable_reblogs')) {
if(!res.data.enable_reblogs) {
this.showReblogBanner = true;
}
} else {
this.showReblogBanner = true;
}
}
})
.finally(() => {
this.fetchTimeline();
})
},
fetchTimeline(scrollToTop = false) {
let url, params;
if(this.getScope() === 'home' && this.settings && this.settings.hasOwnProperty('enable_reblogs') && this.settings.enable_reblogs) {
url = `/api/v1/timelines/home`;
params = {
'_pe': 1,
max_id: this.max_id,
limit: 6,
include_reblogs: true,
}
} else {
url = `/api/pixelfed/v1/timelines/${this.getScope()}`;
params = {
max_id: this.max_id,
limit: 6,
}
}
axios.get(url, {
params: params
}).then(res => {
let ids = res.data.map(p => {
if(p && p.hasOwnProperty('relationship')) {
@ -242,12 +304,24 @@
this.isFetchingMore = true;
let url = `/api/pixelfed/v1/timelines/${this.getScope()}`;
axios.get(url, {
params: {
let url, params;
if(this.getScope() === 'home' && this.settings && this.settings.hasOwnProperty('enable_reblogs') && this.settings.enable_reblogs) {
url = `/api/v1/timelines/home`;
params = {
'_pe': 1,
max_id: this.max_id,
limit: 6
limit: 6,
include_reblogs: true,
}
} else {
url = `/api/pixelfed/v1/timelines/${this.getScope()}`;
params = {
max_id: this.max_id,
limit: 6,
}
}
axios.get(url, {
params: params
}).then(res => {
if(!res.data.length) {
this.endFeedReached = true;
@ -568,7 +642,31 @@
this.$nextTick(() => {
this.forceUpdateIdx++;
});
}
},
enableReblogs() {
this.enablingReblogs = true;
axios.post('/api/pixelfed/v1/web/settings', {
field: 'enable_reblogs',
value: true
})
.then(res => {
setTimeout(() => {
window.location.reload();
}, 1000);
})
},
hideReblogs() {
this.showReblogBanner = false;
axios.post('/api/pixelfed/v1/web/settings', {
field: 'hide_reblog_banner',
value: true
})
.then(res => {
})
},
},
watch: {

View file

@ -1,166 +1,172 @@
@import "lib/ibmplexsans";
:root {
--light: #fff;
--dark: #000;
--body-bg: rgba(243,244,246,1);
--body-color: #212529;
--nav-bg: #fff;
--bg-light: #f8f9fa;
--light: #fff;
--dark: #000;
--body-bg: rgba(243,244,246,1);
--body-color: #212529;
--nav-bg: #fff;
--bg-light: #f8f9fa;
--primary: #3B82F6;
--light-gray: #f8f9fa;
--text-lighter: #94a3b8;
--primary: #3B82F6;
--light-gray: #f8f9fa;
--text-lighter: #94a3b8;
--card-bg: #fff;
--light-hover-bg: #f9fafb;
--btn-light-border: #fff;
--input-border: #e2e8f0;
--comment-bg: #eff2f5;
--border-color: #dee2e6;
--card-header-accent: #f9fafb;
--card-bg: #fff;
--light-hover-bg: #f9fafb;
--btn-light-border: #fff;
--input-border: #e2e8f0;
--comment-bg: #eff2f5;
--border-color: #dee2e6;
--card-header-accent: #f9fafb;
--dropdown-item-hover-bg: #e9ecef;
--dropdown-item-hover-color: #16181b;
--dropdown-item-color: #64748b;
--dropdown-item-active-color: #334155;
--dropdown-item-hover-bg: #e9ecef;
--dropdown-item-hover-color: #16181b;
--dropdown-item-color: #64748b;
--dropdown-item-active-color: #334155;
}
@media (prefers-color-scheme: dark) {
:root {
--light: #000;
--dark: #fff;
--body-bg: #000;
--body-color: #9ca3af;
--nav-bg: #000;
--bg-light: #212124;
:root {
--light: #000;
--dark: #fff;
--body-bg: #000;
--body-color: #9ca3af;
--nav-bg: #000;
--bg-light: #212124;
--light-gray: #212124;
--text-lighter: #818181;
--light-gray: #212124;
--text-lighter: #818181;
--card-bg: #161618;
--light-hover-bg: #212124;
--btn-light-border: #161618;
--input-border: #161618;
--comment-bg: #212124;
--border-color: #212124;
--card-header-accent: #212124;
--card-bg: #161618;
--light-hover-bg: #212124;
--btn-light-border: #161618;
--input-border: #161618;
--comment-bg: #212124;
--border-color: #212124;
--card-header-accent: #212124;
--dropdown-item-hover-bg: #000;
--dropdown-item-hover-color: #818181;
--dropdown-item-color: #64748b;
--dropdown-item-active-color: #fff;
}
--dropdown-item-hover-bg: #000;
--dropdown-item-hover-color: #818181;
--dropdown-item-color: #64748b;
--dropdown-item-active-color: #fff;
}
}
.force-light-mode {
--light: #fff;
--dark: #000;
--body-bg: rgba(243,244,246,1);
--body-color: #212529;
--nav-bg: #fff;
--bg-light: #f8f9fa;
--light: #fff;
--dark: #000;
--body-bg: rgba(243,244,246,1);
--body-color: #212529;
--nav-bg: #fff;
--bg-light: #f8f9fa;
--primary: #3B82F6;
--light-gray: #f8f9fa;
--text-lighter: #94a3b8;
--primary: #3B82F6;
--light-gray: #f8f9fa;
--text-lighter: #94a3b8;
--card-bg: #fff;
--light-hover-bg: #f9fafb;
--btn-light-border: #fff;
--input-border: #e2e8f0;
--comment-bg: #eff2f5;
--border-color: #dee2e6;
--card-header-accent: #f9fafb;
--card-bg: #fff;
--light-hover-bg: #f9fafb;
--btn-light-border: #fff;
--input-border: #e2e8f0;
--comment-bg: #eff2f5;
--border-color: #dee2e6;
--card-header-accent: #f9fafb;
--dropdown-item-hover-bg: #e9ecef;
--dropdown-item-hover-color: #16181b;
--dropdown-item-color: #64748b;
--dropdown-item-active-color: #334155;
--dropdown-item-hover-bg: #e9ecef;
--dropdown-item-hover-color: #16181b;
--dropdown-item-color: #64748b;
--dropdown-item-active-color: #334155;
}
.force-dark-mode {
--light: #000;
--dark: #fff;
--body-bg: #000;
--body-color: #9ca3af;
--nav-bg: #000;
--bg-light: #212124;
--light: #000;
--dark: #fff;
--body-bg: #000;
--body-color: #9ca3af;
--nav-bg: #000;
--bg-light: #212124;
--light-gray: #212124;
--text-lighter: #818181;
--light-gray: #212124;
--text-lighter: #818181;
--card-bg: #161618;
--light-hover-bg: #212124;
--btn-light-border: #161618;
--input-border: #161618;
--comment-bg: #212124;
--border-color: #212124;
--card-header-accent: #212124;
--card-bg: #161618;
--light-hover-bg: #212124;
--btn-light-border: #161618;
--input-border: #161618;
--comment-bg: #212124;
--border-color: #212124;
--card-header-accent: #212124;
--dropdown-item-hover-bg: #000;
--dropdown-item-hover-color: #818181;
--dropdown-item-color: #64748b;
--dropdown-item-active-color: #b3b3b3;
--dropdown-item-hover-bg: #000;
--dropdown-item-hover-color: #818181;
--dropdown-item-color: #64748b;
--dropdown-item-active-color: #b3b3b3;
}
body {
background: var(--body-bg);
font-family: 'IBM Plex Sans', sans-serif;
color: var(--body-color);
background: var(--body-bg);
font-family: 'IBM Plex Sans', sans-serif;
color: var(--body-color);
}
.web-wrapper {
margin-bottom: 10rem;
margin-bottom: 10rem;
}
.container-fluid {
max-width: 1440px !important;
max-width: 1440px !important;
}
.jumbotron {
border-radius: 18px;
border-radius: 18px;
}
.rounded-px {
border-radius: 18px;
border-radius: 18px;
}
.doc-body {
p:last-child {
margin-bottom: 0;
}
p:last-child {
margin-bottom: 0;
}
}
.navbar-laravel {
background-color: var(--nav-bg);
background-color: var(--nav-bg);
}
.sticky-top {
z-index: 2;
z-index: 2;
}
.navbar-light .navbar-brand {
color: var(--dark);
color: var(--dark);
&:hover {
color: var(--dark);
}
&:hover {
color: var(--dark);
}
}
.primary {
color: var(--primary);
color: var(--primary);
}
.bg-g-amin {
background: #8E2DE2;
background: -webkit-linear-gradient(to right, #4A00E0, #8E2DE2);
background: linear-gradient(to left, #4A00E0, #8E2DE2);
}
.text-lighter {
color: var(--text-lighter) !important;
color: var(--text-lighter) !important;
}
.text-dark {
color: var(--body-color) !important;
&:hover {
color: var(--dark) !important;
color: var(--dark) !important;
}
}
@ -169,16 +175,16 @@ a.text-dark:hover {
}
.badge-primary {
background-color: var(--primary);
background-color: var(--primary);
}
.btn-primary {
background-color: var(--primary);
color: #fff !important;
background-color: var(--primary);
color: #fff !important;
}
.btn-outline-light {
border-color: var(--light-gray);
border-color: var(--light-gray);
}
.border {
@ -187,51 +193,51 @@ a.text-dark:hover {
.bg-white,
.bg-light {
background-color: var(--bg-light) !important;
border-color: var(--bg-light) !important;
background-color: var(--bg-light) !important;
border-color: var(--bg-light) !important;
}
.btn-light {
background-color: var(--light-gray);
border-color: var(--btn-light-border);
color: var(--body-color);
background-color: var(--light-gray);
border-color: var(--btn-light-border);
color: var(--body-color);
&:hover {
color: var(--body-color);
background-color: var(--card-bg);
border-color: var(--btn-light-border);
}
&:hover {
color: var(--body-color);
background-color: var(--card-bg);
border-color: var(--btn-light-border);
}
}
.autocomplete-input {
border: 1px solid var(--light-gray) !important;
color: var(--body-color);
border: 1px solid var(--light-gray) !important;
color: var(--body-color);
}
.autocomplete-result-list {
background: var(--light) !important;
z-index: 2 !important;
background: var(--light) !important;
z-index: 2 !important;
}
.dropdown-menu,
span.twitter-typeahead .tt-menu,
.form-control {
border: 1px solid var(--border-color) !important;
color: var(--body-color);
background-color: var(--card-bg);
border: 1px solid var(--border-color) !important;
color: var(--body-color);
background-color: var(--card-bg);
}
.tribute-container li,
.dropdown-item,
span.twitter-typeahead .tt-suggestion {
color: var(--body-color);
color: var(--body-color);
}
.dropdown-item:hover,
span.twitter-typeahead .tt-suggestion:hover,
.dropdown-item:focus,
span.twitter-typeahead .tt-suggestion:focus {
color: var(--dropdown-item-hover-color);
color: var(--dropdown-item-hover-color);
background-color: var(--dropdown-item-hover-bg);
text-decoration: none;
}
@ -245,7 +251,7 @@ span.twitter-typeahead .tt-suggestion:focus {
.card-header,
.card-footer,
.ph-item {
background-color: var(--card-bg);
background-color: var(--card-bg);
}
.badge-light,
@ -253,143 +259,147 @@ span.twitter-typeahead .tt-suggestion:focus {
.ph-avatar,
.ph-picture,
.ph-row div {
background-color: var(--light-gray);
background-color: var(--light-gray);
}
.card-header,
.border-top,
.border-bottom {
border-color: var(--border-color) !important;
border-color: var(--border-color) !important;
}
.modal-header,
.modal-footer {
border-color: var(--border-color);
border-color: var(--border-color);
}
.compose-action:hover {
background-color: var(--light-gray) !important;
background-color: var(--light-gray) !important;
}
.dropdown-divider {
border-color: var(--dropdown-item-hover-bg);
border-color: var(--dropdown-item-hover-bg);
}
.metro-nav {
&.flex-column {
background-color: var(--card-bg);
&.flex-column {
background-color: var(--card-bg);
.nav-item {
.nav-link:hover {
background-color: var(--light-hover-bg);
}
}
}
.nav-item {
.nav-link:hover {
background-color: var(--light-hover-bg);
}
}
}
}
.child-reply-form {
.form-control {
border-color: var(--input-border);
color: var(--body-color);
}
.form-control {
border-color: var(--input-border);
color: var(--body-color);
}
}
.ui-menu {
.btn-group {
.btn:first-child {
border-top-left-radius: 50rem;
border-bottom-left-radius: 50rem;
}
.btn-group {
.btn:first-child {
border-top-left-radius: 50rem;
border-bottom-left-radius: 50rem;
}
.btn:last-child {
border-top-right-radius: 50rem;
border-bottom-right-radius: 50rem;
}
.btn:last-child {
border-top-right-radius: 50rem;
border-bottom-right-radius: 50rem;
}
.btn-primary {
font-weight: bold;
}
}
.btn-primary {
font-weight: bold;
}
}
.b-custom-control-lg {
padding-bottom: 8px;
}
.b-custom-control-lg {
padding-bottom: 8px;
}
}
.content-label {
&-wrapper {
div:not(.content-label) {
height: 100%;
}
}
&-wrapper {
div:not(.content-label) {
height: 100%;
}
}
&-text {
width: 80%;
@media (min-width: 768px) {
width: 50%;
}
}
&-text {
width: 80%;
@media (min-width: 768px) {
width: 50%;
}
}
}
.compose-modal-component {
.form-control:focus {
color: var(--body-color);
.form-control:focus {
color: var(--body-color);
}
}
.modal-body {
.nav-tabs .nav-link.active,
.nav-tabs .nav-item.show .nav-link {
background-color: transparent;
border-color: var(--border-color);
}
.nav-tabs .nav-link.active,
.nav-tabs .nav-item.show .nav-link {
background-color: transparent;
border-color: var(--border-color);
}
.nav-tabs .nav-link:hover,
.nav-tabs .nav-link:focus {
border-color: var(--border-color);
}
.nav-tabs .nav-link:hover,
.nav-tabs .nav-link:focus {
border-color: var(--border-color);
}
.form-control:focus {
color: var(--body-color);
.form-control:focus {
color: var(--body-color);
}
}
.tribute-container {
border: 0;
border: 0;
ul {
margin-top: 0;
border-color: var(--border-color);
}
ul {
margin-top: 0;
border-color: var(--border-color);
}
li {
padding: 0.5rem 1rem;
border-top: 0;
border-left: 0;
border-right: 0;
font-size: 13px;
li {
padding: 0.5rem 1rem;
border-top: 0;
border-left: 0;
border-right: 0;
font-size: 13px;
&:not(:last-child) {
border-bottom: 1px solid var(--border-color);
}
&:not(:last-child) {
border-bottom: 1px solid var(--border-color);
}
&.highlight,
&:hover {
color: var(--body-color);
font-weight: bold;
background: rgba(44, 120, 191, 0.25);
}
}
&.highlight,
&:hover {
color: var(--body-color);
font-weight: bold;
background: rgba(44, 120, 191, 0.25);
}
}
}
.ft-std {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}
.timeline-status-component {
.username {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
margin-bottom: -3px;
word-break: break-word;
.username {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
margin-bottom: -3px;
word-break: break-word;
@media (min-width: 768px) {
font-size: 17px;
}
}
@media (min-width: 768px) {
font-size: 17px;
}
}
}

View file

@ -78,7 +78,7 @@
</div>
<div class="form-group">
<label class="font-weight-bold text-muted">Website</label>
<input type="text" class="form-control" name="website" value="{{$user->website}}" placeholder="No website added">
<input type="text" class="form-control" name="website" value="{{$profile->website}}" placeholder="No website added">
</div>
<div class="form-group">
<label class="font-weight-bold text-muted">Admin</label>
@ -97,4 +97,4 @@
</div>
</div>
@endsection
@endsection

View file

@ -8,17 +8,23 @@
<hr>
<form method="post">
@csrf
<div class="form-check pb-3">
<div class="form-check pb-3 d-none">
<input class="form-check-input" type="checkbox" name="top" {{$top ? 'checked':''}}>
<label class="form-check-label font-weight-bold" for="">Show text-only posts</label>
<p class="text-muted small help-text">Show text-only posts from accounts you follow. (Home timeline only)</p>
</div>
<div class="form-check pb-3">
<div class="form-check pb-3 d-none">
<input class="form-check-input" type="checkbox" name="replies" {{$replies ? 'checked':''}}>
<label class="form-check-label font-weight-bold" for="">Show replies</label>
<p class="text-muted small help-text">Show replies from accounts you follow. (Home timeline only)</p>
</div>
<div class="form-check pb-3">
<input class="form-check-input" type="checkbox" name="enable_reblogs" {{$userSettings['enable_reblogs'] ? 'checked':''}}>
<label class="form-check-label font-weight-bold" for="">Show reblogs</label>
<p class="text-muted small help-text">See reblogs from accounts you follow in your home feed. (Home timeline only)</p>
</div>
<div class="form-group row mt-5 pt-5">
<div class="col-12 text-right">
<hr>

View file

@ -304,6 +304,8 @@ Route::group(['prefix' => 'api'], function() use($middleware) {
Route::get('posts/{id}/{slug}', 'Api\ApiV1Dot1Controller@placesById')->middleware($middleware);
});
Route::get('web/settings', 'Api\ApiV1Dot1Controller@getWebSettings')->middleware($middleware);
Route::post('web/settings', 'Api\ApiV1Dot1Controller@setWebSettings')->middleware($middleware);
Route::get('app/settings', 'UserAppSettingsController@get')->middleware($middleware);
Route::post('app/settings', 'UserAppSettingsController@store')->middleware($middleware);