mirror of
https://github.com/pixelfed/pixelfed.git
synced 2024-11-27 00:33:17 +00:00
Merge pull request #1209 from pixelfed/frontend-ui-refactor
Labs + Profile Suggestions Experiment
This commit is contained in:
commit
ccef4d0939
17 changed files with 389 additions and 39 deletions
|
@ -3,10 +3,14 @@
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Http\Controllers\Api\BaseApiController;
|
use App\Http\Controllers\Api\BaseApiController;
|
||||||
use App\Like;
|
use App\{
|
||||||
|
Like,
|
||||||
|
Profile
|
||||||
|
};
|
||||||
use Auth;
|
use Auth;
|
||||||
use Cache;
|
use Cache;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use App\Services\SuggestionService;
|
||||||
|
|
||||||
class ApiController extends BaseApiController
|
class ApiController extends BaseApiController
|
||||||
{
|
{
|
||||||
|
@ -39,11 +43,51 @@ class ApiController extends BaseApiController
|
||||||
],
|
],
|
||||||
|
|
||||||
'ab' => [
|
'ab' => [
|
||||||
'lc' => config('exp.lc')
|
'lc' => config('exp.lc'),
|
||||||
|
'rec' => config('exp.rec'),
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
return response()->json($res);
|
return response()->json($res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function userRecommendations(Request $request)
|
||||||
|
{
|
||||||
|
abort_if(!Auth::check(), 403);
|
||||||
|
abort_if(!config('exp.rec'), 400);
|
||||||
|
|
||||||
|
$id = Auth::user()->profile->id;
|
||||||
|
|
||||||
|
$following = Cache::get('profile:following:'.$id, []);
|
||||||
|
$ids = SuggestionService::get();
|
||||||
|
|
||||||
|
$res = Cache::remember('api:local:exp:rec:'.$id, now()->addMinutes(5), function() use($id, $following, $ids) {
|
||||||
|
|
||||||
|
array_push($following, $id);
|
||||||
|
|
||||||
|
return Profile::select(
|
||||||
|
'id',
|
||||||
|
'username'
|
||||||
|
)
|
||||||
|
->whereNotIn('id', $following)
|
||||||
|
->whereIn('id', $ids)
|
||||||
|
->whereIsPrivate(0)
|
||||||
|
->whereNull('status')
|
||||||
|
->whereNull('domain')
|
||||||
|
->inRandomOrder()
|
||||||
|
->take(4)
|
||||||
|
->get()
|
||||||
|
->map(function($item, $key) {
|
||||||
|
return [
|
||||||
|
'id' => $item->id,
|
||||||
|
'avatar' => $item->avatarUrl(),
|
||||||
|
'username' => $item->username,
|
||||||
|
'message' => 'Recommended for You'
|
||||||
|
];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return response()->json($res->all());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,6 +57,9 @@ class FollowerController extends Controller
|
||||||
'follower_id' => $user->id,
|
'follower_id' => $user->id,
|
||||||
'following_id' => $target->id
|
'following_id' => $target->id
|
||||||
]);
|
]);
|
||||||
|
if($remote == true) {
|
||||||
|
|
||||||
|
}
|
||||||
} elseif ($isFollowing == 0) {
|
} elseif ($isFollowing == 0) {
|
||||||
$follower = new Follower();
|
$follower = new Follower();
|
||||||
$follower->profile_id = $user->id;
|
$follower->profile_id = $user->id;
|
||||||
|
@ -72,5 +75,6 @@ class FollowerController extends Controller
|
||||||
Cache::forget('profile:followers:'.$target->id);
|
Cache::forget('profile:followers:'.$target->id);
|
||||||
Cache::forget('profile:following:'.$user->id);
|
Cache::forget('profile:following:'.$user->id);
|
||||||
Cache::forget('profile:followers:'.$user->id);
|
Cache::forget('profile:followers:'.$user->id);
|
||||||
|
Cache::forget('api:local:exp:rec:'.$user->id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@ trait HomeSettings
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$changes = false;
|
$changes = false;
|
||||||
$name = strip_tags($request->input('name'));
|
$name = strip_tags(Purify::clean($request->input('name')));
|
||||||
$bio = $request->filled('bio') ? strip_tags(Purify::clean($request->input('bio'))) : null;
|
$bio = $request->filled('bio') ? strip_tags(Purify::clean($request->input('bio'))) : null;
|
||||||
$website = $request->input('website');
|
$website = $request->input('website');
|
||||||
$email = $request->input('email');
|
$email = $request->input('email');
|
||||||
|
|
83
app/Http/Controllers/Settings/LabsSettings.php
Normal file
83
app/Http/Controllers/Settings/LabsSettings.php
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Settings;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Cookie, Redis;
|
||||||
|
use App\Services\SuggestionService;
|
||||||
|
|
||||||
|
trait LabsSettings {
|
||||||
|
|
||||||
|
public function __constructor()
|
||||||
|
{
|
||||||
|
$this->middleware('auth');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function labs(Request $request)
|
||||||
|
{
|
||||||
|
$profile = $request->user()->profile;
|
||||||
|
return view('settings.labs', compact('profile'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function labsStore(Request $request)
|
||||||
|
{
|
||||||
|
$this->validate($request, [
|
||||||
|
'profile_layout' => 'nullable',
|
||||||
|
'dark_mode' => 'nullable',
|
||||||
|
'profile_suggestions' => 'nullable'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$changes = false;
|
||||||
|
|
||||||
|
$profile = $request->user()->profile;
|
||||||
|
|
||||||
|
$cookie = Cookie::forget('dark-mode');
|
||||||
|
if($request->has('dark_mode') && $profile->profile_layout != 'moment') {
|
||||||
|
if($request->dark_mode == 'on') {
|
||||||
|
$cookie = Cookie::make('dark-mode', true, 43800);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if($request->has('profile_layout')) {
|
||||||
|
if($profile->profile_layout != 'moment') {
|
||||||
|
$profile->profile_layout = 'moment';
|
||||||
|
$changes = true;
|
||||||
|
} else {
|
||||||
|
$profile->profile_layout = null;
|
||||||
|
$changes = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if($profile->profile_layout == 'moment') {
|
||||||
|
$profile->profile_layout = null;
|
||||||
|
$changes = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if($request->has('profile_suggestions')) {
|
||||||
|
if($profile->is_suggestable == false) {
|
||||||
|
$profile->is_suggestable = true;
|
||||||
|
$changes = true;
|
||||||
|
SuggestionService::set($profile->id);
|
||||||
|
} else {
|
||||||
|
$profile->is_suggestable = false;
|
||||||
|
$changes = true;
|
||||||
|
SuggestionService::del($profile->id);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if($profile->is_suggestable == true) {
|
||||||
|
$profile->is_suggestable = false;
|
||||||
|
$changes = true;
|
||||||
|
SuggestionService::del($profile->id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if($changes == true) {
|
||||||
|
$profile->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect(route('settings.labs'))
|
||||||
|
->with('status', 'Labs preferences successfully updated!')
|
||||||
|
->cookie($cookie);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
49
app/Services/SuggestionService.php
Normal file
49
app/Services/SuggestionService.php
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
use Redis;
|
||||||
|
use App\Profile;
|
||||||
|
|
||||||
|
class SuggestionService {
|
||||||
|
|
||||||
|
const CACHE_KEY = 'pf:services:suggestion:ids';
|
||||||
|
|
||||||
|
public static function get($start = 0, $stop = -1)
|
||||||
|
{
|
||||||
|
return Redis::zrange(self::CACHE_KEY, $start, $stop);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function set($val)
|
||||||
|
{
|
||||||
|
return Redis::zadd(self::CACHE_KEY, 1, $val);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function del($val)
|
||||||
|
{
|
||||||
|
return Redis::zrem(self::CACHE_KEY, $val);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function add($val)
|
||||||
|
{
|
||||||
|
return self::set($val);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function rem($val)
|
||||||
|
{
|
||||||
|
return self::del($val);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function warmCache($force = false)
|
||||||
|
{
|
||||||
|
if(Redis::zcount(self::CACHE_KEY, '-inf', '+inf') == 0 || $force == true) {
|
||||||
|
$ids = Profile::whereNull('domain')
|
||||||
|
->whereIsSuggestable(true)
|
||||||
|
->whereIsPrivate(false)
|
||||||
|
->pluck('id');
|
||||||
|
foreach($ids as $id) {
|
||||||
|
self::set($id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'lc' => env('EXP_LC', false)
|
'lc' => env('EXP_LC', false),
|
||||||
|
'rec' => env('EXP_REC', false)
|
||||||
|
|
||||||
];
|
];
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
|
||||||
|
class AddSuggestionsToProfilesTable extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::table('profiles', function (Blueprint $table) {
|
||||||
|
$table->boolean('is_suggestable')->default(false)->index();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::table('profiles', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('is_suggestable');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
BIN
public/js/timeline.js
vendored
BIN
public/js/timeline.js
vendored
Binary file not shown.
Binary file not shown.
|
@ -3,16 +3,16 @@
|
||||||
<div class="card notification-card">
|
<div class="card notification-card">
|
||||||
<div class="card-header bg-white">
|
<div class="card-header bg-white">
|
||||||
<p class="mb-0 d-flex align-items-center justify-content-between">
|
<p class="mb-0 d-flex align-items-center justify-content-between">
|
||||||
<span class="text-muted font-weight-bold">Notifications</span>
|
<span class="text-muted">Notifications</span>
|
||||||
<a class="text-dark small" href="/account/activity">See All</a>
|
<a class="text-dark small" href="/account/activity">See All</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body loader text-center" style="height: 270px;">
|
<div class="card-body loader text-center" style="height: 230px;">
|
||||||
<div class="spinner-border" role="status">
|
<div class="spinner-border" role="status">
|
||||||
<span class="sr-only">Loading...</span>
|
<span class="sr-only">Loading...</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body pt-2 contents" style="max-height: 270px; overflow-y: scroll;">
|
<div class="card-body pt-2 contents" style="max-height: 230px; overflow-y: scroll;">
|
||||||
<div v-if="notifications.length > 0" class="media mb-3 align-items-center" v-for="(n, index) in notifications">
|
<div v-if="notifications.length > 0" class="media mb-3 align-items-center" v-for="(n, index) in notifications">
|
||||||
<img class="mr-2 rounded-circle" style="border:1px solid #ccc" :src="n.account.avatar" alt="" width="32px" height="32px">
|
<img class="mr-2 rounded-circle" style="border:1px solid #ccc" :src="n.account.avatar" alt="" width="32px" height="32px">
|
||||||
<div class="media-body font-weight-light small">
|
<div class="media-body font-weight-light small">
|
||||||
|
|
|
@ -213,10 +213,7 @@
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
<p class="font-weight-bold">BETA FEATURES</p>
|
<p class="font-weight-bold">BETA FEATURES</p>
|
||||||
<div class="custom-control custom-switch">
|
<div class="alert alert-primary font-weight-bold text-center">Experimental features have been moved to the <a href="/settings/labs">Labs</a> settings page.</div>
|
||||||
<input type="checkbox" class="custom-control-input" id="mode-dark" v-on:click="modeDarkToggle()" v-model="modes.dark">
|
|
||||||
<label class="custom-control-label font-weight-bold" for="mode-dark">Dark Mode</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -225,6 +222,31 @@
|
||||||
<notification-card></notification-card>
|
<notification-card></notification-card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div v-show="suggestions.length && config.ab && config.ab.rec == true" class="mb-4">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header bg-white text-muted d-flex justify-content-between align-items-center">
|
||||||
|
<div>Suggestions For You</div>
|
||||||
|
<div class="small text-dark"></div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body pt-0">
|
||||||
|
<div v-for="(rec, index) in suggestions" class="media align-items-center mt-3">
|
||||||
|
<a :href="'/'+rec.username">
|
||||||
|
<img :src="rec.avatar" width="32px" height="32px" class="rounded-circle mr-3">
|
||||||
|
</a>
|
||||||
|
<div class="media-body">
|
||||||
|
<p class="mb-0 font-weight-bold small">
|
||||||
|
<a :href="'/'+rec.username" class="text-decoration-none text-dark">
|
||||||
|
{{rec.username}}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<p class="mb-0 small text-muted">{{rec.message}}</p>
|
||||||
|
</div>
|
||||||
|
<a class="font-weight-bold small" href="#" @click.prevent="expRecFollow(rec.id, index)">Follow</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
<div class="container pb-5">
|
<div class="container pb-5">
|
||||||
<p class="mb-0 text-uppercase font-weight-bold text-muted small">
|
<p class="mb-0 text-uppercase font-weight-bold text-muted small">
|
||||||
|
@ -431,6 +453,7 @@
|
||||||
this.max_id = Math.min(...ids);
|
this.max_id = Math.min(...ids);
|
||||||
$('.timeline .pagination').removeClass('d-none');
|
$('.timeline .pagination').removeClass('d-none');
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
|
this.expRec();
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -927,6 +950,29 @@
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
expRec() {
|
||||||
|
if(this.config.ab.rec == false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
axios.get('/api/local/exp/rec')
|
||||||
|
.then(res => {
|
||||||
|
this.suggestions = res.data;
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
expRecFollow(id, index) {
|
||||||
|
if(this.config.ab.rec == false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
axios.post('/i/follow', {
|
||||||
|
item: id
|
||||||
|
}).then(res => {
|
||||||
|
this.suggestions.splice(index, 1);
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<h3 class="font-weight-bold">Data Export</h3>
|
<h3 class="font-weight-bold">Data Export</h3>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
<div class="alert alert-info font-weight-bold">We generate data exports once per hour, and they may not contain the latest data if you've requested them recently.</div>
|
<div class="alert alert-primary px-3 h6">We generate data exports once per hour, and they may not contain the latest data if you've requested them recently.</div>
|
||||||
<ul class="list-group">
|
<ul class="list-group">
|
||||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -101,19 +101,7 @@
|
||||||
<div class="pt-5">
|
<div class="pt-5">
|
||||||
<p class="font-weight-bold text-muted text-center">Layout</p>
|
<p class="font-weight-bold text-muted text-center">Layout</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group row">
|
<div class="alert alert-primary font-weight-bold text-center">Experimental features have been moved to the <a href="/settings/labs">Labs</a> settings page.</div>
|
||||||
<label for="email" class="col-sm-3 col-form-label font-weight-bold text-right">Profile Layout</label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<div class="custom-control custom-radio custom-control-inline">
|
|
||||||
<input type="radio" id="profileLayout1" name="profile_layout" class="custom-control-input" {{Auth::user()->profile->profile_layout != 'moment' ? 'checked':''}} value="metro">
|
|
||||||
<label class="custom-control-label" for="profileLayout1">MetroUI</label>
|
|
||||||
</div>
|
|
||||||
<div class="custom-control custom-radio custom-control-inline">
|
|
||||||
<input type="radio" id="profileLayout2" name="profile_layout" class="custom-control-input" {{Auth::user()->profile->profile_layout == 'moment' ? 'checked':''}} value="moment">
|
|
||||||
<label class="custom-control-label" for="profileLayout2">MomentUI</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr>
|
<hr>
|
||||||
@if(config('pixelfed.account_deletion') == true)
|
@if(config('pixelfed.account_deletion') == true)
|
||||||
<div class="form-group row py-3">
|
<div class="form-group row py-3">
|
||||||
|
|
63
resources/views/settings/labs.blade.php
Normal file
63
resources/views/settings/labs.blade.php
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
@extends('settings.template')
|
||||||
|
|
||||||
|
@section('section')
|
||||||
|
<div class="title">
|
||||||
|
<h3 class="font-weight-bold">Labs</h3>
|
||||||
|
<p class="lead">Experimental features</p>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<div class="alert alert-primary px-3 h6 text-center">
|
||||||
|
<strong>Warning:</strong> Some experimental features may contain bugs or missing functionality
|
||||||
|
</div>
|
||||||
|
<div class="py-3">
|
||||||
|
<p class="font-weight-bold text-muted text-center">UI</p>
|
||||||
|
<hr>
|
||||||
|
</div>
|
||||||
|
<form method="post">
|
||||||
|
@csrf
|
||||||
|
@if(config('exp.lc') == true)
|
||||||
|
<div class="form-check pb-3">
|
||||||
|
<input class="form-check-input" type="checkbox" checked disabled>
|
||||||
|
<label class="form-check-label font-weight-bold">
|
||||||
|
{{__('Hidden like counts on Timelines')}}
|
||||||
|
</label>
|
||||||
|
<p class="text-muted small help-text">Like counts are hidden on timelines. This experiment was enabled for all users and can only be changed by the instance administrator.</p>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
<div class="form-check pb-3">
|
||||||
|
<input class="form-check-input" type="checkbox" name="profile_layout" id="profile_layout" {{$profile->profile_layout == 'moment' ? 'checked':''}} value="{{$profile->profile_layout}}">
|
||||||
|
<label class="form-check-label font-weight-bold" for="profile_layout">
|
||||||
|
{{__('Use MomentUI for posts and your profile')}}
|
||||||
|
</label>
|
||||||
|
<p class="text-muted small help-text">MomentUI offers an alternative layout for posts and your profile.</p>
|
||||||
|
</div>
|
||||||
|
@if($profile->profile_layout != 'moment')
|
||||||
|
<div class="form-check pb-3">
|
||||||
|
<input class="form-check-input" type="checkbox" name="dark_mode" id="dark_mode" {{request()->hasCookie('dark-mode') ? 'checked':''}}>
|
||||||
|
<label class="form-check-label font-weight-bold" for="dark_mode">
|
||||||
|
{{__('MetroUI Dark Mode')}}
|
||||||
|
</label>
|
||||||
|
<p class="text-muted small help-text">Use dark mode theme.</p>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
<div class="py-3">
|
||||||
|
<p class="font-weight-bold text-muted text-center">Discovery</p>
|
||||||
|
<hr>
|
||||||
|
</div>
|
||||||
|
@if(config('exp.rec') == true)
|
||||||
|
<div class="form-check pb-3">
|
||||||
|
<input class="form-check-input" type="checkbox" name="profile_suggestions" id="profile_suggestions" {{$profile->is_suggestable ? 'checked' : ''}}>
|
||||||
|
<label class="form-check-label font-weight-bold" for="profile_suggestions">
|
||||||
|
{{__('Visible on Profile Suggestions')}}
|
||||||
|
</label>
|
||||||
|
<p class="text-muted small help-text">Allow your profile to be listed in Profile Suggestions.</p>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
<div class="form-group row">
|
||||||
|
<div class="col-12">
|
||||||
|
<hr>
|
||||||
|
<button type="submit" class="btn btn-primary font-weight-bold py-1 btn-block">Save Changes</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
@endsection
|
|
@ -6,6 +6,26 @@
|
||||||
<li class="nav-item pl-3 {{request()->is('settings/password')?'active':''}}">
|
<li class="nav-item pl-3 {{request()->is('settings/password')?'active':''}}">
|
||||||
<a class="nav-link font-weight-light text-muted" href="{{route('settings.password')}}">Password</a>
|
<a class="nav-link font-weight-light text-muted" href="{{route('settings.password')}}">Password</a>
|
||||||
</li>
|
</li>
|
||||||
|
{{--
|
||||||
|
<li class="nav-item pl-3 {{request()->is('settings/accessibility')?'active':''}}">
|
||||||
|
<a class="nav-link font-weight-light text-muted" href="{{route('settings.accessibility')}}">Accessibility</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item pl-3 {{request()->is('settings/email')?'active':''}}">
|
||||||
|
<a class="nav-link font-weight-light text-muted" href="{{route('settings.email')}}">Email</a>
|
||||||
|
</li>
|
||||||
|
@if(config('pixelfed.user_invites.enabled'))
|
||||||
|
<li class="nav-item pl-3 {{request()->is('settings/invites*')?'active':''}}">
|
||||||
|
<a class="nav-link font-weight-light text-muted" href="{{route('settings.invites')}}">Invites</a>
|
||||||
|
</li>
|
||||||
|
@endif
|
||||||
|
<li class="nav-item pl-3 {{request()->is('settings/notifications')?'active':''}}">
|
||||||
|
<a class="nav-link font-weight-light text-muted" href="{{route('settings.notifications')}}">Notifications</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item pl-3 {{request()->is('settings/reports*')?'active':''}}">
|
||||||
|
<a class="nav-link font-weight-light text-muted" href="{{route('settings.reports')}}">Reports</a>
|
||||||
|
</li>
|
||||||
|
--}}
|
||||||
|
|
||||||
<li class="nav-item pl-3 {{request()->is('settings/privacy*')?'active':''}}">
|
<li class="nav-item pl-3 {{request()->is('settings/privacy*')?'active':''}}">
|
||||||
<a class="nav-link font-weight-light text-muted" href="{{route('settings.privacy')}}">Privacy</a>
|
<a class="nav-link font-weight-light text-muted" href="{{route('settings.privacy')}}">Privacy</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -15,8 +35,27 @@
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<hr>
|
<hr>
|
||||||
</li>
|
</li>
|
||||||
|
{{-- <li class="nav-item pl-3 {{request()->is('*import*')?'active':''}}">
|
||||||
|
<a class="nav-link font-weight-light text-muted" href="{{route('settings.import')}}">Import</a>
|
||||||
|
</li> --}}
|
||||||
<li class="nav-item pl-3 {{request()->is('settings/data-export')?'active':''}}">
|
<li class="nav-item pl-3 {{request()->is('settings/data-export')?'active':''}}">
|
||||||
<a class="nav-link font-weight-light text-muted" href="{{route('settings.dataexport')}}">Data Export</a>
|
<a class="nav-link font-weight-light text-muted" href="{{route('settings.dataexport')}}">Data Export</a>
|
||||||
</li>
|
</li>
|
||||||
|
{{--
|
||||||
|
<li class="nav-item">
|
||||||
|
<hr>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item pl-3 {{request()->is('settings/applications')?'active':''}}">
|
||||||
|
<a class="nav-link font-weight-light text-muted" href="{{route('settings.applications')}}">Applications</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item pl-3 {{request()->is('settings/developers')?'active':''}}">
|
||||||
|
<a class="nav-link font-weight-light text-muted" href="{{route('settings.developers')}}">Developers</a>
|
||||||
|
</li> --}}
|
||||||
|
<li class="nav-item">
|
||||||
|
<hr>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item pl-3 {{request()->is('settings/labs*')?'active':''}}">
|
||||||
|
<a class="nav-link font-weight-light text-muted" href="{{route('settings.labs')}}">Labs</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
|
@ -1,6 +1,18 @@
|
||||||
@extends('layouts.app')
|
@extends('layouts.app')
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
|
@if (session('status'))
|
||||||
|
<div class="alert alert-primary px-3 h6 text-center">
|
||||||
|
{{ session('status') }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
@if ($errors->any())
|
||||||
|
<div class="alert alert-danger px-3 h6 text-center">
|
||||||
|
@foreach($errors->all() as $error)
|
||||||
|
<p class="font-weight-bold mb-1">{{ $error }}</li>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
|
@ -9,20 +21,6 @@
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@include('settings.partial.sidebar')
|
@include('settings.partial.sidebar')
|
||||||
<div class="col-12 col-md-9 p-5">
|
<div class="col-12 col-md-9 p-5">
|
||||||
@if (session('status'))
|
|
||||||
<div class="alert alert-success font-weight-bold">
|
|
||||||
{{ session('status') }}
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
@if (session('errors'))
|
|
||||||
<div class="alert alert-danger">
|
|
||||||
<ul class="mb-0">
|
|
||||||
@foreach (session('errors') as $error)
|
|
||||||
<li class="font-weight-bold">{{ $error }}</li>
|
|
||||||
@endforeach
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
@yield('section')
|
@yield('section')
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -99,6 +99,7 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
|
||||||
Route::group(['prefix' => 'local'], function () {
|
Route::group(['prefix' => 'local'], function () {
|
||||||
Route::get('i/follow-suggestions', 'ApiController@followSuggestions');
|
Route::get('i/follow-suggestions', 'ApiController@followSuggestions');
|
||||||
Route::post('status/compose', 'InternalApiController@compose');
|
Route::post('status/compose', 'InternalApiController@compose');
|
||||||
|
Route::get('exp/rec', 'ApiController@userRecommendations');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -231,6 +232,8 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
|
||||||
Route::post('data-export/account', 'SettingsController@exportAccount')->middleware('dangerzone');
|
Route::post('data-export/account', 'SettingsController@exportAccount')->middleware('dangerzone');
|
||||||
Route::post('data-export/statuses', 'SettingsController@exportStatuses')->middleware('dangerzone');
|
Route::post('data-export/statuses', 'SettingsController@exportStatuses')->middleware('dangerzone');
|
||||||
Route::get('developers', 'SettingsController@developers')->name('settings.developers')->middleware('dangerzone');
|
Route::get('developers', 'SettingsController@developers')->name('settings.developers')->middleware('dangerzone');
|
||||||
|
Route::get('labs', 'SettingsController@labs')->name('settings.labs');
|
||||||
|
Route::post('labs', 'SettingsController@labsStore');
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::group(['prefix' => 'site'], function () {
|
Route::group(['prefix' => 'site'], function () {
|
||||||
|
|
Loading…
Reference in a new issue